OSDN Git Service

MVVM対応の準備のためにLineBreak,TabStops,DrawLineNumber,LineBreakCharCount,MarkerPatternSetをDo...
[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;
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         }
182
183         void WacthDogPattern_Updated(object sender, EventArgs e)
184         {
185             this._LayoutLines.ClearLayoutCache();
186         }
187
188         /// <summary>
189         /// DrawLineNumberの値が変わったときに通知される
190         /// </summary>
191         public event EventHandler DrawLineNumberChanged;
192
193         /// <summary>
194         /// 行番号を表示するかどうか
195         /// </summary>
196         public bool DrawLineNumber
197         {
198             get { return this._DrawLineNumber; }
199             set
200             {
201                 this._DrawLineNumber = value;
202                 this._LayoutLines.ClearLayoutCache();
203                 this.DrawLineNumberChanged(this, null);
204             }
205         }
206
207         /// <summary>
208         /// URLをハイパーリンクとして表示するなら真。そうでないなら偽
209         /// </summary>
210         public bool UrlMark
211         {
212             get { return this._UrlMark; }
213             set
214             {
215                 this._UrlMark = value;
216                 if (value)
217                 {
218                     Regex regex = new Regex("(http|https|ftp)(:\\/\\/[-_.!~*\\'()a-zA-Z0-9;\\/?:\\@&=+\\$,%#]+)");
219                     this.MarkerPatternSet.Add(MarkerIDs.URL, new RegexMarkerPattern(regex, HilightType.Url, new Color()));
220                 }
221                 else
222                 {
223                     this.MarkerPatternSet.Remove(MarkerIDs.URL);
224                 }
225             }
226         }
227
228         /// <summary>
229         /// LineBreakが変更されたときに通知される
230         /// </summary>
231         public EventHandler LineBreakChanged;
232
233         /// <summary>
234         /// 桁折り処理の方法を指定する
235         /// </summary>
236         /// <remarks>
237         /// 変更した場合、呼び出し側で再描写とレイアウトの再構築を行う必要があります
238         /// </remarks>
239         public LineBreakMethod LineBreak
240         {
241             get
242             {
243                 return this._LineBreak;
244             }
245             set
246             {
247                 this._LineBreak = value;
248                 this.LineBreakChanged(this, null);
249             }
250         }
251
252         /// <summary>
253         /// 折り返し行う文字数。実際に折り返しが行われる幅はem単位×この値となります
254         /// </summary>
255         /// <remarks>この値を変えた場合、LineBreakChangedイベントが発生します</remarks>
256         public int LineBreakCharCount
257         {
258             get
259             {
260                 return this._LineBreakCharCount;
261             }
262             set
263             {
264                 this._LineBreakCharCount = value;
265                 this.LineBreakChanged(this, null);
266             }
267         }
268
269         /// <summary>
270         /// TabStopsの値が変わったことを通知する
271         /// </summary>
272         public event EventHandler TabStopsChanged;
273
274         /// <summary>
275         /// タブの幅
276         /// </summary>
277         /// <remarks>変更した場合、呼び出し側で再描写する必要があります</remarks>
278         public int TabStops
279         {
280             get { return this._TabStops; }
281             set {
282                 this._TabStops = value;
283                 this.TabStopsChanged(this, null);
284             }
285         }
286
287         /// <summary>
288         /// マーカーパターンセット
289         /// </summary>
290         public MarkerPatternSet MarkerPatternSet
291         {
292             get;
293             private set;
294         }
295
296         /// <summary>
297         /// レイアウト行を表す
298         /// </summary>
299         public LineToIndexTable LayoutLines
300         {
301             get
302             {
303                 return this._LayoutLines;
304             }
305         }
306
307         /// <summary>
308         /// レイアウト行を返す
309         /// </summary>
310         /// <param name="index">開始インデックス</param>
311         /// <param name="length">長さ</param>
312         /// <param name="lineLimitLength">1行当たりの最大文字数。-1で無制限</param>
313         /// <returns>レイアウト行リスト</returns>
314         internal IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)
315         {
316             int startIndex = index;
317             int endIndex = index + length - 1;
318             List<LineToIndexTableData> output = new List<LineToIndexTableData>();
319
320             foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))
321             {
322                 int lineHeadIndex = range.Item1;
323                 int lineLength = range.Item2;
324                 char c = this.buffer[lineHeadIndex + lineLength - 1];
325                 bool hasNewLine = c == Document.NewLine;
326                 output.Add(this.LayoutLines.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));
327             }
328
329             if (output.Count > 0)
330                 output.Last().LineEnd = true;
331
332             return output;
333         }
334
335         internal void FireUpdate(DocumentUpdateEventArgs e)
336         {
337             this.buffer_Update(this.buffer, e);
338         }
339
340         /// <summary>
341         /// ドキュメントが更新された時に呼ばれるイベント
342         /// </summary>
343         public event DocumentUpdateEventHandler Update;
344
345         /// <summary>
346         /// ドキュメントが更新された時に呼びされるイベント
347         /// </summary>
348         /// <remarks>
349         /// FireUpdateEventの値に関わらず常に呼びされます
350         /// </remarks>
351         internal event DocumentUpdateEventHandler UpdateCalledAlways;
352
353         /// <summary>
354         /// FireUpdateEventの値が変わったときに呼び出されるイベント
355         /// </summary>
356         public event EventHandler ChangeFireUpdateEvent;
357
358         /// <summary>
359         /// 改行コードの内部表現
360         /// </summary>
361         public const char NewLine = '\n';
362
363         /// <summary>
364         /// EOFの内部表現
365         /// </summary>
366         public const char EndOfFile = '\u001a';
367
368         /// <summary>
369         /// ロック中なら真を返し、そうでないなら偽を返す
370         /// </summary>
371         public bool IsLocked
372         {
373             get
374             {
375                 return this.Semaphore.CurrentCount == 0;
376             }
377         }
378
379         /// <summary>
380         /// アンドゥ管理クラスを表す
381         /// </summary>
382         public UndoManager UndoManager
383         {
384             get;
385             private set;
386         }
387
388         /// <summary>
389         /// 文字列の長さ
390         /// </summary>
391         public int Length
392         {
393             get
394             {
395                 return this.buffer.Length;
396             }
397         }
398
399         /// <summary>
400         /// 変更のたびにUpdateイベントを発生させるかどうか
401         /// </summary>
402         public bool FireUpdateEvent
403         {
404             get
405             {
406                 return this._EnableFireUpdateEvent;
407             }
408             set
409             {
410                 this._EnableFireUpdateEvent = value;
411                 this.ChangeFireUpdateEvent(this, null);
412             }
413         }
414
415         /// <summary>
416         /// インデクサー
417         /// </summary>
418         /// <param name="i">インデックス(自然数でなければならない)</param>
419         /// <returns>Char型</returns>
420         public char this[int i]
421         {
422             get
423             {
424                 return this.buffer[i];
425             }
426         }
427
428         /// <summary>
429         /// マーカーコレクション
430         /// </summary>
431         public MarkerCollection Markers
432         {
433             get;
434             private set;
435         }
436
437         internal StringBuffer StringBuffer
438         {
439             get
440             {
441                 return this.buffer;
442             }
443         }
444
445         /// <summary>
446         /// DocumentReaderを作成します
447         /// </summary>
448         /// <returns>DocumentReaderオブジェクト</returns>
449         public DocumentReader CreateReader()
450         {
451             return new DocumentReader(this.buffer);
452         }
453
454         /// <summary>
455         /// ロックを解除します
456         /// </summary>
457         public void UnLock()
458         {
459             this.Semaphore.Release();
460         }
461
462         /// <summary>
463         /// ロックします
464         /// </summary>
465         public void Lock()
466         {
467             this.Semaphore.Wait();
468         }
469
470         /// <summary>
471         /// ロックします
472         /// </summary>
473         /// <returns>Taskオブジェクト</returns>
474         public Task LockAsync()
475         {
476             return this.Semaphore.WaitAsync();
477         }
478
479         /// <summary>
480         /// マーカーを設定する
481         /// </summary>
482         /// <param name="id">マーカーID</param>
483         /// <param name="m">設定したいマーカー</param>
484         public void SetMarker(int id,Marker m)
485         {
486             if (m.start < 0 || m.start + m.length > this.Length)
487                 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
488
489             this.Markers.Add(id,m);
490         }
491
492         /// <summary>
493         /// マーカーを削除する
494         /// </summary>
495         /// <param name="id">マーカーID</param>
496         /// <param name="start">開始インデックス</param>
497         /// <param name="length">削除する長さ</param>
498         public void RemoveMarker(int id,int start, int length)
499         {
500             if (start < 0 || start + length > this.Length)
501                 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
502
503             this.Markers.RemoveAll(id,start, length);
504         }
505
506         /// <summary>
507         /// マーカーを削除する
508         /// </summary>
509         /// <param name="id">マーカーID</param>
510         /// <param name="type">削除したいマーカーのタイプ</param>
511         public void RemoveMarker(int id, HilightType type)
512         {
513             this.Markers.RemoveAll(id,type);
514         }
515
516         /// <summary>
517         /// インデックスに対応するマーカーを得る
518         /// </summary>
519         /// <param name="id">マーカーID</param>
520         /// <param name="index">インデックス</param>
521         /// <returns>Marker構造体の列挙子</returns>
522         public IEnumerable<Marker> GetMarkers(int id, int index)
523         {
524             if (index < 0 || index > this.Length)
525                 throw new ArgumentOutOfRangeException("indexが範囲を超えています");
526             return this.Markers.Get(id,index);
527         }
528
529         /// <summary>
530         /// 部分文字列を取得する
531         /// </summary>
532         /// <param name="index">開始インデックス</param>
533         /// <param name="length">長さ</param>
534         /// <returns>Stringオブジェクト</returns>
535         public string ToString(int index, int length)
536         {
537             return this.buffer.ToString(index, length);
538         }
539
540         /// <summary>
541         /// インデックスを開始位置とする文字列を返す
542         /// </summary>
543         /// <param name="index">開始インデックス</param>
544         /// <returns>Stringオブジェクト</returns>
545         public string ToString(int index)
546         {
547             return this.ToString(index, this.buffer.Length - index);
548         }
549
550         /// <summary>
551         /// 行を取得する
552         /// </summary>
553         /// <param name="startIndex">開始インデックス</param>
554         /// <param name="endIndex">終了インデックス</param>
555         /// <param name="maxCharCount">最大長</param>
556         /// <returns>行イテレーターが返される</returns>
557         public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
558         {
559             return this.buffer.GetLines(startIndex, endIndex, maxCharCount);
560         }
561
562         internal IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
563         {
564             return this.buffer.ForEachLines(startIndex, endIndex, maxCharCount);
565         }
566
567
568         /// <summary>
569         /// 文字列を追加する
570         /// </summary>
571         /// <param name="s">追加したい文字列</param>
572         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
573         public void Append(string s)
574         {
575             this.Replace(this.buffer.Length, 0, s);
576         }
577
578         /// <summary>
579         /// 文字列を挿入する
580         /// </summary>
581         /// <param name="index">開始インデックス</param>
582         /// <param name="s">追加したい文字列</param>
583         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
584         public void Insert(int index, string s)
585         {
586             this.Replace(index, 0, s);
587         }
588
589         /// <summary>
590         /// 文字列を削除する
591         /// </summary>
592         /// <param name="index">開始インデックス</param>
593         /// <param name="length">長さ</param>
594         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
595         public void Remove(int index, int length)
596         {
597             this.Replace(index, length, "");
598         }
599
600         /// <summary>
601         /// ドキュメントを置き換える
602         /// </summary>
603         /// <param name="index">開始インデックス</param>
604         /// <param name="length">長さ</param>
605         /// <param name="s">文字列</param>
606         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
607         public void Replace(int index, int length, string s)
608         {
609             if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)
610                 throw new ArgumentOutOfRangeException();
611             if (length == 0 && (s == string.Empty || s == null))
612                 return;
613
614             foreach(int id in this.Markers.IDs)
615                 this.RemoveMarker(id,index, length);
616
617             ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);
618             this.UndoManager.push(cmd);
619             cmd.redo();
620         }
621
622         /// <summary>
623         /// 物理行をすべて削除する
624         /// </summary>
625         /// <remarks>Dirtyフラグも同時にクリアーされます</remarks>
626         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
627         public void Clear()
628         {
629             this.buffer.Clear();
630         }
631
632         /// <summary>
633         /// ストリームからドキュメントを非同期的に構築します
634         /// </summary>
635         /// <param name="fs">IStreamReaderオブジェクト</param>
636         /// <param name="tokenSource">キャンセルトークン</param>
637         /// <returns>Taskオブジェクト</returns>
638         /// <remarks>
639         /// 読み取り操作は別スレッドで行われます。
640         /// また、非同期操作中はこのメソッドを実行することはできません。
641         /// </remarks>
642         internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)
643         {
644             if (fs.IsEnd())
645                 return;
646
647             try
648             {
649                 await this.LockAsync().ConfigureAwait(false);
650                 this.Clear();
651                 this.FireUpdateEvent = false;
652                 await this.buffer.LoadAsync(fs, tokenSource);
653             }
654             finally
655             {
656                 this.FireUpdateEvent = true;
657                 this.UnLock();
658             }
659         }
660
661         /// <summary>
662         /// ストリームに非同期モードで保存します
663         /// </summary>
664         /// <param name="fs">IStreamWriterオブジェクト</param>
665         /// <param name="tokenSource">キャンセルトークン</param>
666         /// <returns>Taskオブジェクト</returns>
667         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
668         internal async Task SaveAsync(IStreamWriter fs, CancellationTokenSource tokenSource = null)
669         {
670             try
671             {
672                 await this.LockAsync().ConfigureAwait(false);
673                 StringBuilder line = new StringBuilder();
674                 for (int i = 0; i < this.Length; i++)
675                 {
676                     char c = this[i];
677                     line.Append(c);
678                     if (c == Document.NewLine || i == this.Length - 1)
679                     {
680                         string str = line.ToString();
681                         str = str.Replace(Document.NewLine.ToString(), fs.NewLine);
682                         await fs.WriteAsync(str).ConfigureAwait(false);
683                         line.Clear();
684                         if (tokenSource != null)
685                             tokenSource.Token.ThrowIfCancellationRequested();
686 #if TEST_ASYNC
687                     System.Threading.Thread.Sleep(10);
688 #endif
689                     }
690                 }
691             }
692             finally
693             {
694                 this.UnLock();
695             }
696         }
697
698         /// <summary>
699         /// Find()およびReplaceAll()で使用するパラメーターをセットします
700         /// </summary>
701         /// <param name="pattern">検索したい文字列</param>
702         /// <param name="UseRegex">正規表現を使用するなら真</param>
703         /// <param name="opt">RegexOptions列挙体</param>
704         public void SetFindParam(string pattern, bool UseRegex, RegexOptions opt)
705         {
706             this.match = null;
707             if (UseRegex)
708                 this.regex = new Regex(pattern, opt);
709             else
710                 this.regex = new Regex(Regex.Escape(pattern), opt);
711         }
712
713         /// <summary>
714         /// 現在の検索パラメーターでWatchDogを生成する
715         /// </summary>
716         /// <param name="type">ハイライトタイプ</param>
717         /// <param name="color">色</param>
718         /// <returns>WatchDogオブジェクト</returns>
719         public RegexMarkerPattern CreateWatchDogByFindParam(HilightType type,Color color)
720         {
721             if (this.regex == null)
722                 throw new InvalidOperationException("SetFindParam()を呼び出してください");
723             return new RegexMarkerPattern(this.regex,type,color);
724         }
725
726         /// <summary>
727         /// 指定した文字列を検索します
728         /// </summary>
729         /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
730         /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
731         public IEnumerator<SearchResult> Find()
732         {
733             return this.Find(0, this.Length);
734         }
735
736         /// <summary>
737         /// 指定した文字列を検索します
738         /// </summary>
739         /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
740         /// <param name="start">開始インデックス</param>
741         /// <param name="length">検索する長さ</param>
742         /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
743         public IEnumerator<SearchResult> Find(int start, int length)
744         {
745             if (this.regex == null)
746                 throw new InvalidOperationException();
747             if (start < 0 || start >= this.Length)
748                 throw new ArgumentOutOfRangeException();
749
750             int end = start + length - 1;
751
752             if(end > this.Length - 1)
753                 throw new ArgumentOutOfRangeException();
754
755             StringBuilder line = new StringBuilder();
756             int oldLength = this.Length;
757             for (int i = start; i <= end; i++)
758             {
759                 char c = this[i];
760                 line.Append(c);
761                 if (c == Document.NewLine || i == end)
762                 {
763                     this.match = this.regex.Match(line.ToString());
764                     while (this.match.Success)
765                     {
766                         int startIndex = i - line.Length + 1 + this.match.Index;
767                         int endIndex = startIndex + this.match.Length - 1;
768
769                         yield return new SearchResult(this.match, startIndex, endIndex);
770
771                         if (this.Length != oldLength)   //長さが変わった場合は置き換え後のパターンの終点+1まで戻る
772                         {
773                             int delta = this.Length - oldLength;
774                             i = endIndex + delta;
775                             end = end + delta;
776                             oldLength = this.Length;
777                             break;
778                         }
779
780                         this.match = this.match.NextMatch();
781                     }
782                     line.Clear();
783                 }
784             }
785         }
786
787         /// <summary>
788         /// 任意のパターンですべて置き換えます
789         /// </summary>
790         /// <param name="replacePattern">置き換え後のパターン</param>
791         /// <param name="groupReplace">グループ置き換えを行うなら真。そうでないなら偽</param>
792         public void ReplaceAll(string replacePattern,bool groupReplace)
793         {
794             if (this.regex == null)
795                 throw new InvalidOperationException();
796             ReplaceAllCommand cmd = new ReplaceAllCommand(this.buffer, this.LayoutLines, this.regex, replacePattern, groupReplace);
797             this.UndoManager.push(cmd);
798             cmd.redo();
799         }
800
801         /// <summary>
802         /// 任意のパターンで置き換える
803         /// </summary>
804         /// <param name="target">対象となる文字列</param>
805         /// <param name="pattern">置き換え後の文字列</param>
806         /// <param name="ci">大文字も文字を区別しないなら真。そうでないなら偽</param>
807         /// <remarks>
808         /// 検索時に大文字小文字を区別します。また、このメソッドでは正規表現を使用することはできません
809         /// </remarks>
810         public void ReplaceAll2(string target, string pattern,bool ci = false)
811         {
812             FastReplaceAllCommand cmd = new FastReplaceAllCommand(this.buffer, this.LayoutLines, target, pattern,ci);
813             this.UndoManager.push(cmd);
814             cmd.redo();
815         }
816
817         #region IEnumerable<char> メンバー
818
819         /// <summary>
820         /// 列挙子を返します
821         /// </summary>
822         /// <returns>IEnumeratorオブジェクトを返す</returns>
823         public IEnumerator<char> GetEnumerator()
824         {
825             return this.buffer.GetEnumerator();
826         }
827
828         #endregion
829
830         #region IEnumerable メンバー
831
832         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
833         {
834             throw new NotImplementedException();
835         }
836
837         #endregion
838
839         void buffer_Update(object sender, DocumentUpdateEventArgs e)
840         {
841             switch (e.type)
842             {
843                 case UpdateType.Replace:
844                     if (e.row == null)
845                         this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);
846                     else
847                         this._LayoutLines.UpdateLineAsReplace(e.row.Value, e.removeLength, e.insertLength);
848                     break;
849                 case UpdateType.Clear:
850                     this._LayoutLines.Clear();
851                     break;
852             }
853             this.UpdateCalledAlways(this, e);
854             if(this.FireUpdateEvent)
855                 this.Update(this, e);
856         }
857     }
858
859     public interface IStreamReader
860     {
861         /// <summary>
862         /// ストリームが空かどうかを返す
863         /// </summary>
864         bool IsEnd();
865
866         /// <summary>
867         /// ストリームから行を読み取った物を返す。LoadAsyncを呼び出す場合は必ず実装してください
868         /// </summary>
869         Task<string> ReadLineAsync();
870         /// <summary>
871         /// ストリームから指定した文字数だけ読み取る
872         /// </summary>
873         /// <param name="buffer">書き込み先バッファー</param>
874         /// <param name="index">書き込み先バッファーのインデックス</param>
875         /// <param name="count">読み取る文字数</param>
876         /// <returns>読み取った文字数</returns>
877         Task<int> ReadAsync(char[] buffer, int index, int count);
878     }
879
880     public interface IStreamWriter
881     {
882         /// <summary>
883         /// ストリームに書き込む。SaveAsyncを呼び出す場合は必ず実装してください
884         /// </summary>
885         Task WriteAsync(string str);
886
887         /// <summary>
888         /// 書き込む際に使用する改行コード
889         /// </summary>
890         string NewLine
891         {
892             get;
893             set;
894         }
895     }
896
897     /// <summary>
898     /// 検索結果を表す
899     /// </summary>
900     public class SearchResult
901     {
902         private Match Match;
903
904         /// <summary>
905         /// 一致した場所の開始位置を表す
906         /// </summary>
907         public int Start;
908
909         /// <summary>
910         /// 一致した場所の終了位置を表す
911         /// </summary>
912         public int End;
913
914         /// <summary>
915         /// 見つかった文字列を返す
916         /// </summary>
917         public string Value
918         {
919             get { return this.Match.Value; }
920         }
921
922         /// <summary>
923         /// 指定したパターンを置き換えて返す
924         /// </summary>
925         /// <param name="replacement">置き換える文字列</param>
926         /// <returns>置き換え後の文字列</returns>
927         public string Result(string replacement)
928         {
929             return this.Match.Result(replacement);
930         }
931
932         /// <summary>
933         /// コンストラクター
934         /// </summary>
935         /// <param name="m">Matchオブジェクト</param>
936         /// <param name="start">開始インデックス</param>
937         /// <param name="end">終了インデックス</param>
938         public SearchResult(Match m, int start,int end)
939         {
940             this.Match = m;
941             this.Start = start;
942             this.End = end;
943         }
944     }
945
946     /// <summary>
947     /// ドキュメントリーダー
948     /// </summary>
949     public class DocumentReader : TextReader
950     {
951         StringBuffer document;      
952         int currentIndex;
953
954         /// <summary>
955         /// コンストラクター
956         /// </summary>
957         /// <param name="doc"></param>
958         internal DocumentReader(StringBuffer doc)
959         {
960             if (doc == null)
961                 throw new ArgumentNullException();
962             this.document = doc;
963         }
964
965         /// <summary>
966         /// 文字を取得する
967         /// </summary>
968         /// <returns>文字。取得できない場合は-1</returns>
969         public override int Peek()
970         {
971             if (this.document == null)
972                 throw new InvalidOperationException();
973             if (this.currentIndex >= this.document.Length)
974                 return -1;
975             return this.document[this.currentIndex];
976         }
977
978         /// <summary>
979         /// 文字を取得し、イテレーターを一つ進める
980         /// </summary>
981         /// <returns>文字。取得できない場合は-1</returns>
982         public override int Read()
983         {
984             int c = this.Peek();
985             if(c != -1)
986                 this.currentIndex++;
987             return c;
988         }
989
990         /// <summary>
991         /// 文字列を読み取りバッファーに書き込む
992         /// </summary>
993         /// <param name="buffer">バッファー</param>
994         /// <param name="index">開始インデックス</param>
995         /// <param name="count">カウント</param>
996         /// <returns>読み取られた文字数</returns>
997         public override int Read(char[] buffer, int index, int count)
998         {
999             if (this.document == null)
1000                 throw new InvalidOperationException();
1001
1002             if (buffer == null)
1003                 throw new ArgumentNullException();
1004
1005             if (this.document.Length < count)
1006                 throw new ArgumentException();
1007
1008             if (index < 0 || count < 0)
1009                 throw new ArgumentOutOfRangeException();
1010
1011             if (this.document.Length == 0)
1012                 return 0;
1013
1014             int actualCount = count;
1015             if (index + count - 1 > this.document.Length - 1)
1016                 actualCount = this.document.Length - index;
1017
1018             string str = this.document.ToString(index, actualCount);
1019
1020             for (int i = 0; i < str.Length; i++)    //ToCharArray()だと戻った時に消えてしまう
1021                 buffer[i] = str[i];
1022
1023             this.currentIndex = index + actualCount;
1024             
1025             return actualCount;
1026         }
1027
1028         /// <summary>
1029         /// オブジェクトを破棄する
1030         /// </summary>
1031         /// <param name="disposing">真ならアンマネージドリソースを解放する</param>
1032         protected override void Dispose(bool disposing)
1033         {
1034             this.document = null;
1035         }
1036     }
1037 }