Dnspy/dnSpy/dnSpy/Text/Operations/TextSearchNavigator.cs
2021-09-20 18:20:01 +02:00

159 lines
5.8 KiB
C#

/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Diagnostics;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Operations;
namespace dnSpy.Text.Operations {
sealed class TextSearchNavigator : ITextSearchNavigator {
public string? SearchTerm { get; set; }
public string? ReplaceTerm { get; set; }
public FindOptions SearchOptions { get; set; }
public SnapshotSpan? CurrentResult => currentResult;
SnapshotSpan? currentResult;
public SnapshotPoint? StartPoint {
get {
if (startPoint is not null)
startPoint = startPoint.Value.TranslateTo(buffer.CurrentSnapshot, (SearchOptions & FindOptions.SearchReverse) != 0 ? PointTrackingMode.Negative : PointTrackingMode.Positive);
return startPoint;
}
set {
if (value is not null && value.Value.Snapshot.TextBuffer != buffer)
throw new ArgumentException();
startPoint = value;
}
}
SnapshotPoint? startPoint;
public ITrackingSpan? SearchSpan {
get => searchSpan;
set {
if (value is null)
throw new ArgumentNullException(nameof(value));
if (value.TextBuffer != buffer)
throw new ArgumentException();
searchSpan = value;
}
}
ITrackingSpan? searchSpan;
readonly ITextBuffer buffer;
readonly ITextSearchService2 textSearchService2;
public TextSearchNavigator(ITextBuffer buffer, ITextSearchService2 textSearchService2) {
this.buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
this.textSearchService2 = textSearchService2 ?? throw new ArgumentNullException(nameof(textSearchService2));
}
bool IsValidStartingPosition(SnapshotSpan range, SnapshotPoint startingPosition) =>
range.Snapshot == startingPosition.Snapshot && range.Start <= startingPosition && startingPosition <= range.End;
bool FindFailed() {
currentResult = null;
return false;
}
public bool Find() {
if (SearchTerm is null)
throw new InvalidOperationException();
if (SearchTerm.Length == 0)
throw new InvalidOperationException();
SnapshotPoint startingPosition;
if (CurrentResult is not null) {
if ((SearchOptions & FindOptions.SearchReverse) != 0) {
if (CurrentResult.Value.End.Position > 0)
startingPosition = CurrentResult.Value.End - 1;
else if ((SearchOptions & FindOptions.Wrap) != 0)
startingPosition = new SnapshotPoint(CurrentResult.Value.Snapshot, CurrentResult.Value.Snapshot.Length);
else
return FindFailed();
}
else {
if (CurrentResult.Value.Start.Position != CurrentResult.Value.Snapshot.Length)
startingPosition = CurrentResult.Value.Start + 1;
else if ((SearchOptions & FindOptions.Wrap) != 0)
startingPosition = new SnapshotPoint(CurrentResult.Value.Snapshot, 0);
else
return FindFailed();
}
}
else if (StartPoint is not null)
startingPosition = StartPoint.Value;
else
startingPosition = new SnapshotPoint(buffer.CurrentSnapshot, 0);
startingPosition = startingPosition.TranslateTo(buffer.CurrentSnapshot, (SearchOptions & FindOptions.SearchReverse) != 0 ? PointTrackingMode.Negative : PointTrackingMode.Positive);
var spanToUse = searchSpan?.GetSpan(buffer.CurrentSnapshot) ?? new SnapshotSpan(buffer.CurrentSnapshot, 0, buffer.CurrentSnapshot.Length);
if (!IsValidStartingPosition(spanToUse, startingPosition))
return FindFailed();
foreach (var result in textSearchService2.FindAll(spanToUse, startingPosition, SearchTerm, SearchOptions)) {
currentResult = result;
return true;
}
return FindFailed();
}
public bool Replace() {
if (SearchTerm is null)
throw new InvalidOperationException();
if (SearchTerm.Length == 0)
throw new InvalidOperationException();
if (CurrentResult is null)
throw new InvalidOperationException();
if (ReplaceTerm is null)
throw new InvalidOperationException();
var spanToUse = searchSpan?.GetSpan(CurrentResult.Value.Snapshot) ?? new SnapshotSpan(CurrentResult.Value.Snapshot, 0, CurrentResult.Value.Snapshot.Length);
SnapshotSpan searchRange;
if ((SearchOptions & FindOptions.SearchReverse) != 0) {
Debug.Assert(spanToUse.Start <= CurrentResult.Value.End);
if (spanToUse.Start > CurrentResult.Value.End)
return false;
searchRange = new SnapshotSpan(spanToUse.Start, CurrentResult.Value.End);
}
else {
Debug.Assert(CurrentResult.Value.Start <= spanToUse.End);
if (CurrentResult.Value.Start > spanToUse.End)
return false;
searchRange = new SnapshotSpan(CurrentResult.Value.Start, spanToUse.End);
}
var span = textSearchService2.FindForReplace(searchRange, SearchTerm, ReplaceTerm, SearchOptions, out string expandedReplacePattern);
if (span is null)
return false;
using (var ed = buffer.CreateEdit()) {
var currSpan = span.Value.TranslateTo(buffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);
if (!ed.Replace(currSpan.Span, expandedReplacePattern))
return false;
ed.Apply();
// Don't check the snapshot returned by Apply() since we could've replaced a 0-length span with the
// empty string if regex search was used. In that case, no new snapshot is created.
return !ed.Canceled;
}
}
public void ClearCurrentResult() => currentResult = null;
}
}