SuperDesign/Source/开发辅助工具/Controls/FindAndReplaceForm.cs

521 lines
14 KiB
C#
Raw Normal View History

2020-11-28 08:15:13 +00:00
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor;
using System.Diagnostics;
using System.IO;
namespace XmlPad
{
public partial class FindAndReplaceForm : Form
{
public FindAndReplaceForm()
{
InitializeComponent();
_search = new TextEditorSearcher();
}
TextEditorSearcher _search;
TextEditorControl _editor;
TextEditorControl Editor
{
get { return _editor; }
set
{
_editor = value;
_search.Document = _editor.Document;
UpdateTitleBar();
}
}
private void UpdateTitleBar()
{
string text = ReplaceMode ? "查找和替换" : "查找";
if (_editor != null && _editor.FileName != null)
text += " - " + Path.GetFileName(_editor.FileName);
if (_search.HasScanRegion)
text += " (只可选)";
this.Text = text;
}
public void ShowFor(TextEditorControl editor, bool replaceMode)
{
Editor = editor;
_search.ClearScanRegion();
SelectionManager sm = editor.ActiveTextAreaControl.SelectionManager;
if (sm.HasSomethingSelected && sm.SelectionCollection.Count == 1)
{
ISelection sel = sm.SelectionCollection[0];
if (sel.StartPosition.Y == sel.EndPosition.Y)
txtLookFor.Text = sm.SelectedText;
else
_search.SetScanRegion(sel);
}
else
{
// Get the current word that the caret is on
Caret caret = editor.ActiveTextAreaControl.Caret;
int start = TextUtilities.FindWordStart(editor.Document, caret.Offset);
int endAt = TextUtilities.FindWordEnd(editor.Document, caret.Offset);
txtLookFor.Text = editor.Document.GetText(start, endAt - start);
}
ReplaceMode = replaceMode;
this.Owner = (Form)editor.TopLevelControl;
this.Show();
txtLookFor.SelectAll();
txtLookFor.Focus();
}
public bool ReplaceMode
{
get { return txtReplaceWith.Visible; }
set
{
btnReplace.Visible = btnReplaceAll.Visible = value;
lblReplaceWith.Visible = txtReplaceWith.Visible = value;
btnFindAllHighlightAll.Visible = !value;
this.AcceptButton = value ? btnReplace : btnFindNext;
UpdateTitleBar();
}
}
private void BtnFindPrevious_Click(object sender, EventArgs e)
{
FindNext(false, true, "内容没有找到!");
}
private void BtnFindNext_Click(object sender, EventArgs e)
{
FindNext(false, false, "内容没有找到");
}
public bool _lastSearchWasBackward = false;
public bool _lastSearchLoopedAround;
public TextRange FindNext(bool viaF3, bool searchBackward, string messageIfNotFound)
{
if (string.IsNullOrEmpty(txtLookFor.Text))
{
MessageBox.Show("没有指定要查找的内容!", "提示");
return null;
}
_lastSearchWasBackward = searchBackward;
_search.LookFor = txtLookFor.Text;
_search.MatchCase = chkMatchCase.Checked;
_search.MatchWholeWordOnly = chkMatchWholeWord.Checked;
Caret caret = _editor.ActiveTextAreaControl.Caret;
if (viaF3 && _search.HasScanRegion && !Globals.IsInRange(caret.Offset, _search.BeginOffset, _search.EndOffset))
{
// user moved outside of the originally selected region
_search.ClearScanRegion();
UpdateTitleBar();
}
int startFrom = caret.Offset - (searchBackward ? 1 : 0);
TextRange range = _search.FindNext(startFrom, searchBackward, out _lastSearchLoopedAround);
if (range != null)
SelectResult(range);
else if (messageIfNotFound != null)
MessageBox.Show(messageIfNotFound, "提示");
return range;
}
private void SelectResult(TextRange range)
{
TextLocation p1 = _editor.Document.OffsetToPosition(range.Offset);
TextLocation p2 = _editor.Document.OffsetToPosition(range.Offset + range.Length);
_editor.ActiveTextAreaControl.SelectionManager.SetSelection(p1, p2);
//_editor.ActiveTextAreaControl.ScrollTo(p1.Line, p1.Column);
// Also move the caret to the end of the selection, because when the user
// presses F3, the caret is where we start searching next time.
_editor.ActiveTextAreaControl.Caret.Position =
_editor.Document.OffsetToPosition(range.Offset + range.Length);
}
Dictionary<TextEditorControl, HighlightGroup> _highlightGroups = new Dictionary<TextEditorControl, HighlightGroup>();
private void btnHighlightAll_Click(object sender, EventArgs e)
{
if (!_highlightGroups.ContainsKey(_editor))
_highlightGroups[_editor] = new HighlightGroup(_editor);
HighlightGroup group = _highlightGroups[_editor];
if (string.IsNullOrEmpty(LookFor))
// Clear highlights
group.ClearMarkers();
else
{
_search.LookFor = txtLookFor.Text;
_search.MatchCase = chkMatchCase.Checked;
_search.MatchWholeWordOnly = chkMatchWholeWord.Checked;
bool looped = false;
int offset = 0, count = 0;
for (; ; )
{
TextRange range = _search.FindNext(offset, false, out looped);
if (range == null || looped)
break;
offset = range.Offset + range.Length;
count++;
TextMarker m = new TextMarker(range.Offset, range.Length,
TextMarkerType.SolidBlock, Color.Yellow, Color.Black);
group.AddMarker(m);
}
if (count == 0)
MessageBox.Show("没有找到你要查找的内容", "提示");
else
Close();
}
}
private void FindAndReplaceForm_FormClosing(object sender, FormClosingEventArgs e)
{ // Prevent dispose, as this form can be re-used
if (e.CloseReason != CloseReason.FormOwnerClosing)
{
if (this.Owner != null)
this.Owner.Select(); // prevent another app from being activated instead
e.Cancel = true;
Hide();
// Discard search region
_search.ClearScanRegion();
_editor.Refresh(); // must repaint manually
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
Close();
}
private void btnReplace_Click(object sender, EventArgs e)
{
SelectionManager sm = _editor.ActiveTextAreaControl.SelectionManager;
if (string.Equals(sm.SelectedText, txtLookFor.Text, StringComparison.OrdinalIgnoreCase))
InsertText(txtReplaceWith.Text);
FindNext(false, _lastSearchWasBackward, "内容没有找到!");
}
private void btnReplaceAll_Click(object sender, EventArgs e)
{
int count = 0;
// BUG FIX: if the replacement string contains the original search string
// (e.g. replace "red" with "very red") we must avoid looping around and
// replacing forever! To fix, start replacing at beginning of region (by
// moving the caret) and stop as soon as we loop around.
_editor.ActiveTextAreaControl.Caret.Position =
_editor.Document.OffsetToPosition(_search.BeginOffset);
//_editor.Document.UndoStack.StartUndoGroup();
//try
//{
lock (_editor.Document.UndoStack)
{
while (FindNext(false, false, null) != null)
{
if (_lastSearchLoopedAround)
break;
// Replace
count++;
InsertText(txtReplaceWith.Text);
}
}
//}
//finally
//{
// _editor.Document.UndoStack.EndUndoGroup();
//}
if (count == 0)
MessageBox.Show(string.Format("没有找到目标内容: {0}", txtLookFor.Text), "提示");
else
{
MessageBox.Show(string.Format("替换了 {0} 处内容!", count), "提示");
Close();
}
}
private void InsertText(string text)
{
TextArea textArea = _editor.ActiveTextAreaControl.TextArea;
//textArea.Document.UndoStack.StartUndoGroup();
//try
//{
lock (textArea.Document.UndoStack)
{
if (textArea.SelectionManager.HasSomethingSelected)
{
textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition;
textArea.SelectionManager.RemoveSelectedText();
}
textArea.InsertString(text);
}
//}
//finally
//{
// textArea.Document.UndoStack.EndUndoGroup();
//}
}
public string LookFor { get { return txtLookFor.Text; } }
}
public class TextRange : AbstractSegment
{
IDocument _document;
public TextRange(IDocument document, int offset, int length)
{
_document = document;
this.offset = offset;
this.length = length;
}
}
/// <summary>This class finds occurrances of a search string in a text
/// editor's IDocument... it's like Find box without a GUI.</summary>
public class TextEditorSearcher : IDisposable
{
IDocument _document;
public IDocument Document
{
get { return _document; }
set
{
if (_document != value)
{
ClearScanRegion();
_document = value;
}
}
}
// I would have used the TextAnchor class to represent the beginning and
// end of the region to scan while automatically adjusting to changes in
// the document--but for some reason it is sealed and its constructor is
// internal. Instead I use a TextMarker, which is perhaps even better as
// it gives me the opportunity to highlight the region. Note that all the
// markers and coloring information is associated with the text document,
// not the editor control, so TextEditorSearcher doesn't need a reference
// to the TextEditorControl. After adding the marker to the document, we
// must remember to remove it when it is no longer needed.
TextMarker _region = null;
/// <summary>Sets the region to search. The region is updated
/// automatically as the document changes.</summary>
public void SetScanRegion(ISelection sel)
{
SetScanRegion(sel.Offset, sel.Length);
}
/// <summary>Sets the region to search. The region is updated
/// automatically as the document changes.</summary>
public void SetScanRegion(int offset, int length)
{
Color bkgColor = _document.HighlightingStrategy.GetColorFor("Default").BackgroundColor;
_region = new TextMarker(offset, length, TextMarkerType.SolidBlock,
Globals.HalfMix(bkgColor, Color.FromArgb(160, 160, 160)));
_document.MarkerStrategy.AddMarker(_region);
}
public bool HasScanRegion
{
get { return _region != null; }
}
public void ClearScanRegion()
{
if (_region != null)
{
_document.MarkerStrategy.RemoveMarker(_region);
_region = null;
}
}
public void Dispose() { ClearScanRegion(); GC.SuppressFinalize(this); }
~TextEditorSearcher() { Dispose(); }
/// <summary>Begins the start offset for searching</summary>
public int BeginOffset
{
get
{
if (_region != null)
return _region.Offset;
else
return 0;
}
}
/// <summary>Begins the end offset for searching</summary>
public int EndOffset
{
get
{
if (_region != null)
return _region.EndOffset;
else
return _document.TextLength;
}
}
public bool MatchCase;
public bool MatchWholeWordOnly;
string _lookFor;
string _lookFor2; // uppercase in case-insensitive mode
public string LookFor
{
get { return _lookFor; }
set { _lookFor = value; }
}
/// <summary>Finds next instance of LookFor, according to the search rules
/// (MatchCase, MatchWholeWordOnly).</summary>
/// <param name="beginAtOffset">Offset in Document at which to begin the search</param>
/// <remarks>If there is a match at beginAtOffset precisely, it will be returned.</remarks>
/// <returns>Region of document that matches the search string</returns>
public TextRange FindNext(int beginAtOffset, bool searchBackward, out bool loopedAround)
{
Debug.Assert(!string.IsNullOrEmpty(_lookFor));
loopedAround = false;
int startAt = BeginOffset, endAt = EndOffset;
int curOffs = Globals.InRange(beginAtOffset, startAt, endAt);
_lookFor2 = MatchCase ? _lookFor : _lookFor.ToUpperInvariant();
TextRange result;
if (searchBackward)
{
result = FindNextIn(startAt, curOffs, true);
if (result == null)
{
loopedAround = true;
result = FindNextIn(curOffs, endAt, true);
}
}
else
{
result = FindNextIn(curOffs, endAt, false);
if (result == null)
{
loopedAround = true;
result = FindNextIn(startAt, curOffs, false);
}
}
return result;
}
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
public delegate TResult Func<T, TResult>(T arg);
private TextRange FindNextIn(int offset1, int offset2, bool searchBackward)
{
Debug.Assert(offset2 >= offset1);
offset2 -= _lookFor.Length;
// Make behavior decisions before starting search loop
Func<char, char, bool> matchFirstCh;
Func<int, bool> matchWord;
if (MatchCase)
matchFirstCh = delegate(char lookFor, char c)
{
return lookFor == c;
};
else
matchFirstCh = delegate(char lookFor, char c)
{
return lookFor == Char.ToUpperInvariant(c);
};
if (MatchWholeWordOnly)
matchWord = IsWholeWordMatch;
else
matchWord = IsPartWordMatch;
// Search
char lookForCh = _lookFor2[0];
if (searchBackward)
{
for (int offset = offset2; offset >= offset1; offset--)
{
if (matchFirstCh(lookForCh, _document.GetCharAt(offset))
&& matchWord(offset))
return new TextRange(_document, offset, _lookFor.Length);
}
}
else
{
for (int offset = offset1; offset <= offset2; offset++)
{
if (matchFirstCh(lookForCh, _document.GetCharAt(offset))
&& matchWord(offset))
return new TextRange(_document, offset, _lookFor.Length);
}
}
return null;
}
private bool IsWholeWordMatch(int offset)
{
if (IsWordBoundary(offset) && IsWordBoundary(offset + _lookFor.Length))
return IsPartWordMatch(offset);
else
return false;
}
private bool IsWordBoundary(int offset)
{
return offset <= 0 || offset >= _document.TextLength ||
!IsAlphaNumeric(offset - 1) || !IsAlphaNumeric(offset);
}
private bool IsAlphaNumeric(int offset)
{
char c = _document.GetCharAt(offset);
return Char.IsLetterOrDigit(c) || c == '_';
}
private bool IsPartWordMatch(int offset)
{
string substr = _document.GetText(offset, _lookFor.Length);
if (!MatchCase)
substr = substr.ToUpperInvariant();
return substr == _lookFor2;
}
}
/// <summary>Bundles a group of markers together so that they can be cleared
/// together.</summary>
public class HighlightGroup : IDisposable
{
List<TextMarker> _markers = new List<TextMarker>();
TextEditorControl _editor;
IDocument _document;
public HighlightGroup(TextEditorControl editor)
{
_editor = editor;
_document = editor.Document;
}
public void AddMarker(TextMarker marker)
{
_markers.Add(marker);
_document.MarkerStrategy.AddMarker(marker);
}
public void ClearMarkers()
{
foreach (TextMarker m in _markers)
_document.MarkerStrategy.RemoveMarker(m);
_markers.Clear();
_editor.Refresh();
}
public void Dispose() { ClearMarkers(); GC.SuppressFinalize(this); }
~HighlightGroup() { Dispose(); }
public IList<TextMarker> Markers { get { return _markers.AsReadOnly(); } }
}
}