HelpBrowser: path box becomes a more conventional search box
[supercollider.git] / SCClassLibrary / SCDoc / SCDocParser.sc
blob66ef6e052fd213a28bb8442c96d9523d31abe091
1 SCDocParser {
2     var <>root;
3     var tree;
4     var stack;
5     var current;
6     var singleline;
7     var level;
8     var modalTag;
9     var lastTagLine;
10     var afterClosing;
11     var isWS;
12     var stripFirst;
13     var proseDisplay;
14     var <>currentFile;
15     var <methodList, <keywordList;
16     classvar copyMethodCache;
18     init {
19         root = tree = List.new;
20         stack = List.new;
21         stack.add([tree,0,nil]);
22         current = nil;
23         singleline = false;
24         level = 0;
25         modalTag = nil;
26         isWS = false;
27         afterClosing = false;
28         stripFirst = false;
29         proseDisplay = \block;
30 //        doingInlineTag = false;
31     }
33 //    *new {|filename|
34 //        ^super.newCopyArgs(filename).init;
35 //    }
36 //    *new {
37 //        ^super.new.init;
38 //    }
39 //    isTag {|word| ^"^(::[a-zA-Z]+|[a-zA-Z]+::)$".matchRegexp(word)}
40 //    isOpeningTag {|word| ^"^[a-zA-Z]+::$".matchRegexp(word)}
41 //    isClosingTag {|word| ^"^::[a-zA-Z]+$".matchRegexp(word)}
42     leaveLevel {|n|
43         var p;
44         while({level>=n},{
45             p = stack.pop;
46             tree = p[0];
47             level = p[1];
48         });
49     }
51     popTree {
52         var p = stack.pop;
53         p !? {
54             tree = p[0];
55             level = p[1];
56             p[2] !? {proseDisplay = p[2].display};
57         };
58     }
60     pushTree {
61         stack.add([tree,level,nil]);
62     }
64     setTopNode {|n|
65         stack[stack.size-1][2] = n;
66     }
68     enterLevel {|n|
69         this.leaveLevel(n);
70         this.pushTree;
71         level = n;
72     }
74     endCurrent {
75         current !? {
76             proseDisplay = current.display;
77             current = nil;
78         };
79     }
81     addTag {|tag, text="", children=false, display=\block|
82         var node;
83         this.endCurrent;
84         tag = tag.asString.drop(-2).asSymbol;
85         current = node = (tag:tag, display:display, text:text, children:if(children,{List.new},{nil}));
86         tree.add(current);
87         if(children, {tree = current.children}); //recurse into children list
88         if(text.isNil) {this.endCurrent}; //we don't have any text field to add to for this tag, so start fresh..
89         ^node;
90     }
92     handleWord {|word,lineno,wordno|
93         var tag = word.toLower.asSymbol;
94         var t,x;
95         var simpleTag = {
96             singleline = true;
97             this.addTag(tag);
98             stripFirst = true;
99         };
100         var noNameSection = {
101             singleline = true; //this doesn't actually matter here since we don't have a text field?
102             this.enterLevel(1);
103             this.setTopNode(this.addTag(tag,nil,true));
104         };
105         var namedSection = {|lev|
106             {
107                 singleline = true;
108                 this.enterLevel(lev);
109                 this.setTopNode(this.addTag(tag,"",true));
110                 stripFirst = true;
111             }
112         };
113         var modalRangeTag = {
114             singleline = false;
115             this.addTag(tag);
116             lastTagLine = lineno;
117             modalTag = '::';
118         };
119         var listEnter = {
120             singleline = false; //this doesn't actually matter here since we don't have a text field?
121             this.pushTree;
122             this.setTopNode(this.addTag(tag,nil,true));
123             lastTagLine = lineno;
124         };
126         // modal tags ignore all other tags until their closing tag occurs.
127         // here we check if we are in a modal tag (code, emphasis, link) and then
128         // if we got the closing tag.
129         if(modalTag.notNil, {
130             //only allow modal block tags to be closed with the closing tag as the first word on a line
131             if((tag==modalTag) and: ((wordno==0) or: (lastTagLine==lineno)),{
132                 current !? {
133                     current.display = if(lastTagLine==lineno,\inline,\block);
134                 };
135                 this.endCurrent;
136                 modalTag = nil;
137                 afterClosing = true;
138             },{
139                 if(("[^ ]+[^ \\]::".matchRegexp(word)) and: (lastTagLine==lineno), { //split unhandled tag-like word
140                     this.addText(word.drop(-2));
141                     this.handleWord("::",lineno,wordno+1);
142                 },{
143                     this.addText(word.replace("\\::","::"));
144                 });
145             });
146         },{
147             switch(tag,
148                 'description::',        noNameSection, //level 1
149                 'classmethods::',       noNameSection,
150                 'instancemethods::',    noNameSection,
151                 'examples::',           noNameSection,
152                 'section::',            namedSection.(1),
153                 'subsection::',         namedSection.(2),
154                 'method::',             namedSection.(3),
155                 'copymethod::',         {
156                     this.enterLevel(3);
157                     simpleTag.value;
158                 },
159                 'keyword::',            simpleTag,
160                 'argument::',           namedSection.(4),
161                 'returns::',            {
162                     singleline = true; //this doesn't actually matter here since we don't have a text field?
163                     this.enterLevel(4);
164                     this.setTopNode(this.addTag(tag,nil,true));
165                 },
166                 'discussion::',            {
167                     singleline = true; //this doesn't actually matter here since we don't have a text field?
168                     this.enterLevel(4);
169                     this.setTopNode(this.addTag(tag,nil,true));
170                 },
171                 'class::',              simpleTag,
172                 'redirect::',           simpleTag,
173                 'title::',              simpleTag,
174                 'summary::',            simpleTag,
175                 'related::',            simpleTag,
176 //                'headerimage::',        simpleTag,
177                 'categories::',         simpleTag,
178 //                'note::',               simpleTag,
179 //                'warning::',            simpleTag,
180                 'private::',            simpleTag,
181                 'classtree::',          simpleTag,
183                 'code::',               modalRangeTag,
184                 'formula::',            modalRangeTag,
185                 'emphasis::',           modalRangeTag,
186                 'strong::',             modalRangeTag,
187                 'link::',               modalRangeTag,
188                 'anchor::',             modalRangeTag,
189                 'image::',              modalRangeTag,
190                 'soft::',               modalRangeTag,
192                 'note::',               { listEnter.(); proseDisplay=\inline },
193                 'warning::',            { listEnter.(); proseDisplay=\inline },
195                 'list::',               listEnter,
196                 'tree::',               listEnter,
197                 'numberedlist::',       listEnter,
198                 'definitionlist::',     listEnter,
199                 'table::',              listEnter,
200                 'footnote::',           {
201                     singleline = false;
202                     current !? { //strip trailing whitespace from previous text..
203                         t=current.text;
204                         x=t.size-1;
205                         while({(t[x]==$\n) or: (t[x]==$\ )},{x=x-1});
206                         current.text = t.copyRange(0,x);
207                     };
208                     this.pushTree;
209                     this.setTopNode(this.addTag(tag,nil,true,\inline));
210                     lastTagLine = lineno;
211                     proseDisplay = \inline;
212                 },
213                 '##', {
214                     singleline = false;
215                     this.addTag('##::',nil,false,\inline); //make it look like an ordinary tag since we drop the :: in the output tree
216                 },
217                 '||', {
218                     singleline = false;
219                     this.addTag('||::',nil,false,\inline);
220                 },
221                 '\\||', {
222                     this.addText("||");
223                 },
224                 '::', { //ends tables and lists
225                     this.endCurrent;
226                     this.popTree;
227                 },
228                 '\\::', {
229                     this.addText("::");
230                 },
232                 { //default case
233                     if("^[a-zA-Z]+://.+".matchRegexp(word),{ //auto link URIs
234                         this.addTag('link::',word++" ",false,\inline);
235                         this.endCurrent;
236                     },{
237                         if(("[^ ]+[^ \\]::".matchRegexp(word)) and: (lastTagLine==lineno), { //split unhandled tag-like word
238                             this.addText(word.drop(-2));
239                             this.handleWord("::",lineno,wordno+1);
240                         },{
241                             if(word.endsWith("::")) {
242                                 warn("SCDocParser: Unknown tag:"+word+"in"+currentFile);
243                             };
244                             this.addText(word); //plain text, add the word.
245                         });
246                     });
247                 }
248             );
249         });
250     }
252     addText {|word|
253         if(stripFirst, {
254             stripFirst = false;
255             word = word.stripWhiteSpace;
256         });
257         if(current.notNil, { // add to current element text
258             current.text = current.text ++ word
259         },{ // no current element, so add to new 'prose' element
260             if((isWS.not) or: (afterClosing), { //don't start a new prose element with whitespace
261                 afterClosing = false;
262                 singleline = false;
263                 this.addTag('prose::', word, false, proseDisplay);
264             });
265         });
266     }
268     endLine {
269         if(singleline,{this.endCurrent});
270         // pass through newlines for vari-line tags.
271         current !? {current.text = current.text ++ "\n"};
272     }
274     parse {|string|
275         var lines = string.replace("\r","").split($\n); //split lines
276 //        var lines = string.findRegexp("[^\n]+").flop[1]; //doesn't work for empty lines
278         var w, split, split2, word;
279         this.init;
280         lines.do {|line,l|
281             split = line.findRegexp("[a-zA-Z]+::[^ \n\t]+::|[a-zA-Z]*::|[ \n\t]+|[^ \n\t]+"); //split words and tags and ws
282             w = 0;
283             split.do {|e|
284                 word = e[1];
285                 split2 = word.findRegexp("([a-zA-Z]+::)([^ \n\t]+)(::)")[1..]; //split stuff like::this::...
286                 if(split2.isEmpty,{
287                     isWS = "^[ \n\t]+$".matchRegexp(word);
288                     this.handleWord(word,l,w);
289                     if(isWS.not,{w=w+1});
290                 },{
291                     split2.do {|e2|
292                         isWS = "^[ \n\t]+$".matchRegexp(e2[1]);
293                         this.handleWord(e2[1],l,w);
294                         w=w+1;
295                     };
296                 });
297             };
298             if(modalTag.isNil and: split.isEmpty, { this.endCurrent; proseDisplay=\block; }); //force a new prose on double blank lines
299             this.endLine;
300         };
301         this.handleCopyMethod;
302     }
304     parseFile {|filename|
305         var file = File.open(filename,"r");
306         currentFile = filename;
307         this.parse(file.readAllString);
308         file.close;
309     }
311     merge {|p2|
312         var n;
313         var sects = IdentitySet[\classmethods,\instancemethods,\section,\subsection,\examples];
314         var do_children = {|dest,children|
315             var res;
316             children !? {
317                 children.do {|node|
318                     if(sects.includes(node.tag)) {
319                         n = dest.detect {|x| (x.tag==node.tag) and: {x.text==node.text}};
320                         if(n.isNil) {
321                             dest = dest.add(node);
322                         } {
323                             n.children = do_children.(n.children,node.children);
324                         }
325                     } {
326                         dest = dest.add(node);
327                     }
328                 }
329             };
330             dest;
331         };
332         root = do_children.(root,p2.root);
333     }
335     parseMetaData {|path|
336         var line, file, tag, text, match;
337         var tags = IdentitySet[\class,\title,\summary,\categories,\related,\redirect];
338         var pfx = ".";
339         var methods = Array.new;
340         var keywords = Array.new;
341         var inHeader = true;
342         var class = nil;
343         var cmets = IdentitySet.new;
344         var imets = IdentitySet.new;
345         var m, mets, f, l;
347         this.init;
348         file = File.open(path,"r");
349         block {|break|
350             loop {
351                 line = file.getLine;
352                 if(line.isNil) {break.value};
353                 match = line.findRegexp("([a-zA-Z]+::)\\s*(\\S*.*\\S+)?").flop[1];
354                 if(match.notNil) {
355                     tag = match[1].toLower.drop(-2).asSymbol;
356                     text = match[2];
357                     if(inHeader and: {tags.includes(tag)}) {
358                         root.add((tag:tag, text:text));
359                         if(tag==\class) {
360                             class = text.asSymbol.asClass;
361                         };
362                     } {
363                         inHeader = false;
364                         switch(tag,
365                             \description,       {pfx="."},
366                             \section,           {pfx="."},
367                             \examples,          {pfx="."},
368                             \instancemethods,   {pfx="-"},
369                             \classmethods,      {pfx="*"},
370                             \method, {
371                             //FIXME:
372                             // - m.isExtensionOf(c) (perhaps not very important, we can see this in the class doc)
373                                 match = text.findRegexp("\\(.*\\)|[^ ,]+").flop[1].reject{|x|x[0]==$(};
374                                 match.do {|name|
375                                     if("[a-z][a-zA-Z0-9_]*|[-<>@|&%*+/!?=]+".matchRegexp(name).not) {
376                                         warn("Methodname not valid: '"++name++"' in"+path);
377                                     } {
378                                         m = name.asSymbol;
379                                         if(m.isSetter) {
380                                             warn("Methods should be given as getters, without the _ suffix:"+name+"in"+path);
381                                             m = m.asGetter;
382                                         };
383                                         methods = methods.add("_"++pfx++m);
384                                         switch(pfx,
385                                             "*", {cmets.add(m)},
386                                             "-", {imets.add(m)}
387                                         );
388                                     };
389                                 };
390                             },
391                             \copymethod, {
392                                 match = text.findRegexp("[^ ,]+").flop[1][1];
393                                 if(match.notNil) {
394                                     m = match.drop(1).asSymbol.asGetter;
395                                     methods = methods.add("_"++pfx++m);
396                                     switch(pfx,
397                                         "*", {cmets.add(m)},
398                                         "-", {imets.add(m)}
399                                     );
400                                 }
401                             },
402                             \private, {
403                                 match = text.findRegexp("[^ ,]+").flop[1];
404                                 match.do {|name|
405                                     m = name.asSymbol.asGetter;
406                                     switch(pfx,
407                                         "*", {cmets.add(m)},
408                                         "-", {imets.add(m)}
409                                     );
410                                 };
411                             },
412                             \keyword, {
413                                 match = text.findRegexp("[^ ,]+").flop[1];
414                                 keywords = keywords ++ match;
415                             }
416                         );
417                     };
418                 };
419             };
420         };
421         file.close;
422         // Add undocumented methods
423         if(class.notNil) {
424             f = {|c,docmets,pfx|
425                 l = Array.new;
426                 (mets = c.methods) !? {
427                     //ignore these methods by default. Note that they can still be explicitly documented.
428                     docmets = docmets | IdentitySet[\categories, \init, \checkInputs, \new1, \argNamesInputsOffset];
429                     mets.collectAs({|m|m.name.asGetter},IdentitySet).do {|name|
430                         if(docmets.includes(name).not) {
431                             l = l.add("_"++pfx++name.asString);
432                         }
433                     };
434                 };
435                 l;
436             };
437             methods = methods ++ f.(class,imets,"-") ++ f.(class.class,cmets,"*");
438         };
439         methodList = methods;
440         keywordList = keywords;
441     }
443     *getMethodDoc {|classname,methodname|
444         var a, p, src, node = nil, findparent, parent;
445         var findmet = {|children|
446             children !? {
447                 children.do {|n|
448                     switch(n.tag,
449                         \instancemethods, {parent = n.tag},
450                         \classmethods, {parent = n.tag},
451                         \method, {
452                             if(parent == findparent
453                             and: {n.text.findRegexp("[^ ,]+").flop[1].indexOfEqual(methodname).notNil}) {
454                                 node = n;
455                             }
456                         }
457                     );
458                     if(n.children.size>0 and: {node.isNil}) { findmet.(n.children) };
459                 }
460             }
461         };
463         if(copyMethodCache.isNil) {
464             copyMethodCache = Array.new(4); //create an empty cache with room for 4 class docs
465         };
466         a = copyMethodCache.detect{|a|a.key==classname}; // check if class was already in cache
467         if(a.notNil) {
468             p = a.value;
469         } {
470             block {|break|
471                 src = nil;
472                 SCDoc.helpSourceDirs.do {|dir|
473                     var x = dir+/+"Classes"+/+classname++".schelp";
474                     if(File.exists(x)) {
475                         src = x;
476                         break.value;
477                     };
478                 };
479             };
480             if(src.isNil) {
481                 warn("SCDoc: copymethod:: could not find class doc"+classname);
482                 ^nil;
483             };
484             p = SCDocParser.new;
485             p.parseFile(src);
486             // add to cache, possibly removing the oldest one
487             if(copyMethodCache.size >= copyMethodCache.maxSize) {
488                 copyMethodCache.removeAt(0);
489             };
490             copyMethodCache.add(classname -> p);
491         };
493         findparent = switch(methodname[0],
494             $*, \classmethods,
495             $-, \instancemethods);
496         methodname = methodname.drop(1);
498         findmet.(p.root);
499         if(node.isNil) {
500             warn("SCDoc: copymethod:: could not find"+methodname+"in"+findparent+"of class doc"+classname);
501         };
502         ^node;
503     }
505     handleCopyMethod {
506         var name, met, node;
507         var do_children = {|children|
508             children !? {
509                 children.do {|n,i|
510                     if(n.tag==\copymethod) {
511                         #name, met = n.text.findRegexp("[^ ,]+").flop[1];
512                         node = this.class.getMethodDoc(name,met);
513                         if(node.notNil) {
514                             children[i]=node;
515                         } {
516                             children[i]=(tag:\method, text:met.drop(1), children:[(tag:\prose, text:"(copymethod::"+n.text+"failed)", display:\block)]);
517                         };
518                     };
519                     if(n.children.size>0) { do_children.(n.children) };
520                 };
521             };
522         };
523         do_children.(root);
524     }
526     // FIXME: move to renderer?
527     generateUndocumentedMethods {|class,node,title|
528         var syms, name, mets, l = Array.new;
529         var docmets = IdentitySet.new;
531         var addMet = {|n|
532             n.text.findRegexp("\\(.*\\)|[^ ,]+").flop[1].do {|m|
533                 if(m[0] != $() {
534                     docmets.add(m.asSymbol.asGetter);
535                 };
536             };
537         };
539         var do_children = {|children|
540             children !? {
541                 children.do {|n|
542                     switch(n.tag,
543                         \method, { addMet.(n) },
544                         \private, { addMet.(n) },
545                         \subsection, { do_children.(n.children) }
546                     );
547                 };
548             };
549         };
551         if(class.isNil, {^nil});
553         do_children.(node.children);
555         (mets = class.methods) !? {
556             //ignore these methods by default. Note that they can still be explicitly documented.
557             docmets = docmets | IdentitySet[\categories, \init, \checkInputs, \new1, \argNamesInputsOffset];
558             syms = mets.collectAs({|m|m.name.asGetter},IdentitySet);
559             syms.do {|name|
560                 if(docmets.includes(name).not) {
561                     l = l.add((tag:\method, text:name.asString));
562                 }
563             };
564         };
566         ^ if(l.notEmpty,
567         {
568             (tag:\subsection,
569             text:title,
570             children:l)
571         },
572             nil
573         );
574     }
576     dumpSubTree {|t,i="",lev=1|
577         t.do {|e|
578             "".postln;
579             (i++"TAG:"+e.tag+"( level"+lev+e.display+")").postln;
580             e.text !? {
581                 (i++"TEXT: \""++e.text++"\"").postln;
582             };
583             e.children !? {
584                 (i++"CHILDREN:").postln;
585                 this.dumpSubTree(e.children,i++"    ",lev+1);
586             };
587         }
588     }
590     dump {
591         this.dumpSubTree(root);
592         ^nil;
593     }
595     findNode {|tag,rootNode=nil|
596         var sym = tag.asSymbol;
597         ^((rootNode ?? { root }).detect {|n| n.tag == tag} ?? {
598             (tag:nil, text:"", children:[]);
599         });
600     }
603     makeCategoryTree {|catMap,node,filter=nil,toc=false|
604         var a, p, e, n, l, m, kinds, folder, v, dumpCats, sorted;
605         var tree = Dictionary.new;
607         catMap.pairsDo {|cat,files|
608             p=tree;
609             l=cat.split($>);
610             if(filter.isNil or: {filter.matchRegexp(l.first)}, {
611                 l.do {|c|
612                     if(p[c].isNil,{
613                         p[c]=Dictionary.new;
614                         p[c][\subcats] = Dictionary.new;
615                         p[c][\entries] = List.new;
616                     });
617                     e=p[c];
618                     p=p[c][\subcats];
619                 };
620                 a=e[\entries];
621                 files.do {|f| a.add(f)};
622             });
623         };
626         dumpCats = {|x,l,y|
627             var ents = x[\entries];
628             var subs = x[\subcats];
629             var c, z;
631             if(ents.notEmpty, {
632                 ents.sort {|a,b| a.path.basename < b.path.basename}.do {|doc|
633                     folder = doc.path.dirname;
634                     folder = if(folder==".", {""}, {" ["++folder++"]"});
635                     l.add((tag:'##'));
636                     l.add((tag:'link', text:doc.path++"##"++doc.title));
637                     l.add((tag:'prose', text:" - "++doc.summary));
638                     l.add((tag:'soft', text:folder));
639                     switch(doc.installed,
640                         \extension, { l.add((tag:'soft', text:" (+)")) },
641                         \missing, { l.add((tag:'strong', text:" (not installed)")) }
642                     );
643 /*                    if(doc.path.dirname=="Classes") {
644                         c = doc.path.basename.asSymbol.asClass;
645                         if(c.notNil) {
646                             if(c.filenameSymbol.asString.beginsWith(thisProcess.platform.classLibraryDir).not) {
647                                 l.add((tag:'soft', text:" (+)"));
648                             };
649                         } {
650                             l.add((tag:'strong', text:" (not installed)"));
651                         };
652                     };
654                 };
655             });
657             subs.keys.asList.sort {|a,b| a<b}.do {|k|
658                 z = SCDocRenderer.simplifyName(y++">"++k);
659                 l.add((tag:'##'));
660                 l.add((tag:\anchor, text:z));
661                 l.add((tag:\strong, text:k));
662                 l.add((tag:\tree, children:m=List.new));
663                 dumpCats.value(subs[k],m,z);
664             };
665         };
667         sorted = tree.keys.asList.sort {|a,b| a<b};
669         if(toc) {
670             node.add((tag:'prose', text:"Jump to: ", display:\block));
671             sorted.do {|k,i|
672                 if(i!=0, {node.add((tag:'prose', text:", ", display:\inline))});
673                 node.add((tag:'link', text:"#"++SCDocRenderer.simplifyName(k)++"#"++k));
674 //                node.add((tag:'prose', text:" ", display:\inline));
675             };
676         };
678         sorted.do {|k|
679             node.add((tag:\section, text:k, children:m=List.new));
680             m.add((tag:\tree, children:l=List.new));
681             dumpCats.(tree[k],l,k);
682         };
683     }
685     overviewCategories {|catMap|
686         var r = List.new;
687         r.add((tag:'title', text:"Document Categories"));
688         r.add((tag:'summary', text:"All documents by categories"));
689         r.add((tag:'related', text:"Overviews/Documents, Browse, Search"));
691         this.makeCategoryTree(catMap,r,toc:false);
693         root = r;
694     }
697     overviewAllClasses {|docMap|
698         var name, doc, link, n, r = List.new, cap, old_cap=nil, sortedKeys;
699         r.add((tag:'title', text:"Classes"));
700         r.add((tag:'summary', text:"Alphabetical index of all classes"));
701         r.add((tag:'related', text:"Overviews/ClassTree, Overviews/Methods"));
703         sortedKeys = Class.allClasses.reject {|c| c.name.asString.find("Meta_")==0};
705         old_cap = nil;
706         r.add((tag:'prose', text:"Jump to: ", display:\block));
707         sortedKeys.do {|c|
708             name = c.name.asString;
709             cap = name.first.toUpper;
710             if(cap!=old_cap, {
711                 r.add((tag:'link', text:"#"++cap.asString));
712                 r.add((tag:'prose', text:" ", display:\inline));
713                 old_cap = cap;
714             });
715         };
717         old_cap = nil;
718         sortedKeys.do {|c|
719             name = c.name.asString;
720             link = "Classes" +/+ name;
721             doc = docMap[link];
722             if(doc.notNil) {
723                 cap = name.first.toUpper;
724                 if(cap!=old_cap, {
725                     r.add((tag:'section', text:cap.asString, children:n=List.new));
726                     n.add((tag:'list', children:n=List.new));
727                     old_cap = cap;
728                 });
729                 n.add((tag:'##'));
730                 n.add((tag:'link', text: link));
731                 n.add((tag:'prose', text: " - "++ if(doc.notNil, {doc.summary}, {""})));
732                 switch(doc.installed,
733                     \extension, { n.add((tag:'soft', text:" (+)")) },
734                     \missing, { n.add((tag:'strong', text:" (not installed)")) }
735                 );
736             };
737         };
738         root = r;
739     }
742     overviewAllMethods {|docMap|
743         var name, n, r = List.new, cap, old_cap, t, m, ext, sortedKeys, pfx;
744         r.add((tag:'title', text:"Methods"));
745         r.add((tag:'summary', text:"Alphabetical index of all methods"));
746         r.add((tag:'related', text:"Overviews/ClassTree, Overviews/Classes"));
748         r.add((tag:'prose', text:"This is an alphabetical list of all implemented methods, including private and undocumented methods.", display:\block));
749         r.add((tag:'prose', text:"The classnames are prefixed by * for classmethods and postfixed by + for extensionmethods.", display:\block));
751         t = IdentityDictionary.new;
753         Class.allClasses.do {|c|
754             name = c.name.asString;
755             c.methods.do {|x|
756                 if(t[x.name]==nil, {t[x.name] = List.new});
758                 t[x.name].add([name,x.isExtensionOf(c)]);
759             };
760         };
762         sortedKeys = t.keys.asList.sort {|a,b| a<b};
764         old_cap = nil;
765         r.add((tag:'prose', text:"Jump to: ", display:\block));
766         sortedKeys.do {|k|
767             name = k.asString;
768             cap = name.first.toLower;
769             if(cap!=old_cap, {
770                 r.add((tag:'link', text:"#"++cap.asString));
771                 r.add((tag:'prose', text:" ", display:\inline));
772                 old_cap = cap;
773             });
774         };
776         old_cap = nil;
777         sortedKeys.do {|k|
778             name = k.asString;
779                 cap = name.first.toLower;
780                 if(cap!=old_cap, {
781                     r.add((tag:'section', text:cap.asString, children:n=List.new));
782                     n.add((tag:'definitionlist', children:m=List.new));
783                     old_cap = cap;
784                 });
786             m.add((tag:'##'));
787             m.add((tag:'anchor', text:name));
788             m.add((tag:'code', text:name));
789             m.add((tag:'||'));
790             if(name.last==$_, {name=name.drop(-1)});
791             t[k].do {|c,i|
792                 n = c[0];
793                 pfx = "-";
794                 if(i!=0, {m.add((tag:'prose', text:", ", display:\inline))});
795                 if(n.find("Meta_")==0, {
796                     n = n.drop(5);
797                     m.add((tag:'prose', text:"*", display:\inline));
798                     pfx = "*";
799                 });
800 //                m.add((tag:'link', text: "Classes" +/+ n ++ "#" ++ SCDocRenderer.simplifyName(name)));
801                 m.add((tag:'link', text: "Classes" +/+ n ++ "#" ++ pfx ++ name));
802                 if(c[1], {m.add((tag:'prose', text:"+", display:\inline))});
803             };
804         };
806         root = r;
807         ^t;
808     }
810 /*    overviewAllDocuments {|docMap|
811         var kind, name, doc, link, n, r = List.new, cap, old_cap, sortedKeys;
812         r.add((tag:'title', text:"Documents"));
813         r.add((tag:'summary', text:"Alphabetical index of all documents"));
814         r.add((tag:'related', text:"Overviews/Categories"));
816         sortedKeys = docMap.keys.asList.sort {|a,b| a.split($/).last < b.split($/).last};
818         old_cap = nil;
819         r.add((tag:'prose', text:"Jump to: ", display:\block));
820         sortedKeys.do {|link|
821             name = link.split($/).last;
822             cap = name.first.toUpper;
823             if(cap!=old_cap, {
824                 r.add((tag:'link', text:"#"++cap.asString));
825                 r.add((tag:'prose', text:" ", display:\inline));
826                 old_cap = cap;
827             });
828         };
830         old_cap = nil;
831         sortedKeys.do {|link|
832             doc = docMap[link];
833             name = link.split($/).last;
834             kind = link.dirname;
835             kind = if(kind==".", {""}, {" ["++kind++"]"});
836             cap = name.first.toUpper;
837             if(cap!=old_cap, {
838                 r.add((tag:'section', text:cap.asString, children:n=List.new));
839                 n.add((tag:'list', children:n=List.new));
840                 old_cap = cap;
841             });
842             n.add((tag:'##'));
843             n.add((tag:'link', text: link++"##"++doc.title));
844 //            n.add((tag:'||'));
845             n.add((tag:'prose', text: " - "++if(doc.notNil, {doc.summary}, {""})));
846 //            n.add((tag:'||'));
847             n.add((tag:'soft', text: kind));
848         };
849         root = r;
850     }
851     */
853 /*    overviewServer {|catMap|
854         var r = List.new;
855         r.add((tag:'title', text:"Server stuff"));
856         r.add((tag:'summary', text:"Overview of server related stuff"));
858         this.makeCategoryTree(catMap,r,"^Server$");
859         this.makeCategoryTree(catMap,r,"^UGens$");
860         root = r;
861     }*/