------ #### SuperDesign V3.0.2412.2001 - *.[新增]新增程序更新日志设置和自动发布功能。 - *.[修复]修复Post数据格式不正确时双击文本框会导致软件闪退的BUG。
521 lines
15 KiB
C#
521 lines
15 KiB
C#
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;
|
||
FastColoredTextBoxNS.FastColoredTextBox _editor;
|
||
FastColoredTextBoxNS.FastColoredTextBox 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(FastColoredTextBoxNS.FastColoredTextBox 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(); } }
|
||
}
|
||
}
|