OSDN Git Service

バージョンナンバーを増やした
[fooeditengine/FooEditEngine.git] / Core / Document.cs
index 09b7c9b..dfbf8b3 100644 (file)
@@ -23,6 +23,13 @@ using System.Runtime.CompilerServices;
 namespace FooEditEngine
 {
     /// <summary>
+    /// オートインデントを行うためのデリゲートを表す
+    /// </summary>
+    /// <param name="sender">イベント発生元のオブジェクト</param>
+    /// <param name="e">イベントデーター</param>
+    public delegate void AutoIndentHookerHandler(object sender, EventArgs e);
+
+    /// <summary>
     /// 進行状況を表す列挙体
     /// </summary>
     public enum ProgressState
@@ -75,6 +82,10 @@ namespace FooEditEngine
         /// ドキュメント全体が削除されたことを表す
         /// </summary>
         Clear,
+        /// <summary>
+        /// レイアウトが再構築されたことを表す
+        /// </summary>
+        RebuildLayout,
     }
 
     /// <summary>
@@ -135,7 +146,7 @@ namespace FooEditEngine
     /// ドキュメントの管理を行う
     /// </summary>
     /// <remarks>この型のすべてのメソッド・プロパティはスレッドセーフです</remarks>
-    public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>
+    public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>, IDisposable
     {
         Regex regex;
         Match match;
@@ -172,16 +183,12 @@ namespace FooEditEngine
             else
                 this.buffer = new StringBuffer(doc.buffer);
             this.buffer.Update = new DocumentUpdateEventHandler(buffer_Update);
-            this.UpdateCalledAlways += (s, e) => { };
             this.Update += new DocumentUpdateEventHandler((s, e) => { });
             this.ChangeFireUpdateEvent += new EventHandler((s, e) => { });
             this.StatusUpdate += new EventHandler((s, e) => { });
             this.Markers = new MarkerCollection();
             this.UndoManager = new UndoManager();
             this._LayoutLines = new LineToIndexTable(this);
-            this._LayoutLines.SpilitString = (s,e)=> {
-                return this.CreateLineList(e.index, e.length, MaximumLineLength);
-            };
             this._LayoutLines.Clear();
             this.MarkerPatternSet = new MarkerPatternSet(this._LayoutLines, this.Markers);
             this.MarkerPatternSet.Updated += WacthDogPattern_Updated;
@@ -190,6 +197,10 @@ namespace FooEditEngine
             this.HideLineMarker = true;
             this.SelectGrippers = new GripperRectangle(new Gripper(), new Gripper());
             this.SelectionChanged += new EventHandler((s, e) => { });
+            this.CaretChanged += (s, e) => { };
+            this.AutoIndentHook += (s, e) => { };
+            this.LineBreakChanged += (s, e) => { };
+            this.Dirty = false;
         }
 
         void WacthDogPattern_Updated(object sender, EventArgs e)
@@ -198,6 +209,15 @@ namespace FooEditEngine
         }
 
         /// <summary>
+        /// ダーティフラグ。保存されていなければ真、そうでなければ偽。
+        /// </summary>
+        public bool Dirty
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
         /// キャレットでの選択の起点となる位置
         /// </summary>
         internal int AnchorIndex
@@ -224,6 +244,32 @@ namespace FooEditEngine
             set;
         }
 
+        /// <summary>
+        /// 補完候補プロセッサーが切り替わったときに発生するイベント
+        /// </summary>
+        public event EventHandler AutoCompleteChanged;
+
+        AutoCompleteBoxBase _AutoComplete;
+        /// <summary>
+        /// 補完候補プロセッサー
+        /// </summary>
+        public AutoCompleteBoxBase AutoComplete
+        {
+            get
+            {
+                return this._AutoComplete;
+            }
+            set
+            {
+                this._AutoComplete = value;
+                if (this.AutoCompleteChanged != null)
+                    this.AutoCompleteChanged(this, null);
+            }
+        }
+
+        /// <summary>
+        /// 読み込み中に発生するイベント
+        /// </summary>
         public event ProgressEventHandler LoadProgress;
 
         /// <summary>
@@ -410,14 +456,30 @@ namespace FooEditEngine
             }
         }
 
+        TextPoint _CaretPostion;
         /// <summary>
         /// レイアウト行のどこにキャレットがあるかを表す
         /// </summary>
-        /// <remarks>この値を変更しても反映されないので、EditView側でAdjustCaret()メソッドを呼び出す必要があります</remarks>
         public TextPoint CaretPostion
         {
-            get;
-            set;
+            get
+            {
+                return this._CaretPostion;
+            }
+            set
+            {
+                if(this._CaretPostion != value)
+                {
+                    this._CaretPostion = value;
+                    this.CaretChanged(this, null);
+                }
+            }
+        }
+
+        internal void SetCaretPostionWithoutEvent(TextPoint value)
+        {
+            if (this._CaretPostion != value)
+                this._CaretPostion = value;
         }
 
         /// <summary>
