OSDN Git Service

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