OSDN Git Service

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