@@ -550,34 +612,6 @@ namespace FooEditEngine
             }
         }
 
-        /// <summary>
-        /// レイアウト行を返す
-        /// </summary>
-        /// <param name="index">開始インデックス</param>
-        /// <param name="length">長さ</param>
-        /// <param name="lineLimitLength">1行当たりの最大文字数。-1で無制限</param>
-        /// <returns>レイアウト行リスト</returns>
-        internal IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)
-        {
-            int startIndex = index;
-            int endIndex = index + length - 1;
-            List<LineToIndexTableData> output = new List<LineToIndexTableData>();
-
-            foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))
-            {
-                int lineHeadIndex = range.Item1;
-                int lineLength = range.Item2;
-                char c = this.buffer[lineHeadIndex + lineLength - 1];
-                bool hasNewLine = c == Document.NewLine;
-                output.Add(this.LayoutLines.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));
-            }
-
-            if (output.Count > 0)
-                output.Last().LineEnd = true;
-
-            return output;
-        }
-
         internal void FireUpdate(DocumentUpdateEventArgs e)
         {
             this.buffer_Update(this.buffer, e);
@@ -589,14 +623,6 @@ namespace FooEditEngine
         public event DocumentUpdateEventHandler Update;
 
         /// <summary>
-        /// ドキュメントが更新された時に呼びされるイベント
-        /// </summary>
-        /// <remarks>
-        /// FireUpdateEventの値に関わらず常に呼びされます
-        /// </remarks>
-        internal event DocumentUpdateEventHandler UpdateCalledAlways;
-
-        /// <summary>
         /// FireUpdateEventの値が変わったときに呼び出されるイベント
         /// </summary>
         public event EventHandler ChangeFireUpdateEvent;
@@ -690,27 +716,49 @@ namespace FooEditEngine
             this.IsRequestRedraw = true;
         }
 
+        /// <summary>
+        /// レイアウト行が再構成されたときに発生するイベント
+        /// </summary>
         public event EventHandler PerformLayouted;
         /// <summary>
         /// レイアウト行をすべて破棄し、再度レイアウトを行う
         /// </summary>
-        public void PerformLayout()
+        /// <param name="quick">真の場合、レイアウトキャッシュのみ再構築します</param>
+        public void PerformLayout(bool quick = true)
         {
-            //単に再構築するだけなので行ダーティフラグは更新してはいけない
-            this.LayoutLines.IsFrozneDirtyFlag = true;
-            this.FireUpdate(new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));
-            this.FireUpdate(new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, this.Length));
-            this.LayoutLines.IsFrozneDirtyFlag = false;
+            if (quick)
+            {
+                this.LayoutLines.ClearLayoutCache();
+            }
+            else
+            {
+                this.LayoutLines.IsFrozneDirtyFlag = true;
+                this.FireUpdate(new DocumentUpdateEventArgs(UpdateType.RebuildLayout, -1, -1, -1));
+                this.LayoutLines.IsFrozneDirtyFlag = false;
+            }
             if (this.PerformLayouted != null)
                 this.PerformLayouted(this, null);
         }
 
         /// <summary>
+        /// オードインデントが可能になった時に通知される
+        /// </summary>
+        /// <remarks>
+        /// FireUpdateEventの影響を受けます
+        /// </remarks>
+        public event AutoIndentHookerHandler AutoIndentHook;
+
+        /// <summary>
         /// 選択領域変更時に通知される
         /// </summary>
         public event EventHandler SelectionChanged;
 
         /// <summary>
+        /// キャレット移動時に通知される
+        /// </summary>
+        public event EventHandler CaretChanged;
+
+        /// <summary>
         /// 指定された範囲を選択する
         /// </summary>
         /// <param name="start"></param>
@@ -722,6 +770,9 @@ namespace FooEditEngine
                 throw new InvalidOperationException("");
             if (start < 0 || start + length < 0 || start + length > this.Length)
                 throw new ArgumentOutOfRangeException("startかendが指定できる範囲を超えてます");
+            //選択範囲が消されたとき
+            foreach (Selection sel in this.Selections)
+                this.LayoutLines.ClearLayoutCache(sel.start, sel.length);
             this.Selections.Clear();
             if (length < 0)
             {
@@ -734,10 +785,12 @@ namespace FooEditEngine
                 TextPoint startTextPoint = this.LayoutLines.GetTextPointFromIndex(start);
                 TextPoint endTextPoint = this.LayoutLines.GetTextPointFromIndex(start + length);
                 this.SelectByRectangle(new TextRectangle(startTextPoint, endTextPoint));
+                this.LayoutLines.ClearLayoutCache(start, length);
             }
             else if (length != 0)
             {
                 this.Selections.Add(Selection.Create(start, length));
+                this.LayoutLines.ClearLayoutCache(start, length);
             }
             this.SelectionChanged(this, null);
         }
