/* * Copyright (C) 2013 FooProject * * This program 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. * This program 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 this program. If not, see . */ using System; using System.IO; using System.ComponentModel; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Linq; using System.Runtime.CompilerServices; namespace FooEditEngine { /// /// オートインデントを行うためのデリゲートを表す /// /// イベント発生元のオブジェクト /// イベントデーター public delegate void AutoIndentHookerHandler(object sender, EventArgs e); /// /// 進行状況を表す列挙体 /// public enum ProgressState { /// /// 操作が開始したことを表す /// Start, /// /// 操作が終了したことを表す /// Complete, } /// /// 進行状況を表すためのイベントデータ /// public sealed class ProgressEventArgs : EventArgs { /// /// 進行状況 /// public ProgressState state; /// /// コンストラクター /// /// ProgressStateオブジェクト public ProgressEventArgs(ProgressState state) { this.state = state; } } /// /// 進行状況を通知するためのデリゲート /// /// 送信元クラス /// イベントデータ public delegate void ProgressEventHandler(object sender, ProgressEventArgs e); /// /// 更新タイプを表す列挙体 /// public enum UpdateType { /// /// ドキュメントが置き換えられたことを表す /// Replace, /// /// ドキュメント全体が削除されたことを表す /// Clear, /// /// レイアウトが再構築されたことを表す /// RebuildLayout, } /// /// 更新タイプを通知するためのイベントデータ /// public sealed class DocumentUpdateEventArgs : EventArgs { /// /// 値が指定されていないことを示す /// public const int EmptyValue = -1; /// /// 更新タイプ /// public UpdateType type; /// /// 開始位置 /// public int startIndex; /// /// 削除された長さ /// public int removeLength; /// /// 追加された長さ /// public int insertLength; /// /// 更新イベントが発生した行。行が不明な場合や行をまたぐ場合はnullを指定すること。 /// public int? row; /// /// コンストラクター /// /// 更新タイプ /// 開始インデックス /// 削除された長さ /// 追加された長さ /// 開始行。nullを指定することができる public DocumentUpdateEventArgs(UpdateType type, int startIndex = EmptyValue, int removeLength = EmptyValue, int insertLength = EmptyValue, int? row = null) { this.type = type; this.startIndex = startIndex; this.removeLength = removeLength; this.insertLength = insertLength; this.row = row; } } /// /// ドキュメントに更新があったことを伝えるためのデリゲート /// /// 送信元クラス /// イベントデータ public delegate void DocumentUpdateEventHandler(object sender, DocumentUpdateEventArgs e); /// /// ドキュメントの管理を行う /// /// この型のすべてのメソッド・プロパティはスレッドセーフです public sealed class Document : IEnumerable, IRandomEnumrator, IDisposable { Regex regex; Match match; StringBuffer buffer; LineToIndexTable _LayoutLines; bool _EnableFireUpdateEvent = true,_UrlMark = false, _DrawLineNumber = false, _HideRuler = true, _RightToLeft = false; LineBreakMethod _LineBreak; int _TabStops, _LineBreakCharCount = 80; bool _ShowFullSpace, _ShowHalfSpace, _ShowTab, _ShowLineBreak,_InsertMode, _HideCaret, _HideLineMarker, _RectSelection; IndentMode _IndentMode; /// /// 一行当たりの最大文字数 /// public const int MaximumLineLength = 1000; /// /// コンストラクター /// public Document() : this(null) { } /// /// コンストラクター /// /// ドキュメントオブジェクト /// docが複製されますが、プロパティは引き継がれません public Document(Document doc) { if (doc == null) this.buffer = new StringBuffer(); else this.buffer = new StringBuffer(doc.buffer); this.buffer.Update = new DocumentUpdateEventHandler(buffer_Update); this.Update += new DocumentUpdateEventHandler((s, e) => { }); this.ChangeFireUpdateEvent += new EventHandler((s, e) => { }); this.StatusUpdate += new EventHandler((s, e) => { }); this.Markers = new MarkerCollection(); this.UndoManager = new UndoManager(); this._LayoutLines = new LineToIndexTable(this); this._LayoutLines.Clear(); this.MarkerPatternSet = new MarkerPatternSet(this._LayoutLines, this.Markers); this.MarkerPatternSet.Updated += WacthDogPattern_Updated; this.Selections = new SelectCollection(); this.CaretPostion = new TextPoint(); this.HideLineMarker = true; this.SelectGrippers = new GripperRectangle(new Gripper(), new Gripper()); this.SelectionChanged += new EventHandler((s, e) => { }); this.CaretChanged += (s, e) => { }; this.AutoIndentHook += (s, e) => { }; this.LineBreakChanged += (s, e) => { }; this.Dirty = false; } void WacthDogPattern_Updated(object sender, EventArgs e) { this._LayoutLines.ClearLayoutCache(); } /// /// ダーティフラグ。保存されていなければ真、そうでなければ偽。 /// public bool Dirty { get; set; } /// /// キャレットでの選択の起点となる位置 /// internal int AnchorIndex { get; set; } /// /// レタリングの開始位置を表す /// internal SrcPoint Src { get; set; } /// /// ドキュメントのタイトル /// public string Title { get; set; } /// /// 補完候補プロセッサーが切り替わったときに発生するイベント /// public event EventHandler AutoCompleteChanged; AutoCompleteBoxBase _AutoComplete; /// /// 補完候補プロセッサー /// public AutoCompleteBoxBase AutoComplete { get { return this._AutoComplete; } set { this._AutoComplete = value; if (this.AutoCompleteChanged != null) this.AutoCompleteChanged(this, null); } } /// /// 読み込み中に発生するイベント /// public event ProgressEventHandler LoadProgress; /// /// ルーラーやキャレット・行番号などの表示すべきものが変化した場合に呼び出される。ドキュメントの内容が変化した通知を受け取り場合はUpdateを使用してください /// public event EventHandler StatusUpdate; /// /// 全角スペースを表示するかどうか /// public bool ShowFullSpace { get { return this._ShowFullSpace; } set { if (this._ShowFullSpace == value) return; this._ShowFullSpace = value; this.StatusUpdate(this, null); } } /// /// 半角スペースを表示するかどうか /// public bool ShowHalfSpace { get { return this._ShowHalfSpace; } set { if (this._ShowHalfSpace == value) return; this._ShowHalfSpace = value; this.StatusUpdate(this, null); } } /// /// TABを表示するかどうか /// public bool ShowTab { get { return this._ShowTab; } set { if (this._ShowTab == value) return; this._ShowTab = value; this.StatusUpdate(this, null); } } /// /// 改行を表示するかどうか /// public bool ShowLineBreak { get { return this._ShowLineBreak; } set { if (this._ShowLineBreak == value) return; this._ShowLineBreak = value; this.StatusUpdate(this, null); } } /// /// 選択範囲にあるグリッパーのリスト /// internal GripperRectangle SelectGrippers { private set; get; } /// /// 右から左に表示するなら真 /// public bool RightToLeft { get { return this._RightToLeft; } set { if (this._RightToLeft == value) return; this._RightToLeft = value; this.StatusUpdate(this, null); } } /// /// 矩形選択モードなら真を返し、そうでない場合は偽を返す /// public bool RectSelection { get { return this._RectSelection; } set { this._RectSelection = value; this.StatusUpdate(this, null); } } /// /// インデントの方法を表す /// public IndentMode IndentMode { get { return this._IndentMode; } set { this._IndentMode = value; this.StatusUpdate(this, null); } } /// /// ラインマーカーを描くなら偽。そうでなければ真 /// public bool HideLineMarker { get { return this._HideLineMarker; } set { this._HideLineMarker = value; this.StatusUpdate(this, null); } } /// /// キャレットを描くなら偽。そうでなければ真 /// public bool HideCaret { get { return this._HideCaret; } set { this._HideCaret = value; this.StatusUpdate(this, null); } } /// /// 挿入モードなら真を返し、上書きモードなら偽を返す /// public bool InsertMode { get { return this._InsertMode; } set { this._InsertMode = value; this.StatusUpdate(this, null); } } /// /// ルーラーを表示しないなら真、そうでないなら偽 /// public bool HideRuler { get { return this._HideRuler; } set { if (this._HideRuler == value) return; this._HideRuler = value; this.LayoutLines.ClearLayoutCache(); this.StatusUpdate(this, null); } } TextPoint _CaretPostion; /// /// レイアウト行のどこにキャレットがあるかを表す /// public TextPoint CaretPostion { get { return this._CaretPostion; } set { if(this._CaretPostion != value) { this._CaretPostion = value; this.CaretChanged(this, null); } } } internal void SetCaretPostionWithoutEvent(TextPoint value) { if (this._CaretPostion != value) this._CaretPostion = value; } /// /// 選択範囲コレクション /// internal SelectCollection Selections { get; set; } /// /// 行番号を表示するかどうか /// public bool DrawLineNumber { get { return this._DrawLineNumber; } set { if (this._DrawLineNumber == value) return; this._DrawLineNumber = value; this._LayoutLines.ClearLayoutCache(); this.StatusUpdate(this, null); } } /// /// URLをハイパーリンクとして表示するなら真。そうでないなら偽 /// public bool UrlMark { get { return this._UrlMark; } set { if (this._UrlMark == value) return; this._UrlMark = value; if (value) { Regex regex = new Regex("(http|https|ftp)(:\\/\\/[-_.!~*\\'()a-zA-Z0-9;\\/?:\\@&=+\\$,%#]+)"); this.MarkerPatternSet.Add(MarkerIDs.URL, new RegexMarkerPattern(regex, HilightType.Url, new Color())); } else { this.MarkerPatternSet.Remove(MarkerIDs.URL); } this.StatusUpdate(this, null); } } /// /// 桁折りの方法が変わったことを表す /// public event EventHandler LineBreakChanged; /// /// 桁折り処理の方法を指定する /// /// /// 変更した場合、呼び出し側で再描写とレイアウトの再構築を行う必要があります /// また、StatusUpdatedではなく、LineBreakChangedイベントが発生します /// public LineBreakMethod LineBreak { get { return this._LineBreak; } set { if (this._LineBreak == value) return; this._LineBreak = value; this.LineBreakChanged(this, null); } } /// /// 折り返し行う文字数。実際に折り返しが行われる幅はem単位×この値となります /// /// この値を変えた場合、LineBreakChangedイベントが発生します public int LineBreakCharCount { get { return this._LineBreakCharCount; } set { if (this._LineBreakCharCount == value) return; this._LineBreakCharCount = value; this.LineBreakChanged(this, null); } } /// /// タブの幅 /// /// 変更した場合、呼び出し側で再描写する必要があります public int TabStops { get { return this._TabStops; } set { if (this._TabStops == value) return; this._TabStops = value; this.StatusUpdate(this, null); } } /// /// マーカーパターンセット /// public MarkerPatternSet MarkerPatternSet { get; private set; } /// /// レイアウト行を表す /// public LineToIndexTable LayoutLines { get { return this._LayoutLines; } } internal void FireUpdate(DocumentUpdateEventArgs e) { this.buffer_Update(this.buffer, e); } /// /// ドキュメントが更新された時に呼ばれるイベント /// public event DocumentUpdateEventHandler Update; /// /// FireUpdateEventの値が変わったときに呼び出されるイベント /// public event EventHandler ChangeFireUpdateEvent; /// /// 改行コードの内部表現 /// public const char NewLine = '\n'; /// /// EOFの内部表現 /// public const char EndOfFile = '\u001a'; /// /// アンドゥ管理クラスを表す /// public UndoManager UndoManager { get; private set; } /// /// 文字列の長さ /// public int Length { get { return this.buffer.Length; } } /// /// 変更のたびにUpdateイベントを発生させるかどうか /// public bool FireUpdateEvent { get { return this._EnableFireUpdateEvent; } set { this._EnableFireUpdateEvent = value; this.ChangeFireUpdateEvent(this, null); } } /// /// インデクサー /// /// インデックス(自然数でなければならない) /// Char型 public char this[int i] { get { return this.buffer[i]; } } /// /// マーカーコレクション /// public MarkerCollection Markers { get; private set; } internal StringBuffer StringBuffer { get { return this.buffer; } } /// /// 再描写を要求しているなら真 /// public bool IsRequestRedraw { get; internal set; } /// /// 再描写を要求する /// public void RequestRedraw() { this.IsRequestRedraw = true; } /// /// レイアウト行が再構成されたときに発生するイベント /// public event EventHandler PerformLayouted; /// /// レイアウト行をすべて破棄し、再度レイアウトを行う /// /// 真の場合、レイアウトキャッシュのみ再構築します public void PerformLayout(bool quick = true) { if (quick) { this.LayoutLines.ClearLayoutCache(); } else { this.LayoutLines.IsFrozneDirtyFlag = true; this.FireUpdate(new DocumentUpdateEventArgs(UpdateType.RebuildLayout, -1, -1, -1)); this.LayoutLines.IsFrozneDirtyFlag = false; } if (this.PerformLayouted != null) this.PerformLayouted(this, null); } /// /// オードインデントが可能になった時に通知される /// /// /// FireUpdateEventの影響を受けます /// public event AutoIndentHookerHandler AutoIndentHook; /// /// 選択領域変更時に通知される /// public event EventHandler SelectionChanged; /// /// キャレット移動時に通知される /// public event EventHandler CaretChanged; /// /// 指定された範囲を選択する /// /// /// /// RectSelectionの値によって動作が変わります。真の場合は矩形選択モードに、そうでない場合は行ごとに選択されます public void Select(int start, int length) { if (this.FireUpdateEvent == false) throw new InvalidOperationException(""); if (start < 0 || start + length < 0 || start + length > this.Length) throw new ArgumentOutOfRangeException("startかendが指定できる範囲を超えてます"); //選択範囲が消されたとき foreach (Selection sel in this.Selections) this.LayoutLines.ClearLayoutCache(sel.start, sel.length); this.Selections.Clear(); if (length < 0) { int oldStart = start; start += length; length = oldStart - start; } if (this.RectSelection && length != 0) { TextPoint startTextPoint = this.LayoutLines.GetTextPointFromIndex(start); TextPoint endTextPoint = this.LayoutLines.GetTextPointFromIndex(start + length); this.SelectByRectangle(new TextRectangle(startTextPoint, endTextPoint)); this.LayoutLines.ClearLayoutCache(start, length); } else if (length != 0) { this.Selections.Add(Selection.Create(start, length)); this.LayoutLines.ClearLayoutCache(start, length); } this.SelectionChanged(this, null); } /// /// 矩形選択を行う /// /// 開始位置 /// 桁数 /// 行数 public void Select(TextPoint tp, int width, int height) { if (this.FireUpdateEvent == false || !this.RectSelection) throw new InvalidOperationException(""); TextPoint end = tp; end.row = tp.row + height; end.col = tp.col + width; if (end.row > this.LayoutLines.Count - 1) throw new ArgumentOutOfRangeException(""); this.Selections.Clear(); this.SelectByRectangle(new TextRectangle(tp, end)); this.SelectionChanged(this, null); } private void SelectByRectangle(TextRectangle rect) { if (this.FireUpdateEvent == false) throw new InvalidOperationException(""); if (rect.TopLeft <= rect.BottomRight) { for (int i = rect.TopLeft.row; i <= rect.BottomLeft.row; i++) { int length = this.LayoutLines.GetLengthFromLineNumber(i); int leftCol = rect.TopLeft.col, rightCol = rect.TopRight.col, lastCol = length; if (length > 0 && this.LayoutLines[i][length - 1] == Document.NewLine) lastCol = length - 1; if (lastCol < 0) lastCol = 0; if (rect.TopLeft.col > lastCol) leftCol = lastCol; if (rect.TopRight.col > lastCol) rightCol = lastCol; int StartIndex = this.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, leftCol)); int EndIndex = this.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, rightCol)); Selection sel; sel = Selection.Create(StartIndex, EndIndex - StartIndex); this.Selections.Add(sel); } } } /// /// 単語単位で選択する /// /// 探索を開始するインデックス /// 選択の起点となるとインデックスを変更するなら真。そうでなければ偽 public void SelectWord(int index, bool changeAnchor = false) { this.SelectSepartor(index, (c) => Util.IsWordSeparator(c), changeAnchor); } /// /// 行単位で選択する /// /// 探索を開始するインデックス /// 選択の起点となるとインデックスを変更するなら真。そうでなければ偽 public void SelectLine(int index,bool changeAnchor = false) { this.SelectSepartor(index, (c) => c == Document.NewLine, changeAnchor); } /// /// セパレーターで区切られた領域を取得する /// /// 探索を開始するインデックス /// セパレーターなら真を返し、そうでないなら偽を返す /// 開始インデックス、終了インデックス public Tuple GetSepartor(int index, Func find_sep_func) { if (find_sep_func == null) throw new ArgumentNullException("find_sep_func must not be null"); if (this.Length <= 0 || index >= this.Length) return null; Document str = this; int start = index; while (start > 0 && !find_sep_func(str[start])) start--; if (find_sep_func(str[start])) { start++; } int end = index; while (end < this.Length && !find_sep_func(str[end])) end++; return new Tuple(start, end); } /// /// セパレーターで囲まれた範囲内を選択する /// /// 探索を開始するインデックス /// セパレーターなら真を返し、そうでないなら偽を返す /// 選択の起点となるとインデックスを変更するなら真。そうでなければ偽 public void SelectSepartor(int index,Func find_sep_func, bool changeAnchor = false) { if (this.FireUpdateEvent == false) throw new InvalidOperationException(""); if (find_sep_func == null) throw new ArgumentNullException("find_sep_func must not be null"); var t = this.GetSepartor(index, find_sep_func); if (t == null) return; int start = t.Item1, end = t.Item2; this.Select(start, end - start); if (changeAnchor) this.AnchorIndex = start; } /// /// DocumentReaderを作成します /// /// DocumentReaderオブジェクト public DocumentReader CreateReader() { return new DocumentReader(this.buffer); } /// /// マーカーを設定する /// /// マーカーID /// 設定したいマーカー public void SetMarker(int id,Marker m) { if (m.start < 0 || m.start + m.length > this.Length) throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています"); this.Markers.Add(id,m); } /// /// マーカーを削除する /// /// マーカーID /// 開始インデックス /// 削除する長さ public void RemoveMarker(int id,int start, int length) { if (start < 0 || start + length > this.Length) throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています"); this.Markers.RemoveAll(id,start, length); } /// /// マーカーを削除する /// /// マーカーID /// 削除したいマーカーのタイプ public void RemoveMarker(int id, HilightType type) { this.Markers.RemoveAll(id,type); } /// /// すべてのマーカーを削除する /// /// マーカーID public void RemoveAllMarker(int id) { this.Markers.RemoveAll(id); } /// /// インデックスに対応するマーカーを得る /// /// マーカーID /// インデックス /// Marker構造体の列挙子 public IEnumerable GetMarkers(int id, int index) { if (index < 0 || index > this.Length) throw new ArgumentOutOfRangeException("indexが範囲を超えています"); return this.Markers.Get(id,index); } /// /// 部分文字列を取得する /// /// 開始インデックス /// 長さ /// Stringオブジェクト public string ToString(int index, int length) { return this.buffer.ToString(index, length); } /// /// インデックスを開始位置とする文字列を返す /// /// 開始インデックス /// Stringオブジェクト public string ToString(int index) { return this.ToString(index, this.buffer.Length - index); } /// /// 行を取得する /// /// 開始インデックス /// 終了インデックス /// 最大長 /// 行イテレーターが返される public IEnumerable GetLines(int startIndex, int endIndex, int maxCharCount = -1) { foreach (Tuple range in this.LayoutLines.ForEachLines(startIndex, endIndex, maxCharCount)) { StringBuilder temp = new StringBuilder(); temp.Clear(); int lineEndIndex = range.Item1; if (range.Item2 > 0) lineEndIndex += range.Item2 - 1; for (int i = range.Item1; i <= lineEndIndex; i++) temp.Append(this.buffer[i]); yield return temp.ToString(); } } /// /// 文字列を追加する /// /// 追加したい文字列 /// 非同期操作中はこのメソッドを実行することはできません public void Append(string s) { this.Replace(this.buffer.Length, 0, s); } /// /// 文字列を挿入する /// /// 開始インデックス /// 追加したい文字列 /// 読み出し操作中はこのメソッドを実行することはできません public void Insert(int index, string s) { this.Replace(index, 0, s); } /// /// 文字列を削除する /// /// 開始インデックス /// 長さ /// 読み出し操作中はこのメソッドを実行することはできません public void Remove(int index, int length) { this.Replace(index, length, ""); } /// /// ドキュメントを置き換える /// /// 開始インデックス /// 長さ /// 文字列 /// ユーザーからの入力として扱うなら真 /// 読み出し操作中はこのメソッドを実行することはできません public void Replace(int index, int length, string s, bool UserInput = false) { if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0) throw new ArgumentOutOfRangeException(); if (length == 0 && (s == string.Empty || s == null)) return; foreach(int id in this.Markers.IDs) this.RemoveMarker(id,index, length); ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s); this.UndoManager.push(cmd); cmd.redo(); if (this.FireUpdateEvent && UserInput) { var input_str = string.Empty; if (s == Document.NewLine.ToString()) input_str = s; else if (s == string.Empty && length > 0) input_str = "\b"; //入力は終わっているので空文字を渡すが処理の都合で一部文字だけはそのまま渡す if (this.AutoComplete != null) this.AutoComplete.ParseInput(input_str); if (s == Document.NewLine.ToString()) this.AutoIndentHook(this, null); } } /// /// 物理行をすべて削除する /// /// Dirtyフラグも同時にクリアーされます /// 非同期操作中はこのメソッドを実行することはできません public void Clear() { this.buffer.Clear(); System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(); this.Dirty = false; } /// /// ストリームからドキュメントを非同期的に構築します /// /// IStreamReaderオブジェクト /// キャンセルトークン /// ファイルサイズ。-1を指定しても動作しますが、読み取りが遅くなります /// Taskオブジェクト /// /// 読み取り操作は別スレッドで行われます。 /// また、非同期操作中はこのメソッドを実行することはできません。 /// public async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null, int file_size = -1) { if (fs.Peek() == -1) return; if (this.LoadProgress != null) this.LoadProgress(this, new ProgressEventArgs(ProgressState.Start)); try { this.Clear(); if (file_size > 0) this.buffer.Allocate(file_size); await this.buffer.LoadAsync(fs, tokenSource); } finally { this.PerformLayout(false); if (this.LoadProgress != null) this.LoadProgress(this, new ProgressEventArgs(ProgressState.Complete)); } } /// /// ストリームに非同期モードで保存します /// /// IStreamWriterオブジェクト /// キャンセルトークン /// Taskオブジェクト /// 非同期操作中はこのメソッドを実行することはできません public async Task SaveAsync(TextWriter fs, CancellationTokenSource tokenSource = null) { await this.buffer.SaveAsync(fs, tokenSource); } /// /// Find()およびReplaceAll()で使用するパラメーターをセットします /// /// 検索したい文字列 /// 正規表現を使用するなら真 /// RegexOptions列挙体 public void SetFindParam(string pattern, bool UseRegex, RegexOptions opt) { this.match = null; if (UseRegex) this.regex = new Regex(pattern, opt); else this.regex = new Regex(Regex.Escape(pattern), opt); } /// /// 現在の検索パラメーターでWatchDogを生成する /// /// ハイライトタイプ /// 色 /// WatchDogオブジェクト public RegexMarkerPattern CreateWatchDogByFindParam(HilightType type,Color color) { if (this.regex == null) throw new InvalidOperationException("SetFindParam()を呼び出してください"); return new RegexMarkerPattern(this.regex,type,color); } /// /// 指定した文字列を検索します /// /// 見つかった場合はSearchResult列挙子を返却します /// 見つかったパターン以外を置き換えた場合、正常に動作しないことがあります public IEnumerator Find() { return this.Find(0, this.Length); } /// /// 指定した文字列を検索します /// /// 見つかった場合はSearchResult列挙子を返却します /// 開始インデックス /// 検索する長さ /// 見つかったパターン以外を置き換えた場合、正常に動作しないことがあります public IEnumerator Find(int start, int length) { if (this.regex == null) throw new InvalidOperationException(); if (start < 0 || start >= this.Length) throw new ArgumentOutOfRangeException(); int end = start + length - 1; if(end > this.Length - 1) throw new ArgumentOutOfRangeException(); StringBuilder line = new StringBuilder(); int oldLength = this.Length; for (int i = start; i <= end; i++) { char c = this[i]; line.Append(c); if (c == Document.NewLine || i == end) { this.match = this.regex.Match(line.ToString()); while (this.match.Success) { int startIndex = i - line.Length + 1 + this.match.Index; int endIndex = startIndex + this.match.Length - 1; yield return new SearchResult(this.match, startIndex, endIndex); if (this.Length != oldLength) //長さが変わった場合は置き換え後のパターンの終点+1まで戻る { int delta = this.Length - oldLength; i = endIndex + delta; end = end + delta; oldLength = this.Length; break; } this.match = this.match.NextMatch(); } line.Clear(); } } } /// /// 任意のパターンですべて置き換えます /// /// 置き換え後のパターン /// グループ置き換えを行うなら真。そうでないなら偽 public void ReplaceAll(string replacePattern,bool groupReplace) { if (this.regex == null) throw new InvalidOperationException(); ReplaceAllCommand cmd = new ReplaceAllCommand(this.buffer, this.LayoutLines, this.regex, replacePattern, groupReplace); this.UndoManager.push(cmd); cmd.redo(); } /// /// 任意のパターンで置き換える /// /// 対象となる文字列 /// 置き換え後の文字列 /// 大文字も文字を区別しないなら真。そうでないなら偽 /// /// 検索時に大文字小文字を区別します。また、このメソッドでは正規表現を使用することはできません /// public void ReplaceAll2(string target, string pattern,bool ci = false) { FastReplaceAllCommand cmd = new FastReplaceAllCommand(this.buffer, this.LayoutLines, target, pattern,ci); this.UndoManager.push(cmd); cmd.redo(); } #region IEnumerable メンバー /// /// 列挙子を返します /// /// IEnumeratorオブジェクトを返す public IEnumerator GetEnumerator() { return this.buffer.GetEnumerator(); } #endregion #region IEnumerable メンバー System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw new NotImplementedException(); } #endregion void buffer_Update(object sender, DocumentUpdateEventArgs e) { switch (e.type) { case UpdateType.RebuildLayout: this._LayoutLines.Clear(); this._LayoutLines.UpdateAsReplace(0, 0, this.Length); break; case UpdateType.Replace: if (e.row == null) { this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength); this.Markers.UpdateMarkers(e.startIndex, e.insertLength, e.removeLength); } else { this._LayoutLines.UpdateLineAsReplace(e.row.Value, e.removeLength, e.insertLength); this.Markers.UpdateMarkers(this.LayoutLines.GetIndexFromLineNumber(e.row.Value), e.insertLength, e.removeLength); } this.Dirty = true; break; case UpdateType.Clear: this._LayoutLines.Clear(); this.Dirty = true; break; } if(this.FireUpdateEvent) this.Update(this, e); } #region IDisposable Support private bool disposedValue = false; // 重複する呼び出しを検出するには void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { this.buffer.Clear(); this.LayoutLines.Clear(); } disposedValue = true; } } /// /// ドキュメントを破棄する /// public void Dispose() { Dispose(true); } #endregion } /// /// 検索結果を表す /// public class SearchResult { private Match Match; /// /// 一致した場所の開始位置を表す /// public int Start; /// /// 一致した場所の終了位置を表す /// public int End; /// /// 見つかった文字列を返す /// public string Value { get { return this.Match.Value; } } /// /// 指定したパターンを置き換えて返す /// /// 置き換える文字列 /// 置き換え後の文字列 public string Result(string replacement) { return this.Match.Result(replacement); } /// /// コンストラクター /// /// Matchオブジェクト /// 開始インデックス /// 終了インデックス public SearchResult(Match m, int start,int end) { this.Match = m; this.Start = start; this.End = end; } } /// /// ドキュメントリーダー /// public class DocumentReader : TextReader { StringBuffer document; int currentIndex; /// /// コンストラクター /// /// internal DocumentReader(StringBuffer doc) { if (doc == null) throw new ArgumentNullException(); this.document = doc; } /// /// 文字を取得する /// /// 文字。取得できない場合は-1 public override int Peek() { if (this.document == null) throw new InvalidOperationException(); if (this.currentIndex >= this.document.Length) return -1; return this.document[this.currentIndex]; } /// /// 文字を取得し、イテレーターを一つ進める /// /// 文字。取得できない場合は-1 public override int Read() { int c = this.Peek(); if(c != -1) this.currentIndex++; return c; } /// /// 文字列を読み取りバッファーに書き込む /// /// バッファー /// 開始インデックス /// カウント /// 読み取られた文字数 public override int Read(char[] buffer, int index, int count) { if (this.document == null) throw new InvalidOperationException(); if (buffer == null) throw new ArgumentNullException(); if (this.document.Length < count) throw new ArgumentException(); if (index < 0 || count < 0) throw new ArgumentOutOfRangeException(); if (this.document.Length == 0) return 0; int actualCount = count; if (index + count - 1 > this.document.Length - 1) actualCount = this.document.Length - index; string str = this.document.ToString(index, actualCount); for (int i = 0; i < str.Length; i++) //ToCharArray()だと戻った時に消えてしまう buffer[i] = str[i]; this.currentIndex = index + actualCount; return actualCount; } /// /// オブジェクトを破棄する /// /// 真ならアンマネージドリソースを解放する protected override void Dispose(bool disposing) { this.document = null; } } }