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