@@ -804,25 +857,71 @@ namespace FooEditEngine
         /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
         public void SelectWord(int index, bool changeAnchor = false)
         {
-            if (this.FireUpdateEvent == false)
-                throw new InvalidOperationException("");
+            this.SelectSepartor(index, (c) => Util.IsWordSeparator(c), changeAnchor);
+        }
+
+        /// <summary>
+        /// 行単位で選択する
+        /// </summary>
+        /// <param name="index">探索を開始するインデックス</param>
+        /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
+        public void SelectLine(int index,bool changeAnchor = false)
+        {
+            this.SelectSepartor(index, (c) => c == Document.NewLine, changeAnchor);
+        }
+
+        /// <summary>
+        /// セパレーターで区切られた領域を取得する
+        /// </summary>
+        /// <param name="index">探索を開始するインデックス</param>
+        /// <param name="find_sep_func">セパレーターなら真を返し、そうでないなら偽を返す</param>
+        /// <returns>開始インデックス、終了インデックス</returns>
+        public Tuple<int,int> GetSepartor(int index, Func<char, bool> find_sep_func)
+        {
+            if (find_sep_func == null)
+                throw new ArgumentNullException("find_sep_func must not be null");
 
             if (this.Length <= 0 || index >= this.Length)
-                return;
+                return null;
 
             Document str = this;
 
             int start = index;
-            while (start > 0 && !Util.IsWordSeparator(str[start]))
+            while (start > 0 && !find_sep_func(str[start]))
                 start--;
 
-            if (Util.IsWordSeparator(str[start]))
+            if (find_sep_func(str[start]))
+            {
                 start++;
+            }
 
             int end = index;
-            while (end < this.Length && !Util.IsWordSeparator(str[end]))
+            while (end < this.Length && !find_sep_func(str[end]))
                 end++;
 
+            return new Tuple<int, int>(start, end);
+        }
+
+        /// <summary>
+        /// セパレーターで囲まれた範囲内を選択する
+        /// </summary>
+        /// <param name="index">探索を開始するインデックス</param>
+        /// <param name="find_sep_func">セパレーターなら真を返し、そうでないなら偽を返す</param>
+        /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
+        public void SelectSepartor(int index,Func<char,bool> find_sep_func, bool changeAnchor = false)
+        {
+            if (this.FireUpdateEvent == false)
+                throw new InvalidOperationException("");
+
+            if (find_sep_func == null)
+                throw new ArgumentNullException("find_sep_func must not be null");
+
+            var t = this.GetSepartor(index, find_sep_func);
+            if (t == null)
+                return;
+
+            int start = t.Item1, end = t.Item2;
+
             this.Select(start, end - start);
 
             if (changeAnchor)
@@ -927,15 +1026,19 @@ namespace FooEditEngine
         /// <returns>行イテレーターが返される</returns>
         public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
         {
-            return this.buffer.GetLines(startIndex, endIndex, maxCharCount);
-        }
-
-        internal IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
-        {
-            return this.buffer.ForEachLines(startIndex, endIndex, maxCharCount);
+            foreach (Tuple<int, int> range in this.LayoutLines.ForEachLines(startIndex, endIndex, maxCharCount))
+            {
+                StringBuilder temp = new StringBuilder();
+                temp.Clear();
+                int lineEndIndex = range.Item1;
+                if (range.Item2 > 0)
+                    lineEndIndex += range.Item2 - 1;
+                for (int i = range.Item1; i <= lineEndIndex; i++)
+                    temp.Append(this.buffer[i]);
+                yield return temp.ToString();
+            }
         }
 
-
         /// <summary>
         /// 文字列を追加する
         /// </summary>
@@ -974,8 +1077,9 @@ namespace FooEditEngine
         /// <param name="index">開始インデックス</param>
         /// <param name="length">長さ</param>
         /// <param name="s">文字列</param>
+        /// <param name="UserInput">ユーザーからの入力として扱うなら真</param>
         /// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
-        public void Replace(int index, int length, string s)
+        public void Replace(int index, int length, string s, bool UserInput = false)
         {
             if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)
                 throw new ArgumentOutOfRangeException();
@@ -988,6 +1092,20 @@ namespace FooEditEngine
             ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);
             this.UndoManager.push(cmd);
             cmd.redo();
