OSDN Git Service

高速化のため読込部分をStringBufferに移動した
[fooeditengine/FooEditEngine.git] / Common / Document.cs
index 9e9af35..71019f7 100644 (file)
@@ -9,6 +9,8 @@
 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
@@ -35,24 +37,6 @@ namespace FooEditEngine
         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
@@ -143,16 +127,18 @@ namespace FooEditEngine
     /// <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
@@ -169,6 +155,58 @@ namespace FooEditEngine
             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
@@ -200,21 +238,23 @@ namespace FooEditEngine
         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
@@ -284,49 +324,78 @@ namespace FooEditEngine
         }\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
@@ -409,14 +478,13 @@ namespace FooEditEngine
         /// <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
@@ -430,13 +498,77 @@ namespace FooEditEngine
         /// <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
@@ -451,6 +583,19 @@ namespace FooEditEngine
         }\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
@@ -469,8 +614,6 @@ namespace FooEditEngine
         /// <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
@@ -527,6 +670,22 @@ namespace FooEditEngine
             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
@@ -551,12 +710,51 @@ namespace FooEditEngine
 \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