\r
internal class LineToIndexTableData : IDisposable\r
{\r
- ITextLayout _layout;\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
- {\r
- get\r
- {\r
- if (this._layout != null && this._layout.Invaild)\r
- {\r
- this._layout.Dispose();\r
- this._layout = null;\r
- }\r
- if (this._layout == null || this._layout.Disposed)\r
- this._layout = this.CreateLayout(this);\r
- return _layout;\r
- }\r
- }\r
- internal Func<LineToIndexTableData, ITextLayout> CreateLayout;\r
+ internal ITextLayout Layout;\r
\r
/// <summary>\r
/// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください\r
\r
public void Dispose()\r
{\r
- if(this._layout != null)\r
- this._layout.Dispose();\r
+ if(this.Layout != null)\r
+ this.Layout.Dispose();\r
}\r
}\r
\r
bool _UrlMarker,_IsSync;\r
Regex urlPattern = new Regex("(http|https|ftp)(:\\/\\/[-_.!~*\\'()a-zA-Z0-9;\\/?:\\@&=+\\$,%#]+)");\r
ITextRender render;\r
+ int stepRow = -1,stepLength = 0;\r
+ const int STEP_ROW_IS_NONE = -1;\r
\r
internal LineToIndexTable(Document buf, ITextRender r)\r
{\r
this.render = r;\r
this.FoldingCollection = new FoldingCollection();\r
this._IsSync = true;\r
+#if DEBUG\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
get\r
{\r
LineToIndexTableData data = this.Lines[n];\r
- string str = this.Document.ToString(data.Index, data.Length);\r
+ string str = this.Document.ToString(this.GetLineHeadIndex(n), data.Length);\r
\r
return str;\r
}\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, syntax);\r
- result.CreateLayout = this.LineToIndexTableData_CreatLayout;\r
return result;\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
//削除すべき行の開始位置と終了位置を求める\r
int startRow = this.GetLineNumberFromIndex(index);\r
- if (startRow > 0 && this.Lines[startRow - 1].LineEnd == false)\r
+ while(startRow > 0 && this.Lines[startRow - 1].LineEnd == false)\r
startRow--;\r
\r
int endRow = this.GetLineNumberFromIndex(index + removedLength);\r
\r
int analyzeLength = fisrtPartLength + secondPartLength + insertedLength;\r
\r
- System.Diagnostics.Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);\r
+ Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);\r
\r
- //行を削除する\r
int removeCount = endRow - startRow + 1;\r
- for (int i = startRow; i < removeCount; i++)\r
- this.Lines[i].Dispose();\r
-\r
- this.Lines.RemoveRange(startRow, removeCount);\r
+ this.RemoveLine(startRow, removeCount);\r
\r
SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);\r
IList<LineToIndexTableData> newLines = SpilitString(this, e);\r
\r
- this.Lines.InsertRange(startRow, newLines);\r
+ int newCount = newLines.Count;\r
\r
- this.UpdateLineHeadIndex(insertedLength - removedLength, startRow + newLines.Count);\r
+ if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)\r
+ {\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
+ int deltaLength = insertedLength - removedLength;\r
+\r
+ this.InsertLine(startRow, newLines, deltaLength);\r
+\r
+ this.UpdateLineHeadIndex(deltaLength, startRow, newLines.Count);\r
\r
this.FoldingCollection.UpdateData(this.Document, index, insertedLength, removedLength);\r
\r
this._IsSync = false;\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 deltaLength)\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
- //最終行が削除された場合は追加する\r
LineToIndexTableData dummyLine = null;\r
if (this.Lines.Count == 0)\r
{\r
dummyLine = new LineToIndexTableData();\r
- dummyLine.CreateLayout = this.LineToIndexTableData_CreatLayout;\r
this.Lines.Add(dummyLine);\r
return;\r
}\r
- \r
- int lastLineRow = this.Lines.Count - 1;\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
- dummyLine = new LineToIndexTableData(lastLineHeadIndex + lastLineLength, 0, true, null);\r
- dummyLine.CreateLayout = this.LineToIndexTableData_CreatLayout;\r
+ int realIndex = lastLineHeadIndex + lastLineLength;\r
+ if (lastLineRow >= this.stepRow)\r
+ realIndex -= this.stepLength;\r
+ dummyLine = new LineToIndexTableData(realIndex, 0, true, null);\r
this.Lines.Add(dummyLine);\r
}\r
}\r
\r
- void UpdateLineHeadIndex(int deltaLength,int startRow)\r
+ void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)\r
{\r
- for (int i = startRow; i < this.Lines.Count; i++)\r
+ if (this.Lines.Count == 0)\r
{\r
- this.Lines[i].Index += deltaLength;\r
+ this.stepRow = STEP_ROW_IS_NONE;\r
+ this.stepLength = 0;\r
+ return;\r
}\r
- }\r
\r
- ITextLayout LineToIndexTableData_CreatLayout(LineToIndexTableData lineData)\r
- {\r
- ITextLayout layout;\r
- if (lineData.Length == 0)\r
+ if (this.stepRow == STEP_ROW_IS_NONE)\r
{\r
- layout = this.render.CreateLaytout("", null, null);\r
+ this.stepRow = startRow;\r
+ this.stepLength = deltaLength;\r
+ return;\r
}\r
- else\r
+\r
+\r
+ if (startRow < this.stepRow)\r
{\r
- var markerRange = from s in this.Document.Markers.Get(lineData.Index, lineData.Length)\r
- let n = Util.ConvertAbsIndexToRelIndex(s, lineData.Index, lineData.Length)\r
- select n;\r
- layout = this.render.CreateLaytout(this.Document.ToString(lineData.Index, lineData.Length), lineData.Syntax, markerRange);\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
- if (this.CacheEntries.Count > MaxEntries)\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
- ITextLayout oldItem = this.CacheEntries.Dequeue();\r
- oldItem.Dispose();\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
- this.CacheEntries.Enqueue(layout);\r
- \r
- return layout;\r
+#endif\r
}\r
\r
/// <summary>\r
{\r
if (row < 0 || row > this.Lines.Count)\r
throw new ArgumentOutOfRangeException();\r
- return this.Lines[row].Index;\r
+ return this.GetLineHeadIndex(row);\r
}\r
\r
/// <summary>\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
+ 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
+ var markerRange = from s in this.Document.Markers.Get(lineHeadIndex, lineData.Length)\r
+ let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)\r
+ select n;\r
+ layout = this.render.CreateLaytout(this.Document.ToString(lineHeadIndex, lineData.Length), 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
{\r
if (index < 0)\r
throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");\r
- \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
- if (index >= line.Index && index < line.Index + line.Length)\r
+ lineHeadIndex = this.GetLineHeadIndex(lastLineNumber);\r
+ if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
return lastLineNumber;\r
}\r
\r
{\r
mid = (left + right) / 2;\r
line = this.Lines[mid];\r
- if (index >= line.Index && index < line.Index + line.Length)\r
+ lineHeadIndex = this.GetLineHeadIndex(mid);\r
+ if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
{\r
lastLineNumber = mid;\r
return mid;\r
}\r
- if (index < line.Index)\r
+ if (index < lineHeadIndex)\r
{\r
right = mid - 1;\r
}\r
}\r
}\r
\r
- line = this.Lines.Last();\r
- if (index >= line.Index && index <= line.Index + line.Length) //最終行長+1までキャレットが移動する可能性があるので\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
+\r
throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");\r
}\r
\r
{\r
TextPoint tp = new TextPoint();\r
tp.row = GetLineNumberFromIndex(index);\r
- tp.col = index - this.Lines[tp.row].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
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.Lines[tp.row].Index + tp.col;\r
+ return this.GetLineHeadIndex(tp.row) + tp.col;\r
}\r
\r
/// <summary>\r
this.FoldingCollection.Clear();\r
this.Lines.Clear();\r
LineToIndexTableData dummy = new LineToIndexTableData();\r
- dummy.CreateLayout = this.LineToIndexTableData_CreatLayout;\r
this.Lines.Add(dummy);\r
+ this.stepRow = STEP_ROW_IS_NONE;\r
+ this.stepLength = 0;\r
+ Debug.WriteLine("Clear");\r
}\r
\r
void Hilight(int row, int rowCount)\r
public class UnitTest3
{
[TestMethod]
- public void UpdateLineTest()
+ public void InsertSingleLineTest()
{
Document doc = new Document();
DummyRender render = new DummyRender();
DummyView view = new DummyView(doc,render);
doc.Clear();
- doc.Append("a\nb\nc");
- Assert.IsTrue(view.LayoutLines[0] == "a\n" && view.LayoutLines[1] == "b\n" && view.LayoutLines[2] == "c");
- doc.Replace(0, 3, "x");
- Assert.IsTrue(view.LayoutLines[0] == "x\n");
+ doc.Append("a\nb\nc\nd");
+ Assert.IsTrue(view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "b\n" &&
+ view.LayoutLines[2] == "c\n" &&
+ view.LayoutLines[3] == "d");
+
+ doc.Insert(2, "x");
+ Assert.IsTrue(view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "xb\n" &&
+ view.LayoutLines[2] == "c\n" &&
+ view.LayoutLines[3] == "d");
+
+ doc.Insert(3, "x");
+ Assert.IsTrue(view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "xxb\n" &&
+ view.LayoutLines[2] == "c\n" &&
+ view.LayoutLines[3] == "d");
+
+ doc.Insert(6, "x");
+ Assert.IsTrue(view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "xxb\n" &&
+ view.LayoutLines[2] == "xc\n" &&
+ view.LayoutLines[3] == "d");
+
+ doc.Insert(0, "x");
+ Assert.IsTrue(view.LayoutLines[0] == "xa\n" &&
+ view.LayoutLines[1] == "xxb\n" &&
+ view.LayoutLines[2] == "xc\n" &&
+ view.LayoutLines[3] == "d");
+
+ }
+
+ [TestMethod]
+ public void InsertMultiLineTest()
+ {
+ Document doc = new Document();
+ DummyRender render = new DummyRender();
+ DummyView view = new DummyView(doc, render);
+
+ doc.Clear();
+ doc.Append("a\nb\nc\nd");
+
+ doc.Insert(2, "f\ne");
+ Assert.IsTrue(
+ view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "f\n" &&
+ view.LayoutLines[2] == "eb\n" &&
+ view.LayoutLines[3] == "c\n" &&
+ view.LayoutLines[4] == "d");
+
+ doc.Insert(3, "g\nh");
+ Assert.IsTrue(
+ view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "fg\n" &&
+ view.LayoutLines[2] == "h\n" &&
+ view.LayoutLines[3] == "eb\n" &&
+ view.LayoutLines[4] == "c\n" &&
+ view.LayoutLines[5] == "d");
+
+ doc.Insert(0, "x\ny");
+ Assert.IsTrue(
+ view.LayoutLines[0] == "x\n" &&
+ view.LayoutLines[1] == "ya\n" &&
+ view.LayoutLines[2] == "fg\n" &&
+ view.LayoutLines[3] == "h\n" &&
+ view.LayoutLines[4] == "eb\n" &&
+ view.LayoutLines[5] == "c\n" &&
+ view.LayoutLines[6] == "d");
+ }
+
+ [TestMethod]
+ public void RemoveSingleLineTest()
+ {
+ Document doc = new Document();
+ DummyRender render = new DummyRender();
+ DummyView view = new DummyView(doc, render);
+ doc.Clear();
+ doc.Append("aa\nbb\ncc\ndd");
+
+ doc.Remove(9, 1);
+ Assert.IsTrue(
+ view.LayoutLines[0] == "aa\n" &&
+ view.LayoutLines[1] == "bb\n" &&
+ view.LayoutLines[2] == "cc\n" &&
+ view.LayoutLines[3] == "d"
+ );
+
+ doc.Remove(9, 1);
+ Assert.IsTrue(
+ view.LayoutLines[0] == "aa\n" &&
+ view.LayoutLines[1] == "bb\n" &&
+ view.LayoutLines[2] == "cc\n" &&
+ view.LayoutLines[3] == ""
+ );
+
+ doc.Remove(0, 1);
+ Assert.IsTrue(
+ view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "bb\n" &&
+ view.LayoutLines[2] == "cc\n" &&
+ view.LayoutLines[3] == ""
+ );
+ }
+
+ [TestMethod]
+ public void RemoveMultiLineTest()
+ {
+ Document doc = new Document();
+ DummyRender render = new DummyRender();
+ DummyView view = new DummyView(doc, render);
+
+ doc.Clear();
+ doc.Append("a\n");
+ doc.Append("b\n");
+ doc.Append("c\n");
+ doc.Append("d\n");
+ doc.Append("e\n");
+ doc.Append("f\n");
+
+ doc.Remove(2, 4);
+ Assert.IsTrue(
+ view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "d\n" &&
+ view.LayoutLines[2] == "e\n" &&
+ view.LayoutLines[3] == "f\n");
+
+ doc.Remove(4, 4);
+ Assert.IsTrue(
+ view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "d\n");
+
+ doc.Clear();
+ doc.Append("a\n");
+ doc.Append("b\n");
+ doc.Append("c\n");
+ doc.Append("d\n");
+
+ doc.Remove(2, 6);
+ Assert.IsTrue(view.LayoutLines[0] == "a\n");
+
+ doc.Clear();
+ doc.Append("a\n");
+ doc.Append("b\n");
+ doc.Append("c\n");
+ doc.Append("d\n");
+ doc.Append("e\n");
+ doc.Append("f\n");
+ doc.Insert(4, "a");
+ doc.Remove(2, 5);
+ Assert.IsTrue(
+ view.LayoutLines[0] == "a\n" &&
+ view.LayoutLines[1] == "d\n" &&
+ view.LayoutLines[2] == "e\n" &&
+ view.LayoutLines[3] == "f\n");
}
[TestMethod]
DummyView view = new DummyView(doc, render);
doc.Clear();
doc.Append("a\nb\nc");
+
Assert.IsTrue(view.LayoutLines.GetIndexFromLineNumber(1) == 2);
Assert.IsTrue(view.LayoutLines.GetLengthFromLineNumber(1) == 2);
Assert.IsTrue(view.LayoutLines.GetLineNumberFromIndex(2) == 1);
TextPoint tp = view.LayoutLines.GetTextPointFromIndex(2);
Assert.IsTrue(tp.row == 1 && tp.col == 0);
Assert.IsTrue(view.LayoutLines.GetIndexFromTextPoint(tp) == 2);
+
+ doc.Insert(2, "a");
+
+ Assert.IsTrue(view.LayoutLines.GetIndexFromLineNumber(2) == 5);
+ Assert.IsTrue(view.LayoutLines.GetLineNumberFromIndex(5) == 2);
+ tp = view.LayoutLines.GetTextPointFromIndex(5);
+ Assert.IsTrue(tp.row == 2 && tp.col == 0);
+ Assert.IsTrue(view.LayoutLines.GetIndexFromTextPoint(tp) == 5);
+
+ doc.Insert(0, "a");
+
+ Assert.IsTrue(view.LayoutLines.GetIndexFromLineNumber(2) == 6);
+ Assert.IsTrue(view.LayoutLines.GetLineNumberFromIndex(6) == 2);
+ tp = view.LayoutLines.GetTextPointFromIndex(6);
+ Assert.IsTrue(tp.row == 2 && tp.col == 0);
+ Assert.IsTrue(view.LayoutLines.GetIndexFromTextPoint(tp) == 6);
}
}
}