OSDN Git Service

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