Skip to content

Commit 1d609fc

Browse files
committed
extend search panel by replace posibility
1 parent 7cc1615 commit 1d609fc

8 files changed

Lines changed: 207 additions & 45 deletions

File tree

ICSharpCode.AvalonEdit.Sample/Window1.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ public Window1() {
7070
foldingUpdateTimer.Interval = TimeSpan.FromSeconds(2);
7171
foldingUpdateTimer.Tick += delegate { UpdateFoldings(); };
7272
foldingUpdateTimer.Start();
73+
74+
Search.SearchPanel.Install(textEditor);
7375
}
7476

7577
string currentFileName;

ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,4 +449,10 @@
449449
<ItemGroup>
450450
<EmbeddedResource Include="Highlighting\Resources\MarkDown-Mode.xshd" />
451451
</ItemGroup>
452+
<ItemGroup>
453+
<Resource Include="Search\replacenext.png" />
454+
</ItemGroup>
455+
<ItemGroup>
456+
<Resource Include="Search\replaceall.png" />
457+
</ItemGroup>
452458
</Project>

ICSharpCode.AvalonEdit/Search/Localization.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ public virtual string FindPreviousText {
6060
get { return "Find previous (Shift+F3)"; }
6161
}
6262

63+
/// <summary>
64+
/// Default: 'Replace next (ALT+R)'
65+
/// </summary>
66+
public virtual string ReplaceNextText {
67+
get { return "Replace next (ALT+R)"; }
68+
}
69+
70+
/// <summary>
71+
/// Default: 'Replace all (ALT+A)'
72+
/// </summary>
73+
public virtual string ReplaceAllText {
74+
get { return "Replace all (ALT+A)"; }
75+
}
76+
6377
/// <summary>
6478
/// Default: 'Error: '
6579
/// </summary>

ICSharpCode.AvalonEdit/Search/SearchCommands.cs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,22 @@ public static class SearchCommands {
4949
new InputGestureCollection { new KeyGesture(Key.F3, ModifierKeys.Shift) }
5050
);
5151

52+
/// <summary>
53+
/// Replaces the current occurrence and finds the next occurrence in the file.
54+
/// </summary>
55+
public static readonly RoutedCommand ReplaceNext = new RoutedCommand(
56+
"ReplaceNext", typeof(SearchPanel),
57+
new InputGestureCollection { new KeyGesture(Key.R, ModifierKeys.Alt) }
58+
);
59+
60+
/// <summary>
61+
/// Replaces all occurrence in the file.
62+
/// </summary>
63+
public static readonly RoutedCommand ReplaceAll = new RoutedCommand(
64+
"ReplaceAll", typeof(SearchPanel),
65+
new InputGestureCollection { new KeyGesture(Key.A, ModifierKeys.Alt) }
66+
);
67+
5268
/// <summary>
5369
/// Closes the SearchPanel.
5470
/// </summary>
@@ -80,21 +96,34 @@ internal SearchInputHandler(TextArea textArea, SearchPanel panel)
8096

8197
internal void RegisterGlobalCommands(CommandBindingCollection commandBindings) {
8298
commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind));
99+
commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace));
83100
commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel));
84101
commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel));
102+
commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel));
103+
commandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, ExecuteReplaceAll, CanExecuteWithOpenSearchPanel));
85104
}
86105

87106
void RegisterCommands(ICollection<CommandBinding> commandBindings) {
88107
commandBindings.Add(new CommandBinding(ApplicationCommands.Find, ExecuteFind));
108+
commandBindings.Add(new CommandBinding(ApplicationCommands.Replace, ExecuteReplace));
89109
commandBindings.Add(new CommandBinding(SearchCommands.FindNext, ExecuteFindNext, CanExecuteWithOpenSearchPanel));
90110
commandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, ExecuteFindPrevious, CanExecuteWithOpenSearchPanel));
111+
commandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, ExecuteReplaceNext, CanExecuteWithOpenSearchPanel));
112+
commandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, ExecuteReplaceAll, CanExecuteWithOpenSearchPanel));
91113
commandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, ExecuteCloseSearchPanel, CanExecuteWithOpenSearchPanel));
92114
}
93115

