2 * Copyright (C) 2013 FooProject
3 * * 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
4 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
6 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
14 using System.ComponentModel;
15 using System.Collections.Generic;
17 using System.Text.RegularExpressions;
18 using System.Threading;
19 using System.Threading.Tasks;
21 using System.Runtime.CompilerServices;
23 namespace FooEditEngine
26 /// オートインデントを行うためのデリゲートを表す
28 /// <param name="sender">イベント発生元のオブジェクト</param>
29 /// <param name="e">イベントデーター</param>
30 public delegate void AutoIndentHookerHandler(object sender, EventArgs e);
35 public enum ProgressState
49 public sealed class ProgressEventArgs : EventArgs
54 public ProgressState state;
58 /// <param name="state">ProgressStateオブジェクト</param>
59 public ProgressEventArgs(ProgressState state)
68 /// <param name="sender">送信元クラス</param>
69 /// <param name="e">イベントデータ</param>
70 public delegate void ProgressEventHandler(object sender, ProgressEventArgs e);
75 public enum UpdateType
78 /// ドキュメントが置き換えられたことを表す
82 /// ドキュメント全体が削除されたことを表す
88 /// 更新タイプを通知するためのイベントデータ
90 public sealed class DocumentUpdateEventArgs : EventArgs
95 public const int EmptyValue = -1;
99 public UpdateType type;
103 public int startIndex;
107 public int removeLength;
111 public int insertLength;
113 /// 更新イベントが発生した行。行が不明な場合や行をまたぐ場合はnullを指定すること。
119 /// <param name="type">更新タイプ</param>
120 /// <param name="startIndex">開始インデックス</param>
121 /// <param name="removeLength">削除された長さ</param>
122 /// <param name="insertLength">追加された長さ</param>
123 /// <param name="row">開始行。nullを指定することができる</param>
124 public DocumentUpdateEventArgs(UpdateType type, int startIndex = EmptyValue, int removeLength = EmptyValue, int insertLength = EmptyValue, int? row = null)
127 this.startIndex = startIndex;
128 this.removeLength = removeLength;
129 this.insertLength = insertLength;
135 /// ドキュメントに更新があったことを伝えるためのデリゲート
137 /// <param name="sender">送信元クラス</param>
138 /// <param name="e">イベントデータ</param>
139 public delegate void DocumentUpdateEventHandler(object sender, DocumentUpdateEventArgs e);
144 /// <remarks>この型のすべてのメソッド・プロパティはスレッドセーフです</remarks>
145 public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>, IDisposable
150 LineToIndexTable _LayoutLines;
151 bool _EnableFireUpdateEvent = true,_UrlMark = false, _DrawLineNumber = false, _HideRuler = true, _RightToLeft = false;
152 LineBreakMethod _LineBreak;
153 int _TabStops, _LineBreakCharCount = 80;
154 bool _ShowFullSpace, _ShowHalfSpace, _ShowTab, _ShowLineBreak,_InsertMode, _HideCaret, _HideLineMarker, _RectSelection;
155 IndentMode _IndentMode;
160 public const int MaximumLineLength = 1000;
173 /// <param name="doc">ドキュメントオブジェクト</param>
174 /// <remarks>docが複製されますが、プロパティは引き継がれません</remarks>
175 public Document(Document doc)
178 this.buffer = new StringBuffer();
180 this.buffer = new StringBuffer(doc.buffer);
181 this.buffer.Update = new DocumentUpdateEventHandler(buffer_Update);
182 this.UpdateCalledAlways += (s, e) => { };
183 this.Update += new DocumentUpdateEventHandler((s, e) => { });
184 this.ChangeFireUpdateEvent += new EventHandler((s, e) => { });
185 this.StatusUpdate += new EventHandler((s, e) => { });
186 this.Markers = new MarkerCollection();
187 this.UndoManager = new UndoManager();
188 this._LayoutLines = new LineToIndexTable(this);
189 this._LayoutLines.SpilitString = (s,e)=> {
190 return this.CreateLineList(e.index, e.length, MaximumLineLength);
192 this._LayoutLines.Clear();
193 this.MarkerPatternSet = new MarkerPatternSet(this._LayoutLines, this.Markers);
194 this.MarkerPatternSet.Updated += WacthDogPattern_Updated;
195 this.Selections = new SelectCollection();
196 this.CaretPostion = new TextPoint();
197 this.HideLineMarker = true;
198 this.SelectGrippers = new GripperRectangle(new Gripper(), new Gripper());
199 this.SelectionChanged += new EventHandler((s, e) => { });
200 this.CaretChanged += (s, e) => { };
201 this.AutoIndentHook += (s, e) => { };
204 void WacthDogPattern_Updated(object sender, EventArgs e)
206 this._LayoutLines.ClearLayoutCache();
210 /// キャレットでの選択の起点となる位置
212 internal int AnchorIndex
221 internal SrcPoint Src
236 public AutoCompleteBoxBase AutoComplete
242 public event ProgressEventHandler LoadProgress;
245 /// ルーラーやキャレット・行番号などの表示すべきものが変化した場合に呼び出される。ドキュメントの内容が変化した通知を受け取り場合はUpdateを使用してください
247 public event EventHandler StatusUpdate;
252 public bool ShowFullSpace
254 get { return this._ShowFullSpace; }
257 if (this._ShowFullSpace == value)
259 this._ShowFullSpace = value;
260 this.StatusUpdate(this, null);
267 public bool ShowHalfSpace
269 get { return this._ShowHalfSpace; }
272 if (this._ShowHalfSpace == value)
274 this._ShowHalfSpace = value;
275 this.StatusUpdate(this, null);
284 get { return this._ShowTab; }
287 if (this._ShowTab == value)
289 this._ShowTab = value;
290 this.StatusUpdate(this, null);
297 public bool ShowLineBreak
299 get { return this._ShowLineBreak; }
302 if (this._ShowLineBreak == value)
304 this._ShowLineBreak = value;
305 this.StatusUpdate(this, null);
312 internal GripperRectangle SelectGrippers
321 public bool RightToLeft {
322 get { return this._RightToLeft; }
325 if (this._RightToLeft == value)
327 this._RightToLeft = value;
328 this.StatusUpdate(this, null);
333 /// 矩形選択モードなら真を返し、そうでない場合は偽を返す
335 public bool RectSelection
339 return this._RectSelection;
343 this._RectSelection = value;
344 this.StatusUpdate(this, null);
351 public IndentMode IndentMode
355 return this._IndentMode;
359 this._IndentMode = value;
360 this.StatusUpdate(this, null);
365 /// ラインマーカーを描くなら偽。そうでなければ真
367 public bool HideLineMarker
371 return this._HideLineMarker;
375 this._HideLineMarker = value;
376 this.StatusUpdate(this, null);
381 /// キャレットを描くなら偽。そうでなければ真
383 public bool HideCaret
387 return this._HideCaret;
391 this._HideCaret = value;
392 this.StatusUpdate(this, null);
397 /// 挿入モードなら真を返し、上書きモードなら偽を返す
399 public bool InsertMode
403 return this._InsertMode;
407 this._InsertMode = value;
408 this.StatusUpdate(this, null);
413 /// ルーラーを表示しないなら真、そうでないなら偽
415 public bool HideRuler
417 get { return this._HideRuler; }
420 if (this._HideRuler == value)
422 this._HideRuler = value;
423 this.LayoutLines.ClearLayoutCache();
424 this.StatusUpdate(this, null);
428 TextPoint _CaretPostion;
430 /// レイアウト行のどこにキャレットがあるかを表す
432 public TextPoint CaretPostion
436 return this._CaretPostion;
440 if(this._CaretPostion != value)
442 this._CaretPostion = value;
443 this.CaretChanged(this, null);
448 internal void SetCaretPostionWithoutEvent(TextPoint value)
450 if (this._CaretPostion != value)
451 this._CaretPostion = value;
457 internal SelectCollection Selections
466 public bool DrawLineNumber
468 get { return this._DrawLineNumber; }
471 if (this._DrawLineNumber == value)
473 this._DrawLineNumber = value;
474 this._LayoutLines.ClearLayoutCache();
475 this.StatusUpdate(this, null);
480 /// URLをハイパーリンクとして表示するなら真。そうでないなら偽
484 get { return this._UrlMark; }
487 if (this._UrlMark == value)
489 this._UrlMark = value;
492 Regex regex = new Regex("(http|https|ftp)(:\\/\\/[-_.!~*\\'()a-zA-Z0-9;\\/?:\\@&=+\\$,%#]+)");
493 this.MarkerPatternSet.Add(MarkerIDs.URL, new RegexMarkerPattern(regex, HilightType.Url, new Color()));
497 this.MarkerPatternSet.Remove(MarkerIDs.URL);
499 this.StatusUpdate(this, null);
506 public event EventHandler LineBreakChanged;
512 /// 変更した場合、呼び出し側で再描写とレイアウトの再構築を行う必要があります
513 /// また、StatusUpdatedではなく、LineBreakChangedイベントが発生します
515 public LineBreakMethod LineBreak
519 return this._LineBreak;
523 if (this._LineBreak == value)
525 this._LineBreak = value;
526 this.LineBreakChanged(this, null);
531 /// 折り返し行う文字数。実際に折り返しが行われる幅はem単位×この値となります
533 /// <remarks>この値を変えた場合、LineBreakChangedイベントが発生します</remarks>
534 public int LineBreakCharCount
538 return this._LineBreakCharCount;
542 if (this._LineBreakCharCount == value)
544 this._LineBreakCharCount = value;
545 this.LineBreakChanged(this, null);
552 /// <remarks>変更した場合、呼び出し側で再描写する必要があります</remarks>
555 get { return this._TabStops; }
557 if (this._TabStops == value)
559 this._TabStops = value;
560 this.StatusUpdate(this, null);
567 public MarkerPatternSet MarkerPatternSet
576 public LineToIndexTable LayoutLines
580 return this._LayoutLines;
587 /// <param name="index">開始インデックス</param>
588 /// <param name="length">長さ</param>
589 /// <param name="lineLimitLength">1行当たりの最大文字数。-1で無制限</param>
590 /// <returns>レイアウト行リスト</returns>
591 internal IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)
593 int startIndex = index;
594 int endIndex = index + length - 1;
595 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
597 foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))
599 int lineHeadIndex = range.Item1;
600 int lineLength = range.Item2;
601 char c = this.buffer[lineHeadIndex + lineLength - 1];
602 bool hasNewLine = c == Document.NewLine;
603 output.Add(this.LayoutLines.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));
606 if (output.Count > 0)
607 output.Last().LineEnd = true;
612 internal void FireUpdate(DocumentUpdateEventArgs e)
614 this.buffer_Update(this.buffer, e);
618 /// ドキュメントが更新された時に呼ばれるイベント
620 public event DocumentUpdateEventHandler Update;
623 /// ドキュメントが更新された時に呼びされるイベント
626 /// FireUpdateEventの値に関わらず常に呼びされます
628 internal event DocumentUpdateEventHandler UpdateCalledAlways;
631 /// FireUpdateEventの値が変わったときに呼び出されるイベント
633 public event EventHandler ChangeFireUpdateEvent;
638 public const char NewLine = '\n';
643 public const char EndOfFile = '\u001a';
648 public UndoManager UndoManager
661 return this.buffer.Length;
666 /// 変更のたびにUpdateイベントを発生させるかどうか
668 public bool FireUpdateEvent
672 return this._EnableFireUpdateEvent;
676 this._EnableFireUpdateEvent = value;
677 this.ChangeFireUpdateEvent(this, null);
684 /// <param name="i">インデックス(自然数でなければならない)</param>
685 /// <returns>Char型</returns>
686 public char this[int i]
690 return this.buffer[i];
697 public MarkerCollection Markers
703 internal StringBuffer StringBuffer
714 public bool IsRequestRedraw { get; internal set; }
719 public void RequestRedraw()
721 this.IsRequestRedraw = true;
724 public event EventHandler PerformLayouted;
726 /// レイアウト行をすべて破棄し、再度レイアウトを行う
728 public void PerformLayout()
730 //単に再構築するだけなので行ダーティフラグは更新してはいけない
731 this.LayoutLines.IsFrozneDirtyFlag = true;
732 this.FireUpdate(new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));
733 this.FireUpdate(new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, this.Length));
734 this.LayoutLines.IsFrozneDirtyFlag = false;
735 if (this.PerformLayouted != null)
736 this.PerformLayouted(this, null);
740 /// オードインデントが可能になった時に通知される
743 /// FireUpdateEventの影響を受けます
745 public event AutoIndentHookerHandler AutoIndentHook;
750 public event EventHandler SelectionChanged;
755 public event EventHandler CaretChanged;
760 /// <param name="start"></param>
761 /// <param name="length"></param>
762 /// <remarks>RectSelectionの値によって動作が変わります。真の場合は矩形選択モードに、そうでない場合は行ごとに選択されます</remarks>
763 public void Select(int start, int length)
765 if (this.FireUpdateEvent == false)
766 throw new InvalidOperationException("");
767 if (start < 0 || start + length < 0 || start + length > this.Length)
768 throw new ArgumentOutOfRangeException("startかendが指定できる範囲を超えてます");
769 this.Selections.Clear();
772 int oldStart = start;
774 length = oldStart - start;
776 if (this.RectSelection && length != 0)
778 TextPoint startTextPoint = this.LayoutLines.GetTextPointFromIndex(start);
779 TextPoint endTextPoint = this.LayoutLines.GetTextPointFromIndex(start + length);
780 this.SelectByRectangle(new TextRectangle(startTextPoint, endTextPoint));
782 else if (length != 0)
784 this.Selections.Add(Selection.Create(start, length));
786 this.SelectionChanged(this, null);
792 /// <param name="tp">開始位置</param>
793 /// <param name="width">桁数</param>
794 /// <param name="height">行数</param>
795 public void Select(TextPoint tp, int width, int height)
797 if (this.FireUpdateEvent == false || !this.RectSelection)
798 throw new InvalidOperationException("");
801 end.row = tp.row + height;
802 end.col = tp.col + width;
804 if (end.row > this.LayoutLines.Count - 1)
805 throw new ArgumentOutOfRangeException("");
807 this.Selections.Clear();
809 this.SelectByRectangle(new TextRectangle(tp, end));
811 this.SelectionChanged(this, null);
814 private void SelectByRectangle(TextRectangle rect)
816 if (this.FireUpdateEvent == false)
817 throw new InvalidOperationException("");
818 if (rect.TopLeft <= rect.BottomRight)
820 for (int i = rect.TopLeft.row; i <= rect.BottomLeft.row; i++)
822 int length = this.LayoutLines.GetLengthFromLineNumber(i);
823 int leftCol = rect.TopLeft.col, rightCol = rect.TopRight.col, lastCol = length;
824 if (length > 0 && this.LayoutLines[i][length - 1] == Document.NewLine)
825 lastCol = length - 1;
828 if (rect.TopLeft.col > lastCol)
830 if (rect.TopRight.col > lastCol)
833 int StartIndex = this.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, leftCol));
834 int EndIndex = this.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, rightCol));
837 sel = Selection.Create(StartIndex, EndIndex - StartIndex);
839 this.Selections.Add(sel);
847 /// <param name="index">探索を開始するインデックス</param>
848 /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
849 public void SelectWord(int index, bool changeAnchor = false)
851 if (this.FireUpdateEvent == false)
852 throw new InvalidOperationException("");
854 if (this.Length <= 0 || index >= this.Length)
860 while (start > 0 && !Util.IsWordSeparator(str[start]))
863 if (Util.IsWordSeparator(str[start]))
867 while (end < this.Length && !Util.IsWordSeparator(str[end]))
870 this.Select(start, end - start);
873 this.AnchorIndex = start;
877 /// DocumentReaderを作成します
879 /// <returns>DocumentReaderオブジェクト</returns>
880 public DocumentReader CreateReader()
882 return new DocumentReader(this.buffer);
888 /// <param name="id">マーカーID</param>
889 /// <param name="m">設定したいマーカー</param>
890 public void SetMarker(int id,Marker m)
892 if (m.start < 0 || m.start + m.length > this.Length)
893 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
895 this.Markers.Add(id,m);
901 /// <param name="id">マーカーID</param>
902 /// <param name="start">開始インデックス</param>
903 /// <param name="length">削除する長さ</param>
904 public void RemoveMarker(int id,int start, int length)
906 if (start < 0 || start + length > this.Length)
907 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
909 this.Markers.RemoveAll(id,start, length);
915 /// <param name="id">マーカーID</param>
916 /// <param name="type">削除したいマーカーのタイプ</param>
917 public void RemoveMarker(int id, HilightType type)
919 this.Markers.RemoveAll(id,type);
925 /// <param name="id">マーカーID</param>
926 public void RemoveAllMarker(int id)
928 this.Markers.RemoveAll(id);
932 /// インデックスに対応するマーカーを得る
934 /// <param name="id">マーカーID</param>
935 /// <param name="index">インデックス</param>
936 /// <returns>Marker構造体の列挙子</returns>
937 public IEnumerable<Marker> GetMarkers(int id, int index)
939 if (index < 0 || index > this.Length)
940 throw new ArgumentOutOfRangeException("indexが範囲を超えています");
941 return this.Markers.Get(id,index);
947 /// <param name="index">開始インデックス</param>
948 /// <param name="length">長さ</param>
949 /// <returns>Stringオブジェクト</returns>
950 public string ToString(int index, int length)
952 return this.buffer.ToString(index, length);
956 /// インデックスを開始位置とする文字列を返す
958 /// <param name="index">開始インデックス</param>
959 /// <returns>Stringオブジェクト</returns>
960 public string ToString(int index)
962 return this.ToString(index, this.buffer.Length - index);
968 /// <param name="startIndex">開始インデックス</param>
969 /// <param name="endIndex">終了インデックス</param>
970 /// <param name="maxCharCount">最大長</param>
971 /// <returns>行イテレーターが返される</returns>
972 public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
974 return this.buffer.GetLines(startIndex, endIndex, maxCharCount);
977 internal IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
979 return this.buffer.ForEachLines(startIndex, endIndex, maxCharCount);
986 /// <param name="s">追加したい文字列</param>
987 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
988 public void Append(string s)
990 this.Replace(this.buffer.Length, 0, s);
996 /// <param name="index">開始インデックス</param>
997 /// <param name="s">追加したい文字列</param>
998 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
999 public void Insert(int index, string s)
1001 this.Replace(index, 0, s);
1007 /// <param name="index">開始インデックス</param>
1008 /// <param name="length">長さ</param>
1009 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
1010 public void Remove(int index, int length)
1012 this.Replace(index, length, "");
1018 /// <param name="index">開始インデックス</param>
1019 /// <param name="length">長さ</param>
1020 /// <param name="s">文字列</param>
1021 /// <param name="UserInput">ユーザーからの入力として扱うなら真</param>
1022 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
1023 public void Replace(int index, int length, string s, bool UserInput = false)
1025 if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)
1026 throw new ArgumentOutOfRangeException();
1027 if (length == 0 && (s == string.Empty || s == null))
1030 foreach(int id in this.Markers.IDs)
1031 this.RemoveMarker(id,index, length);
1033 ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);
1034 this.UndoManager.push(cmd);
1037 if (this.FireUpdateEvent && UserInput)
1039 if(this.AutoComplete != null)
1040 this.AutoComplete.ParseInput(string.Empty); //入力は終わっているので空文字を渡す
1041 if (s == Document.NewLine.ToString())
1042 this.AutoIndentHook(this, null);
1049 /// <remarks>Dirtyフラグも同時にクリアーされます</remarks>
1050 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
1053 this.buffer.Clear();
1057 /// ストリームからドキュメントを非同期的に構築します
1059 /// <param name="fs">IStreamReaderオブジェクト</param>
1060 /// <param name="tokenSource">キャンセルトークン</param>
1061 /// <returns>Taskオブジェクト</returns>
1063 /// 読み取り操作は別スレッドで行われます。
1064 /// また、非同期操作中はこのメソッドを実行することはできません。
1066 public async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null)
1068 if (fs.Peek() == -1)
1071 if (this.LoadProgress != null)
1072 this.LoadProgress(this, new ProgressEventArgs(ProgressState.Start));
1077 this.LayoutLines.IsFrozneDirtyFlag = true;
1078 await this.buffer.LoadAsync(fs, tokenSource);
1082 this.PerformLayout();
1083 //これ以降の操作にだけダーティフラグを適用しないとおかしなことになる
1084 this.LayoutLines.IsFrozneDirtyFlag = false;
1085 if (this.LoadProgress != null)
1086 this.LoadProgress(this, new ProgressEventArgs(ProgressState.Complete));
1091 /// ストリームに非同期モードで保存します
1093 /// <param name="fs">IStreamWriterオブジェクト</param>
1094 /// <param name="tokenSource">キャンセルトークン</param>
1095 /// <returns>Taskオブジェクト</returns>
1096 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
1097 public async Task SaveAsync(TextWriter fs, CancellationTokenSource tokenSource = null)
1101 await this.buffer.LockAsync().ConfigureAwait(false);
1102 StringBuilder line = new StringBuilder();
1103 for (int i = 0; i < this.Length; i++)
1107 if (c == Document.NewLine || i == this.Length - 1)
1109 string str = line.ToString();
1110 str = str.Replace(Document.NewLine.ToString(), fs.NewLine);
1111 await fs.WriteAsync(str).ConfigureAwait(false);
1113 if (tokenSource != null)
1114 tokenSource.Token.ThrowIfCancellationRequested();
1116 System.Threading.Thread.Sleep(10);
1123 this.buffer.UnLock();
1128 /// Find()およびReplaceAll()で使用するパラメーターをセットします
1130 /// <param name="pattern">検索したい文字列</param>
1131 /// <param name="UseRegex">正規表現を使用するなら真</param>
1132 /// <param name="opt">RegexOptions列挙体</param>
1133 public void SetFindParam(string pattern, bool UseRegex, RegexOptions opt)
1137 this.regex = new Regex(pattern, opt);
1139 this.regex = new Regex(Regex.Escape(pattern), opt);
1143 /// 現在の検索パラメーターでWatchDogを生成する
1145 /// <param name="type">ハイライトタイプ</param>
1146 /// <param name="color">色</param>
1147 /// <returns>WatchDogオブジェクト</returns>
1148 public RegexMarkerPattern CreateWatchDogByFindParam(HilightType type,Color color)
1150 if (this.regex == null)
1151 throw new InvalidOperationException("SetFindParam()を呼び出してください");
1152 return new RegexMarkerPattern(this.regex,type,color);
1158 /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
1159 /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
1160 public IEnumerator<SearchResult> Find()
1162 return this.Find(0, this.Length);
1168 /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
1169 /// <param name="start">開始インデックス</param>
1170 /// <param name="length">検索する長さ</param>
1171 /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
1172 public IEnumerator<SearchResult> Find(int start, int length)
1174 if (this.regex == null)
1175 throw new InvalidOperationException();
1176 if (start < 0 || start >= this.Length)
1177 throw new ArgumentOutOfRangeException();
1179 int end = start + length - 1;
1181 if(end > this.Length - 1)
1182 throw new ArgumentOutOfRangeException();
1184 StringBuilder line = new StringBuilder();
1185 int oldLength = this.Length;
1186 for (int i = start; i <= end; i++)
1190 if (c == Document.NewLine || i == end)
1192 this.match = this.regex.Match(line.ToString());
1193 while (this.match.Success)
1195 int startIndex = i - line.Length + 1 + this.match.Index;
1196 int endIndex = startIndex + this.match.Length - 1;
1198 yield return new SearchResult(this.match, startIndex, endIndex);
1200 if (this.Length != oldLength) //長さが変わった場合は置き換え後のパターンの終点+1まで戻る
1202 int delta = this.Length - oldLength;
1203 i = endIndex + delta;
1205 oldLength = this.Length;
1209 this.match = this.match.NextMatch();
1217 /// 任意のパターンですべて置き換えます
1219 /// <param name="replacePattern">置き換え後のパターン</param>
1220 /// <param name="groupReplace">グループ置き換えを行うなら真。そうでないなら偽</param>
1221 public void ReplaceAll(string replacePattern,bool groupReplace)
1223 if (this.regex == null)
1224 throw new InvalidOperationException();
1225 ReplaceAllCommand cmd = new ReplaceAllCommand(this.buffer, this.LayoutLines, this.regex, replacePattern, groupReplace);
1226 this.UndoManager.push(cmd);
1233 /// <param name="target">対象となる文字列</param>
1234 /// <param name="pattern">置き換え後の文字列</param>
1235 /// <param name="ci">大文字も文字を区別しないなら真。そうでないなら偽</param>
1237 /// 検索時に大文字小文字を区別します。また、このメソッドでは正規表現を使用することはできません
1239 public void ReplaceAll2(string target, string pattern,bool ci = false)
1241 FastReplaceAllCommand cmd = new FastReplaceAllCommand(this.buffer, this.LayoutLines, target, pattern,ci);
1242 this.UndoManager.push(cmd);
1246 #region IEnumerable<char> メンバー
1251 /// <returns>IEnumeratorオブジェクトを返す</returns>
1252 public IEnumerator<char> GetEnumerator()
1254 return this.buffer.GetEnumerator();
1259 #region IEnumerable メンバー
1261 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
1263 throw new NotImplementedException();
1268 void buffer_Update(object sender, DocumentUpdateEventArgs e)
1272 case UpdateType.Replace:
1275 this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);
1276 this.Markers.UpdateMarkers(e.startIndex, e.insertLength, e.removeLength);
1280 this._LayoutLines.UpdateLineAsReplace(e.row.Value, e.removeLength, e.insertLength);
1281 this.Markers.UpdateMarkers(this.LayoutLines.GetIndexFromLineNumber(e.row.Value), e.insertLength, e.removeLength);
1284 case UpdateType.Clear:
1285 this._LayoutLines.Clear();
1288 this.UpdateCalledAlways(this, e);
1289 if(this.FireUpdateEvent)
1290 this.Update(this, e);
1293 #region IDisposable Support
1294 private bool disposedValue = false; // 重複する呼び出しを検出するには
1296 void Dispose(bool disposing)
1302 this.buffer.Clear();
1303 this.LayoutLines.Clear();
1306 disposedValue = true;
1310 // このコードは、破棄可能なパターンを正しく実装できるように追加されました。
1311 public void Dispose()
1321 public class SearchResult
1323 private Match Match;
1340 get { return this.Match.Value; }
1344 /// 指定したパターンを置き換えて返す
1346 /// <param name="replacement">置き換える文字列</param>
1347 /// <returns>置き換え後の文字列</returns>
1348 public string Result(string replacement)
1350 return this.Match.Result(replacement);
1356 /// <param name="m">Matchオブジェクト</param>
1357 /// <param name="start">開始インデックス</param>
1358 /// <param name="end">終了インデックス</param>
1359 public SearchResult(Match m, int start,int end)
1370 public class DocumentReader : TextReader
1372 StringBuffer document;
1378 /// <param name="doc"></param>
1379 internal DocumentReader(StringBuffer doc)
1382 throw new ArgumentNullException();
1383 this.document = doc;
1389 /// <returns>文字。取得できない場合は-1</returns>
1390 public override int Peek()
1392 if (this.document == null)
1393 throw new InvalidOperationException();
1394 if (this.currentIndex >= this.document.Length)
1396 return this.document[this.currentIndex];
1400 /// 文字を取得し、イテレーターを一つ進める
1402 /// <returns>文字。取得できない場合は-1</returns>
1403 public override int Read()
1405 int c = this.Peek();
1407 this.currentIndex++;
1412 /// 文字列を読み取りバッファーに書き込む
1414 /// <param name="buffer">バッファー</param>
1415 /// <param name="index">開始インデックス</param>
1416 /// <param name="count">カウント</param>
1417 /// <returns>読み取られた文字数</returns>
1418 public override int Read(char[] buffer, int index, int count)
1420 if (this.document == null)
1421 throw new InvalidOperationException();
1424 throw new ArgumentNullException();
1426 if (this.document.Length < count)
1427 throw new ArgumentException();
1429 if (index < 0 || count < 0)
1430 throw new ArgumentOutOfRangeException();
1432 if (this.document.Length == 0)
1435 int actualCount = count;
1436 if (index + count - 1 > this.document.Length - 1)
1437 actualCount = this.document.Length - index;
1439 string str = this.document.ToString(index, actualCount);
1441 for (int i = 0; i < str.Length; i++) //ToCharArray()だと戻った時に消えてしまう
1444 this.currentIndex = index + actualCount;
1452 /// <param name="disposing">真ならアンマネージドリソースを解放する</param>
1453 protected override void Dispose(bool disposing)
1455 this.document = null;