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 /// ドキュメント全体が削除されたことを表す
92 /// 更新タイプを通知するためのイベントデータ
94 public sealed class DocumentUpdateEventArgs : EventArgs
99 public const int EmptyValue = -1;
103 public UpdateType type;
107 public int startIndex;
111 public int removeLength;
115 public int insertLength;
117 /// 更新イベントが発生した行。行が不明な場合や行をまたぐ場合はnullを指定すること。
123 /// <param name="type">更新タイプ</param>
124 /// <param name="startIndex">開始インデックス</param>
125 /// <param name="removeLength">削除された長さ</param>
126 /// <param name="insertLength">追加された長さ</param>
127 /// <param name="row">開始行。nullを指定することができる</param>
128 public DocumentUpdateEventArgs(UpdateType type, int startIndex = EmptyValue, int removeLength = EmptyValue, int insertLength = EmptyValue, int? row = null)
131 this.startIndex = startIndex;
132 this.removeLength = removeLength;
133 this.insertLength = insertLength;
139 /// ドキュメントに更新があったことを伝えるためのデリゲート
141 /// <param name="sender">送信元クラス</param>
142 /// <param name="e">イベントデータ</param>
143 public delegate void DocumentUpdateEventHandler(object sender, DocumentUpdateEventArgs e);
148 /// <remarks>この型のすべてのメソッド・プロパティはスレッドセーフです</remarks>
149 public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>, IDisposable
154 LineToIndexTable _LayoutLines;
155 bool _EnableFireUpdateEvent = true,_UrlMark = false, _DrawLineNumber = false, _HideRuler = true, _RightToLeft = false;
156 LineBreakMethod _LineBreak;
157 int _TabStops, _LineBreakCharCount = 80;
158 bool _ShowFullSpace, _ShowHalfSpace, _ShowTab, _ShowLineBreak,_InsertMode, _HideCaret, _HideLineMarker, _RectSelection;
159 IndentMode _IndentMode;
164 public const int MaximumLineLength = 1000;
177 /// <param name="doc">ドキュメントオブジェクト</param>
178 /// <remarks>docが複製されますが、プロパティは引き継がれません</remarks>
179 public Document(Document doc)
182 this.buffer = new StringBuffer();
184 this.buffer = new StringBuffer(doc.buffer);
185 this.buffer.Update = new DocumentUpdateEventHandler(buffer_Update);
186 this.Update += new DocumentUpdateEventHandler((s, e) => { });
187 this.ChangeFireUpdateEvent += new EventHandler((s, e) => { });
188 this.StatusUpdate += new EventHandler((s, e) => { });
189 this.Markers = new MarkerCollection();
190 this.UndoManager = new UndoManager();
191 this._LayoutLines = new LineToIndexTable(this);
192 this._LayoutLines.SpilitString = (s,e)=> {
193 return this.CreateLineList(e.index, e.length, MaximumLineLength);
195 this._LayoutLines.Clear();
196 this.MarkerPatternSet = new MarkerPatternSet(this._LayoutLines, this.Markers);
197 this.MarkerPatternSet.Updated += WacthDogPattern_Updated;
198 this.Selections = new SelectCollection();
199 this.CaretPostion = new TextPoint();
200 this.HideLineMarker = true;
201 this.SelectGrippers = new GripperRectangle(new Gripper(), new Gripper());
202 this.SelectionChanged += new EventHandler((s, e) => { });
203 this.CaretChanged += (s, e) => { };
204 this.AutoIndentHook += (s, e) => { };
205 this.LineBreakChanged += (s, e) => { };
209 void WacthDogPattern_Updated(object sender, EventArgs e)
211 this._LayoutLines.ClearLayoutCache();
215 /// ダーティフラグ。保存されていなければ真、そうでなければ偽。
224 /// キャレットでの選択の起点となる位置
226 internal int AnchorIndex
235 internal SrcPoint Src
251 /// 補完候補プロセッサーが切り替わったときに発生するイベント
253 public event EventHandler AutoCompleteChanged;
255 AutoCompleteBoxBase _AutoComplete;
259 public AutoCompleteBoxBase AutoComplete
263 return this._AutoComplete;
267 this._AutoComplete = value;
268 if (this.AutoCompleteChanged != null)
269 this.AutoCompleteChanged(this, null);
276 public event ProgressEventHandler LoadProgress;
279 /// ルーラーやキャレット・行番号などの表示すべきものが変化した場合に呼び出される。ドキュメントの内容が変化した通知を受け取り場合はUpdateを使用してください
281 public event EventHandler StatusUpdate;
286 public bool ShowFullSpace
288 get { return this._ShowFullSpace; }
291 if (this._ShowFullSpace == value)
293 this._ShowFullSpace = value;
294 this.StatusUpdate(this, null);
301 public bool ShowHalfSpace
303 get { return this._ShowHalfSpace; }
306 if (this._ShowHalfSpace == value)
308 this._ShowHalfSpace = value;
309 this.StatusUpdate(this, null);
318 get { return this._ShowTab; }
321 if (this._ShowTab == value)
323 this._ShowTab = value;
324 this.StatusUpdate(this, null);
331 public bool ShowLineBreak
333 get { return this._ShowLineBreak; }
336 if (this._ShowLineBreak == value)
338 this._ShowLineBreak = value;
339 this.StatusUpdate(this, null);
346 internal GripperRectangle SelectGrippers
355 public bool RightToLeft {
356 get { return this._RightToLeft; }
359 if (this._RightToLeft == value)
361 this._RightToLeft = value;
362 this.StatusUpdate(this, null);
367 /// 矩形選択モードなら真を返し、そうでない場合は偽を返す
369 public bool RectSelection
373 return this._RectSelection;
377 this._RectSelection = value;
378 this.StatusUpdate(this, null);
385 public IndentMode IndentMode
389 return this._IndentMode;
393 this._IndentMode = value;
394 this.StatusUpdate(this, null);
399 /// ラインマーカーを描くなら偽。そうでなければ真
401 public bool HideLineMarker
405 return this._HideLineMarker;
409 this._HideLineMarker = value;
410 this.StatusUpdate(this, null);
415 /// キャレットを描くなら偽。そうでなければ真
417 public bool HideCaret
421 return this._HideCaret;
425 this._HideCaret = value;
426 this.StatusUpdate(this, null);
431 /// 挿入モードなら真を返し、上書きモードなら偽を返す
433 public bool InsertMode
437 return this._InsertMode;
441 this._InsertMode = value;
442 this.StatusUpdate(this, null);
447 /// ルーラーを表示しないなら真、そうでないなら偽
449 public bool HideRuler
451 get { return this._HideRuler; }
454 if (this._HideRuler == value)
456 this._HideRuler = value;
457 this.LayoutLines.ClearLayoutCache();
458 this.StatusUpdate(this, null);
462 TextPoint _CaretPostion;
464 /// レイアウト行のどこにキャレットがあるかを表す
466 public TextPoint CaretPostion
470 return this._CaretPostion;
474 if(this._CaretPostion != value)
476 this._CaretPostion = value;
477 this.CaretChanged(this, null);
482 internal void SetCaretPostionWithoutEvent(TextPoint value)
484 if (this._CaretPostion != value)
485 this._CaretPostion = value;
491 internal SelectCollection Selections
500 public bool DrawLineNumber
502 get { return this._DrawLineNumber; }
505 if (this._DrawLineNumber == value)
507 this._DrawLineNumber = value;
508 this._LayoutLines.ClearLayoutCache();
509 this.StatusUpdate(this, null);
514 /// URLをハイパーリンクとして表示するなら真。そうでないなら偽
518 get { return this._UrlMark; }
521 if (this._UrlMark == value)
523 this._UrlMark = value;
526 Regex regex = new Regex("(http|https|ftp)(:\\/\\/[-_.!~*\\'()a-zA-Z0-9;\\/?:\\@&=+\\$,%#]+)");
527 this.MarkerPatternSet.Add(MarkerIDs.URL, new RegexMarkerPattern(regex, HilightType.Url, new Color()));
531 this.MarkerPatternSet.Remove(MarkerIDs.URL);
533 this.StatusUpdate(this, null);
540 public event EventHandler LineBreakChanged;
546 /// 変更した場合、呼び出し側で再描写とレイアウトの再構築を行う必要があります
547 /// また、StatusUpdatedではなく、LineBreakChangedイベントが発生します
549 public LineBreakMethod LineBreak
553 return this._LineBreak;
557 if (this._LineBreak == value)
559 this._LineBreak = value;
560 this.LineBreakChanged(this, null);
565 /// 折り返し行う文字数。実際に折り返しが行われる幅はem単位×この値となります
567 /// <remarks>この値を変えた場合、LineBreakChangedイベントが発生します</remarks>
568 public int LineBreakCharCount
572 return this._LineBreakCharCount;
576 if (this._LineBreakCharCount == value)
578 this._LineBreakCharCount = value;
579 this.LineBreakChanged(this, null);
586 /// <remarks>変更した場合、呼び出し側で再描写する必要があります</remarks>
589 get { return this._TabStops; }
591 if (this._TabStops == value)
593 this._TabStops = value;
594 this.StatusUpdate(this, null);
601 public MarkerPatternSet MarkerPatternSet
610 public LineToIndexTable LayoutLines
614 return this._LayoutLines;
621 /// <param name="index">開始インデックス</param>
622 /// <param name="length">長さ</param>
623 /// <param name="lineLimitLength">1行当たりの最大文字数。-1で無制限</param>
624 /// <returns>レイアウト行リスト</returns>
625 internal IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)
627 int startIndex = index;
628 int endIndex = index + length - 1;
629 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
631 foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))
633 int lineHeadIndex = range.Item1;
634 int lineLength = range.Item2;
635 char c = this.buffer[lineHeadIndex + lineLength - 1];
636 bool hasNewLine = c == Document.NewLine;
637 output.Add(this.LayoutLines.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));
640 if (output.Count > 0)
641 output.Last().LineEnd = true;
646 internal void FireUpdate(DocumentUpdateEventArgs e)
648 this.buffer_Update(this.buffer, e);
652 /// ドキュメントが更新された時に呼ばれるイベント
654 public event DocumentUpdateEventHandler Update;
657 /// FireUpdateEventの値が変わったときに呼び出されるイベント
659 public event EventHandler ChangeFireUpdateEvent;
664 public const char NewLine = '\n';
669 public const char EndOfFile = '\u001a';
674 public UndoManager UndoManager
687 return this.buffer.Length;
692 /// 変更のたびにUpdateイベントを発生させるかどうか
694 public bool FireUpdateEvent
698 return this._EnableFireUpdateEvent;
702 this._EnableFireUpdateEvent = value;
703 this.ChangeFireUpdateEvent(this, null);
710 /// <param name="i">インデックス(自然数でなければならない)</param>
711 /// <returns>Char型</returns>
712 public char this[int i]
716 return this.buffer[i];
723 public MarkerCollection Markers
729 internal StringBuffer StringBuffer
740 public bool IsRequestRedraw { get; internal set; }
745 public void RequestRedraw()
747 this.IsRequestRedraw = true;
751 /// レイアウト行が再構成されたときに発生するイベント
753 public event EventHandler PerformLayouted;
755 /// レイアウト行をすべて破棄し、再度レイアウトを行う
757 public void PerformLayout()
759 this.LayoutLines.IsFrozneDirtyFlag = true;
760 this.FireUpdate(new DocumentUpdateEventArgs(UpdateType.RebuildLayout, -1, -1, -1));
761 this.LayoutLines.IsFrozneDirtyFlag = false;
762 if (this.PerformLayouted != null)
763 this.PerformLayouted(this, null);
767 /// オードインデントが可能になった時に通知される
770 /// FireUpdateEventの影響を受けます
772 public event AutoIndentHookerHandler AutoIndentHook;
777 public event EventHandler SelectionChanged;
782 public event EventHandler CaretChanged;
787 /// <param name="start"></param>
788 /// <param name="length"></param>
789 /// <remarks>RectSelectionの値によって動作が変わります。真の場合は矩形選択モードに、そうでない場合は行ごとに選択されます</remarks>
790 public void Select(int start, int length)
792 if (this.FireUpdateEvent == false)
793 throw new InvalidOperationException("");
794 if (start < 0 || start + length < 0 || start + length > this.Length)
795 throw new ArgumentOutOfRangeException("startかendが指定できる範囲を超えてます");
797 foreach (Selection sel in this.Selections)
798 this.LayoutLines.ClearLayoutCache(sel.start, sel.length);
799 this.Selections.Clear();
802 int oldStart = start;
804 length = oldStart - start;
806 if (this.RectSelection && length != 0)
808 TextPoint startTextPoint = this.LayoutLines.GetTextPointFromIndex(start);
809 TextPoint endTextPoint = this.LayoutLines.GetTextPointFromIndex(start + length);
810 this.SelectByRectangle(new TextRectangle(startTextPoint, endTextPoint));
811 this.LayoutLines.ClearLayoutCache(start, length);
813 else if (length != 0)
815 this.Selections.Add(Selection.Create(start, length));
816 this.LayoutLines.ClearLayoutCache(start, length);
818 this.SelectionChanged(this, null);
824 /// <param name="tp">開始位置</param>
825 /// <param name="width">桁数</param>
826 /// <param name="height">行数</param>
827 public void Select(TextPoint tp, int width, int height)
829 if (this.FireUpdateEvent == false || !this.RectSelection)
830 throw new InvalidOperationException("");
833 end.row = tp.row + height;
834 end.col = tp.col + width;
836 if (end.row > this.LayoutLines.Count - 1)
837 throw new ArgumentOutOfRangeException("");
839 this.Selections.Clear();
841 this.SelectByRectangle(new TextRectangle(tp, end));
843 this.SelectionChanged(this, null);
846 private void SelectByRectangle(TextRectangle rect)
848 if (this.FireUpdateEvent == false)
849 throw new InvalidOperationException("");
850 if (rect.TopLeft <= rect.BottomRight)
852 for (int i = rect.TopLeft.row; i <= rect.BottomLeft.row; i++)
854 int length = this.LayoutLines.GetLengthFromLineNumber(i);
855 int leftCol = rect.TopLeft.col, rightCol = rect.TopRight.col, lastCol = length;
856 if (length > 0 && this.LayoutLines[i][length - 1] == Document.NewLine)
857 lastCol = length - 1;
860 if (rect.TopLeft.col > lastCol)
862 if (rect.TopRight.col > lastCol)
865 int StartIndex = this.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, leftCol));
866 int EndIndex = this.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, rightCol));
869 sel = Selection.Create(StartIndex, EndIndex - StartIndex);
871 this.Selections.Add(sel);
879 /// <param name="index">探索を開始するインデックス</param>
880 /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
881 public void SelectWord(int index, bool changeAnchor = false)
883 this.SelectSepartor(index, (c) => Util.IsWordSeparator(c), changeAnchor);
889 /// <param name="index">探索を開始するインデックス</param>
890 /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
891 public void SelectLine(int index,bool changeAnchor = false)
893 this.SelectSepartor(index, (c) => c == Document.NewLine, changeAnchor);
897 /// セパレーターで区切られた領域を取得する
899 /// <param name="index">探索を開始するインデックス</param>
900 /// <param name="find_sep_func">セパレーターなら真を返し、そうでないなら偽を返す</param>
901 /// <returns>開始インデックス、終了インデックス</returns>
902 public Tuple<int,int> GetSepartor(int index, Func<char, bool> find_sep_func)
904 if (find_sep_func == null)
905 throw new ArgumentNullException("find_sep_func must not be null");
907 if (this.Length <= 0 || index >= this.Length)
913 while (start > 0 && !find_sep_func(str[start]))
916 if (find_sep_func(str[start]))
922 while (end < this.Length && !find_sep_func(str[end]))
925 return new Tuple<int, int>(start, end);
929 /// セパレーターで囲まれた範囲内を選択する
931 /// <param name="index">探索を開始するインデックス</param>
932 /// <param name="find_sep_func">セパレーターなら真を返し、そうでないなら偽を返す</param>
933 /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
934 public void SelectSepartor(int index,Func<char,bool> find_sep_func, bool changeAnchor = false)
936 if (this.FireUpdateEvent == false)
937 throw new InvalidOperationException("");
939 if (find_sep_func == null)
940 throw new ArgumentNullException("find_sep_func must not be null");
942 var t = this.GetSepartor(index, find_sep_func);
946 int start = t.Item1, end = t.Item2;
948 this.Select(start, end - start);
951 this.AnchorIndex = start;
955 /// DocumentReaderを作成します
957 /// <returns>DocumentReaderオブジェクト</returns>
958 public DocumentReader CreateReader()
960 return new DocumentReader(this.buffer);
966 /// <param name="id">マーカーID</param>
967 /// <param name="m">設定したいマーカー</param>
968 public void SetMarker(int id,Marker m)
970 if (m.start < 0 || m.start + m.length > this.Length)
971 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
973 this.Markers.Add(id,m);
979 /// <param name="id">マーカーID</param>
980 /// <param name="start">開始インデックス</param>
981 /// <param name="length">削除する長さ</param>
982 public void RemoveMarker(int id,int start, int length)
984 if (start < 0 || start + length > this.Length)
985 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
987 this.Markers.RemoveAll(id,start, length);
993 /// <param name="id">マーカーID</param>
994 /// <param name="type">削除したいマーカーのタイプ</param>
995 public void RemoveMarker(int id, HilightType type)
997 this.Markers.RemoveAll(id,type);
1003 /// <param name="id">マーカーID</param>
1004 public void RemoveAllMarker(int id)
1006 this.Markers.RemoveAll(id);
1010 /// インデックスに対応するマーカーを得る
1012 /// <param name="id">マーカーID</param>
1013 /// <param name="index">インデックス</param>
1014 /// <returns>Marker構造体の列挙子</returns>
1015 public IEnumerable<Marker> GetMarkers(int id, int index)
1017 if (index < 0 || index > this.Length)
1018 throw new ArgumentOutOfRangeException("indexが範囲を超えています");
1019 return this.Markers.Get(id,index);
1025 /// <param name="index">開始インデックス</param>
1026 /// <param name="length">長さ</param>
1027 /// <returns>Stringオブジェクト</returns>
1028 public string ToString(int index, int length)
1030 return this.buffer.ToString(index, length);
1034 /// インデックスを開始位置とする文字列を返す
1036 /// <param name="index">開始インデックス</param>
1037 /// <returns>Stringオブジェクト</returns>
1038 public string ToString(int index)
1040 return this.ToString(index, this.buffer.Length - index);
1046 /// <param name="startIndex">開始インデックス</param>
1047 /// <param name="endIndex">終了インデックス</param>
1048 /// <param name="maxCharCount">最大長</param>
1049 /// <returns>行イテレーターが返される</returns>
1050 public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
1052 return this.buffer.GetLines(startIndex, endIndex, maxCharCount);
1055 internal IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
1057 return this.buffer.ForEachLines(startIndex, endIndex, maxCharCount);
1064 /// <param name="s">追加したい文字列</param>
1065 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
1066 public void Append(string s)
1068 this.Replace(this.buffer.Length, 0, s);
1074 /// <param name="index">開始インデックス</param>
1075 /// <param name="s">追加したい文字列</param>
1076 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
1077 public void Insert(int index, string s)
1079 this.Replace(index, 0, s);
1085 /// <param name="index">開始インデックス</param>
1086 /// <param name="length">長さ</param>
1087 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
1088 public void Remove(int index, int length)
1090 this.Replace(index, length, "");
1096 /// <param name="index">開始インデックス</param>
1097 /// <param name="length">長さ</param>
1098 /// <param name="s">文字列</param>
1099 /// <param name="UserInput">ユーザーからの入力として扱うなら真</param>
1100 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
1101 public void Replace(int index, int length, string s, bool UserInput = false)
1103 if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)
1104 throw new ArgumentOutOfRangeException();
1105 if (length == 0 && (s == string.Empty || s == null))
1108 foreach(int id in this.Markers.IDs)
1109 this.RemoveMarker(id,index, length);
1111 ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);
1112 this.UndoManager.push(cmd);
1115 if (this.FireUpdateEvent && UserInput)
1117 var input_str = string.Empty;
1118 if (s == Document.NewLine.ToString())
1120 else if (s == string.Empty && length > 0)
1122 //入力は終わっているので空文字を渡すが処理の都合で一部文字だけはそのまま渡す
1123 if (this.AutoComplete != null)
1124 this.AutoComplete.ParseInput(input_str);
1125 if (s == Document.NewLine.ToString())
1126 this.AutoIndentHook(this, null);
1133 /// <remarks>Dirtyフラグも同時にクリアーされます</remarks>
1134 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
1137 this.buffer.Clear();
1142 /// ストリームからドキュメントを非同期的に構築します
1144 /// <param name="fs">IStreamReaderオブジェクト</param>
1145 /// <param name="tokenSource">キャンセルトークン</param>
1146 /// <returns>Taskオブジェクト</returns>
1148 /// 読み取り操作は別スレッドで行われます。
1149 /// また、非同期操作中はこのメソッドを実行することはできません。
1151 public async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null)
1153 if (fs.Peek() == -1)
1156 if (this.LoadProgress != null)
1157 this.LoadProgress(this, new ProgressEventArgs(ProgressState.Start));
1162 await this.buffer.LoadAsync(fs, tokenSource);
1166 this.PerformLayout();
1167 if (this.LoadProgress != null)
1168 this.LoadProgress(this, new ProgressEventArgs(ProgressState.Complete));
1173 /// ストリームに非同期モードで保存します
1175 /// <param name="fs">IStreamWriterオブジェクト</param>
1176 /// <param name="tokenSource">キャンセルトークン</param>
1177 /// <returns>Taskオブジェクト</returns>
1178 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
1179 public async Task SaveAsync(TextWriter fs, CancellationTokenSource tokenSource = null)
1181 await this.buffer.SaveAsync(fs, tokenSource);
1185 /// Find()およびReplaceAll()で使用するパラメーターをセットします
1187 /// <param name="pattern">検索したい文字列</param>
1188 /// <param name="UseRegex">正規表現を使用するなら真</param>
1189 /// <param name="opt">RegexOptions列挙体</param>
1190 public void SetFindParam(string pattern, bool UseRegex, RegexOptions opt)
1194 this.regex = new Regex(pattern, opt);
1196 this.regex = new Regex(Regex.Escape(pattern), opt);
1200 /// 現在の検索パラメーターでWatchDogを生成する
1202 /// <param name="type">ハイライトタイプ</param>
1203 /// <param name="color">色</param>
1204 /// <returns>WatchDogオブジェクト</returns>
1205 public RegexMarkerPattern CreateWatchDogByFindParam(HilightType type,Color color)
1207 if (this.regex == null)
1208 throw new InvalidOperationException("SetFindParam()を呼び出してください");
1209 return new RegexMarkerPattern(this.regex,type,color);
1215 /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
1216 /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
1217 public IEnumerator<SearchResult> Find()
1219 return this.Find(0, this.Length);
1225 /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
1226 /// <param name="start">開始インデックス</param>
1227 /// <param name="length">検索する長さ</param>
1228 /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
1229 public IEnumerator<SearchResult> Find(int start, int length)
1231 if (this.regex == null)
1232 throw new InvalidOperationException();
1233 if (start < 0 || start >= this.Length)
1234 throw new ArgumentOutOfRangeException();
1236 int end = start + length - 1;
1238 if(end > this.Length - 1)
1239 throw new ArgumentOutOfRangeException();
1241 StringBuilder line = new StringBuilder();
1242 int oldLength = this.Length;
1243 for (int i = start; i <= end; i++)
1247 if (c == Document.NewLine || i == end)
1249 this.match = this.regex.Match(line.ToString());
1250 while (this.match.Success)
1252 int startIndex = i - line.Length + 1 + this.match.Index;
1253 int endIndex = startIndex + this.match.Length - 1;
1255 yield return new SearchResult(this.match, startIndex, endIndex);
1257 if (this.Length != oldLength) //長さが変わった場合は置き換え後のパターンの終点+1まで戻る
1259 int delta = this.Length - oldLength;
1260 i = endIndex + delta;
1262 oldLength = this.Length;
1266 this.match = this.match.NextMatch();
1274 /// 任意のパターンですべて置き換えます
1276 /// <param name="replacePattern">置き換え後のパターン</param>
1277 /// <param name="groupReplace">グループ置き換えを行うなら真。そうでないなら偽</param>
1278 public void ReplaceAll(string replacePattern,bool groupReplace)
1280 if (this.regex == null)
1281 throw new InvalidOperationException();
1282 ReplaceAllCommand cmd = new ReplaceAllCommand(this.buffer, this.LayoutLines, this.regex, replacePattern, groupReplace);
1283 this.UndoManager.push(cmd);
1290 /// <param name="target">対象となる文字列</param>
1291 /// <param name="pattern">置き換え後の文字列</param>
1292 /// <param name="ci">大文字も文字を区別しないなら真。そうでないなら偽</param>
1294 /// 検索時に大文字小文字を区別します。また、このメソッドでは正規表現を使用することはできません
1296 public void ReplaceAll2(string target, string pattern,bool ci = false)
1298 FastReplaceAllCommand cmd = new FastReplaceAllCommand(this.buffer, this.LayoutLines, target, pattern,ci);
1299 this.UndoManager.push(cmd);
1303 #region IEnumerable<char> メンバー
1308 /// <returns>IEnumeratorオブジェクトを返す</returns>
1309 public IEnumerator<char> GetEnumerator()
1311 return this.buffer.GetEnumerator();
1316 #region IEnumerable メンバー
1318 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
1320 throw new NotImplementedException();
1325 void buffer_Update(object sender, DocumentUpdateEventArgs e)
1329 case UpdateType.RebuildLayout:
1330 this._LayoutLines.Clear();
1331 this._LayoutLines.UpdateAsReplace(0, 0, this.Length);
1333 case UpdateType.Replace:
1336 this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);
1337 this.Markers.UpdateMarkers(e.startIndex, e.insertLength, e.removeLength);
1341 this._LayoutLines.UpdateLineAsReplace(e.row.Value, e.removeLength, e.insertLength);
1342 this.Markers.UpdateMarkers(this.LayoutLines.GetIndexFromLineNumber(e.row.Value), e.insertLength, e.removeLength);
1346 case UpdateType.Clear:
1347 this._LayoutLines.Clear();
1351 if(this.FireUpdateEvent)
1352 this.Update(this, e);
1355 #region IDisposable Support
1356 private bool disposedValue = false; // 重複する呼び出しを検出するには
1358 void Dispose(bool disposing)
1364 this.buffer.Clear();
1365 this.LayoutLines.Clear();
1368 disposedValue = true;
1375 public void Dispose()
1385 public class SearchResult
1387 private Match Match;
1404 get { return this.Match.Value; }
1408 /// 指定したパターンを置き換えて返す
1410 /// <param name="replacement">置き換える文字列</param>
1411 /// <returns>置き換え後の文字列</returns>
1412 public string Result(string replacement)
1414 return this.Match.Result(replacement);
1420 /// <param name="m">Matchオブジェクト</param>
1421 /// <param name="start">開始インデックス</param>
1422 /// <param name="end">終了インデックス</param>
1423 public SearchResult(Match m, int start,int end)
1434 public class DocumentReader : TextReader
1436 StringBuffer document;
1442 /// <param name="doc"></param>
1443 internal DocumentReader(StringBuffer doc)
1446 throw new ArgumentNullException();
1447 this.document = doc;
1453 /// <returns>文字。取得できない場合は-1</returns>
1454 public override int Peek()
1456 if (this.document == null)
1457 throw new InvalidOperationException();
1458 if (this.currentIndex >= this.document.Length)
1460 return this.document[this.currentIndex];
1464 /// 文字を取得し、イテレーターを一つ進める
1466 /// <returns>文字。取得できない場合は-1</returns>
1467 public override int Read()
1469 int c = this.Peek();
1471 this.currentIndex++;
1476 /// 文字列を読み取りバッファーに書き込む
1478 /// <param name="buffer">バッファー</param>
1479 /// <param name="index">開始インデックス</param>
1480 /// <param name="count">カウント</param>
1481 /// <returns>読み取られた文字数</returns>
1482 public override int Read(char[] buffer, int index, int count)
1484 if (this.document == null)
1485 throw new InvalidOperationException();
1488 throw new ArgumentNullException();
1490 if (this.document.Length < count)
1491 throw new ArgumentException();
1493 if (index < 0 || count < 0)
1494 throw new ArgumentOutOfRangeException();
1496 if (this.document.Length == 0)
1499 int actualCount = count;
1500 if (index + count - 1 > this.document.Length - 1)
1501 actualCount = this.document.Length - index;
1503 string str = this.document.ToString(index, actualCount);
1505 for (int i = 0; i < str.Length; i++) //ToCharArray()だと戻った時に消えてしまう
1508 this.currentIndex = index + actualCount;
1516 /// <param name="disposing">真ならアンマネージドリソースを解放する</param>
1517 protected override void Dispose(bool disposing)
1519 this.document = null;