[Mono-dev] [PATCH] Searching for Monodoc
Mario Sopena <[email protected]>
| Newsgroups | gmane.comp.gnome.mono.devel,gmane.comp.gnome.mono.documentation |
|---|---|
| Message-ID | <[email protected]> |
Hey, A patch that I promised to send some time ago. The patch would require that you download my own stripped version of Lucene from here[1]. That version is just the Lucene you can download from http://www.dotlucene.net/ but modified to be in a Monodoc.Lucene... namespace, which let us keep monodoc.dll in the GAC without breaking the Golden Rules [2]. The process to make the index is similar to the actual index. The important method is PopulateSearchableIndex which fills SearchableDocuments that are added to the Lucene index. It is implemented for the ecmaspec and the ecma providers. The SearchableDocuments have 5 fields: - title: nice title to show to the user - url: to retrieve the node later - hottext: the most important bits in this node - text: a big piece of text - examples: the code examples found The ones used for searching are the last 3 in the following order of importance: hottext > text > examples. The important missing things are: - The modifications made by the user aren't added to the index - Nodes added with the --edit parameter won't be searchable either - Right now, the whole Lucene is compiled in monodoc. Will it be better a lighter version of Lucene? Comments please! [1] http://personales.ya.com/msopena/Monodoc.Lucene.Net.tar.gz [2] http://www.mono-project.com/Assemblies_and_the_GAC _______________________________________________ Mono-devel-list mailing list [email protected] http://lists.ximian.com/mailman/listinfo/mono-devel-list
browser.diff
(text/x-patch, 17.8 KB)
Index: browser/provider.cs
===================================================================
--- browser/provider.cs (revision 49194)
+++ browser/provider.cs (working copy)
@@ -21,6 +21,8 @@
using System.Xml.XPath;
using ICSharpCode.SharpZipLib.Zip;
+using Monodoc.Lucene.Net.Index;
+using Monodoc.Lucene.Net.Analysis.Standard;
/// <summary>
/// This tree is populated by the documentation providers, or populated
/// from a binary encoding of the tree. The format of the tree is designed
@@ -667,6 +669,15 @@
output.Write ("</body></html>");
return output.ToString ();
}
+
+ //
+ // Create different Documents for adding to Lucene search index
+ // The default action is do nothing. Subclasses should add the docs
+ //
+ public virtual void PopulateSearchableIndex (IndexWriter writer) {
+ return;
+ }
+
}
public abstract class Provider {
@@ -1272,6 +1283,45 @@
Console.WriteLine ("Documentation index updated");
}
+ // Search Index
+ public SearchableIndex GetSearchIndex ()
+ {
+ return SearchableIndex.Load (Path.Combine (basedir, "search_index"));
+ }
+
+ public static void MakeSearchIndex ()
+ {
+ // Loads the RootTree
+ Console.WriteLine ("Loading the monodoc tree...");
+ RootTree root = LoadTree ();
+ if (root == null)
+ return;
+
+ string dir = Path.Combine (root.basedir, "search_index");
+ IndexWriter writer;
+ //try to create the dir to store the index
+ try {
+ if (!Directory.Exists (dir))
+ Directory.CreateDirectory (dir);
+
+ writer = new IndexWriter(Lucene.Net.Store.FSDirectory.GetDirectory(dir, true), new StandardAnalyzer(), true);
+ } catch (UnauthorizedAccessException) {
+ Console.WriteLine ("You don't have permissions to wirte on " + dir);
+ return;
+ }
+
+ //Collect all the documents
+ Console.WriteLine ("Collecting and adding documents...");
+ foreach (HelpSource hs in root.HelpSources)
+ hs.PopulateSearchableIndex (writer);
+
+ //Optimize and close
+ Console.WriteLine ("Closing...");
+ writer.Optimize();
+ writer.Close();
+ }
+
+
public ICollection HelpSources { get { return new ArrayList(help_sources); } }
[System.Runtime.InteropServices.DllImport ("libc")]
Index: browser/ecmaspec-provider.cs
===================================================================
--- browser/ecmaspec-provider.cs (revision 48386)
+++ browser/ecmaspec-provider.cs (working copy)
@@ -16,6 +16,9 @@
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Xml;
+using System.Collections;
+using Monodoc.Lucene.Net.Index;
+using Monodoc.Lucene.Net.Documents;
public class EcmaSpecProvider : Provider {
string basedir;
@@ -165,5 +168,76 @@
return output.ToString ();
}
+
+ public override void PopulateSearchableIndex (IndexWriter writer)
+ {
+ foreach (Node n in Tree.Nodes)
+ AddDocuments (writer, n);
+ }
+ void AddDocuments (IndexWriter writer, Node node)
+ {
+ string url = node.URL;
+ Stream file_stream = GetHelpStream (url.Substring (9));
+ if (file_stream == null) //Error
+ return;
+ XmlDocument xdoc = new XmlDocument ();
+ xdoc.Load (new XmlTextReader (file_stream));
+
+ //Obtain the title
+ XmlNode nelem = xdoc.DocumentElement;
+ string title = nelem.Attributes["number"].Value + ": " + nelem.Attributes["title"].Value;
+
+ //Obtain the text
+ StringBuilder s = new StringBuilder ();
+ GetTextNode (nelem, s);
+ string text = s.ToString ();
+
+ //Obatin the examples
+ StringBuilder s2 = new StringBuilder ();
+ GetExamples (nelem, s2);
+ string examples = s2.ToString ();
+
+ //Write to the Lucene Index all the parts
+ SearchableDocument doc = new SearchableDocument ();
+ doc.title = title;
+ doc.hottext = title.Substring (title.IndexOf (':'));
+ doc.url = url;
+ doc.text = text;
+ doc.examples = examples;
+ writer.AddDocument (doc.LuceneDoc);
+
+ if (node.IsLeaf)
+ return;
+
+ foreach (Node n in node.Nodes)
+ AddDocuments (writer, n);
+ }
+
+ void GetTextNode (XmlNode n, StringBuilder s)
+ {
+ //dont include c# code
+ if (n.Name == "code_example")
+ return;
+ //include all text from nodes
+ if (n.NodeType == XmlNodeType.Text)
+ s.Append (n.Value);
+
+ //recursively explore all nodes
+ if (n.HasChildNodes)
+ foreach (XmlNode n_child in n.ChildNodes)
+ GetTextNode (n_child, s);
+ }
+
+ void GetExamples (XmlNode n, StringBuilder s)
+ {
+ if (n.Name == "code_example") {
+ if (n.FirstChild.Name == "#cdata-section")
+ s.Append (n.FirstChild.Value);
+ } else {
+ if (n.HasChildNodes)
+ foreach (XmlNode n_child in n.ChildNodes)
+ GetExamples (n_child, s);
+ }
+ }
}
}
Index: browser/SearchableIndex.cs
===================================================================
--- browser/SearchableIndex.cs (revision 0)
+++ browser/SearchableIndex.cs (revision 0)
@@ -0,0 +1,119 @@
+//
+//
+// SearchableIndex.cs: Index that uses Lucene to search through the docs
+//
+// Author: Mario Sopena
+//
+
+using System;
+using System.IO;
+using System.Collections;
+// Lucene imports
+using Monodoc.Lucene.Net.Index;
+using Monodoc.Lucene.Net.Documents;
+using Monodoc.Lucene.Net.Analysis;
+using Monodoc.Lucene.Net.Analysis.Standard;
+using Monodoc.Lucene.Net.Search;
+using Monodoc.Lucene.Net.QueryParsers;
+
+namespace Monodoc
+{
+
+//TODO: where do I call searcher.close()
+public class SearchableIndex
+{
+ IndexSearcher searcher;
+ string dir;
+ public string Dir {
+ get {
+ if (dir == null) dir = "search_index";
+ return dir;
+ }
+ set { dir = value; }
+ }
+ public ArrayList Results;
+
+ public static SearchableIndex Load (string dir) {
+ SearchableIndex s = new SearchableIndex ();
+ s.dir = dir;
+ s.Results = new ArrayList (20);
+ try {
+ s.searcher = new IndexSearcher (dir);
+ } catch (IOException) {
+ Console.WriteLine ("Index nonexistent or in bad format");
+ return null;
+ }
+ return s;
+ }
+
+ //
+ // Search the index with term
+ //
+ public Result Search (string term) {
+ try {
+ Query q1 = QueryParser.Parse (term, "hottext", new StandardAnalyzer ());
+ Query q2 = QueryParser.Parse (term, "text", new StandardAnalyzer ());
+ q2.SetBoost (0.7f);
+ Query q3 = QueryParser.Parse (term, "examples", new StandardAnalyzer ());
+ q3.SetBoost (0.5f);
+ BooleanQuery q = new BooleanQuery();
+ q.Add (q1, false, false);
+ q.Add (q2, false, false);
+ q.Add (q3, false, false);
+ Hits hits = searcher.Search(q);
+ Result r = new Result (term, hits);
+ Results.Add (r);
+ return r;
+ } catch (IOException) {
+ Console.WriteLine ("No index in {0}", dir);
+ return null;
+ }
+ }
+
+}
+//
+// An object representing the search term with the results
+//
+public class Result {
+ string term;
+ public string Term {
+ get { return term;}
+ }
+ public Hits hits;
+
+ public int Count {
+ get { return hits.Length(); }
+ }
+ public Document this [int i] {
+ get { return hits.Doc (i); }
+ }
+
+ public string GetTitle (int i)
+ {
+ Document d = hits.Doc (i);
+ if (d == null)
+ return "";
+ else
+ return d.Get ("title");
+ }
+ public string GetUrl (int i)
+ {
+ Document d = hits.Doc (i);
+ if (d == null)
+ return "";
+ else
+ return d.Get ("url");
+
+ }
+ public float Score (int i)
+ {
+ return hits.Score (i);
+ }
+ public Result (string Term, Hits hits)
+ {
+ this.term = Term;
+ this.hits = hits;
+ }
+}
+}
+
Index: browser/ChangeLog
===================================================================
--- browser/ChangeLog (revision 49194)
+++ browser/ChangeLog (working copy)
@@ -1,4 +1,17 @@
2005-08-31 Mario Sopena Novales <[email protected]>
+ Implement basic searching capabilities
+ * provider.cs:
+ - Add a method (PopulateSearchableIndex) to HelpSource to generate
+ the searchable index which every subclass should implement
+ - Added GetSearchIndex (retrieve the searchable index) and
+ MakeSearchIndex (entry point for the creation of the index) to RootTree
+ * ecmaspec-provider.cs: Implement PopulateSearchableIndex
+ * ecma-provider.cs: Implement PopulateSearchableIndex
+ * SearchableDocument.cs: added. Abstracts the Lucene Document model
+ * SearchableIndex.cs: added. For searching the index
+ * Makefile.am: Added new files and Lucene sources
+
+2005-08-31 Mario Sopena Novales <[email protected]>
* editing.cs:
- Add a new Attribute NodeUrl to Change
- When saving changes, fill the new Attribute NodeUrl
Index: browser/SearchableDocument.cs
===================================================================
--- browser/SearchableDocument.cs (revision 0)
+++ browser/SearchableDocument.cs (revision 0)
@@ -0,0 +1,29 @@
+//
+//
+// SearchableDocument.cs: Abstracts our model of document from the Lucene Document
+//
+// Author: Mario Sopena
+//
+using Monodoc.Lucene.Net.Documents;
+
+namespace Monodoc {
+struct SearchableDocument {
+ public string title;
+ public string url;
+ public string hottext;
+ public string text;
+ public string examples;
+
+ public Document LuceneDoc {
+ get {
+ Document doc = new Document ();
+ doc.Add (Field.UnIndexed ("title", title));
+ doc.Add (Field.UnIndexed ("url", url));
+ doc.Add (Field.UnStored ("hottext", hottext));
+ doc.Add (Field.UnStored ("text", text));
+ doc.Add (Field.UnStored ("examples", examples));
+ return doc;
+ }
+ }
+}
+}
Index: browser/ecma-provider.cs
===================================================================
--- browser/ecma-provider.cs (revision 49194)
+++ browser/ecma-provider.cs (working copy)
@@ -25,6 +25,8 @@
using System.Text;
using System.Collections;
using ICSharpCode.SharpZipLib.Zip;
+using Monodoc.Lucene.Net.Index;
+using Monodoc.Lucene.Net.Documents;
using BF = System.Reflection.BindingFlags;
@@ -1513,6 +1515,205 @@
}
}
}
+ //
+ // Create list of documents for searching
+ //
+ public override void PopulateSearchableIndex (IndexWriter writer)
+ {
+ StringBuilder text;
+ foreach (Node ns_node in Tree.Nodes) {
+ Console.WriteLine ("\tNamespace: {0} ({1})", ns_node.Caption, ns_node.Nodes.Count);
+ foreach (Node type_node in ns_node.Nodes) {
+ string typename = type_node.Caption.Substring (0, type_node.Caption.IndexOf (' '));
+ string full = ns_node.Caption + "." + typename;
+ string doc_tag = GetKindFromCaption (type_node.Caption);
+ string url = "T:" + full;
+ string rest;
+ XmlDocument xdoc = GetXmlFromUrl (type_node.URL, out rest);
+ if (xdoc == null)
+ continue;
+
+ //
+ // For classes, structures or interfaces add a doc for the overview and
+ // add a doc for every constructor, method, event, ...
+ //
+ if (doc_tag == "Class" || doc_tag == "Structure" || doc_tag == "Interface"){
+
+ // Adds a doc for every overview of every type
+ SearchableDocument doc = new SearchableDocument ();
+ doc.title = type_node.Caption;
+ doc.hottext = typename;
+ doc.url = url;
+
+ XmlNode node_sel = xdoc.SelectSingleNode ("/Type/Docs");
+ text = new StringBuilder ();
+ GetTextFromNode (node_sel, text);
+ doc.text = text.ToString ();
+
+ text = new StringBuilder ();
+ GetExamples (node_sel, text);
+ doc.examples = text.ToString ();
+
+ writer.AddDocument (doc.LuceneDoc);
+
+ //Add docs for contructors, methods, etc.
+ foreach (Node c in type_node.Nodes) { // c = Constructors || Fields || Events || Properties || Methods || Operators
+
+ if (c.Element == "*")
+ continue;
+ int i = 1;
+ foreach (Node nc in c.Nodes) {
+ //xpath to the docs xml node
+ string xpath;
+ if (c.Caption == "Constructors")
+ xpath = String.Format ("/Type/Members/Member[{0}]/Docs", i++);
+ else if (c.Caption == "Operators")
+ xpath = String.Format ("/Type/Members/Member[@MemberName='op_{0}']/Docs", nc.Caption);
+ else
+ xpath = String.Format ("/Type/Members/Member[@MemberName='{0}']/Docs", nc.Caption);
+ //construct url of the form M:Array.Sort
+ string urlnc;
+ if (c.Caption == "Constructors")
+ urlnc = String.Format ("{0}:{1}.{2}", c.Caption[0], ns_node.Caption, nc.Caption);
+ else
+ urlnc = String.Format ("{0}:{1}.{2}.{3}", c.Caption[0], ns_node.Caption, typename, nc.Caption);
+
+ //create the doc
+ SearchableDocument doc_nod = new SearchableDocument ();
+ doc_nod.title = LargeName (nc);
+ //dont add the parameters to the hottext
+ int ppos = nc.Caption.IndexOf ('(');
+ if (ppos != -1)
+ doc_nod.hottext = nc.Caption.Substring (0, ppos);
+ else
+ doc_nod.hottext = nc.Caption;
+
+ doc_nod.url = urlnc;
+
+ XmlNode xmln = xdoc.SelectSingleNode (xpath);
+ if (xmln == null) {
+ Console.WriteLine ("Problem: {0}, with xpath: {1}", urlnc, xpath);
+ continue;
+ }
+
+ text = new StringBuilder ();
+ GetTextFromNode (xmln, text);
+ doc_nod.text = text.ToString ();
+
+ text = new StringBuilder ();
+ GetExamples (xmln, text);
+ doc_nod.examples = text.ToString ();
+
+ writer.AddDocument (doc_nod.LuceneDoc);
+ }
+ }
+ //
+ // Enumerations: add the enumeration values
+ //
+ } else if (doc_tag == "Enumeration"){
+
+ XmlNodeList members = xdoc.SelectNodes ("/Type/Members/Member");
+ if (members == null)
+ continue;
+
+ text = new StringBuilder ();
+ foreach (XmlNode member_node in members) {
+ string enum_value = member_node.Attributes ["MemberName"].InnerText;
+ text.Append (enum_value);
+ text.Append (" ");
+ GetTextFromNode (member_node["Docs"], text);
+ text.Append ("\n");
+ }
+ SearchableDocument doc = new SearchableDocument ();
+
+ text = new StringBuilder ();
+ GetExamples (xdoc.SelectSingleNode ("/Type/Docs"), text);
+ doc.examples = text.ToString ();
+
+ doc.title = type_node.Caption;
+ doc.hottext = xdoc.DocumentElement.Attributes["Name"].Value;
+ doc.url = url;
+ doc.text = text.ToString();
+ writer.AddDocument (doc.LuceneDoc);
+ //
+ // Add delegates
+ //
+ } else if (doc_tag == "Delegate"){
+ SearchableDocument doc = new SearchableDocument ();
+ doc.title = type_node.Caption;
+ doc.hottext = xdoc.DocumentElement.Attributes["Name"].Value;
+ doc.url = url;
+
+ XmlNode node_sel = xdoc.SelectSingleNode ("/Type/Docs");
+
+ text = new StringBuilder ();
+ GetTextFromNode (node_sel, text);
+ doc.text = text.ToString();
+
+ text = new StringBuilder ();
+ GetExamples (node_sel, text);
+ doc.examples = text.ToString();
+
+ writer.AddDocument (doc.LuceneDoc);
+ }
+ }
+ }
+ }
+
+ //
+ // Extract the interesting text from the docs node
+ //
+ void GetTextFromNode (XmlNode n, StringBuilder sb)
+ {
+ //don't include example code
+ if (n.Name == "code")
+ return;
+
+ //include the url to which points the see tag
+ if (n.Name == "see" && n.Attributes.Count > 0)
+ sb.Append (n.Attributes [0].Value);
+
+ //include the name of the parameter
+ if (n.Name == "paramref" && n.Attributes.Count > 0)
+ sb.Append (n.Attributes [0].Value);
+
+ //include the contents for the node that contains text
+ if (n.NodeType == XmlNodeType.Text)
+ sb.Append (n.Value);
+
+ //add the rest of xml tags recursively
+ if (n.HasChildNodes)
+ foreach (XmlNode n_child in n.ChildNodes)
+ GetTextFromNode (n_child, sb);
+ }
+ //
+ // Extract the code nodes from the docs
+ //
+ void GetExamples (XmlNode n, StringBuilder sb)
+ {
+ if (n.Name == "code") {
+ sb.Append (n.InnerText);
+ } else {
+ if (n.HasChildNodes)
+ foreach (XmlNode n_child in n.ChildNodes)
+ GetExamples (n_child, sb);
+ }
+ }
+ //
+ // Extract a large name for the Node
+ // (copied from mono-tools/docbrowser/browser.Render()
+ static string LargeName (Node matched_node)
+ {
+ string[] parts = matched_node.URL.Split('/', '#');
+ if(parts.Length == 3 && parts[2] != String.Empty) { //List of Members, properties, events, ...
+ return parts[1] + ": " + matched_node.Caption;
+ } else if(parts.Length >= 4) { //Showing a concrete Member, property, ...
+ return parts[1] + "." + matched_node.Caption;
+ } else {
+ return matched_node.Caption;
+ }
+ }
+
}
public class EcmaUncompiledHelpSource : EcmaHelpSource {
Index: browser/Makefile.am
===================================================================
--- browser/Makefile.am (revision 49194)
+++ browser/Makefile.am (working copy)
@@ -27,8 +27,13 @@
$(srcdir)/settings.cs \
$(srcdir)/commentservice.cs \
$(srcdir)/XmlNodeWriter.cs \
+ $(srcdir)/SearchableIndex.cs \
+ $(srcdir)/SearchableDocument.cs \
AssemblyInfo.cs
+lucene_sources = \
+ $(srcdir)/Lucene.Net.dll.sources
+
assembler_sources = \
$(srcdir)/assembler.cs
@@ -74,7 +79,7 @@
cp $(top_srcdir)/mono.pub .
monodoc.dll: $(monodoc_sources) mono-ecma.xsl mono.pub ecmaspec-html-css.xsl ecmaspec.css base.css mono-ecma-css.xsl mono-ecma.css home.html
- $(CSC) -debug -out:monodoc.dll -target:library /resource:$(srcdir)/mono-ecma.xsl,mono-ecma.xsl /resource:$(srcdir)/ecmaspec-html.xsl,ecmaspec-html.xsl /resource:$(srcdir)/ecmaspec-html-css.xsl,ecmaspec-html-css.xsl /resource:$(srcdir)/base.css,base.css /resource:$(srcdir)/ecmaspec.css,ecmaspec.css /resource:$(srcdir)/mono-ecma-css.xsl,mono-ecma-css.xsl /resource:$(srcdir)/mono-ecma.css,mono-ecma.css /resource:$(srcdir)/home.html,home.html $(monodoc_sources) -r:ICSharpCode.SharpZipLib.dll -r:System.Web -r:System.Web.Services
+ $(CSC) -debug -out:monodoc.dll -target:library /resource:$(srcdir)/mono-ecma.xsl,mono-ecma.xsl /resource:$(srcdir)/ecmaspec-html.xsl,ecmaspec-html.xsl /resource:$(srcdir)/ecmaspec-html-css.xsl,ecmaspec-html-css.xsl /resource:$(srcdir)/base.css,base.css /resource:$(srcdir)/ecmaspec.css,ecmaspec.css /resource:$(srcdir)/mono-ecma-css.xsl,mono-ecma-css.xsl /resource:$(srcdir)/mono-ecma.css,mono-ecma.css /resource:$(srcdir)/home.html,home.html $(monodoc_sources) @$(lucene_sources) -r:ICSharpCode.SharpZipLib.dll -r:System.Web -r:System.Web.Services
monodoc.dll.config: $(srcdir)/monodoc.dll.config.in Makefile
if sed 's,@''monodoc_refdir@,$(monodoc_refdir),' $(srcdir)/monodoc.dll.config.in > $@t; then mv $@t $@; else rm -f $@t ; exit 1; fi
docbrowser.diff
(text/x-patch, 13 KB)
Index: docbrowser/ChangeLog =================================================================== --- docbrowser/ChangeLog (revision 49195) +++ docbrowser/ChangeLog (working copy) @@ -1,3 +1,15 @@ +2005-08-31 Mario Sopena Novales <[email protected]> + Implement basic searching capabilities + * browser.cs: + - Added a new "--make-search-index" parameter + - Added a TreeView to show the search results + - Added a function to Highlight the search result + * browser.glade: + - Repair index and search icons + - Added a TreeView to show the search results + * monodoc.in: + - Added a new "--make-search-index" parameter + 2005-08-22 Mario Sopena Novales <[email protected]> * browser.cs: - Update the treeview everytime we change the tab Index: docbrowser/browser.cs =================================================================== --- docbrowser/browser.cs (revision 49195) +++ docbrowser/browser.cs (working copy) @@ -47,6 +47,10 @@ RootTree.MakeIndex (); return 0; + case "--make-search-index": + RootTree.MakeSearchIndex (); + return 0; + case "--help": Console.WriteLine ("Options are:\n"+ "browser [--html TOPIC] [--make-index] [TOPIC] [--merge-changes CHANGE_FILE TARGET_DIR+]"); @@ -148,6 +152,16 @@ Gdk.Pixbuf monodoc_pixbuf; + // + // Used for searching + // + [Glade.Widget] Entry search_term; + [Glade.Widget] TreeView search_tree; + [Glade.Widget] ScrolledWindow scrolledwindow_search; + TreeStore search_store; + SearchableIndex search_index; + string highlight_text; + // // Left-hand side Browsers // @@ -261,6 +275,21 @@ // // Other bits // + search_index = help_tree.GetSearchIndex(); + if (search_index == null) { + search_term.Editable = false; + Gtk.Label l = new Gtk.Label ("<b>No search index found</b>\n\n" + + "as root, run:\n\n monodoc --make-search-index\n\nto create the index"); + l.UseMarkup = true; + l.Show (); + scrolledwindow_search.Remove (search_tree); + scrolledwindow_search.Add (l); + } else { + search_store = new TreeStore (typeof (string)); + search_tree.Model = search_store; + search_tree.AppendColumn ("Searches", new CellRendererText(), "text", 0); + search_tree.Selection.Changed += new EventHandler (ShowSearchResult); + } bookList = new ArrayList (); index_browser = IndexBrowser.MakeIndexBrowser (this); @@ -311,6 +340,62 @@ if (tree_browser.SelectedNode != CurrentTab.CurrentNode) tree_browser.ShowNode (CurrentTab.CurrentNode); } + + // + // Invoked when the user presses enter on the search_entry + // + void OnSearchActivated (object sender, EventArgs a) + { + search_tree.Model = null; + search_term.Editable = false; + string term = search_term.Text; + //search in the index + Result r = search_index.Search (term); + if (r == null) + return; //There was a problem with the index + //insert the results in the tree + TreeIter iter; + + int max = r.Count > 500? 500:r.Count; + iter = search_store.AppendValues (r.Term + " (" + max + " hits)"); + for (int i = 0; i < max; i++) + search_store.AppendValues (iter, r.GetTitle(i)); + + // Show the results + search_tree.Model = search_store; + search_tree.CollapseAll(); + TreePath p = search_store.GetPath (iter); + search_tree.ExpandToPath (p); + search_tree.Selection.SelectPath (p); + search_term.Editable = true; + } + // + // Invoked when the user click on one of the search results + // + void ShowSearchResult (object sender, EventArgs a) + { + CurrentTab.SetMode (Mode.Viewer); + + Gtk.TreeIter iter; + Gtk.TreeModel model; + + bool selected = search_tree.Selection.GetSelected (out model, out iter); + if (!selected) + return; + + TreePath p = model.GetPath (iter); + if (p.Depth < 2) + return; + int i_0 = p.Indices [0]; + int i_1 = p.Indices [1]; + Result res = (Result) search_index.Results [i_0]; + TreeIter parent; + model.IterParent (out parent, iter); + string term = (string) search_store.GetValue (parent, 0); + highlight_text = term.Substring (0, term.IndexOf ("(")-1); + LoadUrl (res.GetUrl (i_1)); + } + // // Reload current page // @@ -455,6 +540,9 @@ { CurrentUrl = url; CurrentTab.CurrentNode = matched_node; + if (highlight_text != null) + text = DoHighlightText (text); + CurrentTab.html.Render(text); if (matched_node != null) { if (tree_browser.SelectedNode != matched_node) @@ -502,6 +590,62 @@ } // + // Highlights the text of the search + // + // we have to highligh everything that is not inside < and > + string DoHighlightText (string text) { + System.Text.StringBuilder sb = new System.Text.StringBuilder (text); + + //search for the term to highlight in a lower case version of the text + string text_low = text.ToLower(); + string term_low = highlight_text.ToLower(); + + //search for < and > so we dont substitute text of html tags + ArrayList lt = new ArrayList(); + ArrayList gt = new ArrayList(); + int ini = 0; + ini = text_low.IndexOf ('<', ini, text_low.Length); + while (ini != -1) { + lt.Add (ini); + ini = text_low.IndexOf ('<', ini+1, text_low.Length-ini-1); + } + ini = 0; + ini = text_low.IndexOf ('>', ini, text_low.Length); + while (ini != -1) { + gt.Add (ini); + ini = text_low.IndexOf ('>', ini+1, text_low.Length-ini-1); + } + //start searching for the term + int offset = 0; + int p = 0; + ini = 0; + ini = text_low.IndexOf (term_low, ini, text_low.Length); + while (ini != -1) { + bool beforeLt = ini < (int) lt [p]; + //look if term is inside any html tag + while (!beforeLt) { + bool afterGt = ini > (int) gt [p]; + if (afterGt) { + p++; + beforeLt = ini < (int) lt [p]; + continue; + } else { + goto ExtLoop; + } + } + string t = sb.ToString (ini + offset, term_low.Length); + sb.Remove (ini + offset, term_low.Length); + sb.Insert (ini + offset, "<span style=\"background: yellow\">" + t + "</span>"); + offset += 40; //due to the <span> tag inserted + +ExtLoop: + ini = text_low.IndexOf (term_low, ini+1, text_low.Length-ini-1); + } + + highlight_text = null; //only highlight when a search result is clicked + return sb.ToString(); + } + // // Invoked when the mouse is over a link // string last_url = ""; Index: docbrowser/browser.glade =================================================================== --- docbrowser/browser.glade (revision 48726) +++ docbrowser/browser.glade (working copy) @@ -607,7 +607,7 @@ <child> <widget class="GtkImage" id="image47"> <property name="visible">True</property> - <property name="stock">gtk-find</property> + <property name="stock">gtk-index</property> <property name="icon_size">1</property> <property name="xalign">0.5</property> <property name="yalign">0.5</property> @@ -799,18 +799,119 @@ </child> <child> - <widget class="GtkLabel" id="label5"> + <widget class="GtkVBox" id="search_vbox"> + <property name="border_width">3</property> <property name="visible">True</property> - <property name="label" translatable="yes">n/a</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkHBox" id="hbox35"> + <property name="border_width">3</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">3</property> + + <child> + <widget class="GtkImage" id="image133"> + <property name="visible">True</property> + <property name="stock">gtk-find</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label66"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Search for:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">index_entry</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="search_term"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">False</property> + <signal name="activate" handler="OnSearchActivated" last_modification_time="Wed, 13 Jul 2005 23:36:43 GMT"/> + </widget> + <packing> + <property name="padding">3</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow_search"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property> + <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + <signal name="row_activated" handler="ShowSearchResult" /> + + <child> + <widget class="GtkTreeView" id="search_tree"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + <property name="rules_hint">False</property> + <property name="reorderable">False</property> + <property name="enable_search">True</property> + <property name="fixed_height_mode">False</property> + <property name="hover_selection">False</property> + <property name="hover_expand">False</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> </widget> <packing> <property name="tab_expand">False</property> Index: docbrowser/monodoc.in =================================================================== --- docbrowser/monodoc.in (revision 48726) +++ docbrowser/monodoc.in (working copy) @@ -43,10 +43,11 @@ echo " TOPIC Start the browser at TOPIC" echo " (ex. N:System, T:System.Object, M:System.Object.Equals," echo " and P: for properties, F: for fields, E: for events, etc.)" - echo " --help Print this message" - echo " --html TOPIC Print the HTML contents of TOPIC" - echo " --make-index Create the documentation index" - echo " --no-gecko Don't use Mozilla to render the contents" + echo " --help Print this message" + echo " --html TOPIC Print the HTML contents of TOPIC" + echo " --make-index Create the documentation index" + echo " --make-search-index Create the searchable documentation index" + echo " --no-gecko Don't use Mozilla to render the contents" echo echo "The following options are available for authoring documentation:" echo " --edit path Edit (unassembled) documentation at path"