OSDN Git Service

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