OSDN Git Service

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