| Index: utils/dartdoc/dartdoc.dart | 
| diff --git a/utils/dartdoc/dartdoc.dart b/utils/dartdoc/dartdoc.dart | 
| index a85f25fdf9946481bf88f91caab47f3f7f8139dc..7bdc2266e202054cff07f6ee2d94f245370c2513 100644 | 
| --- a/utils/dartdoc/dartdoc.dart | 
| +++ b/utils/dartdoc/dartdoc.dart | 
| @@ -21,6 +21,8 @@ | 
| #import('../markdown/lib.dart', prefix: 'md'); | 
|  | 
| #source('classify.dart'); | 
| +#source('files.dart'); | 
| +#source('utils.dart'); | 
|  | 
| /** Path to corePath library. */ | 
| final corePath = 'lib'; | 
| @@ -29,17 +31,11 @@ final corePath = 'lib'; | 
| final outdir = 'docs'; | 
|  | 
| /** Set to `true` to include the source code in the generated docs. */ | 
| -bool includeSource = true; | 
| +bool includeSource = false; | 
|  | 
| /** Special comment position used to store the library-level doc comment. */ | 
| final _libraryDoc = -1; | 
|  | 
| -/** The path to the file currently being written to, relative to [outdir]. */ | 
| -String _filePath; | 
| - | 
| -/** The file currently being written to. */ | 
| -StringBuffer _file; | 
| - | 
| /** The library that we're currently generating docs for. */ | 
| Library _currentLibrary; | 
|  | 
| @@ -62,8 +58,6 @@ int _totalLibraries = 0; | 
| int _totalTypes = 0; | 
| int _totalMembers = 0; | 
|  | 
| -FileSystem files; | 
| - | 
| /** | 
| * Run this from the `utils/dartdoc` directory. | 
| */ | 
| @@ -102,17 +96,6 @@ void main() { | 
| world.processDartScript(libPath); | 
| world.resolveAll(); | 
|  | 
| -    // Clean the output directory. | 
| -    if (files.fileExists(outdir)) { | 
| -      files.removeDirectory(outdir, recursive: true); | 
| -    } | 
| -    files.createDirectory(outdir, recursive: true); | 
| - | 
| -    // Copy over the static files. | 
| -    for (final file in ['interact.js', 'styles.css']) { | 
| -      copyStatic(file); | 
| -    } | 
| - | 
| // Generate the docs. | 
| for (final library in world.libraries.getValues()) { | 
| docLibrary(library); | 
| @@ -129,47 +112,6 @@ void initializeDartDoc() { | 
| _comments = <String, Map<int, String>>{}; | 
| } | 
|  | 
| -/** Copies the static file at 'static/file' to the output directory. */ | 
| -copyStatic(String file) { | 
| -  var contents = files.readAll(joinPaths('static', file)); | 
| -  files.writeString(joinPaths(outdir, file), contents); | 
| -} | 
| - | 
| -num time(callback()) { | 
| -  // Unlike world.withTiming, returns the elapsed time. | 
| -  final watch = new Stopwatch(); | 
| -  watch.start(); | 
| -  callback(); | 
| -  watch.stop(); | 
| -  return watch.elapsedInMs(); | 
| -} | 
| - | 
| -startFile(String path) { | 
| -  _filePath = path; | 
| -  _file = new StringBuffer(); | 
| -} | 
| - | 
| -write(String s) { | 
| -  _file.add(s); | 
| -} | 
| - | 
| -writeln(String s) { | 
| -  write(s); | 
| -  write('\n'); | 
| -} | 
| - | 
| -endFile() { | 
| -  String outPath = '$outdir/$_filePath'; | 
| -  files.createDirectory(dirname(outPath), recursive: true); | 
| - | 
| -  world.files.writeString(outPath, _file.toString()); | 
| -  _filePath = null; | 
| -  _file = null; | 
| -} | 
| - | 
| -/** Turns a library name into something that's safe to use as a file name. */ | 
| -sanitize(String name) => name.replaceAll(':', '_').replaceAll('/', '_'); | 
| - | 
| docIndex(List<Library> libraries) { | 
| startFile('index.html'); | 
| // TODO(rnystrom): Need to figure out what this should look like. | 
| @@ -204,53 +146,6 @@ docIndex(List<Library> libraries) { | 
| endFile(); | 
| } | 
|  | 
| -/** Returns the number of times [search] occurs in [text]. */ | 
| -int countOccurrences(String text, String search) { | 
| -  int start = 0; | 
| -  int count = 0; | 
| - | 
| -  while (true) { | 
| -    start = text.indexOf(search, start); | 
| -    if (start == -1) break; | 
| -    count++; | 
| -    // Offsetting by needle length means overlapping needles are not counted. | 
| -    start += search.length; | 
| -  } | 
| - | 
| -  return count; | 
| -} | 
| - | 
| -/** Repeats [text] [count] times, separated by [separator] if given. */ | 
| -String repeat(String text, int count, [String separator]) { | 
| -  // TODO(rnystrom): Should be in corelib. | 
| -  final buffer = new StringBuffer(); | 
| -  for (int i = 0; i < count; i++) { | 
| -    buffer.add(text); | 
| -    if ((i < count - 1) && (separator !== null)) buffer.add(separator); | 
| -  } | 
| - | 
| -  return buffer.toString(); | 
| -} | 
| - | 
| -/** | 
| - * Converts [absolute] which is understood to be a full path from the root of | 
| - * the generated docs to one relative to the current file. | 
| - */ | 
| -String relativePath(String absolute) { | 
| -  // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do this | 
| -  // if the paths overlap. | 
| -  return repeat('../', countOccurrences(_filePath, '/')) + absolute; | 
| -} | 
| - | 
| -/** | 
| - * Creates a hyperlink. Handles turning the [href] into an appropriate relative | 
| - * path from the current file. | 
| - */ | 
| -String a(String href, String contents, [String class]) { | 
| -  final css = class == null ? '' : ' class="$class"'; | 
| -  return '<a href="${relativePath(href)}"$css>$contents</a>'; | 
| -} | 
| - | 
| writeHeader(String title) { | 
| writeln( | 
| ''' | 
| @@ -265,21 +160,68 @@ writeHeader(String title) { | 
| <script src="${relativePath('interact.js')}"></script> | 
| </head> | 
| <body> | 
| -      <div class="content"> | 
| +      <div class="page"> | 
| '''); | 
| +  docNavigation(); | 
| +  writeln('<div class="content">'); | 
| } | 
|  | 
| writeFooter() { | 
| writeln( | 
| ''' | 
| </div> | 
| +      <div class="footer"</div> | 
| </body></html> | 
| '''); | 
| } | 
|  | 
| +docNavigation() { | 
| +  writeln( | 
| +      ''' | 
| +      <div class="nav"> | 
| +      <h1>Libraries</h1> | 
| +      '''); | 
| + | 
| +  for (final library in orderValuesByKeys(world.libraries)) { | 
| +    write('<h2><div class="icon-library"></div> '); | 
| + | 
| +    if ((_currentLibrary == library) && (_currentType == null)) { | 
| +      write('<strong>${library.name}</strong>'); | 
| +    } else { | 
| +      write('${a(libraryUrl(library), library.name)}'); | 
| +    } | 
| +    write('</h2>'); | 
| + | 
| +    final types = orderValuesByKeys(library.types); | 
| +    if (types.length > 0) { | 
| +      writeln('<ul>'); | 
| +      for (final type in types) { | 
| +        if (type.isTop) continue; | 
| +        if (type.name.startsWith('_')) continue; | 
| + | 
| +        var icon = type.isClass ? 'icon-class' : 'icon-interface'; | 
| +        write('<li><div class="$icon"></div> '); | 
| + | 
| +        if (_currentType == type) { | 
| +          write('<strong>${type.name}</strong>'); | 
| +        } else { | 
| +          write('${a(typeUrl(type), type.name)}'); | 
| +        } | 
| + | 
| +        writeln('</li>'); | 
| +      } | 
| + | 
| +      writeln('</ul>'); | 
| +    } | 
| +  } | 
| + | 
| +  writeln('</div>'); | 
| +} | 
| + | 
| docLibrary(Library library) { | 
| _totalLibraries++; | 
| _currentLibrary = library; | 
| +  _currentType = null; | 
|  | 
| startFile(libraryUrl(library)); | 
| writeHeader(library.name); | 
| @@ -300,6 +242,7 @@ docLibrary(Library library) { | 
|  | 
| for (final type in orderValuesByKeys(library.types)) { | 
| if (type.isTop) continue; | 
| +    if (type.name.startsWith('_')) continue; | 
| writeln( | 
| ''' | 
| <div class="type"> | 
| @@ -469,10 +412,10 @@ docMethod(Type type, MethodMember method, [String constructorName = null]) { | 
|  | 
| // Translate specially-named methods: getters, setters, operators. | 
| var name = method.name; | 
| -  if (name.startsWith('get\$')) { | 
| +  if (name.startsWith('get:')) { | 
| // Getter. | 
| name = 'get ${name.substring(4)}'; | 
| -  } else if (name.startsWith('set\$')) { | 
| +  } else if (name.startsWith('set:')) { | 
| // Setter. | 
| name = 'set ${name.substring(4)}'; | 
| } else { | 
| @@ -542,6 +485,15 @@ docField(Type type, FieldMember field) { | 
| writeln('</div>'); | 
| } | 
|  | 
| +/** | 
| + * Creates a hyperlink. Handles turning the [href] into an appropriate relative | 
| + * path from the current file. | 
| + */ | 
| +String a(String href, String contents, [String class]) { | 
| +  final css = class == null ? '' : ' class="$class"'; | 
| +  return '<a href="${relativePath(href)}"$css>$contents</a>'; | 
| +} | 
| + | 
| /** Generates a human-friendly string representation for a type. */ | 
| typeName(Type type) { | 
| // See if it's a generic type. | 
| @@ -562,27 +514,6 @@ typeName(Type type) { | 
| return type.name; | 
| } | 
|  | 
| -/** Gets the URL to the documentation for [library]. */ | 
| -libraryUrl(Library library) { | 
| -  return '${sanitize(library.name)}.html'; | 
| -} | 
| - | 
| -/** Gets the URL for the documentation for [type]. */ | 
| -typeUrl(Type type) { | 
| -  // Always get the generic type to strip off any type parameters or arguments. | 
| -  // If the type isn't generic, genericType returns `this`, so it works for | 
| -  // non-generic types too. | 
| -  return '${sanitize(type.library.name)}/${type.genericType.name}.html'; | 
| -} | 
| - | 
| -/** Gets the URL for the documentation for [member]. */ | 
| -memberUrl(Member member) { | 
| -  return '${typeUrl(member.declaringType)}#${member.name}'; | 
| -} | 
| - | 
| -/** Gets the anchor id for the document for [member]. */ | 
| -memberAnchor(Member member) => '${member.name}'; | 
| - | 
| /** Writes a linked cross reference to [type]. */ | 
| typeReference(Type type) { | 
| // TODO(rnystrom): Do we need to handle ParameterTypes here like | 
| @@ -780,17 +711,6 @@ int getSpanColumn(SourceSpan span) { | 
| return span.file.getColumn(line, span.start); | 
| } | 
|  | 
| -/** Removes up to [indentation] leading whitespace characters from [text]. */ | 
| -unindent(String text, int indentation) { | 
| -  var start; | 
| -  for (start = 0; start < Math.min(indentation, text.length); start++) { | 
| -    // Stop if we hit a non-whitespace character. | 
| -    if (text[start] != ' ') break; | 
| -  } | 
| - | 
| -  return text.substring(start); | 
| -} | 
| - | 
| /** | 
| * Pulls the raw text out of a doc comment (i.e. removes the comment | 
| * characters). | 
|  |