-/*\r
- * Copyright (C) 2013 FooProject\r
- * * 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
- * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\r
-\r
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of \r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\r
-\r
-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
-using System;\r
-using System.Text.RegularExpressions;\r
-using System.Threading;\r
-using System.Linq;\r
-using System.Collections.Generic;\r
-using System.Diagnostics;\r
-using Slusser.Collections.Generic;\r
-\r
-namespace FooEditEngine\r
-{\r
- internal interface ITextLayout : IDisposable\r
- {\r
- /// <summary>\r
- /// 文字列の幅\r
- /// </summary>\r
- double Width\r
- {\r
- get;\r
- }\r
-\r
- /// <summary>\r
- /// 文字列の高さ\r
- /// </summary>\r
- double Height\r
- {\r
- get;\r
- }\r
-\r
- /// <summary>\r
- /// Disposeされているなら真を返す\r
- /// </summary>\r
- bool Disposed\r
- {\r
- get;\r
- }\r
-\r
- /// <summary>\r
- /// 破棄すべきなら真。そうでなければ偽\r
- /// </summary>\r
- bool Invaild\r
- {\r
- get;\r
- }\r
-\r
- /// <summary>\r
- /// 桁方向の座標に対応するインデックスを得る\r
- /// </summary>\r
- /// <param name="colpos">桁方向の座標</param>\r
- /// <returns>インデックス</returns>\r
- /// <remarks>行番号の幅は考慮されてないのでView以外のクラスは呼び出さないでください</remarks>\r
- int GetIndexFromColPostion(double colpos);\r
-\r
- /// <summary>\r
- /// インデックスに対応する文字の幅を得る\r
- /// </summary>\r
- /// <param name="index">インデックス</param>\r
- /// <returns>文字の幅</returns>\r
- double GetWidthFromIndex(int index);\r
-\r
- /// <summary>\r
- /// インデックスに対応する桁方向の座標を得る\r
- /// </summary>\r
- /// <param name="index">インデックス</param>\r
- /// <returns>桁方向の座標</returns>\r
- /// <remarks>行頭にEOFが含まれている場合、0が返ります</remarks>\r
- double GetColPostionFromIndex(int index);\r
-\r
- /// <summary>\r
- /// 適切な位置にインデックスを調整する\r
- /// </summary>\r
- /// <param name="index">インデックス</param>\r
- /// <param name="flow">真の場合は隣接するクラスターを指すように調整し、\r
- /// そうでない場合は対応するクラスターの先頭を指すように調整します</param>\r
- /// <returns>調整後のインデックス</returns>\r
- int AlignIndexToNearestCluster(int index, AlignDirection flow);\r
- }\r
-\r
- internal class SpilitStringEventArgs : EventArgs\r
- {\r
- public Document buffer;\r
- public int index;\r
- public int length;\r
- public int row;\r
- public SpilitStringEventArgs(Document buf, int index, int length,int row)\r
- {\r
- this.buffer = buf;\r
- this.index = index;\r
- this.length = length;\r
- this.row = row;\r
- }\r
- }\r
-\r
- internal struct SyntaxInfo\r
- {\r
- public TokenType type;\r
- public int index;\r
- public int length;\r
- public SyntaxInfo(int index, int length, TokenType type)\r
- {\r
- this.type = type;\r
- this.index = index;\r
- this.length = length;\r
- }\r
- }\r
-\r
- internal enum EncloserType\r
- {\r
- None,\r
- Begin,\r
- Now,\r
- End,\r
- }\r
-\r
- internal class LineToIndexTableData : IDisposable\r
- {\r
- /// <summary>\r
- /// 行の先頭。正しい行の先頭位置を取得するにはGetLineHeadIndex()を使用してください\r
- /// </summary>\r
- public int Index;\r
- /// <summary>\r
- /// 行の長さ\r
- /// </summary>\r
- public int Length;\r
- /// <summary>\r
- /// 改行マークかEOFなら真を返す\r
- /// </summary>\r
- public bool LineEnd;\r
- public SyntaxInfo[] Syntax;\r
- public EncloserType EncloserType;\r
- internal ITextLayout Layout;\r
- public bool Dirty = false;\r
-\r
- /// <summary>\r
- /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください\r
- /// </summary>\r
- public LineToIndexTableData()\r
- {\r
- }\r
-\r
- /// <summary>\r
- /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください\r
- /// </summary>\r
- public LineToIndexTableData(int index, int length, bool lineend,bool dirty, SyntaxInfo[] syntax)\r
- {\r
- this.Index = index;\r
- this.Length = length;\r
- this.LineEnd = lineend;\r
- this.Syntax = syntax;\r
- this.EncloserType = EncloserType.None;\r
- this.Dirty = dirty;\r
- }\r
-\r
- public void Dispose()\r
- {\r
- if(this.Layout != null)\r
- this.Layout.Dispose();\r
- }\r
- }\r
-\r
- internal delegate IList<LineToIndexTableData> SpilitStringEventHandler(object sender, SpilitStringEventArgs e);\r
-\r
- internal sealed class CreateLayoutEventArgs\r
- {\r
- /// <summary>\r
- /// 開始インデックス\r
- /// </summary>\r
- public int Index\r
- {\r
- get;\r
- private set;\r
- }\r
- /// <summary>\r
- /// 長さ\r
- /// </summary>\r
- public int Length\r
- {\r
- get;\r
- private set;\r
- }\r
- /// <summary>\r
- /// 文字列\r
- /// </summary>\r
- public string Content\r
- {\r
- get;\r
- private set;\r
- }\r
- public CreateLayoutEventArgs(int index, int length,string content)\r
- {\r
- this.Index = index;\r
- this.Length = length;\r
- this.Content = content;\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// 行番号とインデックスを相互変換するためのクラス\r
- /// </summary>\r
- public sealed class LineToIndexTable : IEnumerable<string>\r
- {\r
- const int MaxEntries = 100;\r
- Queue<ITextLayout> CacheEntries = new Queue<ITextLayout>();\r
- GapBuffer<LineToIndexTableData> Lines = new GapBuffer<LineToIndexTableData>();\r
- Document Document;\r
- long lastUpdateTicks = DateTime.Now.Ticks;\r
- const long AllowCallTicks = 1000 * 10000; //see.DateTime.Ticks プロパティ\r
- bool _IsSync;\r
- ITextRender render;\r
- int stepRow = -1,stepLength = 0;\r
- const int STEP_ROW_IS_NONE = -1;\r
-\r
- internal LineToIndexTable(Document buf)\r
- {\r
- this.Document = buf;\r
- this.Document.Markers.Updated += Markers_Updated;\r
- this.FoldingCollection = new FoldingCollection();\r
- this._IsSync = true;\r
-#if DEBUG && !NETFX_CORE\r
- if (!Debugger.IsAttached)\r
- {\r
- Guid guid = Guid.NewGuid();\r
- string path = string.Format("{0}\\footextbox_lti_debug_{1}.log", System.IO.Path.GetTempPath(), guid);\r
- Debug.Listeners.Add(new TextWriterTraceListener(path));\r
- Debug.AutoFlush = true;\r
- }\r
-#endif\r
- }\r
-\r
- void Markers_Updated(object sender, EventArgs e)\r
- {\r
- this.ClearLayoutCache();\r
- }\r
-\r
- /// <summary>\r
- /// ITextRenderインターフェイスのインスタンス。必ずセットすること\r
- /// </summary>\r
- internal ITextRender Render\r
- {\r
- get { return this.render; }\r
- set\r
- {\r
- this.render = value;\r
- }\r
- }\r
-\r
- internal SpilitStringEventHandler SpilitString;\r
-\r
- /// <summary>\r
- /// 行数を返す\r
- /// </summary>\r
- public int Count\r
- {\r
- get { return this.Lines.Count; }\r
- }\r
-\r
- /// <summary>\r
- /// 折り畳み関係の情報を収めたコレクション\r
- /// </summary>\r
- public FoldingCollection FoldingCollection\r
- {\r
- get;\r
- private set;\r
- }\r
-\r
- /// <summary>\r
- /// シンタックスハイライター\r
- /// </summary>\r
- internal IHilighter Hilighter { get; set; }\r
-\r
- internal IFoldingStrategy FoldingStrategy { get; set; }\r
-\r
- /// <summary>\r
- /// 保持しているレイアウトキャッシュをクリアーする\r
- /// </summary>\r
- public void ClearLayoutCache()\r
- {\r
- foreach (ITextLayout data in this.CacheEntries)\r
- {\r
- data.Dispose();\r
- }\r
- this.CacheEntries.Clear();\r
- }\r
-\r
- /// <summary>\r
- /// 行番号に対応する文字列を返します\r
- /// </summary>\r
- /// <param name="n"></param>\r
- /// <returns></returns>\r
- public string this[int n]\r
- {\r
- get\r
- {\r
- LineToIndexTableData data = this.Lines[n];\r
- string str = this.Document.ToString(this.GetLineHeadIndex(n), data.Length);\r
-\r
- return str;\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// 更新フラグを更新しないなら真\r
- /// </summary>\r
- public bool IsFrozneDirtyFlag\r
- {\r
- get;\r
- set;\r
- }\r
-\r
- int GetLineHeadIndex(int row)\r
- {\r
- if (this.Lines.Count == 0)\r
- return 0;\r
- if (this.stepRow != STEP_ROW_IS_NONE && row > this.stepRow)\r
- return this.Lines[row].Index + this.stepLength;\r
- else\r
- return this.Lines[row].Index;\r
- }\r
-\r
- internal LineToIndexTableData CreateLineToIndexTableData(int index, int length, bool lineend, SyntaxInfo[] syntax)\r
- {\r
- LineToIndexTableData result = new LineToIndexTableData(index, length, lineend,this.IsFrozneDirtyFlag == false, syntax);\r
- return result;\r
- }\r
-\r
- internal void UpdateLineAsReplace(int row,int removedLength, int insertedLength)\r
- {\r
- int deltaLength = insertedLength - removedLength;\r
-\r
- this.Lines[row] = new LineToIndexTableData(this.GetLineHeadIndex(row), this.GetLengthFromLineNumber(row) + deltaLength, true, true, null);\r
-\r
- //行テーブルを更新する\r
- this.UpdateLineHeadIndex(deltaLength, row, 1);\r
-\r
- this.FoldingCollection.UpdateData(this.Document, this.GetLineHeadIndex(row), insertedLength, removedLength);\r
-\r
- this._IsSync = false;\r
-\r
- this.lastUpdateTicks = DateTime.Now.Ticks;\r
- }\r
-\r
- internal void UpdateAsReplace(int index, int removedLength, int insertedLength)\r
- {\r
-#if DEBUG\r
- Debug.WriteLine("Replaced Index:{0} RemoveLength:{1} InsertLength:{2}", index, removedLength, insertedLength);\r
-#endif\r
- int startRow, endRow;\r
- GetRemoveRange(index, removedLength, out startRow, out endRow);\r
-\r
- int deltaLength = insertedLength - removedLength;\r
-\r
- var result = GetAnalyzeLength(startRow, endRow, index, removedLength, insertedLength);\r
- int HeadIndex = result.Item1;\r
- int analyzeLength = result.Item2;\r
-\r
- //挿入範囲内のドキュメントから行を生成する\r
- SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);\r
- IList<LineToIndexTableData> newLines = SpilitString(this, e);\r
-\r
- //消すべき行が複数ある場合は消すが、そうでない場合は最適化のため長さを変えるだけにとどめておく\r
- int removeCount = endRow - startRow + 1;\r
- if (removeCount == 1 && newLines.Count == 1)\r
- {\r
- this.Lines[startRow] = newLines.First();\r
- }\r
- else\r
- {\r
- this.RemoveLine(startRow, removeCount);\r
-\r
- //行を挿入する\r
- this.InsertLine(startRow, newLines, removeCount, deltaLength);\r
- }\r
-\r
- //行テーブルを更新する\r
- this.UpdateLineHeadIndex(deltaLength, startRow, newLines.Count);\r
-\r
- this.AddDummyLine();\r
-\r
- this.FoldingCollection.UpdateData(this.Document, index, insertedLength, removedLength); \r
-\r
- this._IsSync = false;\r
-\r
- this.lastUpdateTicks = DateTime.Now.Ticks;\r
- }\r
-\r
- void GetRemoveRange(int index,int length,out int startRow,out int endRow)\r
- {\r
- startRow = this.GetLineNumberFromIndex(index);\r
- while (startRow > 0 && this.Lines[startRow - 1].LineEnd == false)\r
- startRow--;\r
-\r
- endRow = this.GetLineNumberFromIndex(index + length);\r
- while (endRow < this.Lines.Count && this.Lines[endRow].LineEnd == false)\r
- endRow++;\r
- if (endRow >= this.Lines.Count)\r
- endRow = this.Lines.Count - 1;\r
- }\r
-\r
- Tuple<int,int> GetAnalyzeLength(int startRow,int endRow,int updateStartIndex,int removedLength,int insertedLength)\r
- {\r
- int HeadIndex = this.GetIndexFromLineNumber(startRow);\r
- int LastIndex = this.GetIndexFromLineNumber(endRow) + this.GetLengthFromLineNumber(endRow) - 1;\r
-\r
- //SpilitStringの対象となる範囲を求める\r
- int fisrtPartLength = updateStartIndex - HeadIndex;\r
- int secondPartLength = LastIndex - (updateStartIndex + removedLength - 1);\r
- int analyzeLength = fisrtPartLength + secondPartLength + insertedLength;\r
- Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);\r
-\r
- return new Tuple<int, int>(HeadIndex, analyzeLength);\r
- }\r
-\r
- void RemoveLine(int startRow, int removeCount)\r
- {\r
- for (int i = startRow; i < startRow + removeCount; i++)\r
- this.Lines[i].Dispose();\r
-\r
- this.Lines.RemoveRange(startRow, removeCount);\r
- }\r
-\r
- void InsertLine(int startRow, IList<LineToIndexTableData> collection,int removeCount, int deltaLength)\r
- {\r
- int newCount = collection.Count;\r
- if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)\r
- {\r
- //stepRowは1か2のうち、大きな方になる\r
- // 1.stepRow - (削除された行数 - 挿入された行数)\r
- // 2.行の挿入箇所\r
- //行が削除や置換された場合、1の処理をしないと正しいIndexが求められない\r
- this.stepRow = Math.Max(this.stepRow - (removeCount - newCount), startRow);\r
-#if DEBUG\r
- if (this.stepRow < 0 || this.stepRow > this.Lines.Count + newCount)\r
- {\r
- Debug.WriteLine("step row < 0 or step row >= lines.count");\r
- Debugger.Break();\r
- }\r
-#endif\r
- }\r
-\r
- //startRowが挿入した行の開始位置なのであらかじめ引いておく\r
- for (int i = 1; i < collection.Count; i++)\r
- {\r
- if (this.stepRow != STEP_ROW_IS_NONE && startRow + i > this.stepRow)\r
- collection[i].Index -= deltaLength + this.stepLength;\r
- else\r
- collection[i].Index -= deltaLength;\r
- }\r
-\r
- this.Lines.InsertRange(startRow, collection);\r
- }\r
-\r
- void AddDummyLine()\r
- {\r
- LineToIndexTableData dummyLine = null;\r
- if (this.Lines.Count == 0)\r
- {\r
- dummyLine = new LineToIndexTableData();\r
- this.Lines.Add(dummyLine);\r
- return;\r
- }\r
-\r
- int lastLineRow = this.Lines.Count > 0 ? this.Lines.Count - 1 : 0;\r
- int lastLineHeadIndex = this.GetIndexFromLineNumber(lastLineRow);\r
- int lastLineLength = this.GetLengthFromLineNumber(lastLineRow);\r
-\r
- if (lastLineLength != 0 && this.Document[Document.Length - 1] == Document.NewLine)\r
- {\r
- int realIndex = lastLineHeadIndex + lastLineLength;\r
- if (lastLineRow >= this.stepRow)\r
- realIndex -= this.stepLength;\r
- dummyLine = new LineToIndexTableData(realIndex, 0, true,false, null);\r
- this.Lines.Add(dummyLine);\r
- }\r
- }\r
-\r
- void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)\r
- {\r
- if (this.Lines.Count == 0)\r
- {\r
- this.stepRow = STEP_ROW_IS_NONE;\r
- this.stepLength = 0;\r
- return;\r
- }\r
-\r
- if (this.stepRow == STEP_ROW_IS_NONE)\r
- {\r
- this.stepRow = startRow;\r
- this.stepLength = deltaLength;\r
- return;\r
- }\r
-\r
-\r
- if (startRow < this.stepRow)\r
- {\r
- //ドキュメントの後半部分をごっそり削除した場合、this.stepRow >= this.Lines.Countになる可能性がある\r
- if (this.stepRow >= this.Lines.Count)\r
- this.stepRow = this.Lines.Count - 1;\r
- for (int i = this.stepRow; i > startRow; i--)\r
- this.Lines[i].Index -= this.stepLength;\r
- }\r
- else if (startRow > this.stepRow)\r
- {\r
- for (int i = this.stepRow + 1; i < startRow; i++)\r
- this.Lines[i].Index += this.stepLength;\r
- }\r
-\r
- this.stepRow = startRow;\r
- this.stepLength += deltaLength;\r
-\r
- this.ValidateLines();\r
- }\r
-\r
- void ValidateLines()\r
- {\r
-#if DEBUG\r
- int nextIndex = 0;\r
- for (int i = 0; i < this.Lines.Count; i++)\r
- {\r
- int lineHeadIndex = this.GetLineHeadIndex(i);\r
- if (lineHeadIndex != nextIndex)\r
- {\r
- Debug.WriteLine("Invaild Line");\r
- System.Diagnostics.Debugger.Break();\r
- }\r
- nextIndex = lineHeadIndex + this.Lines[i].Length;\r
- }\r
-#endif\r
- }\r
-\r
- /// <summary>\r
- /// 行番号をインデックスに変換します\r
- /// </summary>\r
- /// <param name="row">行番号</param>\r
- /// <returns>0から始まるインデックスを返す</returns>\r
- public int GetIndexFromLineNumber(int row)\r
- {\r
- if (row < 0 || row > this.Lines.Count)\r
- throw new ArgumentOutOfRangeException();\r
- return this.GetLineHeadIndex(row);\r
- }\r
-\r
- /// <summary>\r
- /// 行の長さを得ます\r
- /// </summary>\r
- /// <param name="row">行番号</param>\r
- /// <returns>行の文字長を返します</returns>\r
- public int GetLengthFromLineNumber(int row)\r
- {\r
- if (row < 0 || row > this.Lines.Count)\r
- throw new ArgumentOutOfRangeException();\r
- return this.Lines[row].Length;\r
- }\r
-\r
- /// <summary>\r
- /// 更新フラグを取得します\r
- /// </summary>\r
- /// <param name="row">行番号</param>\r
- /// <returns>更新されていれば真。そうでなければ偽</returns>\r
- public bool GetDirtyFlag(int row)\r
- {\r
- if (row < 0 || row > this.Lines.Count)\r
- throw new ArgumentOutOfRangeException();\r
- return this.Lines[row].Dirty;\r
- }\r
-\r
- internal ITextLayout GetLayout(int row)\r
- {\r
- if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)\r
- {\r
- this.Lines[row].Layout.Dispose();\r
- this.Lines[row].Layout = null;\r
- }\r
- if (this.Lines[row].Layout == null || this.Lines[row].Layout.Disposed)\r
- this.Lines[row].Layout = this.CreateLayout(row);\r
- return this.Lines[row].Layout;\r
- }\r
-\r
- internal event EventHandler<CreateLayoutEventArgs> CreateingLayout;\r
-\r
- ITextLayout CreateLayout(int row)\r
- {\r
- ITextLayout layout;\r
- LineToIndexTableData lineData = this.Lines[row];\r
- if (lineData.Length == 0)\r
- {\r
- layout = this.render.CreateLaytout("", null, null);\r
- }\r
- else\r
- {\r
- int lineHeadIndex = this.GetLineHeadIndex(row);\r
-\r
- string content = this.Document.ToString(lineHeadIndex, lineData.Length);\r
-\r
- if (this.CreateingLayout != null)\r
- this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));\r
- \r
- var markerRange = from id in this.Document.Markers.IDs\r
- from s in this.Document.Markers.Get(id,lineHeadIndex,lineData.Length)\r
- let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)\r
- select n;\r
- layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange);\r
- }\r
-\r
- if (this.CacheEntries.Count > MaxEntries)\r
- {\r
- ITextLayout oldItem = this.CacheEntries.Dequeue();\r
- oldItem.Dispose();\r
- }\r
- this.CacheEntries.Enqueue(layout);\r
-\r
- return layout;\r
- }\r
-\r
- int lastLineNumber;\r
- /// <summary>\r
- /// インデックスを行番号に変換します\r
- /// </summary>\r
- /// <param name="index">インデックス</param>\r
- /// <returns>行番号を返します</returns>\r
- public int GetLineNumberFromIndex(int index)\r
- {\r
- if (index < 0)\r
- throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");\r
-\r
- if (index == 0 && this.Lines.Count > 0)\r
- return 0;\r
-\r
- LineToIndexTableData line;\r
- int lineHeadIndex;\r
-\r
- if (lastLineNumber < this.Lines.Count - 1)\r
- {\r
- line = this.Lines[lastLineNumber];\r
- lineHeadIndex = this.GetLineHeadIndex(lastLineNumber);\r
- if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
- return lastLineNumber;\r
- }\r
-\r
- int left = 0, right = this.Lines.Count - 1, mid;\r
- while (left <= right)\r
- {\r
- mid = (left + right) / 2;\r
- line = this.Lines[mid];\r
- lineHeadIndex = this.GetLineHeadIndex(mid);\r
- if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
- {\r
- lastLineNumber = mid;\r
- return mid;\r
- }\r
- if (index < lineHeadIndex)\r
- {\r
- right = mid - 1;\r
- }\r
- else\r
- {\r
- left = mid + 1;\r
- }\r
- }\r
-\r
- int lastRow = this.Lines.Count - 1;\r
- line = this.Lines[lastRow];\r
- lineHeadIndex = this.GetLineHeadIndex(lastRow);\r
- if (index >= lineHeadIndex && index <= lineHeadIndex + line.Length) //最終行長+1までキャレットが移動する可能性があるので\r
- {\r
- lastLineNumber = this.Lines.Count - 1;\r
- return lastLineNumber;\r
- }\r
-\r
- throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");\r
- }\r
-\r
- /// <summary>\r
- /// インデックスからテキストポイントに変換します\r
- /// </summary>\r
- /// <param name="index">インデックス</param>\r
- /// <returns>TextPoint構造体を返します</returns>\r
- public TextPoint GetTextPointFromIndex(int index)\r
- {\r
- TextPoint tp = new TextPoint();\r
- tp.row = GetLineNumberFromIndex(index);\r
- tp.col = index - this.GetLineHeadIndex(tp.row);\r
- Debug.Assert(tp.row < this.Lines.Count && tp.col <= this.Lines[tp.row].Length);\r
- return tp;\r
- }\r
-\r
- /// <summary>\r
- /// テキストポイントからインデックスに変換します\r
- /// </summary>\r
- /// <param name="tp">TextPoint構造体</param>\r
- /// <returns>インデックスを返します</returns>\r
- public int GetIndexFromTextPoint(TextPoint tp)\r
- {\r
- if (tp == TextPoint.Null)\r
- throw new ArgumentNullException("TextPoint.Null以外の値でなければなりません");\r
- if(tp.row < 0 || tp.row > this.Lines.Count)\r
- throw new ArgumentOutOfRangeException("tp.rowが設定できる範囲を超えています");\r
- if (tp.col < 0 || tp.col > this.Lines[tp.row].Length)\r
- throw new ArgumentOutOfRangeException("tp.colが設定できる範囲を超えています");\r
- return this.GetLineHeadIndex(tp.row) + tp.col;\r
- }\r
-\r
- /// <summary>\r
- /// フォールディングを再生成します\r
- /// </summary>\r
- /// <param name="force">ドキュメントが更新されていなくても再生成する</param>\r
- /// <returns>生成された場合は真を返す</returns>\r
- /// <remarks>デフォルトではドキュメントが更新されている時にだけ再生成されます</remarks>\r
- public bool GenerateFolding(bool force = false)\r
- {\r
- if (this.Document.Length == 0 || this.Document.IsLocked)\r
- return false;\r
- long nowTick = DateTime.Now.Ticks;\r
- bool sync = force || !this._IsSync;\r
- if (sync || Math.Abs(nowTick - this.lastUpdateTicks) >= AllowCallTicks)\r
- {\r
- this.GenerateFolding(0, this.Document.Length - 1);\r
- this.lastUpdateTicks = nowTick;\r
- return true;\r
- }\r
- return false;\r
- }\r
-\r
- void GenerateFolding(int start, int end)\r
- {\r
- if (start > end)\r
- throw new ArgumentException("start <= endである必要があります");\r
- if (this.FoldingStrategy != null)\r
- {\r
- //再生成するとすべて展開状態になってしまうので、閉じてるやつだけを保存しておく\r
- FoldingItem[] closed_items = this.FoldingCollection.Where((e)=> { return !e.Expand; }).ToArray();\r
-\r
- this.FoldingCollection.Clear();\r
-\r
- var items = this.FoldingStrategy.AnalyzeDocument(this.Document, start, end)\r
- .Where((item) =>\r
- {\r
- int startRow = this.GetLineNumberFromIndex(item.Start);\r
- int endRow = this.GetLineNumberFromIndex(item.End);\r
- return startRow != endRow;\r
- })\r
- .Select((item) => item);\r
- this.FoldingCollection.AddRange(items);\r
-\r
- this.FoldingCollection.ApplyExpandStatus(closed_items);\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// フォールディングをすべて削除します\r
- /// </summary>\r
- public void ClearFolding()\r
- {\r
- this.FoldingCollection.Clear();\r
- this._IsSync = false;\r
- }\r
-\r
- /// <summary>\r
- /// すべての行に対しシンタックスハイライトを行います\r
- /// </summary>\r
- public bool HilightAll(bool force = false)\r
- {\r
- if (this.Hilighter == null || this.Document.IsLocked)\r
- return false;\r
-\r
- long nowTick = DateTime.Now.Ticks;\r
- bool sync = force || !this._IsSync;\r
- if (sync || Math.Abs(nowTick - this.lastUpdateTicks) >= AllowCallTicks)\r
- {\r
- for (int i = 0; i < this.Lines.Count; i++)\r
- this.HilightLine(i);\r
-\r
- this.Hilighter.Reset();\r
- this.ClearLayoutCache();\r
-\r
- this.lastUpdateTicks = nowTick;\r
-\r
- return true;\r
- }\r
- return false;\r
- }\r
-\r
- /// <summary>\r
- /// ハイライト関連の情報をすべて削除します\r
- /// </summary>\r
- public void ClearHilight()\r
- {\r
- foreach (LineToIndexTableData line in this.Lines)\r
- line.Syntax = null;\r
- this.ClearLayoutCache();\r
- }\r
-\r
- /// <summary>\r
- /// すべて削除します\r
- /// </summary>\r
- internal void Clear()\r
- {\r
- this.ClearLayoutCache();\r
- this.FoldingCollection.Clear();\r
- this.Lines.Clear();\r
- LineToIndexTableData dummy = new LineToIndexTableData();\r
- this.Lines.Add(dummy);\r
- this.stepRow = STEP_ROW_IS_NONE;\r
- this.stepLength = 0;\r
- Debug.WriteLine("Clear");\r
- }\r
-\r
- private void HilightLine(int row)\r
- {\r
- //シンタックスハイライトを行う\r
- List<SyntaxInfo> syntax = new List<SyntaxInfo>();\r
- string str = this[row];\r
- int level = this.Hilighter.DoHilight(str, str.Length, (s) =>\r
- {\r
- if (s.type == TokenType.None || s.type == TokenType.Control)\r
- return;\r
- if (str[s.index + s.length - 1] == Document.NewLine)\r
- s.length--;\r
- syntax.Add(new SyntaxInfo(s.index, s.length, s.type));\r
- });\r
-\r
- LineToIndexTableData lineData = this.Lines[row];\r
- lineData.Syntax = syntax.ToArray();\r
-\r
- }\r
-\r
- #region IEnumerable<string> メンバー\r
-\r
- /// <summary>\r
- /// コレクションを反復処理するためのIEnumeratorを返す\r
- /// </summary>\r
- /// <returns>IEnumeratorオブジェクト</returns>\r
- public IEnumerator<string> GetEnumerator()\r
- {\r
- for (int i = 0; i < this.Lines.Count; i++)\r
- yield return this[i];\r
- }\r
-\r
- #endregion\r
-\r
- #region IEnumerable メンバー\r
-\r
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()\r
- {\r
- for (int i = 0; i < this.Lines.Count; i++)\r
- yield return this[i];\r
- }\r
-\r
- #endregion\r
- }\r
-\r
-}\r
+/*
+ * Copyright (C) 2013 FooProject
+ * * 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
+ * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+using System;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Linq;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Slusser.Collections.Generic;
+
+namespace FooEditEngine
+{
+ internal interface ITextLayout : IDisposable
+ {
+ /// <summary>
+ /// 文字列の幅
+ /// </summary>
+ double Width
+ {
+ get;
+ }
+
+ /// <summary>
+ /// 文字列の高さ
+ /// </summary>
+ double Height
+ {
+ get;
+ }
+
+ /// <summary>
+ /// Disposeされているなら真を返す
+ /// </summary>
+ bool Disposed
+ {
+ get;
+ }
+
+ /// <summary>
+ /// 破棄すべきなら真。そうでなければ偽
+ /// </summary>
+ bool Invaild
+ {
+ get;
+ }
+
+ /// <summary>
+ /// 桁方向の座標に対応するインデックスを得る
+ /// </summary>
+ /// <param name="colpos">桁方向の座標</param>
+ /// <returns>インデックス</returns>
+ /// <remarks>行番号の幅は考慮されてないのでView以外のクラスは呼び出さないでください</remarks>
+ int GetIndexFromColPostion(double colpos);
+
+ /// <summary>
+ /// インデックスに対応する文字の幅を得る
+ /// </summary>
+ /// <param name="index">インデックス</param>
+ /// <returns>文字の幅</returns>
+ double GetWidthFromIndex(int index);
+
+ /// <summary>
+ /// インデックスに対応する桁方向の座標を得る
+ /// </summary>
+ /// <param name="index">インデックス</param>
+ /// <returns>桁方向の座標</returns>
+ /// <remarks>行頭にEOFが含まれている場合、0が返ります</remarks>
+ double GetColPostionFromIndex(int index);
+
+ /// <summary>
+ /// 適切な位置にインデックスを調整する
+ /// </summary>
+ /// <param name="index">インデックス</param>
+ /// <param name="flow">真の場合は隣接するクラスターを指すように調整し、
+ /// そうでない場合は対応するクラスターの先頭を指すように調整します</param>
+ /// <returns>調整後のインデックス</returns>
+ int AlignIndexToNearestCluster(int index, AlignDirection flow);
+ }
+
+ internal class SpilitStringEventArgs : EventArgs
+ {
+ public Document buffer;
+ public int index;
+ public int length;
+ public int row;
+ public SpilitStringEventArgs(Document buf, int index, int length,int row)
+ {
+ this.buffer = buf;
+ this.index = index;
+ this.length = length;
+ this.row = row;
+ }
+ }
+
+ internal struct SyntaxInfo
+ {
+ public TokenType type;
+ public int index;
+ public int length;
+ public SyntaxInfo(int index, int length, TokenType type)
+ {
+ this.type = type;
+ this.index = index;
+ this.length = length;
+ }
+ }
+
+ internal enum EncloserType
+ {
+ None,
+ Begin,
+ Now,
+ End,
+ }
+
+ internal class LineToIndexTableData : IDisposable
+ {
+ /// <summary>
+ /// 行の先頭。正しい行の先頭位置を取得するにはGetLineHeadIndex()を使用してください
+ /// </summary>
+ public int Index;
+ /// <summary>
+ /// 行の長さ
+ /// </summary>
+ public int Length;
+ /// <summary>
+ /// 改行マークかEOFなら真を返す
+ /// </summary>
+ public bool LineEnd;
+ public SyntaxInfo[] Syntax;
+ public EncloserType EncloserType;
+ internal ITextLayout Layout;
+ public bool Dirty = false;
+
+ /// <summary>
+ /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
+ /// </summary>
+ public LineToIndexTableData()
+ {
+ }
+
+ /// <summary>
+ /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
+ /// </summary>
+ public LineToIndexTableData(int index, int length, bool lineend,bool dirty, SyntaxInfo[] syntax)
+ {
+ this.Index = index;
+ this.Length = length;
+ this.LineEnd = lineend;
+ this.Syntax = syntax;
+ this.EncloserType = EncloserType.None;
+ this.Dirty = dirty;
+ }
+
+ public void Dispose()
+ {
+ if(this.Layout != null)
+ this.Layout.Dispose();
+ }
+ }
+
+ internal delegate IList<LineToIndexTableData> SpilitStringEventHandler(object sender, SpilitStringEventArgs e);
+
+ internal sealed class CreateLayoutEventArgs
+ {
+ /// <summary>
+ /// 開始インデックス
+ /// </summary>
+ public int Index
+ {
+ get;
+ private set;
+ }
+ /// <summary>
+ /// 長さ
+ /// </summary>
+ public int Length
+ {
+ get;
+ private set;
+ }
+ /// <summary>
+ /// 文字列
+ /// </summary>
+ public string Content
+ {
+ get;
+ private set;
+ }
+ public CreateLayoutEventArgs(int index, int length,string content)
+ {
+ this.Index = index;
+ this.Length = length;
+ this.Content = content;
+ }
+ }
+
+ /// <summary>
+ /// 行番号とインデックスを相互変換するためのクラス
+ /// </summary>
+ public sealed class LineToIndexTable : IEnumerable<string>
+ {
+ const int MaxEntries = 100;
+ Queue<ITextLayout> CacheEntries = new Queue<ITextLayout>();
+ GapBuffer<LineToIndexTableData> Lines = new GapBuffer<LineToIndexTableData>();
+ Document Document;
+ long lastUpdateTicks = DateTime.Now.Ticks;
+ const long AllowCallTicks = 1000 * 10000; //see.DateTime.Ticks プロパティ
+ bool _IsSync;
+ ITextRender render;
+ int stepRow = -1,stepLength = 0;
+ const int STEP_ROW_IS_NONE = -1;
+
+ internal LineToIndexTable(Document buf)
+ {
+ this.Document = buf;
+ this.Document.Markers.Updated += Markers_Updated;
+ this.FoldingCollection = new FoldingCollection();
+ this._IsSync = true;
+#if DEBUG && !NETFX_CORE
+ if (!Debugger.IsAttached)
+ {
+ Guid guid = Guid.NewGuid();
+ string path = string.Format("{0}\\footextbox_lti_debug_{1}.log", System.IO.Path.GetTempPath(), guid);
+ Debug.Listeners.Add(new TextWriterTraceListener(path));
+ Debug.AutoFlush = true;
+ }
+#endif
+ }
+
+ void Markers_Updated(object sender, EventArgs e)
+ {
+ this.ClearLayoutCache();
+ }
+
+ /// <summary>
+ /// ITextRenderインターフェイスのインスタンス。必ずセットすること
+ /// </summary>
+ internal ITextRender Render
+ {
+ get { return this.render; }
+ set
+ {
+ this.render = value;
+ }
+ }
+
+ internal SpilitStringEventHandler SpilitString;
+
+ /// <summary>
+ /// 行数を返す
+ /// </summary>
+ public int Count
+ {
+ get { return this.Lines.Count; }
+ }
+
+ /// <summary>
+ /// 折り畳み関係の情報を収めたコレクション
+ /// </summary>
+ public FoldingCollection FoldingCollection
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// シンタックスハイライター
+ /// </summary>
+ internal IHilighter Hilighter { get; set; }
+
+ internal IFoldingStrategy FoldingStrategy { get; set; }
+
+ /// <summary>
+ /// 保持しているレイアウトキャッシュをクリアーする
+ /// </summary>
+ public void ClearLayoutCache()
+ {
+ foreach (ITextLayout data in this.CacheEntries)
+ {
+ data.Dispose();
+ }
+ this.CacheEntries.Clear();
+ }
+
+ /// <summary>
+ /// 行番号に対応する文字列を返します
+ /// </summary>
+ /// <param name="n"></param>
+ /// <returns></returns>
+ public string this[int n]
+ {
+ get
+ {
+ LineToIndexTableData data = this.Lines[n];
+ string str = this.Document.ToString(this.GetLineHeadIndex(n), data.Length);
+
+ return str;
+ }
+ }
+
+ /// <summary>
+ /// 更新フラグを更新しないなら真
+ /// </summary>
+ public bool IsFrozneDirtyFlag
+ {
+ get;
+ set;
+ }
+
+ int GetLineHeadIndex(int row)
+ {
+ if (this.Lines.Count == 0)
+ return 0;
+ if (this.stepRow != STEP_ROW_IS_NONE && row > this.stepRow)
+ return this.Lines[row].Index + this.stepLength;
+ else
+ return this.Lines[row].Index;
+ }
+
+ internal LineToIndexTableData CreateLineToIndexTableData(int index, int length, bool lineend, SyntaxInfo[] syntax)
+ {
+ LineToIndexTableData result = new LineToIndexTableData(index, length, lineend,this.IsFrozneDirtyFlag == false, syntax);
+ return result;
+ }
+
+ internal void UpdateLineAsReplace(int row,int removedLength, int insertedLength)
+ {
+ int deltaLength = insertedLength - removedLength;
+
+ this.Lines[row] = new LineToIndexTableData(this.GetLineHeadIndex(row), this.GetLengthFromLineNumber(row) + deltaLength, true, true, null);
+
+ //行テーブルを更新する
+ this.UpdateLineHeadIndex(deltaLength, row, 1);
+
+ this.FoldingCollection.UpdateData(this.Document, this.GetLineHeadIndex(row), insertedLength, removedLength);
+
+ this._IsSync = false;
+
+ this.lastUpdateTicks = DateTime.Now.Ticks;
+ }
+
+ internal void UpdateAsReplace(int index, int removedLength, int insertedLength)
+ {
+#if DEBUG
+ Debug.WriteLine("Replaced Index:{0} RemoveLength:{1} InsertLength:{2}", index, removedLength, insertedLength);
+#endif
+ int startRow, endRow;
+ GetRemoveRange(index, removedLength, out startRow, out endRow);
+
+ int deltaLength = insertedLength - removedLength;
+
+ var result = GetAnalyzeLength(startRow, endRow, index, removedLength, insertedLength);
+ int HeadIndex = result.Item1;
+ int analyzeLength = result.Item2;
+
+ //挿入範囲内のドキュメントから行を生成する
+ SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);
+ IList<LineToIndexTableData> newLines = SpilitString(this, e);
+
+ //消すべき行が複数ある場合は消すが、そうでない場合は最適化のため長さを変えるだけにとどめておく
+ int removeCount = endRow - startRow + 1;
+ if (removeCount == 1 && newLines.Count == 1)
+ {
+ this.Lines[startRow] = newLines.First();
+ }
+ else
+ {
+ this.RemoveLine(startRow, removeCount);
+
+ //行を挿入する
+ this.InsertLine(startRow, newLines, removeCount, deltaLength);
+ }
+
+ //行テーブルを更新する
+ this.UpdateLineHeadIndex(deltaLength, startRow, newLines.Count);
+
+ this.AddDummyLine();
+
+ this.FoldingCollection.UpdateData(this.Document, index, insertedLength, removedLength);
+
+ this._IsSync = false;
+
+ this.lastUpdateTicks = DateTime.Now.Ticks;
+ }
+
+ void GetRemoveRange(int index,int length,out int startRow,out int endRow)
+ {
+ startRow = this.GetLineNumberFromIndex(index);
+ while (startRow > 0 && this.Lines[startRow - 1].LineEnd == false)
+ startRow--;
+
+ endRow = this.GetLineNumberFromIndex(index + length);
+ while (endRow < this.Lines.Count && this.Lines[endRow].LineEnd == false)
+ endRow++;
+ if (endRow >= this.Lines.Count)
+ endRow = this.Lines.Count - 1;
+ }
+
+ Tuple<int,int> GetAnalyzeLength(int startRow,int endRow,int updateStartIndex,int removedLength,int insertedLength)
+ {
+ int HeadIndex = this.GetIndexFromLineNumber(startRow);
+ int LastIndex = this.GetIndexFromLineNumber(endRow) + this.GetLengthFromLineNumber(endRow) - 1;
+
+ //SpilitStringの対象となる範囲を求める
+ int fisrtPartLength = updateStartIndex - HeadIndex;
+ int secondPartLength = LastIndex - (updateStartIndex + removedLength - 1);
+ int analyzeLength = fisrtPartLength + secondPartLength + insertedLength;
+ Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);
+
+ return new Tuple<int, int>(HeadIndex, analyzeLength);
+ }
+
+ void RemoveLine(int startRow, int removeCount)
+ {
+ for (int i = startRow; i < startRow + removeCount; i++)
+ this.Lines[i].Dispose();
+
+ this.Lines.RemoveRange(startRow, removeCount);
+ }
+
+ void InsertLine(int startRow, IList<LineToIndexTableData> collection,int removeCount, int deltaLength)
+ {
+ int newCount = collection.Count;
+ if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)
+ {
+ //stepRowは1か2のうち、大きな方になる
+ // 1.stepRow - (削除された行数 - 挿入された行数)
+ // 2.行の挿入箇所
+ //行が削除や置換された場合、1の処理をしないと正しいIndexが求められない
+ this.stepRow = Math.Max(this.stepRow - (removeCount - newCount), startRow);
+#if DEBUG
+ if (this.stepRow < 0 || this.stepRow > this.Lines.Count + newCount)
+ {
+ Debug.WriteLine("step row < 0 or step row >= lines.count");
+ Debugger.Break();
+ }
+#endif
+ }
+
+ //startRowが挿入した行の開始位置なのであらかじめ引いておく
+ for (int i = 1; i < collection.Count; i++)
+ {
+ if (this.stepRow != STEP_ROW_IS_NONE && startRow + i > this.stepRow)
+ collection[i].Index -= deltaLength + this.stepLength;
+ else
+ collection[i].Index -= deltaLength;
+ }
+
+ this.Lines.InsertRange(startRow, collection);
+ }
+
+ void AddDummyLine()
+ {
+ LineToIndexTableData dummyLine = null;
+ if (this.Lines.Count == 0)
+ {
+ dummyLine = new LineToIndexTableData();
+ this.Lines.Add(dummyLine);
+ return;
+ }
+
+ int lastLineRow = this.Lines.Count > 0 ? this.Lines.Count - 1 : 0;
+ int lastLineHeadIndex = this.GetIndexFromLineNumber(lastLineRow);
+ int lastLineLength = this.GetLengthFromLineNumber(lastLineRow);
+
+ if (lastLineLength != 0 && this.Document[Document.Length - 1] == Document.NewLine)
+ {
+ int realIndex = lastLineHeadIndex + lastLineLength;
+ if (lastLineRow >= this.stepRow)
+ realIndex -= this.stepLength;
+ dummyLine = new LineToIndexTableData(realIndex, 0, true,false, null);
+ this.Lines.Add(dummyLine);
+ }
+ }
+
+ void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)
+ {
+ if (this.Lines.Count == 0)
+ {
+ this.stepRow = STEP_ROW_IS_NONE;
+ this.stepLength = 0;
+ return;
+ }
+
+ if (this.stepRow == STEP_ROW_IS_NONE)
+ {
+ this.stepRow = startRow;
+ this.stepLength = deltaLength;
+ return;
+ }
+
+
+ if (startRow < this.stepRow)
+ {
+ //ドキュメントの後半部分をごっそり削除した場合、this.stepRow >= this.Lines.Countになる可能性がある
+ if (this.stepRow >= this.Lines.Count)
+ this.stepRow = this.Lines.Count - 1;
+ for (int i = this.stepRow; i > startRow; i--)
+ this.Lines[i].Index -= this.stepLength;
+ }
+ else if (startRow > this.stepRow)
+ {
+ for (int i = this.stepRow + 1; i < startRow; i++)
+ this.Lines[i].Index += this.stepLength;
+ }
+
+ this.stepRow = startRow;
+ this.stepLength += deltaLength;
+
+ this.ValidateLines();
+ }
+
+ void ValidateLines()
+ {
+#if DEBUG
+ int nextIndex = 0;
+ for (int i = 0; i < this.Lines.Count; i++)
+ {
+ int lineHeadIndex = this.GetLineHeadIndex(i);
+ if (lineHeadIndex != nextIndex)
+ {
+ Debug.WriteLine("Invaild Line");
+ System.Diagnostics.Debugger.Break();
+ }
+ nextIndex = lineHeadIndex + this.Lines[i].Length;
+ }
+#endif
+ }
+
+ /// <summary>
+ /// 行番号をインデックスに変換します
+ /// </summary>
+ /// <param name="row">行番号</param>
+ /// <returns>0から始まるインデックスを返す</returns>
+ public int GetIndexFromLineNumber(int row)
+ {
+ if (row < 0 || row > this.Lines.Count)
+ throw new ArgumentOutOfRangeException();
+ return this.GetLineHeadIndex(row);
+ }
+
+ /// <summary>
+ /// 行の長さを得ます
+ /// </summary>
+ /// <param name="row">行番号</param>
+ /// <returns>行の文字長を返します</returns>
+ public int GetLengthFromLineNumber(int row)
+ {
+ if (row < 0 || row > this.Lines.Count)
+ throw new ArgumentOutOfRangeException();
+ return this.Lines[row].Length;
+ }
+
+ /// <summary>
+ /// 更新フラグを取得します
+ /// </summary>
+ /// <param name="row">行番号</param>
+ /// <returns>更新されていれば真。そうでなければ偽</returns>
+ public bool GetDirtyFlag(int row)
+ {
+ if (row < 0 || row > this.Lines.Count)
+ throw new ArgumentOutOfRangeException();
+ return this.Lines[row].Dirty;
+ }
+
+ internal ITextLayout GetLayout(int row)
+ {
+ if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)
+ {
+ this.Lines[row].Layout.Dispose();
+ this.Lines[row].Layout = null;
+ }
+ if (this.Lines[row].Layout == null || this.Lines[row].Layout.Disposed)
+ this.Lines[row].Layout = this.CreateLayout(row);
+ return this.Lines[row].Layout;
+ }
+
+ internal event EventHandler<CreateLayoutEventArgs> CreateingLayout;
+
+ ITextLayout CreateLayout(int row)
+ {
+ ITextLayout layout;
+ LineToIndexTableData lineData = this.Lines[row];
+ if (lineData.Length == 0)
+ {
+ layout = this.render.CreateLaytout("", null, null);
+ }
+ else
+ {
+ int lineHeadIndex = this.GetLineHeadIndex(row);
+
+ string content = this.Document.ToString(lineHeadIndex, lineData.Length);
+
+ if (this.CreateingLayout != null)
+ this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));
+
+ var markerRange = from id in this.Document.Markers.IDs
+ from s in this.Document.Markers.Get(id,lineHeadIndex,lineData.Length)
+ let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
+ select n;
+ layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange);
+ }
+
+ if (this.CacheEntries.Count > MaxEntries)
+ {
+ ITextLayout oldItem = this.CacheEntries.Dequeue();
+ oldItem.Dispose();
+ }
+ this.CacheEntries.Enqueue(layout);
+
+ return layout;
+ }
+
+ int lastLineNumber;
+ /// <summary>
+ /// インデックスを行番号に変換します
+ /// </summary>
+ /// <param name="index">インデックス</param>
+ /// <returns>行番号を返します</returns>
+ public int GetLineNumberFromIndex(int index)
+ {
+ if (index < 0)
+ throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");
+
+ if (index == 0 && this.Lines.Count > 0)
+ return 0;
+
+ LineToIndexTableData line;
+ int lineHeadIndex;
+
+ if (lastLineNumber < this.Lines.Count - 1)
+ {
+ line = this.Lines[lastLineNumber];
+ lineHeadIndex = this.GetLineHeadIndex(lastLineNumber);
+ if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)
+ return lastLineNumber;
+ }
+
+ int left = 0, right = this.Lines.Count - 1, mid;
+ while (left <= right)
+ {
+ mid = (left + right) / 2;
+ line = this.Lines[mid];
+ lineHeadIndex = this.GetLineHeadIndex(mid);
+ if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)
+ {
+ lastLineNumber = mid;
+ return mid;
+ }
+ if (index < lineHeadIndex)
+ {
+ right = mid - 1;
+ }
+ else
+ {
+ left = mid + 1;
+ }
+ }
+
+ int lastRow = this.Lines.Count - 1;
+ line = this.Lines[lastRow];
+ lineHeadIndex = this.GetLineHeadIndex(lastRow);
+ if (index >= lineHeadIndex && index <= lineHeadIndex + line.Length) //最終行長+1までキャレットが移動する可能性があるので
+ {
+ lastLineNumber = this.Lines.Count - 1;
+ return lastLineNumber;
+ }
+
+ throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");
+ }
+
+ /// <summary>
+ /// インデックスからテキストポイントに変換します
+ /// </summary>
+ /// <param name="index">インデックス</param>
+ /// <returns>TextPoint構造体を返します</returns>
+ public TextPoint GetTextPointFromIndex(int index)
+ {
+ TextPoint tp = new TextPoint();
+ tp.row = GetLineNumberFromIndex(index);
+ tp.col = index - this.GetLineHeadIndex(tp.row);
+ Debug.Assert(tp.row < this.Lines.Count && tp.col <= this.Lines[tp.row].Length);
+ return tp;
+ }
+
+ /// <summary>
+ /// テキストポイントからインデックスに変換します
+ /// </summary>
+ /// <param name="tp">TextPoint構造体</param>
+ /// <returns>インデックスを返します</returns>
+ public int GetIndexFromTextPoint(TextPoint tp)
+ {
+ if (tp == TextPoint.Null)
+ throw new ArgumentNullException("TextPoint.Null以外の値でなければなりません");
+ if(tp.row < 0 || tp.row > this.Lines.Count)
+ throw new ArgumentOutOfRangeException("tp.rowが設定できる範囲を超えています");
+ if (tp.col < 0 || tp.col > this.Lines[tp.row].Length)
+ throw new ArgumentOutOfRangeException("tp.colが設定できる範囲を超えています");
+ return this.GetLineHeadIndex(tp.row) + tp.col;
+ }
+
+ /// <summary>
+ /// フォールディングを再生成します
+ /// </summary>
+ /// <param name="force">ドキュメントが更新されていなくても再生成する</param>
+ /// <returns>生成された場合は真を返す</returns>
+ /// <remarks>デフォルトではドキュメントが更新されている時にだけ再生成されます</remarks>
+ public bool GenerateFolding(bool force = false)
+ {
+ if (this.Document.Length == 0 || this.Document.IsLocked)
+ return false;
+ long nowTick = DateTime.Now.Ticks;
+ bool sync = force || !this._IsSync;
+ if (sync || Math.Abs(nowTick - this.lastUpdateTicks) >= AllowCallTicks)
+ {
+ this.GenerateFolding(0, this.Document.Length - 1);
+ this.lastUpdateTicks = nowTick;
+ return true;
+ }
+ return false;
+ }
+
+ void GenerateFolding(int start, int end)
+ {
+ if (start > end)
+ throw new ArgumentException("start <= endである必要があります");
+ if (this.FoldingStrategy != null)
+ {
+ //再生成するとすべて展開状態になってしまうので、閉じてるやつだけを保存しておく
+ FoldingItem[] closed_items = this.FoldingCollection.Where((e)=> { return !e.Expand; }).ToArray();
+
+ this.FoldingCollection.Clear();
+
+ var items = this.FoldingStrategy.AnalyzeDocument(this.Document, start, end)
+ .Where((item) =>
+ {
+ int startRow = this.GetLineNumberFromIndex(item.Start);
+ int endRow = this.GetLineNumberFromIndex(item.End);
+ return startRow != endRow;
+ })
+ .Select((item) => item);
+ this.FoldingCollection.AddRange(items);
+
+ this.FoldingCollection.ApplyExpandStatus(closed_items);
+ }
+ }
+
+ /// <summary>
+ /// フォールディングをすべて削除します
+ /// </summary>
+ public void ClearFolding()
+ {
+ this.FoldingCollection.Clear();
+ this._IsSync = false;
+ }
+
+ /// <summary>
+ /// すべての行に対しシンタックスハイライトを行います
+ /// </summary>
+ public bool HilightAll(bool force = false)
+ {
+ if (this.Hilighter == null || this.Document.IsLocked)
+ return false;
+
+ long nowTick = DateTime.Now.Ticks;
+ bool sync = force || !this._IsSync;
+ if (sync || Math.Abs(nowTick - this.lastUpdateTicks) >= AllowCallTicks)
+ {
+ for (int i = 0; i < this.Lines.Count; i++)
+ this.HilightLine(i);
+
+ this.Hilighter.Reset();
+ this.ClearLayoutCache();
+
+ this.lastUpdateTicks = nowTick;
+
+ return true;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// ハイライト関連の情報をすべて削除します
+ /// </summary>
+ public void ClearHilight()
+ {
+ foreach (LineToIndexTableData line in this.Lines)
+ line.Syntax = null;
+ this.ClearLayoutCache();
+ }
+
+ /// <summary>
+ /// すべて削除します
+ /// </summary>
+ internal void Clear()
+ {
+ this.ClearLayoutCache();
+ this.FoldingCollection.Clear();
+ this.Lines.Clear();
+ LineToIndexTableData dummy = new LineToIndexTableData();
+ this.Lines.Add(dummy);
+ this.stepRow = STEP_ROW_IS_NONE;
+ this.stepLength = 0;
+ Debug.WriteLine("Clear");
+ }
+
+ private void HilightLine(int row)
+ {
+ //シンタックスハイライトを行う
+ List<SyntaxInfo> syntax = new List<SyntaxInfo>();
+ string str = this[row];
+ int level = this.Hilighter.DoHilight(str, str.Length, (s) =>
+ {
+ if (s.type == TokenType.None || s.type == TokenType.Control)
+ return;
+ if (str[s.index + s.length - 1] == Document.NewLine)
+ s.length--;
+ syntax.Add(new SyntaxInfo(s.index, s.length, s.type));
+ });
+
+ LineToIndexTableData lineData = this.Lines[row];
+ lineData.Syntax = syntax.ToArray();
+
+ }
+
+ #region IEnumerable<string> メンバー
+
+ /// <summary>
+ /// コレクションを反復処理するためのIEnumeratorを返す
+ /// </summary>
+ /// <returns>IEnumeratorオブジェクト</returns>
+ public IEnumerator<string> GetEnumerator()
+ {
+ for (int i = 0; i < this.Lines.Count; i++)
+ yield return this[i];
+ }
+
+ #endregion
+
+ #region IEnumerable メンバー
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ for (int i = 0; i < this.Lines.Count; i++)
+ yield return this[i];
+ }
+
+ #endregion
+ }
+
+}