OSDN Git Service

MVVM対応準備のため、RightToLeftプロパティをDocumentに移動させた
[fooeditengine/FooEditEngine.git] / Core / Document.cs
1 /*
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.
5
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.
8
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/>.
10  */
11
12 //#define TEST_ASYNC
13
14 using System;
15 using System.IO;
16 using System.Collections.Generic;
17 using System.Text;
18 using System.Text.RegularExpressions;
19 using System.Threading;
20 using System.Threading.Tasks;
21 using System.Linq;
22
23 namespace FooEditEngine
24 {
25     /// <summary>
26     /// 進行状況を表す列挙体
27     /// </summary>
28     public enum ProgressState
29     {
30         /// <summary>
31         /// 操作が開始したことを表す
32         /// </summary>
33         Start,
34         /// <summary>
35         /// 操作が終了したことを表す
36         /// </summary>
37         Complete,
38     }
39     /// <summary>
40     /// 進行状況を表すためのイベントデータ
41     /// </summary>
42     public sealed class ProgressEventArgs : EventArgs
43     {
44         /// <summary>
45         /// 進行状況
46         /// </summary>
47         public ProgressState state;
48         /// <summary>
49         /// コンストラクター
50         /// </summary>
51         /// <param name="state">ProgressStateオブジェクト</param>
52         public ProgressEventArgs(ProgressState state)
53         {
54             this.state = state;
55         }
56     }
57
58     /// <summary>
59     /// 進行状況を通知するためのデリゲート
60     /// </summary>
61     /// <param name="sender">送信元クラス</param>
62     /// <param name="e">イベントデータ</param>
63     public delegate void ProgressEventHandler(object sender, ProgressEventArgs e);
64
65     /// <summary>
66     /// 更新タイプを表す列挙体
67     /// </summary>
68     public enum UpdateType
69     {
70         /// <summary>
71         /// ドキュメントが置き換えられたことを表す
72         /// </summary>
73         Replace,
74         /// <summary>
75         /// ドキュメント全体が削除されたことを表す
76         /// </summary>
77         Clear,
78     }
79
80     /// <summary>
81     /// 更新タイプを通知するためのイベントデータ
82     /// </summary>
83     public sealed class DocumentUpdateEventArgs : EventArgs
84     {
85         /// <summary>
86         /// 更新タイプ
87         /// </summary>
88         public UpdateType type;
89         /// <summary>
90         /// 開始位置
91         /// </summary>
92         public int startIndex;
93         /// <summary>
94         /// 削除された長さ
95         /// </summary>
96         public int removeLength;
97         /// <summary>
98         /// 追加された長さ
99         /// </summary>
100         public int insertLength;
101         /// <summary>
102         /// 更新イベントが発生した行。行が不明な場合や行をまたぐ場合はnullを指定すること。
103         /// </summary>
104         public int? row;
105         /// <summary>
106         /// コンストラクター
107         /// </summary>
108         /// <param name="type">更新タイプ</param>
109         /// <param name="startIndex">開始インデックス</param>
110         /// <param name="removeLength">削除された長さ</param>
111         /// <param name="insertLength">追加された長さ</param>
112         /// <param name="row">開始行。nullを指定することができる</param>
113         public DocumentUpdateEventArgs(UpdateType type, int startIndex, int removeLength, int insertLength, int? row = null)
114         {
115             this.type = type;
116             this.startIndex = startIndex;
117             this.removeLength = removeLength;
118             this.insertLength = insertLength;
119             this.row = row;
120         }
121     }
122
123     /// <summary>
124     /// ドキュメントに更新があったことを伝えるためのデリゲート
125     /// </summary>
126     /// <param name="sender">送信元クラス</param>
127     /// <param name="e">イベントデータ</param>
128     public delegate void DocumentUpdateEventHandler(object sender, DocumentUpdateEventArgs e);
129
130     /// <summary>
131     /// ドキュメントの管理を行う
132     /// </summary>
133     /// <remarks>この型のすべてのメソッド・プロパティはスレッドセーフです</remarks>
134     public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>
135     {
136         const int MaxSemaphoreCount = 1;
137         Regex regex;
138         Match match;
139         StringBuffer buffer;
140         LineToIndexTable _LayoutLines;
141         bool _EnableFireUpdateEvent = true,_UrlMark = false, _DrawLineNumber = false, _HideRuler = true, _RightToLeft = false;
142         SemaphoreSlim Semaphore = new SemaphoreSlim(MaxSemaphoreCount);
143         LineBreakMethod _LineBreak;
144         int _TabStops, _LineBreakCharCount = 80;
145
146         /// <summary>
147         /// 一行当たりの最大文字数
148         /// </summary>
149         public const int MaximumLineLength = 1000;
150
151         /// <summary>
152         /// コンストラクター
153         /// </summary>
154         internal Document()
155             : this(null)
156         {
157         }
158
159         internal Document(Document doc)
160         {
161             if (doc == null)
162                 this.buffer = new StringBuffer();
163             else
164                 this.buffer = new StringBuffer(doc.buffer);
165             this.buffer.Update = new DocumentUpdateEventHandler(buffer_Update);
166             this.UpdateCalledAlways += (s, e) => { };
167             this.Update += new DocumentUpdateEventHandler((s, e) => { });
168             this.ChangeFireUpdateEvent += new EventHandler((s, e) => { });
169             this.Markers = new MarkerCollection(this);
170             this.UndoManager = new UndoManager();
171             this._LayoutLines = new LineToIndexTable(this);
172             this._LayoutLines.SpilitString = (s,e)=> {
173                 return this.CreateLineList(e.index, e.length, MaximumLineLength);
174             };
175             this._LayoutLines.Clear();
176             this.MarkerPatternSet = new MarkerPatternSet(this._LayoutLines, this.Markers);
177             this.MarkerPatternSet.Updated += WacthDogPattern_Updated;
178             this.LineBreakChanged += (s, e) => { };
179             this.TabStopsChanged += (s, e) => { };
180             this.DrawLineNumberChanged += (s, e) => { };
181             this.Selections = new SelectCollection();
182             this.HideLineMarker = true;
183             this.HideRulerChanged += (s, e) => { };
184             this.CaretPostion = TextPoint.Null;
185         }
186
187         void WacthDogPattern_Updated(object sender, EventArgs e)
188         {
189             this._LayoutLines.ClearLayoutCache();
190         }
191
192         /// <summary>
193         /// HideRulerの値が変わったときに通知します
194         /// </summary>
195         public event EventHandler RightToLeftChanged;
196
197         /// <summary>
198         /// 右から左に表示するなら真
199         /// </summary>
200         public bool RightToLeft {
201             get { return this._RightToLeft; }
202             set
203             {
204                 this._RightToLeft = value;
205                 this.RightToLeftChanged(this, null);
206             }
207         }
208
209         /// <summary>
210         /// 矩形選択モードなら真を返し、そうでない場合は偽を返す
211         /// </summary>
212         public bool RectSelection
213         {
214             get;
215             set;
216         }
217
218         /// <summary>
219         /// インデントの方法を表す
220         /// </summary>
221         public IndentMode IndentMode
222         {
223             get;
224             set;
225         }
226
227         /// <summary>
228         /// ラインマーカーを描くなら偽。そうでなければ真
229         /// </summary>
230         public bool HideLineMarker
231         {
232             get;
233             set;
234         }
235
236         /// <summary>
237         /// キャレットを描くなら偽。そうでなければ真
238         /// </summary>
239         public bool HideCaret
240         {
241             get;
242             set;
243         }
244
245         /// <summary>
246         /// 挿入モードなら真を返し、上書きモードなら偽を返す
247         /// </summary>
248         public bool InsertMode
249         {
250             get;
251             set;
252         }
253
254         /// <summary>
255         /// HideRulerの値が変わったときに通知します
256         /// </summary>
257         public event EventHandler HideRulerChanged;
258
259         /// <summary>
260         /// ルーラーを表示しないなら真、そうでないなら偽
261         /// </summary>
262         public bool HideRuler
263         {
264             get { return this._HideRuler; }
265             set
266             {
267                 this._HideRuler = value;
268                 this.LayoutLines.ClearLayoutCache();
269                 this.HideRulerChanged(this, null);
270             }
271         }
272
273         /// <summary>
274         /// レイアウト行のどこにキャレットがあるかを表す
275         /// </summary>
276         /// <remarks>この値を変更しても反映されないので、EditView側でAdjustCaret()メソッドを呼び出す必要があります</remarks>
277         public TextPoint CaretPostion
278         {
279             get;
280             set;
281         }
282
283         /// <summary>
284         /// 選択範囲コレクション
285         /// </summary>
286         internal SelectCollection Selections
287         {
288             get;
289             set;
290         }
291
292         /// <summary>
293         /// DrawLineNumberの値が変わったときに通知される
294         /// </summary>
295         public event EventHandler DrawLineNumberChanged;
296
297         /// <summary>
298         /// 行番号を表示するかどうか
299         /// </summary>
300         public bool DrawLineNumber
301         {
302             get { return this._DrawLineNumber; }
303             set
304             {
305                 this._DrawLineNumber = value;
306                 this._LayoutLines.ClearLayoutCache();
307                 this.DrawLineNumberChanged(this, null);
308             }
309         }
310
311         /// <summary>
312         /// URLをハイパーリンクとして表示するなら真。そうでないなら偽
313         /// </summary>
314         public bool UrlMark
315         {
316             get { return this._UrlMark; }
317             set
318             {
319                 this._UrlMark = value;
320                 if (value)
321                 {
322                     Regex regex = new Regex("(http|https|ftp)(:\\/\\/[-_.!~*\\'()a-zA-Z0-9;\\/?:\\@&=+\\$,%#]+)");
323                     this.MarkerPatternSet.Add(MarkerIDs.URL, new RegexMarkerPattern(regex, HilightType.Url, new Color()));
324                 }
325                 else
326                 {
327                     this.MarkerPatternSet.Remove(MarkerIDs.URL);
328                 }
329             }
330         }
331
332         /// <summary>
333         /// LineBreakが変更されたときに通知される
334         /// </summary>
335         public EventHandler LineBreakChanged;
336
337         /// <summary>
338         /// 桁折り処理の方法を指定する
339         /// </summary>
340         /// <remarks>
341         /// 変更した場合、呼び出し側で再描写とレイアウトの再構築を行う必要があります
342         /// </remarks>
343         public LineBreakMethod LineBreak
344         {
345             get
346             {
347                 return this._LineBreak;
348             }
349             set
350             {
351                 this._LineBreak = value;
352                 this.LineBreakChanged(this, null);
353             }
354         }
355
356         /// <summary>
357         /// 折り返し行う文字数。実際に折り返しが行われる幅はem単位×この値となります
358         /// </summary>
359         /// <remarks>この値を変えた場合、LineBreakChangedイベントが発生します</remarks>
360         public int LineBreakCharCount
361         {
362             get
363             {
364                 return this._LineBreakCharCount;
365             }
366             set
367             {
368                 this._LineBreakCharCount = value;
369                 this.LineBreakChanged(this, null);
370             }
371         }
372
373         /// <summary>
374         /// TabStopsの値が変わったことを通知する
375         /// </summary>
376         public event EventHandler TabStopsChanged;
377
378         /// <summary>
379         /// タブの幅
380         /// </summary>
381         /// <remarks>変更した場合、呼び出し側で再描写する必要があります</remarks>
382         public int TabStops
383         {
384             get { return this._TabStops; }
385             set {
386                 this._TabStops = value;
387                 this.TabStopsChanged(this, null);
388             }
389         }
390
391         /// <summary>
392         /// マーカーパターンセット
393         /// </summary>
394         public MarkerPatternSet MarkerPatternSet
395         {
396             get;
397             private set;
398         }
399
400         /// <summary>
401         /// レイアウト行を表す
402         /// </summary>
403         public LineToIndexTable LayoutLines
404         {
405             get
406             {
407                 return this._LayoutLines;
408             }
409         }
410
411         /// <summary>
412         /// レイアウト行を返す
413         /// </summary>
414         /// <param name="index">開始インデックス</param>
415         /// <param name="length">長さ</param>
416         /// <param name="lineLimitLength">1行当たりの最大文字数。-1で無制限</param>
417         /// <returns>レイアウト行リスト</returns>
418         internal IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)
419         {
420             int startIndex = index;
421             int endIndex = index + length - 1;
422             List<LineToIndexTableData> output = new List<LineToIndexTableData>();
423
424             foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))
425             {
426                 int lineHeadIndex = range.Item1;
427                 int lineLength = range.Item2;
428                 char c = this.buffer[lineHeadIndex + lineLength - 1];
429                 bool hasNewLine = c == Document.NewLine;
430                 output.Add(this.LayoutLines.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));
431             }
432
433             if (output.Count > 0)
434                 output.Last().LineEnd = true;
435
436             return output;
437         }
438
439         internal void FireUpdate(DocumentUpdateEventArgs e)
440         {
441             this.buffer_Update(this.buffer, e);
442         }
443
444         /// <summary>
445         /// ドキュメントが更新された時に呼ばれるイベント
446         /// </summary>
447         public event DocumentUpdateEventHandler Update;
448
449         /// <summary>
450         /// ドキュメントが更新された時に呼びされるイベント
451         /// </summary>
452         /// <remarks>
453         /// FireUpdateEventの値に関わらず常に呼びされます
454         /// </remarks>
455         internal event DocumentUpdateEventHandler UpdateCalledAlways;
456
457         /// <summary>
458         /// FireUpdateEventの値が変わったときに呼び出されるイベント
459         /// </summary>
460         public event EventHandler ChangeFireUpdateEvent;
461
462         /// <summary>
463         /// 改行コードの内部表現
464         /// </summary>
465         public const char NewLine = '\n';
466
467         /// <summary>
468         /// EOFの内部表現
469         /// </summary>
470         public const char EndOfFile = '\u001a';
471
472         /// <summary>
473         /// ロック中なら真を返し、そうでないなら偽を返す
474         /// </summary>
475         public bool IsLocked
476         {
477             get
478             {
479                 return this.Semaphore.CurrentCount == 0;
480             }
481         }
482
483         /// <summary>
484         /// アンドゥ管理クラスを表す
485         /// </summary>
486         public UndoManager UndoManager
487         {
488             get;
489             private set;
490         }
491
492         /// <summary>
493         /// 文字列の長さ
494         /// </summary>
495         public int Length
496         {
497             get
498             {
499                 return this.buffer.Length;
500             }
501         }
502
503         /// <summary>
504         /// 変更のたびにUpdateイベントを発生させるかどうか
505         /// </summary>
506         public bool FireUpdateEvent
507         {
508             get
509             {
510                 return this._EnableFireUpdateEvent;
511             }
512             set
513             {
514                 this._EnableFireUpdateEvent = value;
515                 this.ChangeFireUpdateEvent(this, null);
516             }
517         }
518
519         /// <summary>
520         /// インデクサー
521         /// </summary>
522         /// <param name="i">インデックス(自然数でなければならない)</param>
523         /// <returns>Char型</returns>
524         public char this[int i]
525         {
526             get
527             {
528                 return this.buffer[i];
529             }
530         }
531
532         /// <summary>
533         /// マーカーコレクション
534         /// </summary>
535         public MarkerCollection Markers
536         {
537             get;
538             private set;
539         }
540
541         internal StringBuffer StringBuffer
542         {
543             get
544             {
545                 return this.buffer;
546             }
547         }
548
549         /// <summary>
550         /// DocumentReaderを作成します
551         /// </summary>
552         /// <returns>DocumentReaderオブジェクト</returns>
553         public DocumentReader CreateReader()
554         {
555             return new DocumentReader(this.buffer);
556         }
557
558         /// <summary>
559         /// ロックを解除します
560         /// </summary>
561         public void UnLock()
562         {
563             this.Semaphore.Release();
564         }
565
566         /// <summary>
567         /// ロックします
568         /// </summary>
569         public void Lock()
570         {
571             this.Semaphore.Wait();
572         }
573
574         /// <summary>
575         /// ロックします
576         /// </summary>
577         /// <returns>Taskオブジェクト</returns>
578         public Task LockAsync()
579         {
580             return this.Semaphore.WaitAsync();
581         }
582
583         /// <summary>
584         /// マーカーを設定する
585         /// </summary>
586         /// <param name="id">マーカーID</param>
587         /// <param name="m">設定したいマーカー</param>
588         public void SetMarker(int id,Marker m)
589         {
590             if (m.start < 0 || m.start + m.length > this.Length)
591                 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
592
593             this.Markers.Add(id,m);
594         }
595
596         /// <summary>
597         /// マーカーを削除する
598         /// </summary>
599         /// <param name="id">マーカーID</param>
600         /// <param name="start">開始インデックス</param>
601         /// <param name="length">削除する長さ</param>
602         public void RemoveMarker(int id,int start, int length)
603         {
604             if (start < 0 || start + length > this.Length)
605                 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
606
607             this.Markers.RemoveAll(id,start, length);
608         }
609
610         /// <summary>
611         /// マーカーを削除する
612         /// </summary>
613         /// <param name="id">マーカーID</param>
614         /// <param name="type">削除したいマーカーのタイプ</param>
615         public void RemoveMarker(int id, HilightType type)
616         {
617             this.Markers.RemoveAll(id,type);
618         }
619
620         /// <summary>
621         /// インデックスに対応するマーカーを得る
622         /// </summary>
623         /// <param name="id">マーカーID</param>
624         /// <param name="index">インデックス</param>
625         /// <returns>Marker構造体の列挙子</returns>
626         public IEnumerable<Marker> GetMarkers(int id, int index)
627         {
628             if (index < 0 || index > this.Length)
629                 throw new ArgumentOutOfRangeException("indexが範囲を超えています");
630             return this.Markers.Get(id,index);
631         }
632
633         /// <summary>
634         /// 部分文字列を取得する
635         /// </summary>
636         /// <param name="index">開始インデックス</param>
637         /// <param name="length">長さ</param>
638         /// <returns>Stringオブジェクト</returns>
639         public string ToString(int index, int length)
640         {
641             return this.buffer.ToString(index, length);
642         }
643
644         /// <summary>
645         /// インデックスを開始位置とする文字列を返す
646         /// </summary>
647         /// <param name="index">開始インデックス</param>
648         /// <returns>Stringオブジェクト</returns>
649         public string ToString(int index)
650         {
651             return this.ToString(index, this.buffer.Length - index);
652         }
653
654         /// <summary>
655         /// 行を取得する
656         /// </summary>
657         /// <param name="startIndex">開始インデックス</param>
658         /// <param name="endIndex">終了インデックス</param>
659         /// <param name="maxCharCount">最大長</param>
660         /// <returns>行イテレーターが返される</returns>
661         public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
662         {
663             return this.buffer.GetLines(startIndex, endIndex, maxCharCount);
664         }
665
666         internal IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
667         {
668             return this.buffer.ForEachLines(startIndex, endIndex, maxCharCount);
669         }
670
671
672         /// <summary>
673         /// 文字列を追加する
674         /// </summary>
675         /// <param name="s">追加したい文字列</param>
676         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
677         public void Append(string s)
678         {
679             this.Replace(this.buffer.Length, 0, s);
680         }
681
682         /// <summary>
683         /// 文字列を挿入する
684         /// </summary>
685         /// <param name="index">開始インデックス</param>
686         /// <param name="s">追加したい文字列</param>
687         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
688         public void Insert(int index, string s)
689         {
690             this.Replace(index, 0, s);
691         }
692
693         /// <summary>
694         /// 文字列を削除する
695         /// </summary>
696         /// <param name="index">開始インデックス</param>
697         /// <param name="length">長さ</param>
698         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
699         public void Remove(int index, int length)
700         {
701             this.Replace(index, length, "");
702         }
703
704         /// <summary>
705         /// ドキュメントを置き換える
706         /// </summary>
707         /// <param name="index">開始インデックス</param>
708         /// <param name="length">長さ</param>
709         /// <param name="s">文字列</param>
710         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
711         public void Replace(int index, int length, string s)
712         {
713             if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)
714                 throw new ArgumentOutOfRangeException();
715             if (length == 0 && (s == string.Empty || s == null))
716                 return;
717
718             foreach(int id in this.Markers.IDs)
719                 this.RemoveMarker(id,index, length);
720
721             ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);
722             this.UndoManager.push(cmd);
723             cmd.redo();
724         }
725
726         /// <summary>
727         /// 物理行をすべて削除する
728         /// </summary>
729         /// <remarks>Dirtyフラグも同時にクリアーされます</remarks>
730         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
731         public void Clear()
732         {
733             this.buffer.Clear();
734         }
735
736         /// <summary>
737         /// ストリームからドキュメントを非同期的に構築します
738         /// </summary>
739         /// <param name="fs">IStreamReaderオブジェクト</param>
740         /// <param name="tokenSource">キャンセルトークン</param>
741         /// <returns>Taskオブジェクト</returns>
742         /// <remarks>
743         /// 読み取り操作は別スレッドで行われます。
744         /// また、非同期操作中はこのメソッドを実行することはできません。
745         /// </remarks>
746         internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)
747         {
748             if (fs.IsEnd())
749                 return;
750
751             try
752             {
753                 await this.LockAsync().ConfigureAwait(false);
754                 this.Clear();
755                 this.FireUpdateEvent = false;
756                 await this.buffer.LoadAsync(fs, tokenSource);
757             }
758             finally
759             {
760                 this.FireUpdateEvent = true;
761                 this.UnLock();
762             }
763         }
764
765         /// <summary>
766         /// ストリームに非同期モードで保存します
767         /// </summary>
768         /// <param name="fs">IStreamWriterオブジェクト</param>
769         /// <param name="tokenSource">キャンセルトークン</param>
770         /// <returns>Taskオブジェクト</returns>
771         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
772         internal async Task SaveAsync(IStreamWriter fs, CancellationTokenSource tokenSource = null)
773         {
774             try
775             {
776                 await this.LockAsync().ConfigureAwait(false);
777                 StringBuilder line = new StringBuilder();
778                 for (int i = 0; i < this.Length; i++)
779                 {
780                     char c = this[i];
781                     line.Append(c);
782                     if (c == Document.NewLine || i == this.Length - 1)
783                     {
784                         string str = line.ToString();
785                         str = str.Replace(Document.NewLine.ToString(), fs.NewLine);
786                         await fs.WriteAsync(str).ConfigureAwait(false);
787                         line.Clear();
788                         if (tokenSource != null)
789                             tokenSource.Token.ThrowIfCancellationRequested();
790 #if TEST_ASYNC
791                     System.Threading.Thread.Sleep(10);
792 #endif
793                     }
794                 }
795             }
796             finally
797             {
798                 this.UnLock();
799             }
800         }
801
802         /// <summary>
803         /// Find()およびReplaceAll()で使用するパラメーターをセットします
804         /// </summary>
805         /// <param name="pattern">検索したい文字列</param>
806         /// <param name="UseRegex">正規表現を使用するなら真</param>
807         /// <param name="opt">RegexOptions列挙体</param>
808         public void SetFindParam(string pattern, bool UseRegex, RegexOptions opt)
809         {
810             this.match = null;
811             if (UseRegex)
812                 this.regex = new Regex(pattern, opt);
813             else
814                 this.regex = new Regex(Regex.Escape(pattern), opt);
815         }
816
817         /// <summary>
818         /// 現在の検索パラメーターでWatchDogを生成する
819         /// </summary>
820         /// <param name="type">ハイライトタイプ</param>
821         /// <param name="color">色</param>
822         /// <returns>WatchDogオブジェクト</returns>
823         public RegexMarkerPattern CreateWatchDogByFindParam(HilightType type,Color color)
824         {
825             if (this.regex == null)
826                 throw new InvalidOperationException("SetFindParam()を呼び出してください");
827             return new RegexMarkerPattern(this.regex,type,color);
828         }
829
830         /// <summary>
831         /// 指定した文字列を検索します
832         /// </summary>
833         /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
834         /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
835         public IEnumerator<SearchResult> Find()
836         {
837             return this.Find(0, this.Length);
838         }
839
840         /// <summary>
841         /// 指定した文字列を検索します
842         /// </summary>
843         /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
844         /// <param name="start">開始インデックス</param>
845         /// <param name="length">検索する長さ</param>
846         /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
847         public IEnumerator<SearchResult> Find(int start, int length)
848         {
849             if (this.regex == null)
850                 throw new InvalidOperationException();
851             if (start < 0 || start >= this.Length)
852                 throw new ArgumentOutOfRangeException();
853
854             int end = start + length - 1;
855
856             if(end > this.Length - 1)
857                 throw new ArgumentOutOfRangeException();
858
859             StringBuilder line = new StringBuilder();
860             int oldLength = this.Length;
861             for (int i = start; i <= end; i++)
862             {
863                 char c = this[i];
864                 line.Append(c);
865                 if (c == Document.NewLine || i == end)
866                 {
867                     this.match = this.regex.Match(line.ToString());
868                     while (this.match.Success)
869                     {
870                         int startIndex = i - line.Length + 1 + this.match.Index;
871                         int endIndex = startIndex + this.match.Length - 1;
872
873                         yield return new SearchResult(this.match, startIndex, endIndex);
874
875                         if (this.Length != oldLength)   //長さが変わった場合は置き換え後のパターンの終点+1まで戻る
876                         {
877                             int delta = this.Length - oldLength;
878                             i = endIndex + delta;
879                             end = end + delta;
880                             oldLength = this.Length;
881                             break;
882                         }
883
884                         this.match = this.match.NextMatch();
885                     }
886                     line.Clear();
887                 }
888             }
889         }
890
891         /// <summary>
892         /// 任意のパターンですべて置き換えます
893         /// </summary>
894         /// <param name="replacePattern">置き換え後のパターン</param>
895         /// <param name="groupReplace">グループ置き換えを行うなら真。そうでないなら偽</param>
896         public void ReplaceAll(string replacePattern,bool groupReplace)
897         {
898             if (this.regex == null)
899                 throw new InvalidOperationException();
900             ReplaceAllCommand cmd = new ReplaceAllCommand(this.buffer, this.LayoutLines, this.regex, replacePattern, groupReplace);
901             this.UndoManager.push(cmd);
902             cmd.redo();
903         }
904
905         /// <summary>
906         /// 任意のパターンで置き換える
907         /// </summary>
908         /// <param name="target">対象となる文字列</param>
909         /// <param name="pattern">置き換え後の文字列</param>
910         /// <param name="ci">大文字も文字を区別しないなら真。そうでないなら偽</param>
911         /// <remarks>
912         /// 検索時に大文字小文字を区別します。また、このメソッドでは正規表現を使用することはできません
913         /// </remarks>
914         public void ReplaceAll2(string target, string pattern,bool ci = false)
915         {
916             FastReplaceAllCommand cmd = new FastReplaceAllCommand(this.buffer, this.LayoutLines, target, pattern,ci);
917             this.UndoManager.push(cmd);
918             cmd.redo();
919         }
920
921         #region IEnumerable<char> メンバー
922
923         /// <summary>
924         /// 列挙子を返します
925         /// </summary>
926         /// <returns>IEnumeratorオブジェクトを返す</returns>
927         public IEnumerator<char> GetEnumerator()
928         {
929             return this.buffer.GetEnumerator();
930         }
931
932         #endregion
933
934         #region IEnumerable メンバー
935
936         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
937         {
938             throw new NotImplementedException();
939         }
940
941         #endregion
942
943         void buffer_Update(object sender, DocumentUpdateEventArgs e)
944         {
945             switch (e.type)
946             {
947                 case UpdateType.Replace:
948                     if (e.row == null)
949                         this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);
950                     else
951                         this._LayoutLines.UpdateLineAsReplace(e.row.Value, e.removeLength, e.insertLength);
952                     break;
953                 case UpdateType.Clear:
954                     this._LayoutLines.Clear();
955                     break;
956             }
957             this.UpdateCalledAlways(this, e);
958             if(this.FireUpdateEvent)
959                 this.Update(this, e);
960         }
961     }
962
963     public interface IStreamReader
964     {
965         /// <summary>
966         /// ストリームが空かどうかを返す
967         /// </summary>
968         bool IsEnd();
969
970         /// <summary>
971         /// ストリームから行を読み取った物を返す。LoadAsyncを呼び出す場合は必ず実装してください
972         /// </summary>
973         Task<string> ReadLineAsync();
974         /// <summary>
975         /// ストリームから指定した文字数だけ読み取る
976         /// </summary>
977         /// <param name="buffer">書き込み先バッファー</param>
978         /// <param name="index">書き込み先バッファーのインデックス</param>
979         /// <param name="count">読み取る文字数</param>
980         /// <returns>読み取った文字数</returns>
981         Task<int> ReadAsync(char[] buffer, int index, int count);
982     }
983
984     public interface IStreamWriter
985     {
986         /// <summary>
987         /// ストリームに書き込む。SaveAsyncを呼び出す場合は必ず実装してください
988         /// </summary>
989         Task WriteAsync(string str);
990
991         /// <summary>
992         /// 書き込む際に使用する改行コード
993         /// </summary>
994         string NewLine
995         {
996             get;
997             set;
998         }
999     }
1000
1001     /// <summary>
1002     /// 検索結果を表す
1003     /// </summary>
1004     public class SearchResult
1005     {
1006         private Match Match;
1007
1008         /// <summary>
1009         /// 一致した場所の開始位置を表す
1010         /// </summary>
1011         public int Start;
1012
1013         /// <summary>
1014         /// 一致した場所の終了位置を表す
1015         /// </summary>
1016         public int End;
1017
1018         /// <summary>
1019         /// 見つかった文字列を返す
1020         /// </summary>
1021         public string Value
1022         {
1023             get { return this.Match.Value; }
1024         }
1025
1026         /// <summary>
1027         /// 指定したパターンを置き換えて返す
1028         /// </summary>
1029         /// <param name="replacement">置き換える文字列</param>
1030         /// <returns>置き換え後の文字列</returns>
1031         public string Result(string replacement)
1032         {
1033             return this.Match.Result(replacement);
1034         }
1035
1036         /// <summary>
1037         /// コンストラクター
1038         /// </summary>
1039         /// <param name="m">Matchオブジェクト</param>
1040         /// <param name="start">開始インデックス</param>
1041         /// <param name="end">終了インデックス</param>
1042         public SearchResult(Match m, int start,int end)
1043         {
1044             this.Match = m;
1045             this.Start = start;
1046             this.End = end;
1047         }
1048     }
1049
1050     /// <summary>
1051     /// ドキュメントリーダー
1052     /// </summary>
1053     public class DocumentReader : TextReader
1054     {
1055         StringBuffer document;      
1056         int currentIndex;
1057
1058         /// <summary>
1059         /// コンストラクター
1060         /// </summary>
1061         /// <param name="doc"></param>
1062         internal DocumentReader(StringBuffer doc)
1063         {
1064             if (doc == null)
1065                 throw new ArgumentNullException();
1066             this.document = doc;
1067         }
1068
1069         /// <summary>
1070         /// 文字を取得する
1071         /// </summary>
1072         /// <returns>文字。取得できない場合は-1</returns>
1073         public override int Peek()
1074         {
1075             if (this.document == null)
1076                 throw new InvalidOperationException();
1077             if (this.currentIndex >= this.document.Length)
1078                 return -1;
1079             return this.document[this.currentIndex];
1080         }
1081
1082         /// <summary>
1083         /// 文字を取得し、イテレーターを一つ進める
1084         /// </summary>
1085         /// <returns>文字。取得できない場合は-1</returns>
1086         public override int Read()
1087         {
1088             int c = this.Peek();
1089             if(c != -1)
1090                 this.currentIndex++;
1091             return c;
1092         }
1093
1094         /// <summary>
1095         /// 文字列を読み取りバッファーに書き込む
1096         /// </summary>
1097         /// <param name="buffer">バッファー</param>
1098         /// <param name="index">開始インデックス</param>
1099         /// <param name="count">カウント</param>
1100         /// <returns>読み取られた文字数</returns>
1101         public override int Read(char[] buffer, int index, int count)
1102         {
1103             if (this.document == null)
1104                 throw new InvalidOperationException();
1105
1106             if (buffer == null)
1107                 throw new ArgumentNullException();
1108
1109             if (this.document.Length < count)
1110                 throw new ArgumentException();
1111
1112             if (index < 0 || count < 0)
1113                 throw new ArgumentOutOfRangeException();
1114
1115             if (this.document.Length == 0)
1116                 return 0;
1117
1118             int actualCount = count;
1119             if (index + count - 1 > this.document.Length - 1)
1120                 actualCount = this.document.Length - index;
1121
1122             string str = this.document.ToString(index, actualCount);
1123
1124             for (int i = 0; i < str.Length; i++)    //ToCharArray()だと戻った時に消えてしまう
1125                 buffer[i] = str[i];
1126
1127             this.currentIndex = index + actualCount;
1128             
1129             return actualCount;
1130         }
1131
1132         /// <summary>
1133         /// オブジェクトを破棄する
1134         /// </summary>
1135         /// <param name="disposing">真ならアンマネージドリソースを解放する</param>
1136         protected override void Dispose(bool disposing)
1137         {
1138             this.document = null;
1139         }
1140     }
1141 }