Example #1
0
 private void AdvanceToSnapshot(ITextSnapshot snapshot)
 {
     if ((_searchSpans != null) && (_searchSpans.Count > 0) && (_searchSpans[0].Snapshot != snapshot))
     {
         NormalizedSpanCollection newSpans = TextSearchNavigator.TranslateTo(_searchSpans[0].Snapshot, _searchSpans, snapshot);
         this.SearchSpans = new NormalizedSnapshotSpanCollection(snapshot, newSpans);
     }
 }
Example #2
0
        internal ITextSnapshot AdvanceToCurrentSnapshot()
        {
            //We don't need to take a snapshot of the results because the results are only modified on this thread.
            ITextSnapshot oldSnapshot = _results.Snapshot;
            ITextSnapshot newSnapshot = _buffer.CurrentSnapshot;

            if (oldSnapshot != newSnapshot)
            {
                //The results are all on an old snapshot. We need to project them forward (even though that might cause some stale and incorrect
                //results).
                NormalizedSpanCollection newMatches       = TextSearchNavigator.TranslateTo(oldSnapshot, _results.Matches, newSnapshot);
                NormalizedSpanCollection newSearchedSpans = NormalizedSpanCollection.Empty;

                if ((_results.SearchedSpans.Count != 0) && !_options.HasFlag(FindOptions.Multiline))
                {
                    //Advance our record of the spans that have already been searched to the new snapshot as well.
                    newSearchedSpans = BackgroundSearch <T> .TranslateToAndExtend(oldSnapshot, _results.SearchedSpans, newSnapshot);

                    //But remove anything on a TextSnapshotLine that was modified by the change.
                    List <Span>  changedSpansOnNewSnapshot = new List <Span>();
                    ITextVersion version = oldSnapshot.Version;
                    while (version != newSnapshot.Version)
                    {
                        foreach (var change in version.Changes)
                        {
                            changedSpansOnNewSnapshot.Add(BackgroundSearch <T> .Extend(newSnapshot, Tracking.TrackSpanForwardInTime(SpanTrackingMode.EdgeInclusive, change.NewSpan,
                                                                                                                                    version.Next, newSnapshot.Version)));
                        }

                        version = version.Next;
                    }

                    if (changedSpansOnNewSnapshot.Count > 0)
                    {
                        NormalizedSpanCollection changes = new NormalizedSpanCollection(changedSpansOnNewSnapshot);

                        //Remove the spans touched by changes from the spans we've searched
                        newSearchedSpans = NormalizedSpanCollection.Difference(newSearchedSpans, changes);
                    }
                }

                _results = new SearchResults(newSnapshot, newMatches, newSearchedSpans);
            }

            return(newSnapshot);
        }
Example #3
0
        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);
        }
Example #4
0
        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 IEnumerable <ITagSpan <T> > GetTags(NormalizedSnapshotSpanCollection requestedSpans)
        {
            //We should always be called with a non-empty span.
            if (requestedSpans != null && requestedSpans.Count > 0)
            {
                ITextSnapshot searchSnapshot = _buffer.CurrentSnapshot;
                requestedSpans = new NormalizedSnapshotSpanCollection(searchSnapshot, TextSearchNavigator.TranslateTo(requestedSpans[0].Snapshot, requestedSpans, searchSnapshot));

                if ((_searchSpans != null) && (_searchSpans.Count > 0))
                {
                    //The search has been narrowed via _searchSpan ... limit the request to the search range (after making sure it is on the correct snapshot).
                    if (_searchSpans[0].Snapshot != searchSnapshot)
                    {
                        NormalizedSpanCollection newSpans = TextSearchNavigator.TranslateTo(_searchSpans[0].Snapshot, _searchSpans, searchSnapshot);
                        _searchSpans = new NormalizedSnapshotSpanCollection(searchSnapshot, newSpans);
                    }

                    requestedSpans = new NormalizedSnapshotSpanCollection(searchSnapshot, NormalizedSpanCollection.Intersection(requestedSpans, _searchSpans));

                    if (requestedSpans.Count == 0)
                    {
                        yield break;
                    }
                }

                foreach (var search in _searchTerms)
                {
                    //Queue up a search if we need one.
                    search.QueueSearch(requestedSpans);

                    //Report any results from the search (if we've got them)
                    var results = search.Results;
                    if (results.Count > 0)
                    {
                        //Results could be on an old snapshot (and, if so, a new search has already been queued up) but we need to get the results on the current snapshot.
                        if (results[0].Snapshot != searchSnapshot)
                        {
                            results = new NormalizedSnapshotSpanCollection(searchSnapshot, TextSearchNavigator.TranslateTo(results[0].Snapshot, results, searchSnapshot));
                        }

                        if (_searchSpans != null)
                        {
                            results = new NormalizedSnapshotSpanCollection(searchSnapshot, NormalizedSpanCollection.Intersection(results, _searchSpans));
                        }

                        int start = 0;
                        foreach (var span in requestedSpans)
                        {
                            start = TextSearchTagger <T> .IndexOfContainingSpan(results, span.Start, start, false);

                            if (start >= results.Count)
                            {
                                break;      //All done.
                            }
                            int end = TextSearchTagger <T> .IndexOfContainingSpan(results, span.End, start, true);

                            while (start < end)
                            {
                                T tag = search.TagFactory.Invoke(results[start]);

                                if (tag != null)
                                {
                                    yield return(new TagSpan <T>(results[start], tag));
                                }

                                start++;
                            }
                        }
                    }
                }
            }
        }