OSDN Git Service

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