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