@@ -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