OSDN Git Service

コア部分を共通プロジェクト化した
[fooeditengine/FooEditEngine.git] / Core / Document.cs
1 /*
2  * Copyright (C) 2013 FooProject
3  * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
5
6  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
10  */
11
12 //#define TEST_ASYNC
13
14 using System;
15 using System.IO;
16 using System.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;
142         SemaphoreSlim Semaphore = new SemaphoreSlim(MaxSemaphoreCount);
143
144         /// <summary>
145         /// コンストラクター
146         /// </summary>
147         internal Document()
148             : this(null)
149         {
150         }
151
152         internal Document(Document doc)
153         {
154             if (doc == null)
155                 this.buffer = new StringBuffer();
156             else
157                 this.buffer = new StringBuffer(doc.buffer);
158             this.buffer.Update = new DocumentUpdateEventHandler(buffer_Update);
159             this.UpdateCalledAlways += (s, e) => { };
160             this.Update += new DocumentUpdateEventHandler((s, e) => { });
161             this.ChangeFireUpdateEvent += new EventHandler((s, e) => { });
162             this.Markers = new MarkerCollection(this);
163             this.UndoManager = new UndoManager();
164             this._LayoutLines = new LineToIndexTable(this);
165             this._LayoutLines.SpilitString = LayoutLines_SpilitStringByChar;
166             this._LayoutLines.Clear();
167         }
168
169         /// <summary>
170         /// レイアウト行を表す
171         /// </summary>
172         public LineToIndexTable LayoutLines
173         {
174             get
175             {
176                 return this._LayoutLines;
177             }
178         }
179
180         IList<LineToIndexTableData> LayoutLines_SpilitStringByChar(object sender, SpilitStringEventArgs e)
181         {
182             return this.CreateLineList(e.index, e.length);
183         }
184
185         /// <summary>
186         /// レイアウト行を返す
187         /// </summary>
188         /// <param name="index">開始インデックス</param>
189         /// <param name="length">長さ</param>
190         /// <param name="lineLimitLength">1行当たりの最大文字数。-1で無制限</param>
191         /// <returns>レイアウト行リスト</returns>
192         internal IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)
193         {
194             int startIndex = index;
195             int endIndex = index + length - 1;
196             List<LineToIndexTableData> output = new List<LineToIndexTableData>();
197
198             foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))
199             {
200                 int lineHeadIndex = range.Item1;
201                 int lineLength = range.Item2;
202                 char c = this.buffer[lineHeadIndex + lineLength - 1];
203                 bool hasNewLine = c == Document.NewLine;
204                 output.Add(this.LayoutLines.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));
205             }
206
207             if (output.Count > 0)
208                 output.Last().LineEnd = true;
209
210             return output;
211         }
212
213         internal void FireUpdate(DocumentUpdateEventArgs e)
214         {
215             this.buffer_Update(this.buffer, e);
216         }
217
218         /// <summary>
219         /// ドキュメントが更新された時に呼ばれるイベント
220         /// </summary>
221         public event DocumentUpdateEventHandler Update;
222
223         /// <summary>
224         /// ドキュメントが更新された時に呼びされるイベント
225         /// </summary>
226         /// <remarks>
227         /// FireUpdateEventの値に関わらず常に呼びされます
228         /// </remarks>
229         internal event DocumentUpdateEventHandler UpdateCalledAlways;
230
231         /// <summary>
232         /// FireUpdateEventの値が変わったときに呼び出されるイベント
233         /// </summary>
234         public event EventHandler ChangeFireUpdateEvent;
235
236         /// <summary>
237         /// 改行コードの内部表現
238         /// </summary>
239         public const char NewLine = '\n';
240
241         /// <summary>
242         /// EOFの内部表現
243         /// </summary>
244         public const char EndOfFile = '\u001a';
245
246         /// <summary>
247         /// ロック中なら真を返し、そうでないなら偽を返す
248         /// </summary>
249         public bool IsLocked
250         {
251             get
252             {
253                 return this.Semaphore.CurrentCount == 0;
254             }
255         }
256
257         /// <summary>
258         /// アンドゥ管理クラスを表す
259         /// </summary>
260         public UndoManager UndoManager
261         {
262             get;
263             private set;
264         }
265
266         /// <summary>
267         /// 文字列の長さ
268         /// </summary>
269         public int Length
270         {
271             get
272             {
273                 return this.buffer.Length;
274             }
275         }
276
277         /// <summary>
278         /// 変更のたびにUpdateイベントを発生させるかどうか
279         /// </summary>
280         public bool FireUpdateEvent
281         {
282             get
283             {
284                 return this._EnableFireUpdateEvent;
285             }
286             set
287             {
288                 this._EnableFireUpdateEvent = value;
289                 this.ChangeFireUpdateEvent(this, null);
290             }
291         }
292
293         /// <summary>
294         /// インデクサー
295         /// </summary>
296         /// <param name="i">インデックス(自然数でなければならない)</param>
297         /// <returns>Char型</returns>
298         public char this[int i]
299         {
300             get
301             {
302                 return this.buffer[i];
303             }
304         }
305
306         /// <summary>
307         /// マーカーコレクション
308         /// </summary>
309         public MarkerCollection Markers
310         {
311             get;
312             private set;
313         }
314
315         internal StringBuffer StringBuffer
316         {
317             get
318             {
319                 return this.buffer;
320             }
321         }
322
323         /// <summary>
324         /// DocumentReaderを作成します
325         /// </summary>
326         /// <returns>DocumentReaderオブジェクト</returns>
327         public DocumentReader CreateReader()
328         {
329             return new DocumentReader(this.buffer);
330         }
331
332         /// <summary>
333         /// ロックを解除します
334         /// </summary>
335         public void UnLock()
336         {
337             this.Semaphore.Release();
338         }
339
340         /// <summary>
341         /// ロックします
342         /// </summary>
343         public void Lock()
344         {
345             this.Semaphore.Wait();
346         }
347
348         /// <summary>
349         /// ロックします
350         /// </summary>
351         /// <returns>Taskオブジェクト</returns>
352         public Task LockAsync()
353         {
354             return this.Semaphore.WaitAsync();
355         }
356
357         /// <summary>
358         /// マーカーを設定する
359         /// </summary>
360         /// <param name="id">マーカーID</param>
361         /// <param name="m">設定したいマーカー</param>
362         public void SetMarker(int id,Marker m)
363         {
364             if (m.start < 0 || m.start + m.length > this.Length)
365                 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
366
367             this.Markers.Add(id,m);
368         }
369
370         /// <summary>
371         /// マーカーを削除する
372         /// </summary>
373         /// <param name="id">マーカーID</param>
374         /// <param name="start">開始インデックス</param>
375         /// <param name="length">削除する長さ</param>
376         public void RemoveMarker(int id,int start, int length)
377         {
378             if (start < 0 || start + length > this.Length)
379                 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
380
381             this.Markers.RemoveAll(id,start, length);
382         }
383
384         /// <summary>
385         /// マーカーを削除する
386         /// </summary>
387         /// <param name="id">マーカーID</param>
388         /// <param name="type">削除したいマーカーのタイプ</param>
389         public void RemoveMarker(int id, HilightType type)
390         {
391             this.Markers.RemoveAll(id,type);
392         }
393
394         /// <summary>
395         /// インデックスに対応するマーカーを得る
396         /// </summary>
397         /// <param name="id">マーカーID</param>
398         /// <param name="index">インデックス</param>
399         /// <returns>Marker構造体の列挙子</returns>
400         public IEnumerable<Marker> GetMarkers(int id, int index)
401         {
402             if (index < 0 || index > this.Length)
403                 throw new ArgumentOutOfRangeException("indexが範囲を超えています");
404             return this.Markers.Get(id,index);
405         }
406
407         /// <summary>
408         /// 部分文字列を取得する
409         /// </summary>
410         /// <param name="index">開始インデックス</param>
411         /// <param name="length">長さ</param>
412         /// <returns>Stringオブジェクト</returns>
413         public string ToString(int index, int length)
414         {
415             return this.buffer.ToString(index, length);
416         }
417
418         /// <summary>
419         /// インデックスを開始位置とする文字列を返す
420         /// </summary>
421         /// <param name="index">開始インデックス</param>
422         /// <returns>Stringオブジェクト</returns>
423         public string ToString(int index)
424         {
425             return this.ToString(index, this.buffer.Length - index);
426         }
427
428         /// <summary>
429         /// 行を取得する
430         /// </summary>
431         /// <param name="startIndex">開始インデックス</param>
432         /// <param name="endIndex">終了インデックス</param>
433         /// <param name="maxCharCount">最大長</param>
434         /// <returns>行イテレーターが返される</returns>
435         public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
436         {
437             return this.buffer.GetLines(startIndex, endIndex, maxCharCount);
438         }
439
440         internal IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
441         {
442             return this.buffer.ForEachLines(startIndex, endIndex, maxCharCount);
443         }
444
445
446         /// <summary>
447         /// 文字列を追加する
448         /// </summary>
449         /// <param name="s">追加したい文字列</param>
450         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
451         public void Append(string s)
452         {
453             this.Replace(this.buffer.Length, 0, s);
454         }
455
456         /// <summary>
457         /// 文字列を挿入する
458         /// </summary>
459         /// <param name="index">開始インデックス</param>
460         /// <param name="s">追加したい文字列</param>
461         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
462         public void Insert(int index, string s)
463         {
464             this.Replace(index, 0, s);
465         }
466
467         /// <summary>
468         /// 文字列を削除する
469         /// </summary>
470         /// <param name="index">開始インデックス</param>
471         /// <param name="length">長さ</param>
472         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
473         public void Remove(int index, int length)
474         {
475             this.Replace(index, length, "");
476         }
477
478         /// <summary>
479         /// ドキュメントを置き換える
480         /// </summary>
481         /// <param name="index">開始インデックス</param>
482         /// <param name="length">長さ</param>
483         /// <param name="s">文字列</param>
484         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
485         public void Replace(int index, int length, string s)
486         {
487             if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)
488                 throw new ArgumentOutOfRangeException();
489             if (length == 0 && (s == string.Empty || s == null))
490                 return;
491
492             foreach(int id in this.Markers.IDs)
493                 this.RemoveMarker(id,index, length);
494
495             ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);
496             this.UndoManager.push(cmd);
497             cmd.redo();
498         }
499
500         /// <summary>
501         /// 物理行をすべて削除する
502         /// </summary>
503         /// <remarks>Dirtyフラグも同時にクリアーされます</remarks>
504         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
505         public void Clear()
506         {
507             this.buffer.Clear();
508         }
509
510         /// <summary>
511         /// ストリームからドキュメントを非同期的に構築します
512         /// </summary>
513         /// <param name="fs">IStreamReaderオブジェクト</param>
514         /// <param name="tokenSource">キャンセルトークン</param>
515         /// <returns>Taskオブジェクト</returns>
516         /// <remarks>
517         /// 読み取り操作は別スレッドで行われます。
518         /// また、非同期操作中はこのメソッドを実行することはできません。
519         /// </remarks>
520         internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)
521         {
522             if (fs.IsEnd())
523                 return;
524
525             try
526             {
527                 await this.LockAsync().ConfigureAwait(false);
528                 this.Clear();
529                 this.FireUpdateEvent = false;
530                 await this.buffer.LoadAsync(fs, tokenSource);
531             }
532             finally
533             {
534                 this.FireUpdateEvent = true;
535                 this.UnLock();
536             }
537         }
538
539         /// <summary>
540         /// ストリームに非同期モードで保存します
541         /// </summary>
542         /// <param name="fs">IStreamWriterオブジェクト</param>
543         /// <param name="tokenSource">キャンセルトークン</param>
544         /// <returns>Taskオブジェクト</returns>
545         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
546         internal async Task SaveAsync(IStreamWriter fs, CancellationTokenSource tokenSource = null)
547         {
548             try
549             {
550                 await this.LockAsync().ConfigureAwait(false);
551                 StringBuilder line = new StringBuilder();
552                 for (int i = 0; i < this.Length; i++)
553                 {
554                     char c = this[i];
555                     line.Append(c);
556                     if (c == Document.NewLine || i == this.Length - 1)
557                     {
558                         string str = line.ToString();
559                         str = str.Replace(Document.NewLine.ToString(), fs.NewLine);
560                         await fs.WriteAsync(str).ConfigureAwait(false);
561                         line.Clear();
562                         if (tokenSource != null)
563                             tokenSource.Token.ThrowIfCancellationRequested();
564 #if TEST_ASYNC
565                     System.Threading.Thread.Sleep(10);
566 #endif
567                     }
568                 }
569             }
570             finally
571             {
572                 this.UnLock();
573             }
574         }
575
576         /// <summary>
577         /// Find()およびReplaceAll()で使用するパラメーターをセットします
578         /// </summary>
579         /// <param name="pattern">検索したい文字列</param>
580         /// <param name="UseRegex">正規表現を使用するなら真</param>
581         /// <param name="opt">RegexOptions列挙体</param>
582         public void SetFindParam(string pattern, bool UseRegex, RegexOptions opt)
583         {
584             this.match = null;
585             if (UseRegex)
586                 this.regex = new Regex(pattern, opt);
587             else
588                 this.regex = new Regex(Regex.Escape(pattern), opt);
589         }
590
591         /// <summary>
592         /// 現在の検索パラメーターでWatchDogを生成する
593         /// </summary>
594         /// <param name="type">ハイライトタイプ</param>
595         /// <param name="color">色</param>
596         /// <returns>WatchDogオブジェクト</returns>
597         public RegexMarkerPattern CreateWatchDogByFindParam(HilightType type,Color color)
598         {
599             if (this.regex == null)
600                 throw new InvalidOperationException("SetFindParam()を呼び出してください");
601             return new RegexMarkerPattern(this.regex,type,color);
602         }
603
604         /// <summary>
605         /// 指定した文字列を検索します
606         /// </summary>
607         /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
608         /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
609         public IEnumerator<SearchResult> Find()
610         {
611             return this.Find(0, this.Length);
612         }
613
614         /// <summary>
615         /// 指定した文字列を検索します
616         /// </summary>
617         /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
618         /// <param name="start">開始インデックス</param>
619         /// <param name="length">検索する長さ</param>
620         /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
621         public IEnumerator<SearchResult> Find(int start, int length)
622         {
623             if (this.regex == null)
624                 throw new InvalidOperationException();
625             if (start < 0 || start >= this.Length)
626                 throw new ArgumentOutOfRangeException();
627
628             int end = start + length - 1;
629
630             if(end > this.Length - 1)
631                 throw new ArgumentOutOfRangeException();
632
633             StringBuilder line = new StringBuilder();
634             int oldLength = this.Length;
635             for (int i = start; i <= end; i++)
636             {
637                 char c = this[i];
638                 line.Append(c);
639                 if (c == Document.NewLine || i == end)
640                 {
641                     this.match = this.regex.Match(line.ToString());
642                     while (this.match.Success)
643                     {
644                         int startIndex = i - line.Length + 1 + this.match.Index;
645                         int endIndex = startIndex + this.match.Length - 1;
646
647                         yield return new SearchResult(this.match, startIndex, endIndex);
648
649                         if (this.Length != oldLength)   //長さが変わった場合は置き換え後のパターンの終点+1まで戻る
650                         {
651                             int delta = this.Length - oldLength;
652                             i = endIndex + delta;
653                             end = end + delta;
654                             oldLength = this.Length;
655                             break;
656                         }
657
658                         this.match = this.match.NextMatch();
659                     }
660                     line.Clear();
661                 }
662             }
663         }
664
665         /// <summary>
666         /// 任意のパターンですべて置き換えます
667         /// </summary>
668         /// <param name="replacePattern">置き換え後のパターン</param>
669         /// <param name="groupReplace">グループ置き換えを行うなら真。そうでないなら偽</param>
670         public void ReplaceAll(string replacePattern,bool groupReplace)
671         {
672             if (this.regex == null)
673                 throw new InvalidOperationException();
674             ReplaceAllCommand cmd = new ReplaceAllCommand(this.buffer, this.LayoutLines, this.regex, replacePattern, groupReplace);
675             this.UndoManager.push(cmd);
676             cmd.redo();
677         }
678
679         /// <summary>
680         /// 任意のパターンで置き換える
681         /// </summary>
682         /// <param name="target">対象となる文字列</param>
683         /// <param name="pattern">置き換え後の文字列</param>
684         /// <param name="ci">大文字も文字を区別しないなら真。そうでないなら偽</param>
685         /// <remarks>
686         /// 検索時に大文字小文字を区別します。また、このメソッドでは正規表現を使用することはできません
687         /// </remarks>
688         public void ReplaceAll2(string target, string pattern,bool ci = false)
689         {
690             FastReplaceAllCommand cmd = new FastReplaceAllCommand(this.buffer, this.LayoutLines, target, pattern,ci);
691             this.UndoManager.push(cmd);
692             cmd.redo();
693         }
694
695         #region IEnumerable<char> メンバー
696
697         /// <summary>
698         /// 列挙子を返します
699         /// </summary>
700         /// <returns>IEnumeratorオブジェクトを返す</returns>
701         public IEnumerator<char> GetEnumerator()
702         {
703             return this.buffer.GetEnumerator();
704         }
705
706         #endregion
707
708         #region IEnumerable メンバー
709
710         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
711         {
712             throw new NotImplementedException();
713         }
714
715         #endregion
716
717         void buffer_Update(object sender, DocumentUpdateEventArgs e)
718         {
719             switch (e.type)
720             {
721                 case UpdateType.Replace:
722                     if (e.row == null)
723                         this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);
724                     else
725                         this._LayoutLines.UpdateLineAsReplace(e.row.Value, e.removeLength, e.insertLength);
726                     break;
727                 case UpdateType.Clear:
728                     this._LayoutLines.Clear();
729                     break;
730             }
731             this.UpdateCalledAlways(this, e);
732             if(this.FireUpdateEvent)
733                 this.Update(this, e);
734         }
735     }
736
737     public interface IStreamReader
738     {
739         /// <summary>
740         /// ストリームが空かどうかを返す
741         /// </summary>
742         bool IsEnd();
743
744         /// <summary>
745         /// ストリームから行を読み取った物を返す。LoadAsyncを呼び出す場合は必ず実装してください
746         /// </summary>
747         Task<string> ReadLineAsync();
748         /// <summary>
749         /// ストリームから指定した文字数だけ読み取る
750         /// </summary>
751         /// <param name="buffer">書き込み先バッファー</param>
752         /// <param name="index">書き込み先バッファーのインデックス</param>
753         /// <param name="count">読み取る文字数</param>
754         /// <returns>読み取った文字数</returns>
755         Task<int> ReadAsync(char[] buffer, int index, int count);
756     }
757
758     public interface IStreamWriter
759     {
760         /// <summary>
761         /// ストリームに書き込む。SaveAsyncを呼び出す場合は必ず実装してください
762         /// </summary>
763         Task WriteAsync(string str);
764
765         /// <summary>
766         /// 書き込む際に使用する改行コード
767         /// </summary>
768         string NewLine
769         {
770             get;
771             set;
772         }
773     }
774
775     /// <summary>
776     /// 検索結果を表す
777     /// </summary>
778     public class SearchResult
779     {
780         private Match Match;
781
782         /// <summary>
783         /// 一致した場所の開始位置を表す
784         /// </summary>
785         public int Start;
786
787         /// <summary>
788         /// 一致した場所の終了位置を表す
789         /// </summary>
790         public int End;
791
792         /// <summary>
793         /// 見つかった文字列を返す
794         /// </summary>
795         public string Value
796         {
797             get { return this.Match.Value; }
798         }
799
800         /// <summary>
801         /// 指定したパターンを置き換えて返す
802         /// </summary>
803         /// <param name="replacement">置き換える文字列</param>
804         /// <returns>置き換え後の文字列</returns>
805         public string Result(string replacement)
806         {
807             return this.Match.Result(replacement);
808         }
809
810         /// <summary>
811         /// コンストラクター
812         /// </summary>
813         /// <param name="m">Matchオブジェクト</param>
814         /// <param name="start">開始インデックス</param>
815         /// <param name="end">終了インデックス</param>
816         public SearchResult(Match m, int start,int end)
817         {
818             this.Match = m;
819             this.Start = start;
820             this.End = end;
821         }
822     }
823
824     /// <summary>
825     /// ドキュメントリーダー
826     /// </summary>
827     public class DocumentReader : TextReader
828     {
829         StringBuffer document;      
830         int currentIndex;
831
832         /// <summary>
833         /// コンストラクター
834         /// </summary>
835         /// <param name="doc"></param>
836         internal DocumentReader(StringBuffer doc)
837         {
838             if (doc == null)
839                 throw new ArgumentNullException();
840             this.document = doc;
841         }
842
843         /// <summary>
844         /// 文字を取得する
845         /// </summary>
846         /// <returns>文字。取得できない場合は-1</returns>
847         public override int Peek()
848         {
849             if (this.document == null)
850                 throw new InvalidOperationException();
851             if (this.currentIndex >= this.document.Length)
852                 return -1;
853             return this.document[this.currentIndex];
854         }
855
856         /// <summary>
857         /// 文字を取得し、イテレーターを一つ進める
858         /// </summary>
859         /// <returns>文字。取得できない場合は-1</returns>
860         public override int Read()
861         {
862             int c = this.Peek();
863             if(c != -1)
864                 this.currentIndex++;
865             return c;
866         }
867
868         /// <summary>
869         /// 文字列を読み取りバッファーに書き込む
870         /// </summary>
871         /// <param name="buffer">バッファー</param>
872         /// <param name="index">開始インデックス</param>
873         /// <param name="count">カウント</param>
874         /// <returns>読み取られた文字数</returns>
875         public override int Read(char[] buffer, int index, int count)
876         {
877             if (this.document == null)
878                 throw new InvalidOperationException();
879
880             if (buffer == null)
881                 throw new ArgumentNullException();
882
883             if (this.document.Length < count)
884                 throw new ArgumentException();
885
886             if (index < 0 || count < 0)
887                 throw new ArgumentOutOfRangeException();
888
889             if (this.document.Length == 0)
890                 return 0;
891
892             int actualCount = count;
893             if (index + count - 1 > this.document.Length - 1)
894                 actualCount = this.document.Length - index;
895
896             string str = this.document.ToString(index, actualCount);
897
898             for (int i = 0; i < str.Length; i++)    //ToCharArray()だと戻った時に消えてしまう
899                 buffer[i] = str[i];
900
901             this.currentIndex = index + actualCount;
902             
903             return actualCount;
904         }
905
906         /// <summary>
907         /// オブジェクトを破棄する
908         /// </summary>
909         /// <param name="disposing">真ならアンマネージドリソースを解放する</param>
910         protected override void Dispose(bool disposing)
911         {
912             this.document = null;
913         }
914     }
915 }