OSDN Git Service

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