94116
SearchPanel panel;
95117

96118
void ExecuteFind(object sender, ExecutedRoutedEventArgs e) {
97-
panel.Open();
119+
panel.Open(false);
120+
if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline))
121+
panel.SearchPattern = TextArea.Selection.GetText();
122+
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); });
123+
}
124+
125+
void ExecuteReplace(object sender, ExecutedRoutedEventArgs e) {
126+
panel.Open(true);
98127
if (!(TextArea.Selection.IsEmpty || TextArea.Selection.IsMultiline))
99128
panel.SearchPattern = TextArea.Selection.GetText();
100129
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate { panel.Reactivate(); });
@@ -125,6 +154,20 @@ void ExecuteFindPrevious(object sender, ExecutedRoutedEventArgs e) {
125154
}
126155
}
127156

157+
void ExecuteReplaceNext(object sender, ExecutedRoutedEventArgs e) {
158+
if (!panel.IsClosed) {
159+
panel.ReplaceNext();
160+
e.Handled = true;
161+
}
162+
}
163+
164+
void ExecuteReplaceAll(object sender, ExecutedRoutedEventArgs e) {
165+
if (!panel.IsClosed) {
166+
panel.ReplaceAll();
167+
e.Handled = true;
168+
}
169+
}
170+
128171
void ExecuteCloseSearchPanel(object sender, ExecutedRoutedEventArgs e) {
129172
if (!panel.IsClosed) {
130173
panel.Close();

ICSharpCode.AvalonEdit/Search/SearchPanel.cs

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class SearchPanel : Control {
4444
TextDocument currentDocument;
4545
SearchResultBackgroundRenderer renderer;
4646
TextBox searchTextBox;
47+
TextBox replaceTextBox;
4748
SearchPanelAdorner adorner;
4849

4950
#region DependencyProperties
@@ -107,6 +108,36 @@ public string SearchPattern {
107108
set { SetValue(SearchPatternProperty, value); }
108109
}
109110

111+
/// <summary>
112+
/// Dependency property for <see cref="Replacement"/>.
113+
/// </summary>
114+
public static readonly DependencyProperty ReplacementProperty =
115+
DependencyProperty.Register("Replacement", typeof(string), typeof(SearchPanel),
116+
new FrameworkPropertyMetadata(""));
117+
118+
/// <summary>
119+
/// Gets/sets the replacement.
120+
/// </summary>
121+
public string Replacement {
122+
get { return (string)GetValue(ReplacementProperty); }
123+
set { SetValue(ReplacementProperty, value); }
124+
}
125+
126+
/// <summary>
127+
/// Dependency property for <see cref="ShowReplace"/>.
128+
/// </summary>
129+
public static readonly DependencyProperty ShowReplaceProperty =
130+
DependencyProperty.Register("ShowReplace", typeof(bool), typeof(SearchPanel),
131+
new FrameworkPropertyMetadata(false));
132+
133+
/// <summary>
134+
/// Gets/sets whether the replace is shown.
135+
/// </summary>
136+
public bool ShowReplace {
137+
get { return (bool)GetValue(ShowReplaceProperty); }
138+
set { SetValue(ShowReplaceProperty, value); }
139+
}
140+
110141
/// <summary>
111142
/// Dependency property for <see cref="MarkerBrush"/>.
112143
/// </summary>
@@ -238,6 +269,8 @@ void AttachInternal(TextArea textArea) {
238269

239270
this.CommandBindings.Add(new CommandBinding(SearchCommands.FindNext, (sender, e) => FindNext()));
240271
this.CommandBindings.Add(new CommandBinding(SearchCommands.FindPrevious, (sender, e) => FindPrevious()));
272+
this.CommandBindings.Add(new CommandBinding(SearchCommands.ReplaceNext, (sender, e) => ReplaceNext()));
273+
this.CommandBindings.Add(new CommandBinding(SearchCommands.ReplaceAll, (sender, e) => ReplaceAll()));
241274
this.CommandBindings.Add(new CommandBinding(SearchCommands.CloseSearchPanel, (sender, e) => Close()));
242275
IsClosed = true;
243276
}
@@ -260,6 +293,7 @@ void textArea_Document_TextChanged(object sender, EventArgs e) {
260293
public override void OnApplyTemplate() {
261294
base.OnApplyTemplate();
262295
searchTextBox = Template.FindName("PART_searchTextBox", this) as TextBox;
296+
replaceTextBox = Template.FindName("PART_replaceTextBox", this) as TextBox;
263297
}
264298

265299
void ValidateSearchText() {
@@ -311,6 +345,41 @@ public void FindPrevious() {
311345
}
312346
}
313347

348+
/// <summary>
349+
/// Replaces current result if any and moves to the next occurrence in the file.
350+
/// </summary>
351+
public int ReplaceNext() {
352+
SearchResult result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset);
353+
var count = renderer.CurrentResults.Count;
354+
if (result != null
355+
&& textArea.Document.GetOffset(textArea.Selection.StartPosition.Location) == result.StartOffset
356+
&& textArea.Document.GetOffset(textArea.Selection.EndPosition.Location) == result.EndOffset) {
357+
Replace(result);
358+
--count;
359+
}
360+
result = renderer.CurrentResults.FindFirstSegmentWithStartAfter(textArea.Caret.Offset + textArea.Selection.Length);
361+
if (result == null)
362+
result = renderer.CurrentResults.FirstSegment;
363+
if (result != null) {
364+
SelectResult(result);
365+
return count;
366+
}
367+
return 0;
368+
}
369+
370+
/// <summary>
371+
/// Replaces all occurrences in the file.
372+
/// </summary>
373+
public void ReplaceAll() {
374+
var count = ReplaceNext();
375+
while (count-- > 0)
376+
ReplaceNext();
377+
}
378+
379+
void Replace(SearchResult result) {
380+
currentDocument.Replace(textArea.Selection.Segments.FirstOrDefault(), result.ReplaceWith(Replacement));
381+
}
382+
314383
ToolTip messageView = new ToolTip { Placement = PlacementMode.Bottom, StaysOpen = true, Focusable = false };
315384

316385
void DoSearch(bool changeSelection) {
@@ -353,16 +422,21 @@ void SearchLayerKeyDown(object sender, KeyEventArgs e) {
353422
switch (e.Key) {
354423
case Key.Enter:
355424
e.Handled = true;
356-
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
357-
FindPrevious();
358-
else
359-
FindNext();
360-
if (searchTextBox != null) {
361-
var error = Validation.GetErrors(searchTextBox).FirstOrDefault();
362-
if (error != null) {
363-
messageView.Content = Localization.ErrorText + " " + error.ErrorContent;
364-
messageView.PlacementTarget = searchTextBox;
365-
messageView.IsOpen = true;
425+
if (replaceTextBox != null
426+
&& replaceTextBox.IsFocused) {
427+
ReplaceNext();
428+
} else {
429+
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
430+
FindPrevious();
431+
else
432+
FindNext();
433+
if (searchTextBox != null) {
434+
var error = Validation.GetErrors(searchTextBox).FirstOrDefault();
435+
if (error != null) {
436+
messageView.Content = Localization.ErrorText + " " + error.ErrorContent;
437+
messageView.PlacementTarget = searchTextBox;
438+
messageView.IsOpen = true;
439+
}
366440
}
367441
}
368442
break;
@@ -411,7 +485,8 @@ public void CloseAndRemove() {
411485
/// <summary>
412486
/// Opens the an existing search panel.
413487
/// </summary>
414-
public void Open() {
488+
public void Open(bool showReplace) {
489+
ShowReplace = showReplace;
415490
if (!IsClosed) return;
416491
var layer = AdornerLayer.GetAdornerLayer(textArea);
417492
if (layer != null)

0 commit comments

Comments
 (0)