OSDN Git Service

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