From f93aa9c02dc19de509e308267135b86dd3e46ad9 Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Mon, 3 Apr 2006 16:22:49 +0000 Subject: [PATCH] SD2-567. Implemented search and replace inside the current text selection. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/branches/2.0@1261 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../Commands/SearchMainMenuCommands.cs | 24 +- .../SearchAndReplace/Engine/Search.cs | 32 +++ .../Engine/SearchReplaceInFilesManager.cs | 48 ++++ .../Engine/SearchReplaceManager.cs | 96 ++++++- .../Engine/SearchReplaceUtilities.cs | 22 ++ .../BruteForceSearchStrategy.cs | 27 ++ .../Engine/SearchStrategy/ISearchStrategy.cs | 5 + .../SearchStrategy/RegExSearchStrategy.cs | 25 ++ .../SearchStrategy/WildcardSearchStrategy.cs | 27 ++ .../SearchAndReplace/Engine/TextSelection.cs | 49 ++++ .../Gui/SearchAndReplaceDialog.cs | 9 +- .../Gui/SearchAndReplacePanel.cs | 262 +++++++++++++++++- 13 files changed, 600 insertions(+), 27 deletions(-) create mode 100644 src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/TextSelection.cs diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 19e60f5894..aeeeeafdcc 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -752,6 +752,7 @@ + diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Commands/SearchMainMenuCommands.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Commands/SearchMainMenuCommands.cs index 5511552e58..60ebb429d5 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Commands/SearchMainMenuCommands.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Commands/SearchMainMenuCommands.cs @@ -32,14 +32,10 @@ namespace SearchAndReplace public static void SetSearchPattern() { // Get Highlighted value and set it to FindDialog.searchPattern - IWorkbenchWindow window = WorkbenchSingleton.Workbench.ActiveWorkbenchWindow; - - if (window != null && (window.ViewContent is ITextEditorControlProvider)) { - TextEditorControl textarea = ((ITextEditorControlProvider)window.ViewContent).TextEditorControl; - string selectedText = textarea.ActiveTextAreaControl.TextArea.SelectionManager.SelectedText; - if (selectedText != null && selectedText.Length > 0) { - SearchOptions.CurrentFindPattern = selectedText; - } + TextEditorControl textArea = SearchReplaceUtilities.GetActiveTextEditor(); + string selectedText = textArea.ActiveTextAreaControl.TextArea.SelectionManager.SelectedText; + if (selectedText != null && selectedText.Length > 0 && !IsMultipleLines(selectedText)) { + SearchOptions.CurrentFindPattern = selectedText; } } @@ -48,13 +44,23 @@ namespace SearchAndReplace SetSearchPattern(); SearchAndReplaceDialog.ShowSingleInstance(SearchAndReplaceMode.Search); } + + static bool IsMultipleLines(string text) + { + return text.IndexOf('\n') != -1; + } } public class FindNext : AbstractMenuCommand { public override void Run() { - SearchReplaceManager.FindNext(); + if (SearchOptions.CurrentFindPattern.Length > 0) { + SearchReplaceManager.FindNext(); + } else { + Find find = new Find(); + find.Run(); + } } } diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/Search.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/Search.cs index 7ad01b5a94..2505d82edb 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/Search.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/Search.cs @@ -119,5 +119,37 @@ namespace SearchAndReplace } return null; } + + public SearchResult FindNext(int offset, int length) + { + if (info != null && textIterator != null && documentIterator.CurrentFileName != null) { + if (info.FileName != documentIterator.CurrentFileName) { // create new iterator, if document changed + info = documentIterator.Current; + textIterator = textIteratorBuilder.BuildTextIterator(info); + } else { // old document -> initialize iterator position to caret pos + textIterator.Position = info.CurrentOffset; + } + + SearchResult result = CreateNamedSearchResult(searchStrategy.FindNext(textIterator, offset, length)); + if (result != null) { + info.CurrentOffset = textIterator.Position; + return result; + } + } + + // not found or first start -> move forward to the next document + if (documentIterator.MoveForward()) { + info = documentIterator.Current; + // document is valid for searching -> set iterator & fileName + if (info != null && info.TextBuffer != null && info.EndOffset >= 0 && info.EndOffset < info.TextBuffer.Length) { + textIterator = textIteratorBuilder.BuildTextIterator(info); + } else { + textIterator = null; + } + + return FindNext(offset, length); + } + return null; + } } } diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceInFilesManager.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceInFilesManager.cs index 6fd4816d9e..02d690e4db 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceInFilesManager.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceInFilesManager.cs @@ -100,6 +100,37 @@ namespace SearchAndReplace FinishSearchInFiles(results); } + public static void ReplaceAll(int offset, int length) + { + if (!InitializeSearchInFiles()) { + return; + } + + List results = new List(); + + while (true) { + SearchResult result = find.FindNext(offset, length); + if (result == null) { + break; + } + + string replacement = result.TransformReplacePattern(SearchOptions.ReplacePattern); + find.Replace(result.Offset, + result.Length, + replacement); + length -= result.Length - replacement.Length; + + // HACK - Move the cursor to the correct offset - the caret gets + // moved before the replace range if we replace a string with a + // single character. The ProvidedDocInfo.Replace method assumes that + // the current offset is at the end of the found text which it is not. + find.CurrentDocumentInformation.CurrentOffset = result.Offset + replacement.Length - 1; + results.Add(result); + } + + FinishSearchInFiles(results); + } + public static void FindAll() { if (!InitializeSearchInFiles()) { @@ -116,6 +147,23 @@ namespace SearchAndReplace } FinishSearchInFiles(results); } + + public static void FindAll(int offset, int length) + { + if (!InitializeSearchInFiles()) { + return; + } + + List results = new List(); + while (true) { + SearchResult result = find.FindNext(offset, length); + if (result == null) { + break; + } + results.Add(result); + } + FinishSearchInFiles(results); + } static void OnSearchAllFinished(SearchAllFinishedEventArgs e) { diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceManager.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceManager.cs index 9a084d4aab..7ebcd66562 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceManager.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceManager.cs @@ -63,6 +63,42 @@ namespace SearchAndReplace FindNext(); } + static TextSelection textSelection; + + public static void ReplaceFirstInSelection(int offset, int length) + { + SetSearchOptions(); + FindFirstInSelection(offset, length); + } + + public static bool ReplaceNextInSelection() + { + if (lastResult != null && WorkbenchSingleton.Workbench.ActiveWorkbenchWindow != null) { + ITextEditorControlProvider provider = WorkbenchSingleton.Workbench.ActiveWorkbenchWindow.ViewContent as ITextEditorControlProvider; + if (provider != null) { + TextEditorControl textarea = provider.TextEditorControl; + SelectionManager selectionManager = textarea.ActiveTextAreaControl.TextArea.SelectionManager; + + if (selectionManager.SelectionCollection.Count == 1 + && selectionManager.SelectionCollection[0].Offset == lastResult.Offset + && selectionManager.SelectionCollection[0].Length == lastResult.Length + && lastResult.FileName == textarea.FileName) + { + string replacePattern = lastResult.TransformReplacePattern(SearchOptions.ReplacePattern); + + textarea.BeginUpdate(); + selectionManager.ClearSelection(); + textarea.Document.Replace(lastResult.Offset, lastResult.Length, replacePattern); + textarea.ActiveTextAreaControl.Caret.Position = textarea.Document.OffsetToPosition(lastResult.Offset + replacePattern.Length); + textarea.EndUpdate(); + + textSelection.Length -= lastResult.Length - replacePattern.Length; + } + } + } + return FindNextInSelection(); + } + public static void MarkAll() { SetSearchOptions(); @@ -185,15 +221,65 @@ namespace SearchAndReplace int startPos = Math.Min(textArea.Document.TextLength, Math.Max(0, result.Offset)); int endPos = Math.Min(textArea.Document.TextLength, startPos + result.Length); - textArea.ActiveTextAreaControl.Caret.Position = textArea.Document.OffsetToPosition(endPos); - textArea.ActiveTextAreaControl.TextArea.SelectionManager.ClearSelection(); - textArea.ActiveTextAreaControl.TextArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, textArea.Document.OffsetToPosition(startPos), - textArea.Document.OffsetToPosition(endPos))); - textArea.Refresh(); + SearchReplaceUtilities.SelectText(textArea, startPos, endPos); + lastResult = result; + } + } + } + } + + static bool foundAtLeastOneItem = false; + + public static void FindFirstInSelection(int offset, int length) + { + foundAtLeastOneItem = false; + textSelection = null; + SetSearchOptions(); + + if (find == null || + SearchOptions.FindPattern == null || + SearchOptions.FindPattern.Length == 0) { + return; + } + + if (!find.SearchStrategy.CompilePattern()) { + find.Reset(); + lastResult = null; + return; + } + + textSelection = new TextSelection(offset, length); + FindNextInSelection(); + } + + public static bool FindNextInSelection() + { + TextEditorControl textArea = null; + while (textArea == null) { + SearchResult result = find.FindNext(textSelection.Offset, textSelection.Length); + if (result == null) { + if (!foundAtLeastOneItem) { + ShowNotFoundMessage(); + } + find.Reset(); + lastResult = null; + foundAtLeastOneItem = false; + return false; + } else { + textArea = OpenTextArea(result.FileName); + if (textArea != null) { + foundAtLeastOneItem = true; + if (lastResult != null && lastResult.FileName == result.FileName && + textArea.ActiveTextAreaControl.Caret.Offset != lastResult.Offset + lastResult.Length) { + } + int startPos = Math.Min(textArea.Document.TextLength, Math.Max(0, result.Offset)); + int endPos = Math.Min(textArea.Document.TextLength, startPos + result.Length); + SearchReplaceUtilities.SelectText(textArea, startPos, endPos); lastResult = result; } } } + return true; } static void ShowNotFoundMessage() diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceUtilities.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceUtilities.cs index 446f784d73..9225144ce2 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceUtilities.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchReplaceUtilities.cs @@ -26,6 +26,15 @@ namespace SearchAndReplace } } + public static TextEditorControl GetActiveTextEditor() + { + IWorkbenchWindow window = WorkbenchSingleton.Workbench.ActiveWorkbenchWindow; + if (window != null && (window.ViewContent is ITextEditorControlProvider)) { + return ((ITextEditorControlProvider)window.ViewContent).TextEditorControl; + } + return null; + } + public static bool IsWholeWordAt(ITextBufferStrategy document, int offset, int length) { return (offset - 1 < 0 || Char.IsWhiteSpace(document.GetCharAt(offset - 1))) && @@ -95,5 +104,18 @@ namespace SearchAndReplace } return true; } + + public static void SelectText(TextEditorControl textArea, int offset, int endOffset) + { + int textLength = textArea.ActiveTextAreaControl.Document.TextLength; + if (textLength < endOffset) { + endOffset = textLength - 1; + } + textArea.ActiveTextAreaControl.Caret.Position = textArea.Document.OffsetToPosition(endOffset); + textArea.ActiveTextAreaControl.TextArea.SelectionManager.ClearSelection(); + textArea.ActiveTextAreaControl.TextArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, textArea.Document.OffsetToPosition(offset), + textArea.Document.OffsetToPosition(endOffset))); + textArea.Refresh(); + } } } diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/BruteForceSearchStrategy.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/BruteForceSearchStrategy.cs index e58f079faf..857156e65d 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/BruteForceSearchStrategy.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/BruteForceSearchStrategy.cs @@ -58,6 +58,22 @@ namespace SearchAndReplace return -1; } + int InternalFindNext(ITextIterator textIterator, int offset, int length) + { + while (textIterator.MoveAhead(1) && TextSelection.IsInsideRange(textIterator.Position, offset, length)) { + if (SearchOptions.MatchCase ? MatchCaseSensitive(textIterator.TextBuffer, textIterator.Position, searchPattern) : MatchCaseInsensitive(textIterator.TextBuffer, textIterator.Position, searchPattern)) { + if (!SearchOptions.MatchWholeWord || IsWholeWordAt(textIterator.TextBuffer, textIterator.Position, searchPattern.Length)) { + if (TextSelection.IsInsideRange(textIterator.Position + searchPattern.Length - 1, offset, length)) { + return textIterator.Position; + } else { + return -1; + } + } + } + } + return -1; + } + public bool CompilePattern() { searchPattern = SearchOptions.MatchCase ? SearchOptions.FindPattern : SearchOptions.FindPattern.ToUpper(); @@ -67,6 +83,17 @@ namespace SearchAndReplace public SearchResult FindNext(ITextIterator textIterator) { int offset = InternalFindNext(textIterator); + return GetSearchResult(offset); + } + + public SearchResult FindNext(ITextIterator textIterator, int offset, int length) + { + int foundOffset = InternalFindNext(textIterator, offset, length); + return GetSearchResult(foundOffset); + } + + SearchResult GetSearchResult(int offset) + { return offset >= 0 ? new SearchResult(offset, searchPattern.Length) : null; } } diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/ISearchStrategy.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/ISearchStrategy.cs index 1f84f7bf45..6f2d4e061c 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/ISearchStrategy.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/ISearchStrategy.cs @@ -31,5 +31,10 @@ namespace SearchAndReplace /// compiled pattern in the text using the textIterator and options. /// SearchResult FindNext(ITextIterator textIterator); + + /// + /// Find only in the specified range. + /// + SearchResult FindNext(ITextIterator textIterator, int offset, int length); } } diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/RegExSearchStrategy.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/RegExSearchStrategy.cs index a6d09517b0..657787fce2 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/RegExSearchStrategy.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/RegExSearchStrategy.cs @@ -53,6 +53,31 @@ namespace SearchAndReplace return null; } + public SearchResult FindNext(ITextIterator textIterator, int offset, int length) + { + string document = textIterator.TextBuffer.GetText(0, textIterator.TextBuffer.Length); + + while (textIterator.MoveAhead(1) && TextSelection.IsInsideRange(textIterator.Position, offset, length)) { + Match m = regex.Match(document, textIterator.Position); + if (m == null || m.Index <= 0 || m.Length <= 0) { + + } else { + int delta = m.Index - textIterator.Position; + if (delta <= 0 || textIterator.MoveAhead(delta)) { + if (TextSelection.IsInsideRange(m.Index + m.Length - 1, offset, length)) { + return new RegexSearchResult(m); + } else { + return null; + } + } else { + return null; + } + } + } + + return null; + } + private class RegexSearchResult : SearchResult { Match m; diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/WildcardSearchStrategy.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/WildcardSearchStrategy.cs index 5f606ff318..18d9e383d1 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/WildcardSearchStrategy.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/SearchStrategy/WildcardSearchStrategy.cs @@ -144,6 +144,22 @@ namespace SearchAndReplace return -1; } + int InternalFindNext(ITextIterator textIterator, int offset, int length) + { + while (textIterator.MoveAhead(1) && TextSelection.IsInsideRange(textIterator.Position, offset, length)) { + if (Match(textIterator.TextBuffer, textIterator.Position, !SearchOptions.MatchCase, 0)) { + if (!SearchOptions.MatchWholeWord || SearchReplaceUtilities.IsWholeWordAt(textIterator.TextBuffer, textIterator.Position, curMatchEndOffset - textIterator.Position)) { + if (TextSelection.IsInsideRange(curMatchEndOffset - 1, offset, length)) { + return textIterator.Position; + } else { + return -1; + } + } + } + } + return -1; + } + public bool CompilePattern() { CompilePattern(SearchOptions.FindPattern, !SearchOptions.MatchCase); @@ -153,6 +169,17 @@ namespace SearchAndReplace public SearchResult FindNext(ITextIterator textIterator) { int offset = InternalFindNext(textIterator); + return GetSearchResult(offset); + } + + public SearchResult FindNext(ITextIterator textIterator, int offset, int length) + { + int foundOffset = InternalFindNext(textIterator, offset, length); + return GetSearchResult(foundOffset); + } + + SearchResult GetSearchResult(int offset) + { return offset >= 0 ? new SearchResult(offset, curMatchEndOffset - offset) : null; } } diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/TextSelection.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/TextSelection.cs new file mode 100644 index 0000000000..9d570f389b --- /dev/null +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Engine/TextSelection.cs @@ -0,0 +1,49 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace SearchAndReplace +{ + public class TextSelection + { + int offset; + int length; + + public TextSelection(int offset, int length) + { + this.offset = offset; + this.length = length; + } + + public int Length { + get { + return length; + } + set { + length = value; + } + } + + public int Offset { + get { + return offset; + } + set { + offset = value; + } + } + + /// + /// Checks whether a position is in a specified range. + /// + public static bool IsInsideRange(int position, int offset, int length) + { + return position >= offset && position < offset + length; + } + } +} diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Gui/SearchAndReplaceDialog.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Gui/SearchAndReplaceDialog.cs index 374184669d..5d478636dc 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Gui/SearchAndReplaceDialog.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Gui/SearchAndReplaceDialog.cs @@ -11,6 +11,7 @@ using System.Windows.Forms; using ICSharpCode.Core; using ICSharpCode.SharpDevelop.Gui; +using ICSharpCode.TextEditor.Document; namespace SearchAndReplace { @@ -21,8 +22,8 @@ namespace SearchAndReplace public class SearchAndReplaceDialog : Form { - public static string SearchPattern = ""; - public static string ReplacePattern = ""; + public static string SearchPattern = String.Empty; + public static string ReplacePattern = String.Empty; static SearchAndReplaceDialog Instance; @@ -45,7 +46,7 @@ namespace SearchAndReplace ToolStripButton replaceButton = new ToolStripButton(); SearchAndReplacePanel searchAndReplacePanel; - + public SearchAndReplaceDialog(SearchAndReplaceMode searchAndReplaceMode) { this.Owner = WorkbenchSingleton.MainForm; @@ -96,7 +97,6 @@ namespace SearchAndReplace } } - void SearchButtonClick(object sender, EventArgs e) { if (!searchButton.Checked) { @@ -126,6 +126,5 @@ namespace SearchAndReplace this.ClientSize = new Size(430, 385); } } - } } diff --git a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Gui/SearchAndReplacePanel.cs b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Gui/SearchAndReplacePanel.cs index fb44eb18a9..f157c219a8 100644 --- a/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Gui/SearchAndReplacePanel.cs +++ b/src/Main/Base/Project/Src/TextEditor/SearchAndReplace/Gui/SearchAndReplacePanel.cs @@ -24,6 +24,10 @@ namespace SearchAndReplace public class SearchAndReplacePanel : BaseSharpDevelopUserControl { SearchAndReplaceMode searchAndReplaceMode; + ISelection selection; + TextEditorControl textEditor; + bool ignoreSelectionChanges; + bool findFirst; public SearchAndReplaceMode SearchAndReplaceMode { get { @@ -36,12 +40,12 @@ namespace SearchAndReplace switch (searchAndReplaceMode) { case SearchAndReplaceMode.Search: SetupFromXmlStream(this.GetType().Assembly.GetManifestResourceStream("Resources.FindPanel.xfrm")); - Get