+
+            if (this.FireUpdateEvent && UserInput)
+            {
+                var input_str = string.Empty;
+                if (s == Document.NewLine.ToString())
+                    input_str = s;
+                else if (s == string.Empty && length > 0)
+                    input_str = "\b";
+                //入力は終わっているので空文字を渡すが処理の都合で一部文字だけはそのまま渡す
+                if (this.AutoComplete != null)
+                    this.AutoComplete.ParseInput(input_str);
+                if (s == Document.NewLine.ToString())
+                    this.AutoIndentHook(this, null);
+            }
         }
 
         /// <summary>
@@ -998,6 +1116,9 @@ namespace FooEditEngine
         public void Clear()
         {
             this.buffer.Clear();
+            System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
+            GC.Collect();
+            this.Dirty = false;
         }
 
         /// <summary>
@@ -1005,12 +1126,13 @@ namespace FooEditEngine
         /// </summary>
         /// <param name="fs">IStreamReaderオブジェクト</param>
         /// <param name="tokenSource">キャンセルトークン</param>
+        /// <param name="file_size">ファイルサイズ。-1を指定しても動作しますが、読み取りが遅くなります</param>
         /// <returns>Taskオブジェクト</returns>
         /// <remarks>
         /// 読み取り操作は別スレッドで行われます。
         /// また、非同期操作中はこのメソッドを実行することはできません。
         /// </remarks>
-        public async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null)
+        public async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null, int file_size = -1)
         {
             if (fs.Peek() == -1)
                 return;
@@ -1021,15 +1143,13 @@ namespace FooEditEngine
             try
             {
                 this.Clear();
-                this.FireUpdateEvent = false;
-                this.LayoutLines.IsFrozneDirtyFlag = true;
+                if (file_size > 0)
+                    this.buffer.Allocate(file_size);
                 await this.buffer.LoadAsync(fs, tokenSource);
             }
             finally
             {
-                this.FireUpdateEvent = true;
-                //これ以降の操作にだけダーティフラグを適用しないとおかしなことになる
-                this.LayoutLines.IsFrozneDirtyFlag = false;
+                this.PerformLayout(false);
                 if (this.LoadProgress != null)
                     this.LoadProgress(this, new ProgressEventArgs(ProgressState.Complete));
             }
@@ -1044,32 +1164,7 @@ namespace FooEditEngine
         /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
         public async Task SaveAsync(TextWriter fs, CancellationTokenSource tokenSource = null)
         {
-            try
-            {
-                await this.buffer.LockAsync().ConfigureAwait(false);
-                StringBuilder line = new StringBuilder();
-                for (int i = 0; i < this.Length; i++)
-                {
-                    char c = this[i];
-                    line.Append(c);
-                    if (c == Document.NewLine || i == this.Length - 1)
-                    {
-                        string str = line.ToString();
-                        str = str.Replace(Document.NewLine.ToString(), fs.NewLine);
-                        await fs.WriteAsync(str).ConfigureAwait(false);
-                        line.Clear();
-                        if (tokenSource != null)
-                            tokenSource.Token.ThrowIfCancellationRequested();
-#if TEST_ASYNC
-                    System.Threading.Thread.Sleep(10);
-#endif
-                    }
-                }
-            }
-            finally
-            {
-                this.buffer.UnLock();
-            }
+            await this.buffer.SaveAsync(fs, tokenSource);
         }
 
         /// <summary>
@@ -1217,6 +1312,10 @@ namespace FooEditEngine
         {
             switch (e.type)
             {
+                case UpdateType.RebuildLayout:
+                    this._LayoutLines.Clear();
+                    this._LayoutLines.UpdateAsReplace(0, 0, this.Length);
+                    break;
                 case UpdateType.Replace:
                     if (e.row == null)
                     {
@@ -1228,15 +1327,42 @@ namespace FooEditEngine
                         this._LayoutLines.UpdateLineAsReplace(e.row.Value, e.removeLength, e.insertLength);
                         this.Markers.UpdateMarkers(this.LayoutLines.GetIndexFromLineNumber(e.row.Value), e.insertLength, e.removeLength);
                     }
+                    this.Dirty = true;
                     break;
                 case UpdateType.Clear:
                     this._LayoutLines.Clear();
+                    this.Dirty = true;
                     break;
             }
-            this.UpdateCalledAlways(this, e);
             if(this.FireUpdateEvent)
                 this.Update(this, e);
         }
+
+        #region IDisposable Support
+        private bool disposedValue = false; // 重複する呼び出しを検出するには
+
+        void Dispose(bool disposing)
+        {
+            if (!disposedValue)
+            {
+                if (disposing)
+                {
+                    this.buffer.Clear();
+                    this.LayoutLines.Clear();
+                }
+
+                disposedValue = true;
+            }
+        }
+
+        /// <summary>
+        /// ドキュメントを破棄する
+        /// </summary>
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+        #endregion
     }
 
     /// <summary>