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
*/\r
\r
+//#define TEST_ASYNC\r
+\r
using System;\r
using System.IO;\r
using System.Collections.Generic;\r
Complete,\r
}\r
/// <summary>\r
- /// 非同期操作の状態を表す\r
- /// </summary>\r
- public enum AsyncState\r
- {\r
- /// <summary>\r
- /// 非同期操作は行われていないことを表す\r
- /// </summary>\r
- None,\r
- /// <summary>\r
- /// 読み出し中であることを表す\r
- /// </summary>\r
- Loading,\r
- /// <summary>\r
- /// 書き込み中であることを表す\r
- /// </summary>\r
- Saving\r
- }\r
- /// <summary>\r
/// 進行状況を表すためのイベントデータ\r
/// </summary>\r
public sealed class ProgressEventArgs : EventArgs\r
/// <remarks>この型のすべてのメソッド・プロパティはスレッドセーフです</remarks>\r
public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>\r
{\r
- const int ProgressNotifyCount = 100;\r
+ const int MaxSemaphoreCount = 1;\r
Regex regex;\r
Match match;\r
StringBuffer buffer;\r
+ LineToIndexTable _LayoutLines;\r
bool _EnableFireUpdateEvent = true;\r
+ SemaphoreSlim Semaphore = new SemaphoreSlim(MaxSemaphoreCount);\r
\r
/// <summary>\r
/// コンストラクター\r
/// </summary>\r
- public Document()\r
+ internal Document()\r
: this(null)\r
{\r
}\r
this.ChangeFireUpdateEvent += new EventHandler((s, e) => { });\r
this.Markers = new MarkerCollection(this);\r
this.UndoManager = new UndoManager();\r
+ this._LayoutLines = new LineToIndexTable(this);\r
+ this._LayoutLines.SpilitString = LayoutLines_SpilitStringByChar;\r
+ this._LayoutLines.Clear();\r
+ }\r
+\r
+ /// <summary>\r
+ /// レイアウト行を表す\r
+ /// </summary>\r
+ public LineToIndexTable LayoutLines\r
+ {\r
+ get\r
+ {\r
+ return this._LayoutLines;\r
+ }\r
+ }\r
+\r
+ IList<LineToIndexTableData> LayoutLines_SpilitStringByChar(object sender, SpilitStringEventArgs e)\r
+ {\r
+ return this.CreateLineList(e.index, e.length);\r
+ }\r
+\r
+ /// <summary>\r
+ /// レイアウト行を返す\r
+ /// </summary>\r
+ /// <param name="index">開始インデックス</param>\r
+ /// <param name="length">長さ</param>\r
+ /// <param name="lineLimitLength">1行当たりの最大文字数。-1で無制限</param>\r
+ /// <returns>レイアウト行リスト</returns>\r
+ internal IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)\r
+ {\r
+ int startIndex = index;\r
+ int endIndex = index + length - 1;\r
+ List<LineToIndexTableData> output = new List<LineToIndexTableData>();\r
+\r
+ foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))\r
+ {\r
+ int lineHeadIndex = range.Item1;\r
+ int lineLength = range.Item2;\r
+ char c = this.buffer[lineHeadIndex + lineLength - 1];\r
+ bool hasNewLine = c == Document.NewLine;\r
+ output.Add(this.LayoutLines.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));\r
+ }\r
+\r
+ if (output.Count > 0)\r
+ output.Last().LineEnd = true;\r
+\r
+ return output;\r
+ }\r
+\r
+ internal void FireUpdate(DocumentUpdateEventArgs e)\r
+ {\r
+ this.buffer_Update(this.buffer, e);\r
}\r
\r
/// <summary>\r
public const char EndOfFile = '\u001a';\r
\r
/// <summary>\r
- /// ã\82¢ã\83³ã\83\89ã\82¥ç®¡ç\90\86ã\82¯ã\83©ã\82¹ã\82\92表す\r
+ /// ã\83ã\83\83ã\82¯ä¸ã\81ªã\82\89ç\9c\9fã\82\92è¿\94ã\81\97ã\80\81ã\81\9dã\81\86ã\81§ã\81ªã\81\84ã\81ªã\82\89å\81½ã\82\92è¿\94す\r
/// </summary>\r
- public UndoManager UndoManager\r
+ public bool IsLocked\r
{\r
- get;\r
- private set;\r
+ get\r
+ {\r
+ return this.Semaphore.CurrentCount == 0;\r
+ }\r
}\r
\r
/// <summary>\r
- /// 非同期操作の状態を表す\r
+ /// アンドゥ管理クラスを表す\r
/// </summary>\r
- public AsyncState State\r
+ public UndoManager UndoManager\r
{\r
get;\r
- internal set;\r
+ private set;\r
}\r
\r
/// <summary>\r
}\r
\r
/// <summary>\r
+ /// ロックを解除します\r
+ /// </summary>\r
+ public void UnLock()\r
+ {\r
+ this.Semaphore.Release();\r
+ }\r
+\r
+ /// <summary>\r
+ /// ロックします\r
+ /// </summary>\r
+ public void Lock()\r
+ {\r
+ this.Semaphore.Wait();\r
+ }\r
+\r
+ /// <summary>\r
+ /// ロックします\r
+ /// </summary>\r
+ /// <returns>Taskオブジェクト</returns>\r
+ public Task LockAsync()\r
+ {\r
+ return this.Semaphore.WaitAsync();\r
+ }\r
+\r
+ /// <summary>\r
/// マーカーを設定する\r
/// </summary>\r
+ /// <param name="id">マーカーID</param>\r
/// <param name="m">設定したいマーカー</param>\r
- public void SetMarker(Marker m)\r
+ public void SetMarker(int id,Marker m)\r
{\r
if (m.start < 0 || m.start + m.length > this.Length)\r
throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");\r
\r
- this.Markers.Add(m);\r
+ this.Markers.Add(id,m);\r
}\r
\r
/// <summary>\r
/// マーカーを削除する\r
/// </summary>\r
+ /// <param name="id">マーカーID</param>\r
/// <param name="start">開始インデックス</param>\r
/// <param name="length">削除する長さ</param>\r
- public void RemoveMarker(int start, int length)\r
+ public void RemoveMarker(int id,int start, int length)\r
{\r
if (start < 0 || start + length > this.Length)\r
throw new ArgumentOutOfRangeException("startもしくはendが指定できる範囲を超えています");\r
\r
- this.Markers.RemoveAll(start, length);\r
+ this.Markers.RemoveAll(id,start, length);\r
}\r
\r
/// <summary>\r
/// マーカーを削除する\r
/// </summary>\r
+ /// <param name="id">マーカーID</param>\r
/// <param name="type">削除したいマーカーのタイプ</param>\r
- public void RemoveMarker(HilightType type)\r
+ public void RemoveMarker(int id, HilightType type)\r
{\r
- this.Markers.RemoveAll(type);\r
+ this.Markers.RemoveAll(id,type);\r
}\r
\r
/// <summary>\r
/// インデックスに対応するマーカーを得る\r
/// </summary>\r
+ /// <param name="id">マーカーID</param>\r
/// <param name="index">インデックス</param>\r
/// <returns>Marker構造体の列挙子</returns>\r
- public IEnumerable<Marker> GetMarkers(int index)\r
+ public IEnumerable<Marker> GetMarkers(int id, int index)\r
{\r
if (index < 0 || index > this.Length)\r
throw new ArgumentOutOfRangeException("indexが範囲を超えています");\r
- return this.Markers.Get(index);\r
+ return this.Markers.Get(id,index);\r
}\r
\r
/// <summary>\r
/// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>\r
public void Replace(int index, int length, string s)\r
{\r
- if (this.State == AsyncState.Loading)\r
- throw new InvalidOperationException();\r
if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)\r
throw new ArgumentOutOfRangeException();\r
if (length == 0 && (s == string.Empty || s == null))\r
return;\r
\r
- this.RemoveMarker(index, length);\r
+ foreach(int id in this.Markers.IDs)\r
+ this.RemoveMarker(id,index, length);\r
\r
ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);\r
this.UndoManager.push(cmd);\r
/// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>\r
public void Clear()\r
{\r
- if (this.State == AsyncState.Loading)\r
- throw new InvalidOperationException();\r
this.buffer.Clear();\r
}\r
\r
/// <summary>\r
- /// Find()で使用するパラメーターをセットします\r
+ /// ストリームからドキュメントを非同期的に構築します\r
+ /// </summary>\r
+ /// <param name="fs">IStreamReaderオブジェクト</param>\r
+ /// <param name="tokenSource">キャンセルトークン</param>\r
+ /// <returns>Taskオブジェクト</returns>\r
+ /// <remarks>\r
+ /// 読み取り操作は別スレッドで行われます。\r
+ /// また、非同期操作中はこのメソッドを実行することはできません。\r
+ /// </remarks>\r
+ internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)\r
+ {\r
+ if (fs.IsEnd())\r
+ return;\r
+\r
+ try\r
+ {\r
+ await this.LockAsync().ConfigureAwait(false);\r
+ this.Clear();\r
+ this.FireUpdateEvent = false;\r
+ await this.buffer.LoadAsync(fs, tokenSource);\r
+ }\r
+ finally\r
+ {\r
+ this.FireUpdateEvent = true;\r
+ this.UnLock();\r
+ }\r
+ }\r
+\r
+ /// <summary>\r
+ /// ストリームに非同期モードで保存します\r
+ /// </summary>\r
+ /// <param name="fs">IStreamWriterオブジェクト</param>\r
+ /// <param name="tokenSource">キャンセルトークン</param>\r
+ /// <returns>Taskオブジェクト</returns>\r
+ /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>\r
+ internal async Task SaveAsync(IStreamWriter fs, CancellationTokenSource tokenSource = null)\r
+ {\r
+ try\r
+ {\r
+ await this.LockAsync().ConfigureAwait(false);\r
+ StringBuilder line = new StringBuilder();\r
+ for (int i = 0; i < this.Length; i++)\r
+ {\r
+ char c = this[i];\r
+ line.Append(c);\r
+ if (c == Document.NewLine || i == this.Length - 1)\r
+ {\r
+ string str = line.ToString();\r
+ str = str.Replace(Document.NewLine.ToString(), fs.NewLine);\r
+ await fs.WriteAsync(str).ConfigureAwait(false);\r
+ line.Clear();\r
+ if (tokenSource != null)\r
+ tokenSource.Token.ThrowIfCancellationRequested();\r
+#if TEST_ASYNC\r
+ System.Threading.Thread.Sleep(10);\r
+#endif\r
+ }\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ this.UnLock();\r
+ }\r
+ }\r
+\r
+ /// <summary>\r
+ /// Find()およびReplaceAll()で使用するパラメーターをセットします\r
/// </summary>\r
/// <param name="pattern">検索したい文字列</param>\r
/// <param name="UseRegex">正規表現を使用するなら真</param>\r
}\r
\r
/// <summary>\r
+ /// 現在の検索パラメーターでWatchDogを生成する\r
+ /// </summary>\r
+ /// <param name="type">ハイライトタイプ</param>\r
+ /// <param name="color">色</param>\r
+ /// <returns>WatchDogオブジェクト</returns>\r
+ public RegexMarkerPattern CreateWatchDogByFindParam(HilightType type,Color color)\r
+ {\r
+ if (this.regex == null)\r
+ throw new InvalidOperationException("SetFindParam()を呼び出してください");\r
+ return new RegexMarkerPattern(this.regex,type,color);\r
+ }\r
+\r
+ /// <summary>\r
/// 指定した文字列を検索します\r
/// </summary>\r
/// <returns>見つかった場合はSearchResult列挙子を返却します</returns>\r
/// <remarks>見つかったパターン以外を置き換えた場合、正常に動作しないことがあります</remarks>\r
public IEnumerator<SearchResult> Find(int start, int length)\r
{\r
- if (this.State == AsyncState.Loading)\r
- throw new InvalidOperationException();\r
if (this.regex == null)\r
throw new InvalidOperationException();\r
if (start < 0 || start >= this.Length)\r
cmd.redo();\r
}\r
\r
+ /// <summary>\r
+ /// 任意のパターンで置き換える\r
+ /// </summary>\r
+ /// <param name="target">対象となる文字列</param>\r
+ /// <param name="pattern">置き換え後の文字列</param>\r
+ /// <param name="ci">大文字も文字を区別しないなら真。そうでないなら偽</param>\r
+ /// <remarks>\r
+ /// 検索時に大文字小文字を区別します。また、このメソッドでは正規表現を使用することはできません\r
+ /// </remarks>\r
+ public void ReplaceAll2(string target, string pattern,bool ci = false)\r
+ {\r
+ FastReplaceAllCommand cmd = new FastReplaceAllCommand(this.buffer, target, pattern,ci);\r
+ this.UndoManager.push(cmd);\r
+ cmd.redo();\r
+ }\r
+\r
#region IEnumerable<char> メンバー\r
\r
/// <summary>\r
\r
void buffer_Update(object sender, DocumentUpdateEventArgs e)\r
{\r
+ switch (e.type)\r
+ {\r
+ case UpdateType.Replace:\r
+ this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);\r
+ break;\r
+ case UpdateType.Clear:\r
+ this._LayoutLines.Clear();\r
+ break;\r
+ }\r
this.UpdateCalledAlways(this, e);\r
if(this.FireUpdateEvent)\r
this.Update(this, e);\r
}\r
}\r
\r
+ public interface IStreamReader\r
+ {\r
+ /// <summary>\r
+ /// ストリームが空かどうかを返す\r
+ /// </summary>\r
+ bool IsEnd();\r
+\r
+ /// <summary>\r
+ /// ストリームから行を読み取った物を返す。LoadAsyncを呼び出す場合は必ず実装してください\r
+ /// </summary>\r
+ Task<string> ReadLineAsync();\r
+ }\r
+\r
+ public interface IStreamWriter\r
+ {\r
+ /// <summary>\r
+ /// ストリームに書き込む。SaveAsyncを呼び出す場合は必ず実装してください\r
+ /// </summary>\r
+ Task WriteAsync(string str);\r
+\r
+ /// <summary>\r
+ /// 書き込む際に使用する改行コード\r
+ /// </summary>\r
+ string NewLine\r
+ {\r
+ get;\r
+ set;\r
+ }\r
+ }\r
+\r
/// <summary>\r
/// 検索結果を表す\r
/// </summary>\r