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 _highlightGroups = new Dictionary(); 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; } } /// This class finds occurrances of a search string in a text /// editor's IDocument... it's like Find box without a GUI. 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; /// Sets the region to search. The region is updated /// automatically as the document changes. public void SetScanRegion(ISelection sel) { SetScanRegion(sel.Offset, sel.Length); } /// Sets the region to search. The region is updated /// automatically as the document changes. 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(); } /// Begins the start offset for searching public int BeginOffset { get { if (_region != null) return _region.Offset; else return 0; } } /// Begins the end offset for searching 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; } } /// Finds next instance of LookFor, according to the search rules /// (MatchCase, MatchWholeWordOnly). /// Offset in Document at which to begin the search /// If there is a match at beginAtOffset precisely, it will be returned. /// Region of document that matches the search string 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 arg1, T2 arg2); public delegate TResult Func(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 matchFirstCh; Func 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; } } /// Bundles a group of markers together so that they can be cleared /// together. public class HighlightGroup : IDisposable { List _markers = new List(); 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 Markers { get { return _markers.AsReadOnly(); } } } }