diff options
author | Mike Krüger <mkrueger@novell.com> | 2010-04-23 17:23:56 +0400 |
---|---|---|
committer | Mike Krüger <mkrueger@novell.com> | 2010-04-23 17:23:56 +0400 |
commit | b64eb3ed14823853adbcf4f108f683f1e676e010 (patch) | |
tree | 44572477c60fdd25b67845c5a422beddd2a7c0a8 /main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog | |
parent | 50702a45715deb37035e2568f75a7e88886decc1 (diff) |
* Makefile.am:
* gtk-gui/gui.stetic:
* MonoDevelop.Ide.csproj:
* MonoDevelop.Ide.addin.xml:
* MonoDevelop.Ide.NavigateToDialog:
* MonoDevelop.Components/ListView.cs:
* MonoDevelop.Ide.Gui.Dialogs/GoToDialog.cs:
* MonoDevelop.Ide.Commands/SearchCommands.cs:
* gtk-gui/MonoDevelop.Ide.Gui.Dialogs.GoToDialog.cs:
* MonoDevelop.Ide.NavigateToDialog/NavigateToDialog.cs:
* MonoDevelop.Ide.NavigateToDialog/NavigateToCommand.cs:
* gtk-gui/MonoDevelop.Ide.NavigateToDialog.NavigateToDialog.cs: Worked
on go to dialog. (go to file/type are unaffected in function - only
the gui changed). Added 'Navigate To' command which allows to
navigate to any file/type/member.
svn path=/trunk/monodevelop/; revision=156006
Diffstat (limited to 'main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog')
-rw-r--r-- | main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog/NavigateToCommand.cs | 108 | ||||
-rw-r--r-- | main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog/NavigateToDialog.cs | 1010 |
2 files changed, 1118 insertions, 0 deletions
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog/NavigateToCommand.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog/NavigateToCommand.cs new file mode 100644 index 0000000000..46e6db921d --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog/NavigateToCommand.cs @@ -0,0 +1,108 @@ +// +// NavigateToCommand.cs +// +// Author: +// Mike Krüger <mkrueger@novell.com> +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Gtk; +using MonoDevelop.Components.Commands; +using MonoDevelop.Core; + +namespace MonoDevelop.Ide.NavigateToDialog +{ + public enum Commands { + NavigateTo + } + + class GotoTypeHandler : CommandHandler + { + protected override void Run () + { + NavigateToDialog dialog = new NavigateToDialog (NavigateToType.Types, false); + dialog.Title = GettextCatalog.GetString ("Go to Type"); + dialog.TransientFor = MonoDevelop.Ide.IdeApp.Workbench.RootWindow; + try { + if ((ResponseType)dialog.Run () == ResponseType.Ok) { + dialog.Sensitive = false; + foreach (var loc in dialog.Locations) + IdeApp.Workbench.OpenDocument (loc.Filename, loc.Line, loc.Column, true); + } + } finally { + dialog.Destroy (); + } + } + + protected override void Update (CommandInfo info) + { + info.Enabled = IdeApp.Workspace.IsOpen || IdeApp.Workbench.Documents.Count != 0; + } + } + + class GotoFileHandler : CommandHandler + { + protected override void Run () + { + NavigateToDialog dialog = new NavigateToDialog (NavigateToType.Files, false); + dialog.Title = GettextCatalog.GetString ("Go to File"); + dialog.TransientFor = MonoDevelop.Ide.IdeApp.Workbench.RootWindow; + try { + if ((ResponseType)dialog.Run () == ResponseType.Ok) { + dialog.Sensitive = false; + foreach (var loc in dialog.Locations) + IdeApp.Workbench.OpenDocument (loc.Filename, loc.Line, loc.Column, true); + } + } finally { + dialog.Destroy (); + } + } + + protected override void Update (CommandInfo info) + { + info.Enabled = IdeApp.Workspace.IsOpen || IdeApp.Workbench.Documents.Count != 0; + } + } + + class NavigateToHandler : CommandHandler + { + protected override void Run () + { + NavigateToDialog dialog = new NavigateToDialog (NavigateToType.All, true); + dialog.TransientFor = MonoDevelop.Ide.IdeApp.Workbench.RootWindow; + try { + if ((ResponseType)dialog.Run () == ResponseType.Ok) { + dialog.Sensitive = false; + foreach (var loc in dialog.Locations) + IdeApp.Workbench.OpenDocument (loc.Filename, loc.Line, loc.Column, true); + } + } finally { + dialog.Destroy (); + } + } + + protected override void Update (CommandInfo info) + { + info.Enabled = IdeApp.Workspace.IsOpen || IdeApp.Workbench.Documents.Count != 0; + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog/NavigateToDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog/NavigateToDialog.cs new file mode 100644 index 0000000000..9887aba251 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.NavigateToDialog/NavigateToDialog.cs @@ -0,0 +1,1010 @@ +// +// NavigateToDialog.cs +// +// Author: +// Zach Lute (zach.lute@gmail.com) +// Aaron Bockover (abockover@novell.com) +// Jacob Ilsø Christensen +// Lluis Sanchez +// Mike Krüger <mkrueger@novell.com> +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading; +using Gdk; +using Gtk; +using MonoDevelop.Projects; +using MonoDevelop.Projects.Dom; +using MonoDevelop.Projects.Dom.Parser; +using MonoDevelop.Components; +using MonoDevelop.Core; +using MonoDevelop.Core.Instrumentation; +using MonoDevelop.Ide.Gui; +using MonoDevelop.Projects.Dom.Output; +using MonoDevelop.Ide.CodeCompletion; + +namespace MonoDevelop.Ide.NavigateToDialog +{ + [Flags] + public enum NavigateToType { + Files = 1, + Types = 2, + Members = 4, + All = Files | Types | Members, + NonMembers = Files | Types + } + + partial class NavigateToDialog : Gtk.Dialog + { + ListView list; + ResultsDataSource currentResults; + + object matchLock = new object (); + string matchString = ""; + + // Thread management + Thread searchThread; + AutoResetEvent searchThreadWait; + bool searchCycleActive; + bool searchThreadDispose; + + public NavigateToType NavigateToType { + get; + set; + } + public struct OpenLocation + { + public string Filename; + public int Line; + public int Column; + + public OpenLocation (string filename, int line, int column) + { + this.Filename = filename; + this.Line = line; + this.Column = column; + } + } + + List<OpenLocation> locations = new List<OpenLocation> (); + public IEnumerable<OpenLocation> Locations { + get { + return locations.ToArray (); + } + } + bool isAbleToSearchMembers; + public NavigateToDialog (NavigateToType navigateTo, bool isAbleToSearchMembers) + { + this.NavigateToType = navigateTo; + this.isAbleToSearchMembers = isAbleToSearchMembers; + this.Build (); + this.label1.MnemonicWidget = matchEntry.Entry; + this.matchEntry.Ready = true; + this.matchEntry.Visible = true; + this.matchEntry.IsCheckMenu = true; + + CheckMenuItem includeFilesItem = this.matchEntry.AddFilterOption (0, GettextCatalog.GetString ("Include _Files")); + includeFilesItem.DrawAsRadio = false; + includeFilesItem.Active = (navigateTo & NavigateToType.Files) == NavigateToType.Files; + includeFilesItem.Toggled += delegate { + if (includeFilesItem.Active) { + this.NavigateToType |= NavigateToType.Files; + } else { + this.NavigateToType &= ~NavigateToType.Files; + } + PerformSearch (); + }; + + CheckMenuItem includeTypes = this.matchEntry.AddFilterOption (1, GettextCatalog.GetString ("Include _Types")); + includeTypes.DrawAsRadio = false; + includeTypes.Active = (navigateTo & NavigateToType.Files) == NavigateToType.Files; + includeTypes.Toggled += delegate { + if (includeTypes.Active) { + this.NavigateToType |= NavigateToType.Types; + } else { + this.NavigateToType &= ~NavigateToType.Types; + } + PerformSearch (); + }; + + if (this.isAbleToSearchMembers) { + CheckMenuItem includeMembers = this.matchEntry.AddFilterOption (2, GettextCatalog.GetString ("Include _Members")); + includeMembers.DrawAsRadio = false; + includeMembers.Active = (navigateTo & NavigateToType.Members) == NavigateToType.Members; + includeMembers.Toggled += delegate { + if (includeTypes.Active) { + this.NavigateToType |= NavigateToType.Members; + } else { + this.NavigateToType &= ~NavigateToType.Members; + } + PerformSearch (); + }; + } + + this.matchEntry.Changed += delegate { + PerformSearch (); + }; + SetupTreeView (); + this.labelResults.MnemonicWidget = list; + + StartCollectThreads (); + this.matchEntry.Entry.KeyPressEvent += HandleKeyPress; + this.matchEntry.Activated += delegate { + OpenFile (); + }; + this.buttonOpen.Clicked += delegate { + OpenFile (); + }; + + this.matchEntry.Entry.GrabFocus (); + } + + Thread collectFiles, collectTypes, collectMembers; + void StartCollectThreads () + { + StartCollectFiles (); + StartCollectTypes (); + } + + static TimerCounter getMembersTimer = InstrumentationService.CreateTimerCounter ("Time to get all members", "NavigateToDialog"); + + void StartCollectMembers () + { + collectMembers = new Thread (new ThreadStart (delegate { + Console.WriteLine ("start"); + DateTime t = DateTime.Now; + getMembersTimer.BeginTiming (); + try { + members = new List<IMember> (); + foreach (IType type in types) { + foreach (IMember m in type.Members) { + if (m is IType) + continue; + members.Add (m); + } + } + } finally { + getMembersTimer.EndTiming (); + Console.WriteLine ("done" + (DateTime.Now - t).TotalMilliseconds); + } + })); + collectMembers.IsBackground = true; + collectMembers.Name = "Navigate to: Collect Members"; + collectMembers.Priority = ThreadPriority.Lowest; + collectMembers.Start (); + } + + void StartCollectTypes () + { + collectTypes = new Thread (new ThreadStart (delegate { + types = GetTypes (); + if (isAbleToSearchMembers) + StartCollectMembers (); + })); + collectTypes.IsBackground = true; + collectTypes.Name = "Navigate to: Collect Types"; + collectTypes.Priority = ThreadPriority.Lowest; + collectTypes.Start (); + } + + void StartCollectFiles () + { + collectFiles= new Thread (new ThreadStart (delegate { + files = GetFiles (); + })); + collectFiles.IsBackground = true; + collectFiles.Name = "Navigate to: Collect Files"; + collectFiles.Priority = ThreadPriority.Lowest; + collectFiles.Start (); + } + + void SetupTreeView () + { + list = new ListView (); + list.AllowMultipleSelection = true; + currentResults = new ResultsDataSource (); + list.DataSource = currentResults; + list.Show (); + list.ItemActivated += delegate { + OpenFile (); + }; + scrolledwindow1.Add (list); + } + + void OpenFile () + { + locations.Clear (); + if (list.SelectedRows.Count != 0) { + foreach (int sel in list.SelectedRows) { + SearchResult res = currentResults [sel]; + OpenLocation loc = new OpenLocation (res.File, res.Row, res.Column); + if (loc.Line == -1) { + int i = matchEntry.Query.LastIndexOf (':'); + if (i != -1) { + if (!int.TryParse (matchEntry.Query.Substring (i+1), out loc.Line)) + loc.Line = -1; + } + } + locations.Add (loc); + } + Respond (ResponseType.Ok); + } else { + Respond (ResponseType.Cancel); + } + } + + void StopActiveSearch () + { + // Tell the thread's search code that it should stop working and + // then have the thread wait on the handle until told to resume + if (searchCycleActive && searchThread != null && searchThreadWait != null) { + searchCycleActive = false; + searchThreadWait.Reset (); + } + } + + void PerformSearch () + { + StopActiveSearch (); + + string toMatch = matchEntry.Query.ToLower (); + lock (matchLock) { + matchString = toMatch; + savedMatches.Clear (); + } + if (string.IsNullOrEmpty (toMatch)) { + list.DataSource = currentResults = new ResultsDataSource (); + labelResults.LabelProp = GettextCatalog.GetString ("_Results: Enter search term to start."); + return; + } + + if (!string.IsNullOrEmpty (previousPattern) && toMatch.StartsWith (previousPattern)) { + list.DataSource = currentResults = new ResultsDataSource (); + } + + if (searchThread == null) { + // Create the handle the search thread will wait on when there is nothing to do + searchThreadWait = new AutoResetEvent (false); + + // Only a single thread will be used for searching + ThreadStart start = new ThreadStart (SearchThread); + searchThread = new Thread (start); + searchThread.IsBackground = true; + searchThread.Name = "Navigate to thread"; + searchThread.Priority = ThreadPriority.Lowest; + searchThread.Start (); + } + + // Wake the handle up so the search thread can do some work + searchCycleActive = true; + searchThreadWait.Set (); + } + + void SearchThread () + { + // The thread will remain active until the dialog goes away + while (true) { + searchThreadWait.WaitOne (); + if (searchThreadDispose) { + break; + } + + try { + SearchThreadCycle (); + } catch (Exception ex) { + LoggingService.LogError ("Exception in NavigateToDialog", ex); + } + } + + // Reset all thread state even though this shouldn't be + // necessary since we destroy and never reuse the dialog + searchCycleActive = false; + searchThreadDispose = false; + + searchThreadWait.Close (); + searchThreadWait = null; + searchThread = null; + } + + IEnumerable<ProjectFile> files; + List<IType> types; + List<IMember> members; + + List<ProjectFile> filteredFiles; + List<IType> filteredTypes; + List<IMember> filteredMembers; + + string previousPattern; + + void SearchThreadCycle () + { + // This is the inner thread worker; it actually does the searching + // Any where we enter loop, a check is added to see if the search + // should be aborted entirely so we can return to the wait handle + + ResultsDataSource results = new ResultsDataSource (); + + foreach (SearchResult result in AllResults ()) { + if (!searchCycleActive) + return; + results.AddResult (result); + } + + if (!searchCycleActive) + return; + results.Sort (new DataItemComparer ()); + + int best = results.IndexOf (results.BestResult); + if (best == -1) + best = 0; + + Application.Invoke (delegate { + list.DataSource = results; + currentResults = results; + list.SelectedRow = best; + list.CenterViewToSelection (); + labelResults.LabelProp = String.Format (GettextCatalog.GetPluralString ("_Results: {0} match found.", "_Results: {0} matches found.", results.ItemCount), results.ItemCount); + }); + } + + IEnumerable<SearchResult> AllResults () + { + string toMatch = matchString; + int i = toMatch.IndexOf (':'); + if (i != -1) + toMatch = toMatch.Substring (0,i); + + // Search files + if ((NavigateToType & NavigateToType.Files) == NavigateToType.Files) { + WaitForCollectFiles (); + List<ProjectFile> newFilteredFiles = new List<ProjectFile> (); + bool startsWithLastFilter = previousPattern != null && toMatch.StartsWith (previousPattern) && filteredFiles != null; + IEnumerable<ProjectFile> allFiles = startsWithLastFilter ? filteredFiles : files; + foreach (ProjectFile file in allFiles) { + if (!searchCycleActive) + yield break; + SearchResult curResult = CheckFile (file, toMatch); + if (curResult != null) { + newFilteredFiles.Add (file); + yield return curResult; + } + } + filteredFiles = newFilteredFiles; + } else { + filteredFiles = null; + } + + // Search Types + if ((NavigateToType & NavigateToType.Types) == NavigateToType.Types) { + WaitForCollectTypes (); + List<IType> newFilteredTypes = new List<IType> (); + bool startsWithLastFilter = previousPattern != null && toMatch.StartsWith (previousPattern) && filteredTypes != null; + List<IType> allTypes = startsWithLastFilter ? filteredTypes : types; + foreach (IType type in allTypes) { + if (!searchCycleActive) + yield break; + SearchResult curResult = CheckType (type, toMatch); + if (curResult != null) { + newFilteredTypes.Add (type); + yield return curResult; + } + } + filteredTypes = newFilteredTypes; + } else { + filteredTypes = null; + } + + // Search members + if ((NavigateToType & NavigateToType.Members) == NavigateToType.Members) { + WaitForCollectMembers (); + List<IMember> newFilteredMembers = new List<IMember> (); + bool startsWithLastFilter = previousPattern != null && toMatch.StartsWith (previousPattern) && filteredMembers != null; + List<IMember> allMembers = startsWithLastFilter ? filteredMembers : members; + foreach (IMember member in allMembers) { + if (!searchCycleActive) + yield break; + SearchResult curResult = CheckMember (member, toMatch); + if (curResult != null) { + newFilteredMembers.Add (member); + yield return curResult; + } + } + filteredMembers = newFilteredMembers; + } else { + filteredMembers = null; + } + + previousPattern = toMatch; + } + + void WaitForCollectMembers () + { + WaitForCollectTypes (); + if (collectMembers != null) { + collectMembers.Join (); + collectMembers = null; + } + } + + void WaitForCollectTypes () + { + if (collectTypes != null) { + collectTypes.Join (); + collectTypes= null; + } + } + + void WaitForCollectFiles () + { + if (collectFiles != null) { + collectFiles.Join (); + collectFiles = null; + } + } + + class DataItemComparer : IComparer<SearchResult> + { + public int Compare (SearchResult o1, SearchResult o2) + { + return String.CompareOrdinal (o1.PlainText, o2.PlainText); + } + } + + IEnumerable<ProjectFile> GetFiles () + { + HashSet<ProjectFile> list = new HashSet<ProjectFile> (); + foreach (Document doc in IdeApp.Workbench.Documents) { + // We only want to check it here if it's not part + // of the open combine. Otherwise, it will get + // checked down below. + if (doc.Project == null && doc.IsFile) + list.Add (new ProjectFile (doc.Name)); + } + + ReadOnlyCollection<Project> projects = IdeApp.Workspace.GetAllProjects (); + + foreach (Project p in projects) { + foreach (ProjectFile file in p.Files) { + if (file.Subtype != Subtype.Directory) + list.Add (file); + } + } + return list; + } + + static TimerCounter getTypesTimer = InstrumentationService.CreateTimerCounter ("Time to get all types", "NavigateToDialog"); + + List<IType> GetTypes () + { + List<IType> list = new List<IType> (); + getTypesTimer.BeginTiming (); + try { + foreach (Document doc in IdeApp.Workbench.Documents) { + // We only want to check it here if it's not part + // of the open combine. Otherwise, it will get + // checked down below. + if (doc.Project == null && doc.IsFile) { + ICompilationUnit info = doc.CompilationUnit; + if (info != null) { + foreach (IType c in info.Types) { + list.Add (c); + } + } + } + } + + ReadOnlyCollection<Project> projects = IdeApp.Workspace.GetAllProjects (); + + foreach (Project p in projects) { + ProjectDom dom = ProjectDomService.GetProjectDom (p); + if (dom == null) + continue; + foreach (IType c in dom.Types) + AddType (c, list); + } + } finally { + getTypesTimer.EndTiming (); + } + return list; + } + + void AddType (IType c, List<IType> list) + { + list.Add (c); + foreach (IType ct in c.InnerTypes) + AddType (ct, list); + } + + SearchResult CheckFile (ProjectFile file, string toMatch) + { + int rank; + if (!MatchName (FileSearchResult.GetRelProjectPath (file), toMatch, out rank)) + return null; + return new FileSearchResult (toMatch, rank, file); + } + + SearchResult CheckType (IType type, string toMatch) + { + int rank; + if (type.CompilationUnit == null || !MatchName (type.Name, toMatch, out rank)) + return null; + return new TypeSearchResult (toMatch, rank, type); + } + + SearchResult CheckMember (IMember member, string toMatch) + { + int rank; + string memberName = (member is IMethod && ((IMethod)member).IsConstructor) ? member.DeclaringType.Name : member.Name; + if (!MatchName (memberName, toMatch, out rank)) + return null; + return new MemberSearchResult (toMatch, rank, member); + } + + struct MatchResult + { + public bool Match; + public int Rank; + + public MatchResult (bool match, int rank) + { + this.Match = match; + this.Rank = rank; + } + } + + Dictionary<string, MatchResult> savedMatches = new Dictionary<string, MatchResult> (); + + bool MatchName (string name, string toMatch, out int matchRank) + { + MatchResult savedMatch; + if (!savedMatches.TryGetValue (name, out savedMatch)) { + if (MonoDevelop.Ide.CodeCompletion.ListWidget.Matches (toMatch, name)) { + CalcMatchRank (name, toMatch, out matchRank); + savedMatch = new MatchResult (true, matchRank); + } else { + savedMatch = new MatchResult (false, int.MinValue); + } + savedMatches[name] = savedMatch; + } + + matchRank = savedMatch.Rank; + return savedMatch.Match; + } + + static bool CalcMatchRank (string name, string toMatch, out int matchRank) + { + if (toMatch.Length == 0) { + matchRank = int.MinValue; + return true; + } + MatchLane lane = MatchString (name, toMatch); + if (lane != null) { + matchRank = -(lane.Positions [0] + (name.Length - toMatch.Length)); + return true; + } + matchRank = int.MinValue; + return false; + } + + internal static MatchLane MatchString (string text, string toMatch) + { + if (text.Length < toMatch.Length) + return null; + + List<MatchLane> matchLanes = null; + bool lastWasSeparator = false; + int tn = 0; + + while (tn < text.Length) { + char ct = text [tn]; + + // Keep the lane count in a var because new lanes don't have to be updated + // until the next iteration + int laneCount = matchLanes != null ? matchLanes.Count : 0; + + char cm = toMatch [0]; + if (char.ToLower (ct) == char.ToLower (cm)) { + if (matchLanes == null) + matchLanes = new List<MatchLane> (); + matchLanes.Add (new MatchLane (MatchMode.Substring, tn, text.Length - tn)); + if (toMatch.Length == 1) + return matchLanes[0]; + if (char.IsUpper (ct) || lastWasSeparator) + matchLanes.Add (new MatchLane (MatchMode.Acronym, tn, text.Length - tn)); + } + + for (int n=0; n<laneCount; n++) { + MatchLane lane = matchLanes [n]; + if (lane == null) + continue; + cm = toMatch [lane.MatchIndex]; + bool match = char.ToLower (ct) == char.ToLower (cm); + bool wordStartMatch = match && (tn == 0 || char.IsUpper (ct) || lastWasSeparator); + + if (lane.MatchMode == MatchMode.Substring) { + if (wordStartMatch) { + // Possible acronym match after a substring. Start a new lane. + MatchLane newLane = lane.Clone (); + newLane.MatchMode = MatchMode.Acronym; + newLane.Index++; + newLane.Positions [newLane.Index] = tn; + newLane.Lengths [newLane.Index] = 1; + newLane.MatchIndex++; + matchLanes.Add (newLane); + } + if (match) { + // Maybe it is a false substring start, so add a new lane to keep + // track of the old lane + MatchLane newLane = lane.Clone (); + newLane.MatchMode = MatchMode.Acronym; + matchLanes.Add (newLane); + + // Update the current lane + lane.Lengths [lane.Index]++; + lane.MatchIndex++; + } else { + if (lane.Lengths [lane.Index] > 1) + lane.MatchMode = MatchMode.Acronym; + else + matchLanes [n] = null; // Kill the lane + } + } + else if (lane.MatchMode == MatchMode.Acronym) { + if (match && lane.Positions [lane.Index] == tn - 1) { + // Possible substring match after an acronim. Start a new lane. + MatchLane newLane = lane.Clone (); + newLane.MatchMode = MatchMode.Substring; + newLane.Index++; + newLane.Positions [newLane.Index] = tn; + newLane.Lengths [newLane.Index] = 1; + newLane.MatchIndex++; + matchLanes.Add (newLane); + if (newLane.MatchIndex == toMatch.Length) + return newLane; + } + if (wordStartMatch || (match && char.IsPunctuation (cm))) { + // Maybe it is a false acronym start, so add a new lane to keep + // track of the old lane + MatchLane newLane = lane.Clone (); + matchLanes.Add (newLane); + + // Update the current lane + lane.Index++; + lane.Positions [lane.Index] = tn; + lane.Lengths [lane.Index] = 1; + lane.MatchIndex++; + } + } + if (lane.MatchIndex == toMatch.Length) + return lane; + } + lastWasSeparator = (ct == '.' || ct == '_' || ct == '-' || ct == ' ' || ct == '/' || ct == '\\'); + tn++; + } + return null; + } + + internal enum MatchMode + { + Substring, + Acronym + } + + internal class MatchLane + { + public int[] Positions; + public int[] Lengths; + public MatchMode MatchMode; + public int Index; + public int MatchIndex; + + public MatchLane () + { + } + + public MatchLane (MatchMode mode, int pos, int len) + { + MatchMode = mode; + Positions = new int [len]; + Lengths = new int [len]; + Positions [0] = pos; + Lengths [0] = 1; + Index = 0; + MatchIndex = 1; + } + + public MatchLane Clone () + { + MatchLane lane = new MatchLane (); + lane.Positions = (int[]) Positions.Clone (); + lane.Lengths = (int[]) Lengths.Clone (); + lane.MatchMode = MatchMode; + lane.MatchIndex = MatchIndex; + lane.Index = Index; + return lane; + } + } + + protected virtual void HandleKeyPress (object o, KeyPressEventArgs args) + { + // Up and down move the tree selection up and down + // for rapid selection changes. + Gdk.EventKey key = args.Event; + switch (key.Key) { + case Gdk.Key.Page_Down: + list.ModifySelection (false, true, (args.Event.State & ModifierType.ShiftMask) == ModifierType.ShiftMask); + args.RetVal = true; + break; + case Gdk.Key.Page_Up: + list.ModifySelection (true, true, (args.Event.State & ModifierType.ShiftMask) == ModifierType.ShiftMask); + args.RetVal = true; + break; + case Gdk.Key.Up: + list.ModifySelection (true, false, (args.Event.State & ModifierType.ShiftMask) == ModifierType.ShiftMask); + args.RetVal = true; + break; + case Gdk.Key.Down: + list.ModifySelection (false, false, (args.Event.State & ModifierType.ShiftMask) == ModifierType.ShiftMask); + args.RetVal = true; + break; + case Gdk.Key.Escape: + Destroy (); + args.RetVal = true; + break; + } + } + } + + abstract class SearchResult + { + protected string match; + + public virtual string MarkupText { + get { + return HighlightMatch (PlainText, match); + } + } + + public abstract string PlainText { get; } + + public int Rank { get; private set; } + + public virtual int Row { get { return -1; } } + public virtual int Column { get { return -1; } } + + public abstract string File { get; } + public abstract Gdk.Pixbuf Icon { get; } + + public abstract string Description { get; } + + public SearchResult (string match, int rank) + { + this.match = match; + Rank = rank; + } + + protected static string HighlightMatch (string text, string toMatch) + { + var lane = !string.IsNullOrEmpty (toMatch) ? NavigateToDialog.MatchString (text, toMatch) : null; + if (lane != null) { + StringBuilder result = new StringBuilder (); + int lastPos = 0; + for (int n=0; n <= lane.Index; n++) { + int pos = lane.Positions [n]; + int len = lane.Lengths [n]; + if (pos - lastPos > 0) + result.Append (GLib.Markup.EscapeText (text.Substring (lastPos, pos - lastPos))); + result.Append ("<span foreground=\"blue\">"); + result.Append (GLib.Markup.EscapeText (text.Substring (pos, len))); + result.Append ("</span>"); + lastPos = pos + len; + } + if (lastPos < text.Length) + result.Append (GLib.Markup.EscapeText (text.Substring (lastPos, text.Length - lastPos))); + return result.ToString (); + } + + return GLib.Markup.EscapeText (text); + } + } + + class TypeSearchResult : MemberSearchResult + { + public override string File { + get { return ((IType)member).CompilationUnit.FileName; } + } + + public override string Description { + get { + return String.Format (GettextCatalog.GetString ("from Project \"{0}\""), ((IType)member).SourceProject.Name); + } + } + + public TypeSearchResult (string match, int rank, IType type) : base (match, rank, type) + { + } + } + + class FileSearchResult: SearchResult + { + ProjectFile file; + + public override string PlainText { + get { + return GetRelProjectPath (file); + } + } + + public override string File { + get { + return file.FilePath; + } + } + + public override Gdk.Pixbuf Icon { + get { + return DesktopService.GetPixbufForFile (file.FilePath, IconSize.Menu); + } + } + + public override string Description { + get { + return String.Format (GettextCatalog.GetString ("from \"{0}\""), GetRelProjectPath (file)); + } + } + + public FileSearchResult (string match, int rank, ProjectFile file) : base (match, rank) + { + this.file = file; + } + + internal static string GetRelProjectPath (ProjectFile file) + { + if (file.Project != null) + return System.IO.Path.Combine (System.IO.Path.GetFileName (file.Project.BaseDirectory), file.ProjectVirtualPath); + return file.FilePath; + } + } + + class MemberSearchResult : SearchResult + { + protected IMember member; + + public override string MarkupText { + get { + OutputSettings settings = new OutputSettings (OutputFlags.IncludeParameters | OutputFlags.IncludeGenerics | OutputFlags.IncludeMarkup); + settings.EmitNameCallback = delegate (INode domVisitable, ref string outString) { + if (domVisitable == member) + outString = HighlightMatch (outString, match); + }; + return Ambience.GetString (member, settings); + } + } + + public override string PlainText { + get { + return Ambience.GetString (member, OutputFlags.IncludeParameters | OutputFlags.IncludeGenerics); + } + } + + public override string File { + get { return member.DeclaringType.CompilationUnit.FileName; } + } + + public override Gdk.Pixbuf Icon { + get { + return ImageService.GetPixbuf (member.StockIcon, IconSize.Menu); + } + } + + public override int Row { + get { return member.Location.Line; } + } + + public override int Column { + get { return member.Location.Column; } + } + + public override string Description { + get { + return String.Format (GettextCatalog.GetString ("from Type \"{0}\""), member.DeclaringType.Name); + } + } + + public MemberSearchResult (string match, int rank, IMember member) : base (match, rank) + { + this.member= member; + } + + protected Ambience Ambience { + get { + IType type = member is IType ? (IType)member : member.DeclaringType; + if (type.SourceProject is DotNetProject) + return ((DotNetProject)type.SourceProject).Ambience; + return AmbienceService.DefaultAmbience; + } + } + } + + class ResultsDataSource: List<SearchResult>, IListViewDataSource + { + SearchResult bestResult; + int bestRank = int.MinValue; + Dictionary<string,bool> names = new Dictionary<string,bool> (); + + public string GetText (int n) + { + string descr = this[n].Description; + if (string.IsNullOrEmpty (descr)) + return this[n].MarkupText; + return this[n].MarkupText + " <span foreground=\"darkgray\">[" + descr + "]</span>"; + } + + public string GetSelectedText (int n) + { + string descr = this[n].Description; + if (string.IsNullOrEmpty (descr)) + return GLib.Markup.EscapeText (this[n].PlainText); + return GLib.Markup.EscapeText (this[n].PlainText) + " [" + descr + "]"; + } + + public Pixbuf GetIcon (int n) + { + return this[n].Icon; + } + + public bool UseMarkup (int n) + { + return true; + } + + public int ItemCount { + get { + return Count; + } + } + + public SearchResult BestResult { + get { + return bestResult; + } + } + + public void AddResult (SearchResult res) + { + Add (res); + if (names.ContainsKey (res.PlainText)) + names[res.PlainText] = true; + else + names.Add (res.PlainText, false); + if (res.Rank > bestRank) { + bestResult = res; + bestRank = res.Rank; + } + } + } +} + |