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
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
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
14 using System.Collections.Generic;
\r
16 using System.Text.RegularExpressions;
\r
17 using System.Threading;
\r
18 using System.Threading.Tasks;
\r
21 namespace FooEditEngine
\r
26 public enum ProgressState
\r
40 public enum AsyncState
\r
43 /// 非同期操作は行われていないことを表す
\r
56 /// 進行状況を表すためのイベントデータ
\r
58 public sealed class ProgressEventArgs : EventArgs
\r
63 public ProgressState state;
\r
67 /// <param name="state">ProgressStateオブジェクト</param>
\r
68 public ProgressEventArgs(ProgressState state)
\r
75 /// 進行状況を通知するためのデリゲート
\r
77 /// <param name="sender">送信元クラス</param>
\r
78 /// <param name="e">イベントデータ</param>
\r
79 public delegate void ProgressEventHandler(object sender, ProgressEventArgs e);
\r
84 public enum UpdateType
\r
87 /// ドキュメントが置き換えられたことを表す
\r
91 /// ドキュメント全体が削除されたことを表す
\r
97 /// 更新タイプを通知するためのイベントデータ
\r
99 public sealed class DocumentUpdateEventArgs : EventArgs
\r
104 public UpdateType type;
\r
108 public int startIndex;
\r
112 public int removeLength;
\r
116 public int insertLength;
\r
120 /// <param name="type">更新タイプ</param>
\r
121 /// <param name="startIndex">開始インデックス</param>
\r
122 /// <param name="removeLength">削除された長さ</param>
\r
123 /// <param name="insertLength">追加された長さ</param>
\r
124 public DocumentUpdateEventArgs(UpdateType type, int startIndex, int removeLength, int insertLength)
\r
127 this.startIndex = startIndex;
\r
128 this.removeLength = removeLength;
\r
129 this.insertLength = insertLength;
\r
134 /// ドキュメントに更新があったことを伝えるためのデリゲート
\r
136 /// <param name="sender">送信元クラス</param>
\r
137 /// <param name="e">イベントデータ</param>
\r
138 public delegate void DocumentUpdateEventHandler(object sender, DocumentUpdateEventArgs e);
\r
143 /// <remarks>この型のすべてのメソッド・プロパティはスレッドセーフです</remarks>
\r
144 public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>
\r
146 const int ProgressNotifyCount = 100;
\r
149 StringBuffer buffer;
\r
150 bool _EnableFireUpdateEvent = true;
\r
160 internal Document(Document doc)
\r
163 this.buffer = new StringBuffer();
\r
165 this.buffer = new StringBuffer(doc.buffer);
\r
166 this.buffer.Update += new DocumentUpdateEventHandler(buffer_Update);
\r
167 this.UpdateCalledAlways += (s, e) => { };
\r
168 this.Update += new DocumentUpdateEventHandler((s, e) => { });
\r
169 this.ChangeFireUpdateEvent += new EventHandler((s, e) => { });
\r
170 this.Markers = new MarkerCollection(this);
\r
171 this.UndoManager = new UndoManager();
\r
175 /// ドキュメントが更新された時に呼ばれるイベント
\r
177 public event DocumentUpdateEventHandler Update;
\r
180 /// ドキュメントが更新された時に呼びされるイベント
\r
183 /// FireUpdateEventの値に関わらず常に呼びされます
\r
185 internal event DocumentUpdateEventHandler UpdateCalledAlways;
\r
188 /// FireUpdateEventの値が変わったときに呼び出されるイベント
\r
190 public event EventHandler ChangeFireUpdateEvent;
\r
195 public const char NewLine = '\n';
\r
200 public const char EndOfFile = '\u001a';
\r
205 public UndoManager UndoManager
\r
214 public AsyncState State
\r
227 return this.buffer.Length;
\r
232 /// 変更のたびにUpdateイベントを発生させるかどうか
\r
234 public bool FireUpdateEvent
\r
238 return this._EnableFireUpdateEvent;
\r
242 this._EnableFireUpdateEvent = value;
\r
243 this.ChangeFireUpdateEvent(this, null);
\r
250 /// <param name="i">インデックス(自然数でなければならない)</param>
\r
251 /// <returns>Char型</returns>
\r
252 public char this[int i]
\r
256 return this.buffer[i];
\r
263 public MarkerCollection Markers
\r
269 internal StringBuffer StringBuffer
\r
273 return this.buffer;
\r
278 /// DocumentReaderを作成します
\r
280 /// <returns>DocumentReaderオブジェクト</returns>
\r
281 public DocumentReader CreateReader()
\r
283 return new DocumentReader(this.buffer);
\r
289 /// <param name="m">設定したいマーカー</param>
\r
290 public void SetMarker(Marker m)
\r
292 if (m.start < 0 || m.start + m.length > this.Length)
\r
293 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
\r
295 this.Markers.Add(m);
\r
301 /// <param name="start">開始インデックス</param>
\r
302 /// <param name="length">削除する長さ</param>
\r
303 public void RemoveMarker(int start, int length)
\r
305 if (start < 0 || start + length > this.Length)
\r
306 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
\r
308 this.Markers.RemoveAll(start, length);
\r
314 /// <param name="type">削除したいマーカーのタイプ</param>
\r
315 public void RemoveMarker(HilightType type)
\r
317 this.Markers.RemoveAll(type);
\r
321 /// インデックスに対応するマーカーを得る
\r
323 /// <param name="index">インデックス</param>
\r
324 /// <returns>Marker構造体の列挙子</returns>
\r
325 public IEnumerable<Marker> GetMarkers(int index)
\r
327 if (index < 0 || index > this.Length)
\r
328 throw new ArgumentOutOfRangeException("indexが範囲を超えています");
\r
329 return this.Markers.Get(index);
\r
335 /// <param name="index">開始インデックス</param>
\r
336 /// <param name="length">長さ</param>
\r
337 /// <returns>Stringオブジェクト</returns>
\r
338 public string ToString(int index, int length)
\r
340 return this.buffer.ToString(index, length);
\r
344 /// インデックスを開始位置とする文字列を返す
\r
346 /// <param name="index">開始インデックス</param>
\r
347 /// <returns>Stringオブジェクト</returns>
\r
348 public string ToString(int index)
\r
350 return this.ToString(index, this.buffer.Length - index);
\r
356 /// <param name="startIndex">開始インデックス</param>
\r
357 /// <param name="endIndex">終了インデックス</param>
\r
358 /// <param name="maxCharCount">最大長</param>
\r
359 /// <returns>行イテレーターが返される</returns>
\r
360 public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
\r
362 return this.buffer.GetLines(startIndex, endIndex, maxCharCount);
\r
368 /// <param name="s">追加したい文字列</param>
\r
369 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
\r
370 public void Append(string s)
\r
372 this.Replace(this.buffer.Length, 0, s);
\r
378 /// <param name="index">開始インデックス</param>
\r
379 /// <param name="s">追加したい文字列</param>
\r
380 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
\r
381 public void Insert(int index, string s)
\r
383 this.Replace(index, 0, s);
\r
389 /// <param name="index">開始インデックス</param>
\r
390 /// <param name="length">長さ</param>
\r
391 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
\r
392 public void Remove(int index, int length)
\r
394 this.Replace(index, length, "");
\r
400 /// <param name="index">開始インデックス</param>
\r
401 /// <param name="length">長さ</param>
\r
402 /// <param name="s">文字列</param>
\r
403 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
\r
404 public void Replace(int index, int length, string s)
\r
406 if (this.State == AsyncState.Loading)
\r
407 throw new InvalidOperationException();
\r
408 if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)
\r
409 throw new ArgumentOutOfRangeException();
\r
410 if (length == 0 && (s == string.Empty || s == null))
\r
413 this.RemoveMarker(index, length);
\r
415 ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);
\r
416 this.UndoManager.push(cmd);
\r
423 /// <remarks>Dirtyフラグも同時にクリアーされます</remarks>
\r
424 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
\r
425 public void Clear()
\r
427 if (this.State == AsyncState.Loading)
\r
428 throw new InvalidOperationException();
\r
429 this.buffer.Clear();
\r
433 /// Find()で使用するパラメーターをセットします
\r
435 /// <param name="pattern">検索したい文字列</param>
\r
436 /// <param name="UseRegex">正規表現を使用するなら真</param>
\r
437 /// <param name="opt">RegexOptions列挙体</param>
\r
438 public void SetFindParam(string pattern, bool UseRegex, RegexOptions opt)
\r
442 this.regex = new Regex(pattern, opt);
\r
444 this.regex = new Regex(Regex.Escape(pattern), opt);
\r
450 /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
\r
451 /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
\r
452 public IEnumerator<SearchResult> Find()
\r
454 return this.Find(0, this.Length);
\r
460 /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
\r
461 /// <param name="start">開始インデックス</param>
\r
462 /// <param name="length">検索する長さ</param>
\r
463 /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
\r
464 public IEnumerator<SearchResult> Find(int start, int length)
\r
466 if (this.State == AsyncState.Loading)
\r
467 throw new InvalidOperationException();
\r
468 if (this.regex == null)
\r
469 throw new InvalidOperationException();
\r
470 if (start < 0 || start >= this.Length)
\r
471 throw new ArgumentOutOfRangeException();
\r
473 int end = start + length - 1;
\r
475 if(end > this.Length - 1)
\r
476 throw new ArgumentOutOfRangeException();
\r
478 StringBuilder line = new StringBuilder();
\r
479 int oldLength = this.Length;
\r
480 for (int i = start; i <= end; i++)
\r
484 if (c == Document.NewLine || i == end)
\r
486 this.match = this.regex.Match(line.ToString());
\r
487 while (this.match.Success)
\r
489 int startIndex = i - line.Length + 1 + this.match.Index;
\r
490 int endIndex = startIndex + this.match.Length - 1;
\r
492 yield return new SearchResult(this.match, startIndex, endIndex);
\r
494 if (this.Length != oldLength) //長さが変わった場合は置き換え後のパターンの終点+1まで戻る
\r
496 int delta = this.Length - oldLength;
\r
497 i = endIndex + delta;
\r
499 oldLength = this.Length;
\r
503 this.match = this.match.NextMatch();
\r
511 /// 任意のパターンですべて置き換えます
\r
513 /// <param name="replacePattern">置き換え後のパターン</param>
\r
514 /// <param name="groupReplace">グループ置き換えを行うなら真。そうでないなら偽</param>
\r
515 public void ReplaceAll(string replacePattern,bool groupReplace)
\r
517 if (this.regex == null)
\r
518 throw new InvalidOperationException();
\r
519 ReplaceAllCommand cmd = new ReplaceAllCommand(this.buffer, this.regex, replacePattern, groupReplace);
\r
520 this.UndoManager.push(cmd);
\r
524 #region IEnumerable<char> メンバー
\r
529 /// <returns>IEnumeratorオブジェクトを返す</returns>
\r
530 public IEnumerator<char> GetEnumerator()
\r
532 return this.buffer.GetEnumerator();
\r
537 #region IEnumerable メンバー
\r
539 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
\r
541 throw new NotImplementedException();
\r
546 void buffer_Update(object sender, DocumentUpdateEventArgs e)
\r
548 this.UpdateCalledAlways(this, e);
\r
549 if(this.FireUpdateEvent)
\r
550 this.Update(this, e);
\r
557 public class SearchResult
\r
559 private Match Match;
\r
574 public string Value
\r
576 get { return this.Match.Value; }
\r
580 /// 指定したパターンを置き換えて返す
\r
582 /// <param name="replacement">置き換える文字列</param>
\r
583 /// <returns>置き換え後の文字列</returns>
\r
584 public string Result(string replacement)
\r
586 return this.Match.Result(replacement);
\r
592 /// <param name="m">Matchオブジェクト</param>
\r
593 /// <param name="start">開始インデックス</param>
\r
594 /// <param name="end">終了インデックス</param>
\r
595 public SearchResult(Match m, int start,int end)
\r
598 this.Start = start;
\r
606 public class DocumentReader : TextReader
\r
608 StringBuffer document;
\r
614 /// <param name="doc"></param>
\r
615 internal DocumentReader(StringBuffer doc)
\r
618 throw new ArgumentNullException();
\r
619 this.document = doc;
\r
625 /// <returns>文字。取得できない場合は-1</returns>
\r
626 public override int Peek()
\r
628 if (this.document == null)
\r
629 throw new InvalidOperationException();
\r
630 if (this.currentIndex >= this.document.Length)
\r
632 return this.document[this.currentIndex];
\r
636 /// 文字を取得し、イテレーターを一つ進める
\r
638 /// <returns>文字。取得できない場合は-1</returns>
\r
639 public override int Read()
\r
641 int c = this.Peek();
\r
643 this.currentIndex++;
\r
648 /// 文字列を読み取りバッファーに書き込む
\r
650 /// <param name="buffer">バッファー</param>
\r
651 /// <param name="index">開始インデックス</param>
\r
652 /// <param name="count">カウント</param>
\r
653 /// <returns>読み取られた文字数</returns>
\r
654 public override int Read(char[] buffer, int index, int count)
\r
656 if (this.document == null)
\r
657 throw new InvalidOperationException();
\r
659 if (buffer == null)
\r
660 throw new ArgumentNullException();
\r
662 if (this.document.Length < count)
\r
663 throw new ArgumentException();
\r
665 if (index < 0 || count < 0)
\r
666 throw new ArgumentOutOfRangeException();
\r
668 if (this.document.Length == 0)
\r
671 int actualCount = count;
\r
672 if (index + count - 1 > this.document.Length - 1)
\r
673 actualCount = this.document.Length - index;
\r
675 string str = this.document.ToString(index, actualCount);
\r
677 for (int i = 0; i < str.Length; i++) //ToCharArray()だと戻った時に消えてしまう
\r
678 buffer[i] = str[i];
\r
680 this.currentIndex = index + actualCount;
\r
682 return actualCount;
\r
688 /// <param name="disposing">真ならアンマネージドリソースを解放する</param>
\r
689 protected override void Dispose(bool disposing)
\r
691 this.document = null;
\r