OSDN Git Service

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