public bool Replace() { if (this.ReplaceTerm == null) { throw new InvalidOperationException("Can't replace with a null value. Set ReplaceTerm before performing a replace operation."); } if (!this.CurrentResult.HasValue) { throw new InvalidOperationException("Need to have a current result before being able to replace. Perform a FindNext or FindPrevious operation first."); } bool forward = (this.SearchOptions & FindOptions.SearchReverse) != FindOptions.SearchReverse; bool regEx = (this.SearchOptions & FindOptions.UseRegularExpressions) == FindOptions.UseRegularExpressions; //This may not be the text buffer's current snapshot but that is the desired behavior. We're replacing the current result //with the replace tuern. SnapshotSpan result = this.CurrentResult.Value; ITextSnapshot replaceSnapshot = result.Snapshot; SnapshotPoint searchStart = forward ? result.Start : result.End; SnapshotSpan searchSpan; NormalizedSnapshotSpanCollection searchSpans = this.SearchSpans; if ((searchSpans != null) && (searchSpans.Count > 0)) { //There could be a version skew here. if (searchSpans[0].Snapshot != replaceSnapshot) { searchSpans = new NormalizedSnapshotSpanCollection(replaceSnapshot, TextSearchNavigator.TranslateTo(searchSpans[0].Snapshot, searchSpans, replaceSnapshot)); } int index; if (!TextSearchNavigator.TryGetIndexOfContainingSpan(searchSpans, searchStart, out index)) { // If the match is outside of the search range, then we should noop return(false); } searchSpan = searchSpans[index]; } else { searchSpan = new SnapshotSpan(replaceSnapshot, 0, replaceSnapshot.Length); } searchSpan = forward ? new SnapshotSpan(searchStart, searchSpan.End) : new SnapshotSpan(searchSpan.Start, searchStart); //Ask the search engine to find the actual span we need to replace (& the corresponding replacement string). string replacementValue = null; SnapshotSpan?toReplace = _textSearchService.FindForReplace(searchSpan, this.SearchTerm, this.ReplaceTerm, this.SearchOptions, out replacementValue); if (toReplace.HasValue) { using (ITextEdit edit = _buffer.CreateEdit()) { Span replacementSpan = toReplace.Value.TranslateTo(edit.Snapshot, SpanTrackingMode.EdgeInclusive); if (!edit.Replace(replacementSpan, replacementValue)) { // The edit failed for some reason, perhaps read-only regions? return(false); } edit.Apply(); if (edit.Canceled) { // The edit failed, most likely a handler of the changed event forced the edit to be canceled. return(false); } } return(true); } return(false); }
public bool Find() { if (string.IsNullOrEmpty(this.SearchTerm)) { throw new InvalidOperationException("You must set a non-empty search term before searching."); } bool forward = (this.SearchOptions & FindOptions.SearchReverse) != FindOptions.SearchReverse; bool wrap = (this.SearchOptions & FindOptions.Wrap) == FindOptions.Wrap; bool regEx = (this.SearchOptions & FindOptions.UseRegularExpressions) == FindOptions.UseRegularExpressions; ITextSnapshot searchSnapshot = _buffer.CurrentSnapshot; //There could be a version skew here if someone calls find from inside a text changed callback on the buffer. That probably wouldn't be a good //idea but we need to handle it gracefully. this.AdvanceToSnapshot(searchSnapshot); SnapshotPoint?searchStart = this.CalculateStartPoint(searchSnapshot, wrap, forward); if (searchStart.HasValue) { int index = 0; NormalizedSnapshotSpanCollection searchSpans = this.SearchSpans; if (searchSpans != null) { Debug.Assert(searchSpans.Count > 0); //Index is potentially outside the range of [0...searchSpans.Count-1] but we handle that below. if (!(TextSearchNavigator.TryGetIndexOfContainingSpan(searchSpans, searchStart.Value, out index) || forward)) { //For reversed searches, we want the index of the span before the point if we can't get a span that contains the point. --index; } } else { searchSpans = new NormalizedSnapshotSpanCollection(new SnapshotSpan(searchSnapshot, Span.FromBounds(0, searchSnapshot.Length))); } int searchIterations = searchSpans.Count; for (int i = 0; (i < searchIterations); ++i) { //index needs to be normalized to [0 ... searchSpans.Count - 1] but could be negative. index = (index + searchSpans.Count) % searchSpans.Count; SnapshotSpan searchSpan = searchSpans[index]; if ((i != 0) || (searchStart.Value < searchSpan.Start) || (searchStart.Value > searchSpan.End)) { searchStart = forward ? searchSpan.Start : searchSpan.End; } else if (wrap && (i == 0)) { //We will need to repeat the search to account for wrap being on and we are not searching everything in searchSpans[0]. //This is the same as simply doing a search for i == searchSpans.Count we we can make happen by bumping the number of iterations. ++searchIterations; } foreach (var result in _textSearchService.FindAll(searchSpan, searchStart.Value, this.SearchTerm, this.SearchOptions & ~FindOptions.Wrap)) { // As a safety measure, we don't include results of length zero in the navigator unless regular expressions are being used. // Zero width matches could be useful in RegEx when for example somebody is trying to replace the start of the line using the "^" // pattern. if (result.Length == 0 && !regEx) { continue; } else { // We accept the first match this.CurrentResult = result; return(true); } } if (forward) { ++index; } else { --index; } } } // If nothing was found, then clear the current result this.ClearCurrentResult(); return(false); }