[monkeydoc] Correctly append kind caption to types
[mono.git] / mcs / tools / monkeydoc / Monkeydoc / providers / ecma-provider.cs
1 //
2 // The ecmaspec provider is for ECMA specifications
3 //
4 // Authors:
5 //      John Luke (jluke@cfl.rr.com)
6 //      Ben Maurer (bmaurer@users.sourceforge.net)
7 //
8 // Use like this:
9 //   mono assembler.exe --ecmaspec DIRECTORY --out name
10 //
11
12 using System;
13 using System.Linq;
14 using System.IO;
15 using System.Text;
16 using System.Xml;
17 using System.Xml.Linq;
18 using System.Collections.Generic;
19
20 using Lucene.Net.Index;
21 using Lucene.Net.Documents;
22
23 using Monkeydoc.Ecma;
24 using Mono.Utilities;
25
26 namespace MonkeyDoc.Providers
27 {
28         public enum EcmaNodeType {
29                 Invalid,
30                 Namespace,
31                 Type,
32                 Member,
33                 Meta, // A node that's here to serve as a header for other node
34         }
35
36         public class EcmaProvider : Provider
37         {
38                 HashSet<string> directories = new HashSet<string> ();
39
40                 public EcmaProvider ()
41                 {
42                 }
43
44                 public EcmaProvider (string baseDir)
45                 {
46                         AddDirectory (baseDir);
47                 }
48
49                 public void AddDirectory (string directory)
50                 {
51                         if (string.IsNullOrEmpty (directory))
52                                 throw new ArgumentNullException ("directory");
53
54                         directories.Add (directory);
55                 }
56
57                 public override void PopulateTree (Tree tree)
58                 {
59                         var root = tree.RootNode;
60                         var storage = tree.HelpSource.Storage;
61                         int resID = 0;
62                         var nsSummaries = new Dictionary<string, XElement> ();
63
64                         foreach (var asm in directories) {
65                                 var indexFilePath = Path.Combine (asm, "index.xml");
66                                 if (!File.Exists (indexFilePath)) {
67                                         Console.Error.WriteLine ("Warning: couldn't process directory `{0}' as it has no index.xml file", asm);
68                                         continue;
69                                 }
70                                 using (var reader = XmlReader.Create (File.OpenRead (indexFilePath))) {
71                                         reader.ReadToFollowing ("Types");
72                                         var types = XElement.Load (reader.ReadSubtree ());
73
74                                         foreach (var ns in types.Elements ("Namespace")) {
75                                                 var nsName = (string)ns.Attribute ("Name");
76                                                 nsName = !string.IsNullOrEmpty (nsName) ? nsName : "global";
77                                                 var nsNode = root.GetOrCreateNode (nsName, "N:" + nsName);
78
79                                                 XElement nsElements;
80                                                 if (!nsSummaries.TryGetValue (nsName, out nsElements))
81                                                         nsSummaries[nsName] = nsElements = new XElement ("elements",
82                                                                                                          new XElement ("summary"),
83                                                                                                          new XElement ("remarks"));
84
85                                                 foreach (var type in ns.Elements ("Type")) {
86                                                         // Add the XML file corresponding to the type to our storage
87                                                         var id = resID++;
88                                                         var typeFilePath = Path.Combine (asm, nsName, Path.ChangeExtension (type.Attribute ("Name").Value, ".xml"));
89                                                         if (!File.Exists (typeFilePath)) {
90                                                                 Console.Error.WriteLine ("Warning: couldn't process type file `{0}' as it doesn't exist", typeFilePath);
91                                                                 continue;
92                                                         }
93                                                         using (var file = File.OpenRead (typeFilePath))
94                                                                 storage.Store (id.ToString (), file);
95                                                         nsElements.Add (ExtractClassSummary (typeFilePath));
96                                                         var typeDocument = XDocument.Load (typeFilePath);
97
98                                                         var typeCaption = ((string)(type.Attribute ("DisplayName") ?? type.Attribute ("Name"))).Replace ('+', '.');
99                                                         var url = "ecma:" + id + '#' + typeCaption + '/';
100                                                         typeCaption += " " + (string)type.Attribute ("Kind");
101                                                         var typeNode = nsNode.CreateNode (typeCaption, url);
102
103                                                         // Add meta "Members" node
104                                                         typeNode.CreateNode ("Members", "*");
105                                                         var membersNode = typeDocument.Root.Element ("Members");
106                                                         if (membersNode == null || !membersNode.Elements ().Any ())
107                                                                 continue;
108                                                         var members = membersNode
109                                                                 .Elements ("Member")
110                                                                 .ToLookup (m => m.Attribute ("MemberName").Value.StartsWith ("op_") ? "Operator" : m.Element ("MemberType").Value);
111
112                                                         foreach (var memberType in members) {
113                                                                 // We pluralize the member type to get the caption and take the first letter as URL
114                                                                 var node = typeNode.CreateNode (PluralizeMemberType (memberType.Key), memberType.Key[0].ToString ());
115                                                                 var memberIndex = 0;
116
117                                                                 var isCtors = memberType.Key[0] == 'C';
118
119                                                                 // We do not escape much member name here
120                                                                 foreach (var memberGroup in memberType.GroupBy (m => MakeMemberCaption (m, isCtors))) {
121                                                                         if (memberGroup.Count () > 1) {
122                                                                                 // Generate overload
123                                                                                 var overloadCaption = MakeMemberCaption (memberGroup.First (), false);
124                                                                                 var overloadNode = node.CreateNode (overloadCaption, overloadCaption);
125                                                                                 foreach (var member in memberGroup)
126                                                                                         overloadNode.CreateNode (MakeMemberCaption (member, true), (memberIndex++).ToString ());
127                                                                                 overloadNode.Sort ();
128                                                                         } else {
129                                                                                 // We treat constructor differently by showing their argument list in all cases
130                                                                                 node.CreateNode (MakeMemberCaption (memberGroup.First (), isCtors), (memberIndex++).ToString ());
131                                                                         }
132                                                                 }
133                                                                 node.Sort ();
134                                                         }
135                                                 }
136
137                                                 nsNode.Sort ();
138                                         }
139                                         root.Sort ();
140                                 }
141                         }
142
143                         foreach (var summary in nsSummaries)
144                                 storage.Store ("xml.summary." + summary.Key, summary.Value.ToString ());
145
146                         var masterSummary = new XElement ("elements",
147                                                           directories
148                                                           .SelectMany (d => Directory.EnumerateFiles (d, "ns-*.xml"))
149                                                           .Select (ExtractNamespaceSummary));
150                         storage.Store ("mastersummary.xml", masterSummary.ToString ());
151                 }
152
153                 string PluralizeMemberType (string memberType)
154                 {
155                         switch (memberType) {
156                         case "Property":
157                                 return "Properties";
158                         default:
159                                 return memberType + "s";
160                         }
161                 }
162
163                 string MakeMemberCaption (XElement member, bool withArguments)
164                 {
165                         var caption = (string)member.Attribute ("MemberName");
166                         // Use type name instead of .ctor for cosmetic sake
167                         if (caption == ".ctor") {
168                                 caption = (string)member.Ancestors ("Type").First ().Attribute ("Name");
169                                 // If this is an inner type ctor, strip the parent type reference
170                                 var plusIndex = caption.LastIndexOf ('+');
171                                 if (plusIndex != -1)
172                                         caption = caption.Substring (plusIndex + 1);
173                         }
174                         if (caption.StartsWith ("op_")) {
175                                 string sig;
176                                 caption = MakeOperatorSignature (member, out sig);
177                                 caption = withArguments ? sig : caption;
178                                 return caption;
179                         }
180                         if (withArguments) {
181                                 var args = member.Element ("Parameters");
182                                 caption += '(';
183                                 if (args != null && args.Elements ("Parameter").Any ()) {
184                                         caption += args.Elements ("Parameter")
185                                                 .Select (p => (string)p.Attribute ("Type"))
186                                                 .Aggregate ((p1, p2) => p1 + "," + p2);
187                                 }
188                                 caption += ')';
189                         }
190                         
191                         return caption;
192                 }
193
194                 XElement ExtractClassSummary (string typeFilePath)
195                 {
196                         using (var reader = XmlReader.Create (typeFilePath)) {
197                                 reader.ReadToFollowing ("Type");
198                                 var name = reader.GetAttribute ("Name");
199                                 var fullName = reader.GetAttribute ("FullName");
200                                 reader.ReadToFollowing ("AssemblyName");
201                                 var assemblyName = reader.ReadElementString ();
202                                 reader.ReadToFollowing ("summary");
203                                 var summary = reader.ReadInnerXml ();
204                                 reader.ReadToFollowing ("remarks");
205                                 var remarks = reader.ReadInnerXml ();
206
207                                 return new XElement ("class",
208                                                      new XAttribute ("name", name ?? string.Empty),
209                                                      new XAttribute ("fullname", fullName ?? string.Empty),
210                                                      new XAttribute ("assembly", assemblyName ?? string.Empty),
211                                                      new XElement ("summary", new XCData (summary)),
212                                                      new XElement ("remarks", new XCData (remarks)));
213                         }
214                 }
215
216                 XElement ExtractNamespaceSummary (string nsFile)
217                 {
218                         using (var reader = XmlReader.Create (nsFile)) {
219                                 reader.ReadToFollowing ("Namespace");
220                                 var name = reader.GetAttribute ("Name");
221                                 reader.ReadToFollowing ("summary");
222                                 var summary = reader.ReadInnerXml ();
223                                 reader.ReadToFollowing ("remarks");
224                                 var remarks = reader.ReadInnerXml ();
225
226                                 return new XElement ("namespace",
227                                                      new XAttribute ("ns", name ?? string.Empty),
228                                                      new XElement ("summary", new XCData (summary)),
229                                                      new XElement ("remarks", new XCData (remarks)));
230                         }
231                 }
232
233                 public override void CloseTree (HelpSource hs, Tree tree)
234                 {
235                         AddImages (hs);
236                         AddExtensionMethods (hs);
237                 }
238
239                 void AddEcmaXml (HelpSource hs)
240                 {
241                         var xmls = directories
242                                 .SelectMany (Directory.EnumerateDirectories) // Assemblies
243                                 .SelectMany (Directory.EnumerateDirectories) // Namespaces
244                                 .SelectMany (Directory.EnumerateFiles)
245                                 .Where (f => f.EndsWith (".xml")); // Type XML files
246
247                         int resID = 0;
248                         foreach (var xml in xmls)
249                                 using (var file = File.OpenRead (xml))
250                                         hs.Storage.Store ((resID++).ToString (), file);
251                 }
252
253                 void AddImages (HelpSource hs)
254                 {
255                         var imgs = directories
256                                 .SelectMany (Directory.EnumerateDirectories)
257                                 .Select (d => Path.Combine (d, "_images"))
258                                 .Where (Directory.Exists)
259                                 .SelectMany (Directory.EnumerateFiles);
260
261                         foreach (var img in imgs)
262                                 using (var file = File.OpenRead (img))
263                                         hs.Storage.Store (Path.GetFileName (img), file);
264                 }
265
266                 void AddExtensionMethods (HelpSource hs)
267                 {
268                         var extensionMethods = directories
269                                 .SelectMany (Directory.EnumerateDirectories)
270                                 .Select (d => Path.Combine (d, "index.xml"))
271                                 .Where (File.Exists)
272                                 .Select (f => {
273                                         using (var file = File.OpenRead (f)) {
274                                                 var reader = XmlReader.Create (file);
275                                                 reader.ReadToFollowing ("ExtensionMethods");
276                                                 return reader.ReadInnerXml ();
277                                         }
278                             })
279                             .DefaultIfEmpty (string.Empty);
280
281                         hs.Storage.Store ("ExtensionMethods.xml",
282                                           "<ExtensionMethods>" + extensionMethods.Aggregate (string.Concat) + "</ExtensionMethods>");
283                 }
284
285                 IEnumerable<string> GetEcmaXmls ()
286                 {
287                         return directories
288                                 .SelectMany (Directory.EnumerateDirectories) // Assemblies
289                                 .SelectMany (Directory.EnumerateDirectories) // Namespaces
290                                 .SelectMany (Directory.EnumerateFiles)
291                                 .Where (f => f.EndsWith (".xml")); // Type XML files
292                 }
293
294                 string MakeOperatorSignature (XElement member, out string memberSignature)
295                 {
296                         string name = (string)member.Attribute ("MemberName");
297                         var nicename = name.Substring(3);
298                         memberSignature = null;
299
300                         switch (name) {
301                         // unary operators: no overloading possible     [ECMA-335 Â§10.3.1]
302                         case "op_UnaryPlus":                    // static     R operator+       (T)
303                         case "op_UnaryNegation":                // static     R operator-       (T)
304                         case "op_LogicalNot":                   // static     R operator!       (T)
305                         case "op_OnesComplement":               // static     R operator~       (T)
306                         case "op_Increment":                    // static     R operator++      (T)
307                         case "op_Decrement":                    // static     R operator--      (T)
308                         case "op_True":                         // static  bool operator true   (T)
309                         case "op_False":                        // static  bool operator false  (T)
310                         case "op_AddressOf":                    // static     R operator&       (T)
311                         case "op_PointerDereference":           // static     R operator*       (T)
312                                 memberSignature = nicename;
313                                 break;
314                         // conversion operators: overloading based on parameter and return type [ECMA-335 Â§10.3.3]
315                         case "op_Implicit":                    // static implicit operator R (T)
316                         case "op_Explicit":                    // static explicit operator R (T)
317                                 nicename = name.EndsWith ("Implicit") ? "ImplicitConversion" : "ExplicitConversion";
318                                 string arg = (string)member.Element ("Parameters").Element ("Parameter").Attribute ("Type");
319                                 string ret = (string)member.Element ("ReturnValue").Element ("ReturnType");
320                                 memberSignature = arg + " to " + ret;
321                                 break;
322                         // binary operators: overloading is possible [ECMA-335 Â§10.3.2]
323                         default:
324                                 memberSignature =
325                                         nicename + "("
326                                         + string.Join (",", member.Element ("Parameters").Elements ("Parameter").Select (p => (string)p.Attribute ("Type")))
327                                         + ")";
328                                 break;
329                         }
330
331                         return nicename;
332                 }
333         }
334
335         public class EcmaHelpSource : HelpSource
336         {
337                 const string EcmaPrefix = "ecma:";
338                 EcmaUrlParser parser = new EcmaUrlParser ();
339                 LRUCache<string, Node> cache = new LRUCache<string, Node> (4);
340
341                 public EcmaHelpSource (string base_file, bool create) : base (base_file, create)
342                 {
343                 }
344
345                 protected override string UriPrefix {
346                         get {
347                                 return EcmaPrefix;
348                         }
349                 }
350
351                 public override bool CanHandleUrl (string url)
352                 {
353                         if (url.Length > 2 && url[1] == ':') {
354                                 switch (url[0]) {
355                                 case 'T':
356                                 case 'M':
357                                 case 'C':
358                                 case 'P':
359                                 case 'E':
360                                 case 'F':
361                                 case 'N':
362                                 case 'O':
363                                         return true;
364                                 }
365                         }
366                         return base.CanHandleUrl (url);
367                 }
368
369                 // Clean the extra paramers in the id
370                 public override Stream GetHelpStream (string id)
371                 {
372                         var idParts = id.Split ('?');
373                         return base.GetHelpStream (idParts[0]);
374                 }
375
376                 public override Stream GetCachedHelpStream (string id)
377                 {
378                         var idParts = id.Split ('?');
379                         return base.GetCachedHelpStream (idParts[0]);
380                 }
381
382                 public override DocumentType GetDocumentTypeForId (string id, out Dictionary<string, string> extraParams)
383                 {
384                         extraParams = null;
385                         int interMark = id.LastIndexOf ('?');
386                         if (interMark != -1)
387                                 extraParams = id.Substring (interMark)
388                                         .Split ('&')
389                                         .Select (nvp => {
390                                                 var eqIdx = nvp.IndexOf ('=');
391                                                 return new { Key = nvp.Substring (0, eqIdx < 0 ? nvp.Length : eqIdx), Value = nvp.Substring (eqIdx + 1) };
392                                         })
393                                         .ToDictionary (kvp => kvp.Key, kvp => kvp.Value );
394                         return DocumentType.EcmaXml;
395                 }
396
397                 public override string GetPublicUrl (Node node)
398                 {
399                         string url = string.Empty;
400                         var type = GetNodeType (node);
401                         //Console.WriteLine ("GetPublicUrl {0} : {1} [{2}]", node.Element, node.Caption, type.ToString ());
402                         switch (type) {
403                         case EcmaNodeType.Namespace:
404                                 return node.Element; // A namespace node has already a well formated internal url
405                         case EcmaNodeType.Type:
406                                 return MakeTypeNodeUrl (node);
407                         case EcmaNodeType.Meta:
408                                 return MakeTypeNodeUrl (GetNodeTypeParent (node)) + GenerateMetaSuffix (node);
409                         case EcmaNodeType.Member:
410                                 var typeChar = GetNodeMemberTypeChar (node);
411                                 var parentNode = GetNodeTypeParent (node);
412                                 var typeNode = MakeTypeNodeUrl (parentNode).Substring (2);
413                                 return typeChar + ":" + typeNode + MakeMemberNodeUrl (typeChar, node);
414                         default:
415                                 return null;
416                         }
417                 }
418
419                 string MakeTypeNodeUrl (Node node)
420                 {
421                         // A Type node has a Element property of the form: 'ecma:{number}#{typename}/'
422                         var hashIndex = node.Element.IndexOf ('#');
423                         var typeName = node.Element.Substring (hashIndex + 1, node.Element.Length - hashIndex - 2);
424                         return "T:" + node.Parent.Caption + '.' + typeName.Replace ('.', '+');
425                 }
426
427                 string MakeMemberNodeUrl (char typeChar, Node node)
428                 {
429                         // We clean inner type ctor name which may contain the outer type name
430                         var caption = node.Caption;
431
432                         // Sanitize constructor caption of inner types
433                         if (typeChar == 'C') {
434                                 int lastDot = -1;
435                                 for (int i = 0; i < caption.Length && caption[i] != '('; i++)
436                                         lastDot = caption[i] == '.' ? i : lastDot;
437                                 return lastDot == -1 ? '.' + caption : caption.Substring (lastDot);
438                         }
439
440                         /* We handle type conversion operator by checking if the name contains " to "
441                          * (as in 'foo to bar') and we generate a corresponding conversion signature
442                          */
443                         if (typeChar == 'O' && caption.IndexOf (" to ") != -1) {
444                                 var parts = caption.Split (' ');
445                                 return "." + node.Parent.Caption + "(" + parts[0] + ", " + parts[2] + ")";
446                         }
447
448                         /* The goal here is to treat method which are explicit interface definition
449                          * such as 'void IDisposable.Dispose ()' for which the caption is a dot
450                          * expression thus colliding with the ecma parser.
451                          * If the first non-alpha character in the caption is a dot then we have an
452                          * explicit member implementation (we assume the interface has namespace)
453                          */
454                         var firstNonAlpha = caption.FirstOrDefault (c => !char.IsLetterOrDigit (c));
455                         if (firstNonAlpha == '.')
456                                 return "$" + caption;
457
458                         return "." + caption;
459                 }
460
461                 EcmaNodeType GetNodeType (Node node)
462                 {
463                         // We guess the node type by checking the depth level it's at in the tree
464                         int level = GetNodeLevel (node);
465                         switch (level) {
466                         case 0:
467                                 return EcmaNodeType.Namespace;
468                         case 1:
469                                 return EcmaNodeType.Type;
470                         case 2:
471                                 return EcmaNodeType.Meta;
472                         case 3: // Here it's either a member or, in case of overload, a meta
473                                 return node.IsLeaf ? EcmaNodeType.Member : EcmaNodeType.Meta;
474                         case 4: // At this level, everything is necessarily a member
475                                 return EcmaNodeType.Member;
476                         default:
477                                 return EcmaNodeType.Invalid;
478                         }
479                 }
480
481                 int GetNodeLevel (Node node)
482                 {
483                         int i = 0;
484                         for (; !node.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase); i++) {
485                                 //Console.WriteLine ("\tLevel {0} : {1} {2}", i, node.Element, node.Caption);
486                                 node = node.Parent;
487                         }
488                         return i - 1;
489                 }
490
491                 char GetNodeMemberTypeChar (Node node)
492                 {
493                         int level = GetNodeLevel (node);
494                         // We try to reach the member group node depending on node nested level
495                         switch (level) {
496                         case 2:
497                                 return node.Element[0];
498                         case 3:
499                                 return node.Parent.Element[0];
500                         case 4:
501                                 return node.Parent.Parent.Element[0];
502                         default:
503                                 throw new ArgumentException ("node", "Couldn't determine member type of node `" + node.Caption + "'");
504                         }
505                 }
506
507                 Node GetNodeTypeParent (Node node)
508                 {
509                         // Type nodes are always at level 2 so we just need to get there
510                         while (node != null && node.Parent != null && !node.Parent.Parent.Element.StartsWith ("root:/", StringComparison.OrdinalIgnoreCase))
511                                 node = node.Parent;
512                         return node;
513                 }
514
515                 string GenerateMetaSuffix (Node node)
516                 {
517                         string suffix = string.Empty;
518                         // A meta node has always a type element to begin with
519                         while (GetNodeType (node) != EcmaNodeType.Type) {
520                                 suffix = '/' + node.Element + suffix;
521                                 node = node.Parent;
522                         }
523                         return suffix;
524                 }
525
526                 public override string GetInternalIdForUrl (string url, out Node node)
527                 {
528                         var id = string.Empty;
529                         node = null;
530
531                         if (!url.StartsWith (EcmaPrefix, StringComparison.OrdinalIgnoreCase)) {
532                                 node = MatchNode (url);
533                                 if (node == null)
534                                         return null;
535                                 id = node.GetInternalUrl ();
536                         }
537
538                         if (id.StartsWith (UriPrefix, StringComparison.OrdinalIgnoreCase))
539                                 id = id.Substring (UriPrefix.Length);
540                         else if (id.StartsWith ("N:", StringComparison.OrdinalIgnoreCase))
541                                 id = "xml.summary." + id.Substring ("N:".Length);
542
543                         var hashIndex = id.IndexOf ('#');
544                         var hash = string.Empty;
545                         if (hashIndex != -1) {
546                                 hash = id.Substring (hashIndex + 1);
547                                 id = id.Substring (0, hashIndex);
548                         }
549
550                         return id + GetArgs (hash, node);
551                 }
552
553                 public override Node MatchNode (string url)
554                 {
555                         Node node = null;
556                         if ((node = cache.Get (url)) == null) {
557                                 node = InternalMatchNode (url);
558                                 if (node != null)
559                                         cache.Put (url, node);
560                         }
561                         return node;
562                 }
563
564                 public Node InternalMatchNode (string url)
565                 {
566                         Node result = null;
567                         EcmaDesc desc;
568                         if (!parser.TryParse (url, out desc))
569                                 return null;
570
571                         // Namespace search
572                         Node currentNode = Tree.RootNode;
573                         Node searchNode = new Node () { Caption = desc.Namespace };
574                         int index = currentNode.Nodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
575                         if (index >= 0)
576                                 result = currentNode.Nodes[index];
577                         if (desc.DescKind == EcmaDesc.Kind.Namespace || index < 0)
578                                 return result;
579
580                         // Type search
581                         currentNode = result;
582                         result = null;
583                         searchNode.Caption = desc.ToCompleteTypeName ();
584                         index = currentNode.Nodes.BinarySearch (searchNode, EcmaTypeNodeComparer.Instance);
585                         if (index >= 0)
586                                 result = currentNode.Nodes[index];
587                         if ((desc.DescKind == EcmaDesc.Kind.Type && !desc.IsEtc) || index < 0)
588                                 return result;
589
590                         // Member selection
591                         currentNode = result;
592                         result = null;
593                         var caption = desc.IsEtc ? EtcKindToCaption (desc.Etc) : MemberKindToCaption (desc.DescKind);
594                         currentNode = FindNodeForCaption (currentNode.Nodes, caption);
595                         if (currentNode == null 
596                             || (desc.IsEtc && desc.DescKind == EcmaDesc.Kind.Type && string.IsNullOrEmpty (desc.EtcFilter)))
597                                 return currentNode;
598
599                         // Member search
600                         result = null;
601                         var format = desc.DescKind == EcmaDesc.Kind.Constructor ? EcmaDesc.Format.WithArgs : EcmaDesc.Format.WithoutArgs;
602                         searchNode.Caption = desc.ToCompleteMemberName (format);
603                         index = currentNode.Nodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
604                         if (index < 0)
605                                 return null;
606                         result = currentNode.Nodes[index];
607                         if (result.Nodes.Count == 0 || desc.IsEtc)
608                                 return result;
609
610                         // Overloads search
611                         currentNode = result;
612                         searchNode.Caption = desc.ToCompleteMemberName (EcmaDesc.Format.WithArgs);
613                         index = currentNode.Nodes.BinarySearch (searchNode, EcmaGenericNodeComparer.Instance);
614                         if (index < 0)
615                                 return result;
616                         result = result.Nodes[index];
617                         
618                         return result;
619                 }
620
621                 // This comparer returns the answer straight from caption comparison
622                 class EcmaGenericNodeComparer : IComparer<Node>
623                 {
624                         public static readonly EcmaGenericNodeComparer Instance = new EcmaGenericNodeComparer ();
625
626                         public int Compare (Node n1, Node n2)
627                         {
628                                 return string.Compare (n1.Caption, n2.Caption, StringComparison.Ordinal);
629                         }
630                 }
631
632                 // This comparer take into account the space in the caption
633                 class EcmaTypeNodeComparer : IComparer<Node>
634                 {
635                         public static readonly EcmaTypeNodeComparer Instance = new EcmaTypeNodeComparer ();
636
637                         public int Compare (Node n1, Node n2)
638                         {
639                                 int length1 = CaptionLength (n1.Caption);
640                                 int length2 = CaptionLength (n2.Caption);
641
642                                 return string.Compare (n1.Caption, 0, n2.Caption, 0, Math.Max (length1, length2), StringComparison.Ordinal);
643                         }
644
645                         int CaptionLength (string caption)
646                         {
647                                 var length = caption.LastIndexOf (' ');
648                                 return length == -1 ? caption.Length : length;
649                         }
650                 }
651
652                 string EtcKindToCaption (char etc)
653                 {
654                         switch (etc) {
655                         case 'M':
656                                 return "Methods";
657                         case 'P':
658                                 return "Properties";
659                         case 'C':
660                                 return "Constructors";
661                         case 'F':
662                                 return "Fields";
663                         case 'E':
664                                 return "Events";
665                         case 'O':
666                                 return "Operators";
667                         case '*':
668                                 return "Members";
669                         default:
670                                 return null;
671                         }
672                 }
673
674                 string MemberKindToCaption (EcmaDesc.Kind kind)
675                 {
676                         switch (kind) {
677                         case EcmaDesc.Kind.Method:
678                                 return "Methods";
679                         case EcmaDesc.Kind.Property:
680                                 return "Properties";
681                         case EcmaDesc.Kind.Constructor:
682                                 return "Constructors";
683                         case EcmaDesc.Kind.Field:
684                                 return "Fields";
685                         case EcmaDesc.Kind.Event:
686                                 return "Events";
687                         case EcmaDesc.Kind.Operator:
688                                 return "Operators";
689                         default:
690                                 return null;
691                         }
692                 }
693
694                 Node FindNodeForCaption (List<Node> nodes, string caption)
695                 {
696                         foreach (var node in nodes)
697                                 if (node.Caption.Equals (caption, StringComparison.OrdinalIgnoreCase))
698                                         return node;
699                         return null;
700                 }
701
702                 string GetArgs (string hash, Node node)
703                 {
704                         var args = new Dictionary<string, string> ();
705                         
706                         args["source-id"] = SourceID.ToString ();
707                         
708                         if (node != null) {
709                                 var nodeType = GetNodeType (node);
710                                 switch (nodeType) {
711                                 case EcmaNodeType.Namespace:
712                                         args["show"] = "namespace";
713                                         args["namespace"] = node.Element.Substring ("N:".Length);
714                                         break;
715                                 case EcmaNodeType.Type:
716                                         args["show"] = "typeoverview";
717                                         break;
718                                 case EcmaNodeType.Member:
719                                 case EcmaNodeType.Meta:
720                                         switch (GetNodeMemberTypeChar (node)){
721                                         case 'C':
722                                                 args["membertype"] = "Constructor";
723                                                 break;
724                                         case 'M':
725                                                 args["membertype"] = "Method";
726                                                 break;
727                                         case 'P':
728                                                 args["membertype"] = "Property";
729                                                 break;
730                                         case 'F':
731                                                 args["membertype"] = "Field";
732                                                 break;
733                                         case 'E':
734                                                 args["membertype"] = "Event";
735                                                 break;
736                                         case 'O':
737                                                 args["membertype"] = "Operator";
738                                                 break;
739                                         case 'X':
740                                                 args["membertype"] = "ExtensionMethod";
741                                                 break;
742                                         case '*':
743                                                 args["membertype"] = "All";
744                                                 break;
745                                         }
746
747                                         if (nodeType == EcmaNodeType.Meta) {
748                                                 args["show"] = "members";
749                                                 args["index"] = "all";
750                                         } else {
751                                                 args["show"] = "member";
752                                                 args["index"] = node.Element;
753                                         }
754                                         break;
755                                 }
756                         }
757
758                         if (!string.IsNullOrEmpty (hash))
759                                 args["hash"] = hash;
760
761                         return "?" + string.Join ("&", args.Select (kvp => kvp.Key == kvp.Value ? kvp.Key : kvp.Key + '=' + kvp.Value));
762                 }
763         }
764 }