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.
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.
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/>.
16 using System.Collections.Generic;
18 using System.Text.RegularExpressions;
19 using System.Threading;
20 using System.Threading.Tasks;
23 namespace FooEditEngine
28 public enum ProgressState
42 public sealed class ProgressEventArgs : EventArgs
47 public ProgressState state;
51 /// <param name="state">ProgressStateオブジェクト</param>
52 public ProgressEventArgs(ProgressState state)
61 /// <param name="sender">送信元クラス</param>
62 /// <param name="e">イベントデータ</param>
63 public delegate void ProgressEventHandler(object sender, ProgressEventArgs e);
68 public enum UpdateType
71 /// ドキュメントが置き換えられたことを表す
75 /// ドキュメント全体が削除されたことを表す
81 /// 更新タイプを通知するためのイベントデータ
83 public sealed class DocumentUpdateEventArgs : EventArgs
88 public UpdateType type;
92 public int startIndex;
96 public int removeLength;
100 public int insertLength;
102 /// 更新イベントが発生した行。行が不明な場合や行をまたぐ場合はnullを指定すること。
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)
116 this.startIndex = startIndex;
117 this.removeLength = removeLength;
118 this.insertLength = insertLength;
124 /// ドキュメントに更新があったことを伝えるためのデリゲート
126 /// <param name="sender">送信元クラス</param>
127 /// <param name="e">イベントデータ</param>
128 public delegate void DocumentUpdateEventHandler(object sender, DocumentUpdateEventArgs e);
133 /// <remarks>この型のすべてのメソッド・プロパティはスレッドセーフです</remarks>
134 public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>
136 const int MaxSemaphoreCount = 1;
140 LineToIndexTable _LayoutLines;
141 bool _EnableFireUpdateEvent = true;
142 SemaphoreSlim Semaphore = new SemaphoreSlim(MaxSemaphoreCount);
152 internal Document(Document doc)
155 this.buffer = new StringBuffer();
157 this.buffer = new StringBuffer(doc.buffer);
158 this.buffer.Update = new DocumentUpdateEventHandler(buffer_Update);
159 this.UpdateCalledAlways += (s, e) => { };
160 this.Update += new DocumentUpdateEventHandler((s, e) => { });
161 this.ChangeFireUpdateEvent += new EventHandler((s, e) => { });
162 this.Markers = new MarkerCollection(this);
163 this.UndoManager = new UndoManager();
164 this._LayoutLines = new LineToIndexTable(this);
165 this._LayoutLines.SpilitString = LayoutLines_SpilitStringByChar;
166 this._LayoutLines.Clear();
172 public LineToIndexTable LayoutLines
176 return this._LayoutLines;
180 IList<LineToIndexTableData> LayoutLines_SpilitStringByChar(object sender, SpilitStringEventArgs e)
182 return this.CreateLineList(e.index, e.length);
188 /// <param name="index">開始インデックス</param>
189 /// <param name="length">長さ</param>
190 /// <param name="lineLimitLength">1行当たりの最大文字数。-1で無制限</param>
191 /// <returns>レイアウト行リスト</returns>
192 internal IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)
194 int startIndex = index;
195 int endIndex = index + length - 1;
196 List<LineToIndexTableData> output = new List<LineToIndexTableData>();
198 foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))
200 int lineHeadIndex = range.Item1;
201 int lineLength = range.Item2;
202 char c = this.buffer[lineHeadIndex + lineLength - 1];
203 bool hasNewLine = c == Document.NewLine;
204 output.Add(this.LayoutLines.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));
207 if (output.Count > 0)
208 output.Last().LineEnd = true;
213 internal void FireUpdate(DocumentUpdateEventArgs e)
215 this.buffer_Update(this.buffer, e);
219 /// ドキュメントが更新された時に呼ばれるイベント
221 public event DocumentUpdateEventHandler Update;
224 /// ドキュメントが更新された時に呼びされるイベント
227 /// FireUpdateEventの値に関わらず常に呼びされます
229 internal event DocumentUpdateEventHandler UpdateCalledAlways;
232 /// FireUpdateEventの値が変わったときに呼び出されるイベント
234 public event EventHandler ChangeFireUpdateEvent;
239 public const char NewLine = '\n';
244 public const char EndOfFile = '\u001a';
247 /// ロック中なら真を返し、そうでないなら偽を返す
253 return this.Semaphore.CurrentCount == 0;
260 public UndoManager UndoManager
273 return this.buffer.Length;
278 /// 変更のたびにUpdateイベントを発生させるかどうか
280 public bool FireUpdateEvent
284 return this._EnableFireUpdateEvent;
288 this._EnableFireUpdateEvent = value;
289 this.ChangeFireUpdateEvent(this, null);
296 /// <param name="i">インデックス(自然数でなければならない)</param>
297 /// <returns>Char型</returns>
298 public char this[int i]
302 return this.buffer[i];
309 public MarkerCollection Markers
315 internal StringBuffer StringBuffer
324 /// DocumentReaderを作成します
326 /// <returns>DocumentReaderオブジェクト</returns>
327 public DocumentReader CreateReader()
329 return new DocumentReader(this.buffer);
337 this.Semaphore.Release();
345 this.Semaphore.Wait();
351 /// <returns>Taskオブジェクト</returns>
352 public Task LockAsync()
354 return this.Semaphore.WaitAsync();
360 /// <param name="id">マーカーID</param>
361 /// <param name="m">設定したいマーカー</param>
362 public void SetMarker(int id,Marker m)
364 if (m.start < 0 || m.start + m.length > this.Length)
365 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
367 this.Markers.Add(id,m);
373 /// <param name="id">マーカーID</param>
374 /// <param name="start">開始インデックス</param>
375 /// <param name="length">削除する長さ</param>
376 public void RemoveMarker(int id,int start, int length)
378 if (start < 0 || start + length > this.Length)
379 throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");
381 this.Markers.RemoveAll(id,start, length);
387 /// <param name="id">マーカーID</param>
388 /// <param name="type">削除したいマーカーのタイプ</param>
389 public void RemoveMarker(int id, HilightType type)
391 this.Markers.RemoveAll(id,type);
395 /// インデックスに対応するマーカーを得る
397 /// <param name="id">マーカーID</param>
398 /// <param name="index">インデックス</param>
399 /// <returns>Marker構造体の列挙子</returns>
400 public IEnumerable<Marker> GetMarkers(int id, int index)
402 if (index < 0 || index > this.Length)
403 throw new ArgumentOutOfRangeException("indexが範囲を超えています");
404 return this.Markers.Get(id,index);
410 /// <param name="index">開始インデックス</param>
411 /// <param name="length">長さ</param>
412 /// <returns>Stringオブジェクト</returns>
413 public string ToString(int index, int length)
415 return this.buffer.ToString(index, length);
419 /// インデックスを開始位置とする文字列を返す
421 /// <param name="index">開始インデックス</param>
422 /// <returns>Stringオブジェクト</returns>
423 public string ToString(int index)
425 return this.ToString(index, this.buffer.Length - index);
431 /// <param name="startIndex">開始インデックス</param>
432 /// <param name="endIndex">終了インデックス</param>
433 /// <param name="maxCharCount">最大長</param>
434 /// <returns>行イテレーターが返される</returns>
435 public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
437 return this.buffer.GetLines(startIndex, endIndex, maxCharCount);
440 internal IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
442 return this.buffer.ForEachLines(startIndex, endIndex, maxCharCount);
449 /// <param name="s">追加したい文字列</param>
450 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
451 public void Append(string s)
453 this.Replace(this.buffer.Length, 0, s);
459 /// <param name="index">開始インデックス</param>
460 /// <param name="s">追加したい文字列</param>
461 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
462 public void Insert(int index, string s)
464 this.Replace(index, 0, s);
470 /// <param name="index">開始インデックス</param>
471 /// <param name="length">長さ</param>
472 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
473 public void Remove(int index, int length)
475 this.Replace(index, length, "");
481 /// <param name="index">開始インデックス</param>
482 /// <param name="length">長さ</param>
483 /// <param name="s">文字列</param>
484 /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
485 public void Replace(int index, int length, string s)
487 if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)
488 throw new ArgumentOutOfRangeException();
489 if (length == 0 && (s == string.Empty || s == null))
492 foreach(int id in this.Markers.IDs)
493 this.RemoveMarker(id,index, length);
495 ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);
496 this.UndoManager.push(cmd);
503 /// <remarks>Dirtyフラグも同時にクリアーされます</remarks>
504 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
511 /// ストリームからドキュメントを非同期的に構築します
513 /// <param name="fs">IStreamReaderオブジェクト</param>
514 /// <param name="tokenSource">キャンセルトークン</param>
515 /// <returns>Taskオブジェクト</returns>
517 /// 読み取り操作は別スレッドで行われます。
518 /// また、非同期操作中はこのメソッドを実行することはできません。
520 internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)
527 await this.LockAsync().ConfigureAwait(false);
529 this.FireUpdateEvent = false;
530 await this.buffer.LoadAsync(fs, tokenSource);
534 this.FireUpdateEvent = true;
540 /// ストリームに非同期モードで保存します
542 /// <param name="fs">IStreamWriterオブジェクト</param>
543 /// <param name="tokenSource">キャンセルトークン</param>
544 /// <returns>Taskオブジェクト</returns>
545 /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
546 internal async Task SaveAsync(IStreamWriter fs, CancellationTokenSource tokenSource = null)
550 await this.LockAsync().ConfigureAwait(false);
551 StringBuilder line = new StringBuilder();
552 for (int i = 0; i < this.Length; i++)
556 if (c == Document.NewLine || i == this.Length - 1)
558 string str = line.ToString();
559 str = str.Replace(Document.NewLine.ToString(), fs.NewLine);
560 await fs.WriteAsync(str).ConfigureAwait(false);
562 if (tokenSource != null)
563 tokenSource.Token.ThrowIfCancellationRequested();
565 System.Threading.Thread.Sleep(10);
577 /// Find()およびReplaceAll()で使用するパラメーターをセットします
579 /// <param name="pattern">検索したい文字列</param>
580 /// <param name="UseRegex">正規表現を使用するなら真</param>
581 /// <param name="opt">RegexOptions列挙体</param>
582 public void SetFindParam(string pattern, bool UseRegex, RegexOptions opt)
586 this.regex = new Regex(pattern, opt);
588 this.regex = new Regex(Regex.Escape(pattern), opt);
592 /// 現在の検索パラメーターでWatchDogを生成する
594 /// <param name="type">ハイライトタイプ</param>
595 /// <param name="color">色</param>
596 /// <returns>WatchDogオブジェクト</returns>
597 public RegexMarkerPattern CreateWatchDogByFindParam(HilightType type,Color color)
599 if (this.regex == null)
600 throw new InvalidOperationException("SetFindParam()を呼び出してください");
601 return new RegexMarkerPattern(this.regex,type,color);
607 /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
608 /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
609 public IEnumerator<SearchResult> Find()
611 return this.Find(0, this.Length);
617 /// <returns>見つかった場合はSearchResult列挙子を返却します</returns>
618 /// <param name="start">開始インデックス</param>
619 /// <param name="length">検索する長さ</param>
620 /// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>
621 public IEnumerator<SearchResult> Find(int start, int length)
623 if (this.regex == null)
624 throw new InvalidOperationException();
625 if (start < 0 || start >= this.Length)
626 throw new ArgumentOutOfRangeException();
628 int end = start + length - 1;
630 if(end > this.Length - 1)
631 throw new ArgumentOutOfRangeException();
633 StringBuilder line = new StringBuilder();
634 int oldLength = this.Length;
635 for (int i = start; i <= end; i++)
639 if (c == Document.NewLine || i == end)
641 this.match = this.regex.Match(line.ToString());
642 while (this.match.Success)
644 int startIndex = i - line.Length + 1 + this.match.Index;
645 int endIndex = startIndex + this.match.Length - 1;
647 yield return new SearchResult(this.match, startIndex, endIndex);
649 if (this.Length != oldLength) //長さが変わった場合は置き換え後のパターンの終点+1まで戻る
651 int delta = this.Length - oldLength;
652 i = endIndex + delta;
654 oldLength = this.Length;
658 this.match = this.match.NextMatch();
666 /// 任意のパターンですべて置き換えます
668 /// <param name="replacePattern">置き換え後のパターン</param>
669 /// <param name="groupReplace">グループ置き換えを行うなら真。そうでないなら偽</param>
670 public void ReplaceAll(string replacePattern,bool groupReplace)
672 if (this.regex == null)
673 throw new InvalidOperationException();
674 ReplaceAllCommand cmd = new ReplaceAllCommand(this.buffer, this.LayoutLines, this.regex, replacePattern, groupReplace);
675 this.UndoManager.push(cmd);
682 /// <param name="target">対象となる文字列</param>
683 /// <param name="pattern">置き換え後の文字列</param>
684 /// <param name="ci">大文字も文字を区別しないなら真。そうでないなら偽</param>
686 /// 検索時に大文字小文字を区別します。また、このメソッドでは正規表現を使用することはできません
688 public void ReplaceAll2(string target, string pattern,bool ci = false)
690 FastReplaceAllCommand cmd = new FastReplaceAllCommand(this.buffer, this.LayoutLines, target, pattern,ci);
691 this.UndoManager.push(cmd);
695 #region IEnumerable<char> メンバー
700 /// <returns>IEnumeratorオブジェクトを返す</returns>
701 public IEnumerator<char> GetEnumerator()
703 return this.buffer.GetEnumerator();
708 #region IEnumerable メンバー
710 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
712 throw new NotImplementedException();
717 void buffer_Update(object sender, DocumentUpdateEventArgs e)
721 case UpdateType.Replace:
723 this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);
725 this._LayoutLines.UpdateLineAsReplace(e.row.Value, e.removeLength, e.insertLength);
727 case UpdateType.Clear:
728 this._LayoutLines.Clear();
731 this.UpdateCalledAlways(this, e);
732 if(this.FireUpdateEvent)
733 this.Update(this, e);
737 public interface IStreamReader
745 /// ストリームから行を読み取った物を返す。LoadAsyncを呼び出す場合は必ず実装してください
747 Task<string> ReadLineAsync();
749 /// ストリームから指定した文字数だけ読み取る
751 /// <param name="buffer">書き込み先バッファー</param>
752 /// <param name="index">書き込み先バッファーのインデックス</param>
753 /// <param name="count">読み取る文字数</param>
754 /// <returns>読み取った文字数</returns>
755 Task<int> ReadAsync(char[] buffer, int index, int count);
758 public interface IStreamWriter
761 /// ストリームに書き込む。SaveAsyncを呼び出す場合は必ず実装してください
763 Task WriteAsync(string str);
778 public class SearchResult
797 get { return this.Match.Value; }
803 /// <param name="replacement">置き換える文字列</param>
804 /// <returns>置き換え後の文字列</returns>
805 public string Result(string replacement)
807 return this.Match.Result(replacement);
813 /// <param name="m">Matchオブジェクト</param>
814 /// <param name="start">開始インデックス</param>
815 /// <param name="end">終了インデックス</param>
816 public SearchResult(Match m, int start,int end)
827 public class DocumentReader : TextReader
829 StringBuffer document;
835 /// <param name="doc"></param>
836 internal DocumentReader(StringBuffer doc)
839 throw new ArgumentNullException();
846 /// <returns>文字。取得できない場合は-1</returns>
847 public override int Peek()
849 if (this.document == null)
850 throw new InvalidOperationException();
851 if (this.currentIndex >= this.document.Length)
853 return this.document[this.currentIndex];
857 /// 文字を取得し、イテレーターを一つ進める
859 /// <returns>文字。取得できない場合は-1</returns>
860 public override int Read()
869 /// 文字列を読み取りバッファーに書き込む
871 /// <param name="buffer">バッファー</param>
872 /// <param name="index">開始インデックス</param>
873 /// <param name="count">カウント</param>
874 /// <returns>読み取られた文字数</returns>
875 public override int Read(char[] buffer, int index, int count)
877 if (this.document == null)
878 throw new InvalidOperationException();
881 throw new ArgumentNullException();
883 if (this.document.Length < count)
884 throw new ArgumentException();
886 if (index < 0 || count < 0)
887 throw new ArgumentOutOfRangeException();
889 if (this.document.Length == 0)
892 int actualCount = count;
893 if (index + count - 1 > this.document.Length - 1)
894 actualCount = this.document.Length - index;
896 string str = this.document.ToString(index, actualCount);
898 for (int i = 0; i < str.Length; i++) //ToCharArray()だと戻った時に消えてしまう
901 this.currentIndex = index + actualCount;
909 /// <param name="disposing">真ならアンマネージドリソースを解放する</param>
910 protected override void Dispose(bool disposing)
912 this.document = null;