OSDN Git Service

高速化のため読込部分をStringBufferに移動した
[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()\r
142             : this(null)\r
143         {\r
144         }\r
145 \r
146         internal Document(Document doc)\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);\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                 await this.buffer.LoadAsync(fs, tokenSource);\r
525             }\r
526             finally\r
527             {\r
528                 this.FireUpdateEvent = true;\r
529                 this.UnLock();\r
530             }\r
531         }\r
532 \r
533         /// <summary>\r
534         /// ストリームに非同期モードで保存します\r
535         /// </summary>\r
536         /// <param name="fs">IStreamWriterオブジェクト</param>\r
537         /// <param name="tokenSource">キャンセルトークン</param>\r
538         /// <returns>Taskオブジェクト</returns>\r
539         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>\r
540         internal async Task SaveAsync(IStreamWriter fs, CancellationTokenSource tokenSource = null)\r
541         {\r
542             try\r
543             {\r
544                 await this.LockAsync().ConfigureAwait(false);\r
545                 StringBuilder line = new StringBuilder();\r
546                 for (int i = 0; i < this.Length; i++)\r
547                 {\r
548                     char c = this[i];\r
549                     line.Append(c);\r
550                     if (c == Document.NewLine || i == this.Length - 1)\r
551                     {\r
552                         string str = line.ToString();\r
553                         str = str.Replace(Document.NewLine.ToString(), fs.NewLine);\r
554                         await fs.WriteAsync(str).ConfigureAwait(false);\r
555                         line.Clear();\r
556                         if (tokenSource != null)\r
557                             tokenSource.Token.ThrowIfCancellationRequested();\r
558 #if TEST_ASYNC\r
559                     System.Threading.Thread.Sleep(10);\r
560 #endif\r
561                     }\r
562                 }\r
563             }\r
564             finally\r
565             {\r
566                 this.UnLock();\r
567             }\r
568         }\r
569 \r
570         /// <summary>\r
571         /// Find()およびReplaceAll()で使用するパラメーターをセットします\r
572         /// </summary>\r
573         /// <param name="pattern">検索したい文字列</param>\r
574         /// <param name="UseRegex">正規表現を使用するなら真</param>\r
575         /// <param name="opt">RegexOptions列挙体</param>\r
576         public void SetFindParam(string pattern, bool UseRegex, RegexOptions opt)\r
577         {\r
578             this.match = null;\r
579             if (UseRegex)\r
580                 this.regex = new Regex(pattern, opt);\r
581             else\r
582                 this.regex = new Regex(Regex.Escape(pattern), opt);\r
583         }\r
584 \r
585         /// <summary>\r
586         /// 現在の検索パラメーターでWatchDogを生成する\r
587         /// </summary>\r
588         /// <param name="type">ハイライトタイプ</param>\r
589         /// <param name="color">色</param>\r
590         /// <returns>WatchDogオブジェクト</returns>\r
591         public RegexMarkerPattern CreateWatchDogByFindParam(HilightType type,Color color)\r
592         {\r
593             if (this.regex == null)\r
594                 throw new InvalidOperationException("SetFindParam()を呼び出してください");\r
595             return new RegexMarkerPattern(this.regex,type,color);\r
596         }\r
597 \r
598         /// <summary>\r
599         /// 指定した文字列を検索します\r
600         /// </summary>\r
601         /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>\r
602         /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>\r
603         public IEnumerator<SearchResult> Find()\r
604         {\r
605             return this.Find(0, this.Length);\r
606         }\r
607 \r
608         /// <summary>\r
609         /// 指定した文字列を検索します\r
610         /// </summary>\r
611         /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>\r
612         /// <param name="start">開始インデックス</param>\r
613         /// <param name="length">検索する長さ</param>\r
614         /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>\r
615         public IEnumerator<SearchResult> Find(int start, int length)\r
616         {\r
617             if (this.regex == null)\r
618                 throw new InvalidOperationException();\r
619             if (start < 0 || start >= this.Length)\r
620                 throw new ArgumentOutOfRangeException();\r
621 \r
622             int end = start + length - 1;\r
623 \r
624             if(end > this.Length - 1)\r
625                 throw new ArgumentOutOfRangeException();\r
626 \r
627             StringBuilder line = new StringBuilder();\r
628             int oldLength = this.Length;\r
629             for (int i = start; i <= end; i++)\r
630             {\r
631                 char c = this[i];\r
632                 line.Append(c);\r
633                 if (c == Document.NewLine || i == end)\r
634                 {\r
635                     this.match = this.regex.Match(line.ToString());\r
636                     while (this.match.Success)\r
637                     {\r
638                         int startIndex = i - line.Length + 1 + this.match.Index;\r
639                         int endIndex = startIndex + this.match.Length - 1;\r
640 \r
641                         yield return new SearchResult(this.match, startIndex, endIndex);\r
642 \r
643                         if (this.Length != oldLength)   //長さが変わった場合は置き換え後のパターンの終点+1まで戻る\r
644                         {\r
645                             int delta = this.Length - oldLength;\r
646                             i = endIndex + delta;\r
647                             end = end + delta;\r
648                             oldLength = this.Length;\r
649                             break;\r
650                         }\r
651 \r
652                         this.match = this.match.NextMatch();\r
653                     }\r
654                     line.Clear();\r
655                 }\r
656             }\r
657         }\r
658 \r
659         /// <summary>\r
660         /// 任意のパターンですべて置き換えます\r
661         /// </summary>\r
662         /// <param name="replacePattern">置き換え後のパターン</param>\r
663         /// <param name="groupReplace">グループ置き換えを行うなら真。そうでないなら偽</param>\r
664         public void ReplaceAll(string replacePattern,bool groupReplace)\r
665         {\r
666             if (this.regex == null)\r
667                 throw new InvalidOperationException();\r
668             ReplaceAllCommand cmd = new ReplaceAllCommand(this.buffer, this.regex, replacePattern, groupReplace);\r
669             this.UndoManager.push(cmd);\r
670             cmd.redo();\r
671         }\r
672 \r
673         /// <summary>\r
674         /// 任意のパターンで置き換える\r
675         /// </summary>\r
676         /// <param name="target">対象となる文字列</param>\r
677         /// <param name="pattern">置き換え後の文字列</param>\r
678         /// <param name="ci">大文字も文字を区別しないなら真。そうでないなら偽</param>\r
679         /// <remarks>\r
680         /// 検索時に大文字小文字を区別します。また、このメソッドでは正規表現を使用することはできません\r
681         /// </remarks>\r
682         public void ReplaceAll2(string target, string pattern,bool ci = false)\r
683         {\r
684             FastReplaceAllCommand cmd = new FastReplaceAllCommand(this.buffer, target, pattern,ci);\r
685             this.UndoManager.push(cmd);\r
686             cmd.redo();\r
687         }\r
688 \r
689         #region IEnumerable<char> メンバー\r
690 \r
691         /// <summary>\r
692         /// 列挙子を返します\r
693         /// </summary>\r
694         /// <returns>IEnumeratorオブジェクトを返す</returns>\r
695         public IEnumerator<char> GetEnumerator()\r
696         {\r
697             return this.buffer.GetEnumerator();\r
698         }\r
699 \r
700         #endregion\r
701 \r
702         #region IEnumerable メンバー\r
703 \r
704         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()\r
705         {\r
706             throw new NotImplementedException();\r
707         }\r
708 \r
709         #endregion\r
710 \r
711         void buffer_Update(object sender, DocumentUpdateEventArgs e)\r
712         {\r
713             switch (e.type)\r
714             {\r
715                 case UpdateType.Replace:\r
716                     this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);\r
717                     break;\r
718                 case UpdateType.Clear:\r
719                     this._LayoutLines.Clear();\r
720                     break;\r
721             }\r
722             this.UpdateCalledAlways(this, e);\r
723             if(this.FireUpdateEvent)\r
724                 this.Update(this, e);\r
725         }\r
726     }\r
727 \r
728     public interface IStreamReader\r
729     {\r
730         /// <summary>\r
731         /// ストリームが空かどうかを返す\r
732         /// </summary>\r
733         bool IsEnd();\r
734 \r
735         /// <summary>\r
736         /// ストリームから行を読み取った物を返す。LoadAsyncを呼び出す場合は必ず実装してください\r
737         /// </summary>\r
738         Task<string> ReadLineAsync();\r
739     }\r
740 \r
741     public interface IStreamWriter\r
742     {\r
743         /// <summary>\r
744         /// ストリームに書き込む。SaveAsyncを呼び出す場合は必ず実装してください\r
745         /// </summary>\r
746         Task WriteAsync(string str);\r
747 \r
748         /// <summary>\r
749         /// 書き込む際に使用する改行コード\r
750         /// </summary>\r
751         string NewLine\r
752         {\r
753             get;\r
754             set;\r
755         }\r
756     }\r
757 \r
758     /// <summary>\r
759     /// 検索結果を表す\r
760     /// </summary>\r
761     public class SearchResult\r
762     {\r
763         private Match Match;\r
764 \r
765         /// <summary>\r
766         /// 一致した場所の開始位置を表す\r
767         /// </summary>\r
768         public int Start;\r
769 \r
770         /// <summary>\r
771         /// 一致した場所の終了位置を表す\r
772         /// </summary>\r
773         public int End;\r
774 \r
775         /// <summary>\r
776         /// 見つかった文字列を返す\r
777         /// </summary>\r
778         public string Value\r
779         {\r
780             get { return this.Match.Value; }\r
781         }\r
782 \r
783         /// <summary>\r
784         /// 指定したパターンを置き換えて返す\r
785         /// </summary>\r
786         /// <param name="replacement">置き換える文字列</param>\r
787         /// <returns>置き換え後の文字列</returns>\r
788         public string Result(string replacement)\r
789         {\r
790             return this.Match.Result(replacement);\r
791         }\r
792 \r
793         /// <summary>\r
794         /// コンストラクター\r
795         /// </summary>\r
796         /// <param name="m">Matchオブジェクト</param>\r
797         /// <param name="start">開始インデックス</param>\r
798         /// <param name="end">終了インデックス</param>\r
799         public SearchResult(Match m, int start,int end)\r
800         {\r
801             this.Match = m;\r
802             this.Start = start;\r
803             this.End = end;\r
804         }\r
805     }\r
806 \r
807     /// <summary>\r
808     /// ドキュメントリーダー\r
809     /// </summary>\r
810     public class DocumentReader : TextReader\r
811     {\r
812         StringBuffer document;      \r
813         int currentIndex;\r
814 \r
815         /// <summary>\r
816         /// コンストラクター\r
817         /// </summary>\r
818         /// <param name="doc"></param>\r
819         internal DocumentReader(StringBuffer doc)\r
820         {\r
821             if (doc == null)\r
822                 throw new ArgumentNullException();\r
823             this.document = doc;\r
824         }\r
825 \r
826         /// <summary>\r
827         /// 文字を取得する\r
828         /// </summary>\r
829         /// <returns>文字。取得できない場合は-1</returns>\r
830         public override int Peek()\r
831         {\r
832             if (this.document == null)\r
833                 throw new InvalidOperationException();\r
834             if (this.currentIndex >= this.document.Length)\r
835                 return -1;\r
836             return this.document[this.currentIndex];\r
837         }\r
838 \r
839         /// <summary>\r
840         /// 文字を取得し、イテレーターを一つ進める\r
841         /// </summary>\r
842         /// <returns>文字。取得できない場合は-1</returns>\r
843         public override int Read()\r
844         {\r
845             int c = this.Peek();\r
846             if(c != -1)\r
847                 this.currentIndex++;\r
848             return c;\r
849         }\r
850 \r
851         /// <summary>\r
852         /// 文字列を読み取りバッファーに書き込む\r
853         /// </summary>\r
854         /// <param name="buffer">バッファー</param>\r
855         /// <param name="index">開始インデックス</param>\r
856         /// <param name="count">カウント</param>\r
857         /// <returns>読み取られた文字数</returns>\r
858         public override int Read(char[] buffer, int index, int count)\r
859         {\r
860             if (this.document == null)\r
861                 throw new InvalidOperationException();\r
862 \r
863             if (buffer == null)\r
864                 throw new ArgumentNullException();\r
865 \r
866             if (this.document.Length < count)\r
867                 throw new ArgumentException();\r
868 \r
869             if (index < 0 || count < 0)\r
870                 throw new ArgumentOutOfRangeException();\r
871 \r
872             if (this.document.Length == 0)\r
873                 return 0;\r
874 \r
875             int actualCount = count;\r
876             if (index + count - 1 > this.document.Length - 1)\r
877                 actualCount = this.document.Length - index;\r
878 \r
879             string str = this.document.ToString(index, actualCount);\r
880 \r
881             for (int i = 0; i < str.Length; i++)    //ToCharArray()だと戻った時に消えてしまう\r
882                 buffer[i] = str[i];\r
883 \r
884             this.currentIndex = index + actualCount;\r
885             \r
886             return actualCount;\r
887         }\r
888 \r
889         /// <summary>\r
890         /// オブジェクトを破棄する\r
891         /// </summary>\r
892         /// <param name="disposing">真ならアンマネージドリソースを解放する</param>\r
893         protected override void Dispose(bool disposing)\r
894         {\r
895             this.document = null;\r
896         }\r
897     }\r
898 }\r