2 * Copyright (C) 2013 FooProject
3 * * 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
4 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
6 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
12 using System.Text.RegularExpressions;
13 using System.Threading;
15 using System.Collections.Generic;
16 using System.Diagnostics;
17 using Slusser.Collections.Generic;
19 namespace FooEditEngine
21 internal interface ITextLayout : IDisposable
40 /// Disposeされているなら真を返す
56 /// 桁方向の座標に対応するインデックスを得る
58 /// <param name="colpos">桁方向の座標</param>
59 /// <returns>インデックス</returns>
60 /// <remarks>行番号の幅は考慮されてないのでView以外のクラスは呼び出さないでください</remarks>
61 int GetIndexFromColPostion(double colpos);
64 /// インデックスに対応する文字の幅を得る
66 /// <param name="index">インデックス</param>
67 /// <returns>文字の幅</returns>
68 double GetWidthFromIndex(int index);
71 /// インデックスに対応する桁方向の座標を得る
73 /// <param name="index">インデックス</param>
74 /// <returns>桁方向の座標</returns>
75 /// <remarks>行頭にEOFが含まれている場合、0が返ります</remarks>
76 double GetColPostionFromIndex(int index);
81 /// <param name="index">インデックス</param>
82 /// <param name="flow">真の場合は隣接するクラスターを指すように調整し、
83 /// そうでない場合は対応するクラスターの先頭を指すように調整します</param>
84 /// <returns>調整後のインデックス</returns>
85 int AlignIndexToNearestCluster(int index, AlignDirection flow);
88 internal class SpilitStringEventArgs : EventArgs
90 public Document buffer;
94 public SpilitStringEventArgs(Document buf, int index, int length,int row)
103 internal struct SyntaxInfo
105 public TokenType type;
108 public SyntaxInfo(int index, int length, TokenType type)
112 this.length = length;
116 internal enum EncloserType
124 interface ILineInfoGenerator
126 void Update(Document doc, int startIndex, int insertLength, int removeLength);
127 void Clear(LineToIndexTable lti);
128 bool Generate(Document doc, LineToIndexTable lti, bool force = true);
131 internal class LineToIndexTableData : IDisposable
134 /// 行の先頭。正しい行の先頭位置を取得するにはGetLineHeadIndex()を使用してください
145 public SyntaxInfo[] Syntax;
146 public EncloserType EncloserType;
147 internal ITextLayout Layout;
148 public bool Dirty = false;
151 /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
153 public LineToIndexTableData()
158 /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
160 public LineToIndexTableData(int index, int length, bool lineend,bool dirty, SyntaxInfo[] syntax)
163 this.Length = length;
164 this.LineEnd = lineend;
165 this.Syntax = syntax;
166 this.EncloserType = EncloserType.None;
170 public void Dispose()
172 if(this.Layout != null)
173 this.Layout.Dispose();
177 internal delegate IList<LineToIndexTableData> SpilitStringEventHandler(object sender, SpilitStringEventArgs e);
179 internal sealed class CreateLayoutEventArgs
200 public string Content
205 public CreateLayoutEventArgs(int index, int length,string content)
208 this.Length = length;
209 this.Content = content;
214 /// 行番号とインデックスを相互変換するためのクラス
216 public sealed class LineToIndexTable : IEnumerable<string>
218 const int MaxEntries = 100;
219 GapBuffer<LineToIndexTableData> Lines = new GapBuffer<LineToIndexTableData>();
222 int stepRow = -1,stepLength = 0;
223 const int STEP_ROW_IS_NONE = -1;
225 const int FOLDING_INDEX = 0;
226 const int SYNTAX_HIGLITHER_INDEX = 1;
227 ILineInfoGenerator[] _generators = new ILineInfoGenerator[2];
229 internal LineToIndexTable(Document buf)
232 this.Document.Markers.Updated += Markers_Updated;
233 this._generators[FOLDING_INDEX] = new FoldingGenerator();
234 this._generators[SYNTAX_HIGLITHER_INDEX] = new SyntaxHilightGenerator();
235 #if DEBUG && !NETFX_CORE
236 if (!Debugger.IsAttached)
238 Guid guid = Guid.NewGuid();
239 string path = string.Format("{0}\\footextbox_lti_debug_{1}.log", System.IO.Path.GetTempPath(), guid);
240 Debug.Listeners.Add(new TextWriterTraceListener(path));
241 Debug.AutoFlush = true;
246 void Markers_Updated(object sender, EventArgs e)
248 this.ClearLayoutCache();
252 /// ITextRenderインターフェイスのインスタンス。必ずセットすること
254 internal ITextRender Render
256 get { return this.render; }
263 internal SpilitStringEventHandler SpilitString;
270 get { return this.Lines.Count; }
274 /// 折り畳み関係の情報を収めたコレクション
276 public FoldingCollection FoldingCollection
280 return ((FoldingGenerator)this._generators[FOLDING_INDEX]).FoldingCollection;
290 public IHilighter Hilighter
294 return ((SyntaxHilightGenerator)this._generators[SYNTAX_HIGLITHER_INDEX]).Hilighter;
298 ((SyntaxHilightGenerator)this._generators[SYNTAX_HIGLITHER_INDEX]).Hilighter = value;
300 this._generators[FOLDING_INDEX].Clear(this);
307 public IFoldingStrategy FoldingStrategy
311 return ((FoldingGenerator)this._generators[FOLDING_INDEX]).FoldingStrategy;
315 ((FoldingGenerator)this._generators[FOLDING_INDEX]).FoldingStrategy = value;
317 this._generators[FOLDING_INDEX].Clear(this);
322 /// 保持しているレイアウトキャッシュをクリアーする
324 public void ClearLayoutCache()
326 foreach (LineToIndexTableData data in this.Lines)
333 /// 保持しているレイアウトキャッシュをクリアーする
335 public void ClearLayoutCache(int index,int length)
337 if (index >= this.Document.Length)
339 int startRow = this.GetLineNumberFromIndex(index);
340 int lastIndex = Math.Min(index + length - 1, this.Document.Length - 1);
343 int endRow = this.GetLineNumberFromIndex(lastIndex);
344 for (int i = startRow; i <= endRow; i++)
345 this.Lines[i].Dispose();
351 /// <param name="n"></param>
352 /// <returns></returns>
353 public string this[int n]
357 LineToIndexTableData data = this.Lines[n];
358 string str = this.Document.ToString(this.GetLineHeadIndex(n), data.Length);
367 public bool IsFrozneDirtyFlag
373 int GetLineHeadIndex(int row)
375 if (this.Lines.Count == 0)
377 if (this.stepRow != STEP_ROW_IS_NONE && row > this.stepRow)
378 return this.Lines[row].Index + this.stepLength;
380 return this.Lines[row].Index;
383 internal LineToIndexTableData CreateLineToIndexTableData(int index, int length, bool lineend, SyntaxInfo[] syntax)
385 LineToIndexTableData result = new LineToIndexTableData(index, length, lineend,this.IsFrozneDirtyFlag == false, syntax);
389 internal void UpdateLineAsReplace(int row,int removedLength, int insertedLength)
391 int deltaLength = insertedLength - removedLength;
393 this.Lines[row] = new LineToIndexTableData(this.GetLineHeadIndex(row), this.GetLengthFromLineNumber(row) + deltaLength, true, true, null);
396 this.UpdateLineHeadIndex(deltaLength, row, 1);
398 foreach(var generator in this._generators)
399 generator.Update(this.Document, this.GetLineHeadIndex(row), insertedLength, removedLength);
402 internal void UpdateAsReplace(int index, int removedLength, int insertedLength)
405 Debug.WriteLine("Replaced Index:{0} RemoveLength:{1} InsertLength:{2}", index, removedLength, insertedLength);
407 int startRow, endRow;
408 GetRemoveRange(index, removedLength, out startRow, out endRow);
410 int deltaLength = insertedLength - removedLength;
412 var result = GetAnalyzeLength(startRow, endRow, index, removedLength, insertedLength);
413 int HeadIndex = result.Item1;
414 int analyzeLength = result.Item2;
416 //挿入範囲内のドキュメントから行を生成する
417 SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);
418 IList<LineToIndexTableData> newLines = SpilitString(this, e);
420 //消すべき行が複数ある場合は消すが、そうでない場合は最適化のため長さを変えるだけにとどめておく
421 int removeCount = endRow - startRow + 1;
422 if (removeCount == 1 && newLines.Count == 1)
424 this.Lines[startRow] = newLines.First();
428 this.RemoveLine(startRow, removeCount);
431 this.InsertLine(startRow, newLines, removeCount, deltaLength);
435 this.UpdateLineHeadIndex(deltaLength, startRow, newLines.Count);
439 foreach (var generator in this._generators)
440 generator.Update(this.Document, index, insertedLength, removedLength);
443 void GetRemoveRange(int index,int length,out int startRow,out int endRow)
445 startRow = this.GetLineNumberFromIndex(index);
446 while (startRow > 0 && this.Lines[startRow - 1].LineEnd == false)
449 endRow = this.GetLineNumberFromIndex(index + length);
450 while (endRow < this.Lines.Count && this.Lines[endRow].LineEnd == false)
452 if (endRow >= this.Lines.Count)
453 endRow = this.Lines.Count - 1;
456 Tuple<int,int> GetAnalyzeLength(int startRow,int endRow,int updateStartIndex,int removedLength,int insertedLength)
458 int HeadIndex = this.GetIndexFromLineNumber(startRow);
459 int LastIndex = this.GetIndexFromLineNumber(endRow) + this.GetLengthFromLineNumber(endRow) - 1;
461 //SpilitStringの対象となる範囲を求める
462 int fisrtPartLength = updateStartIndex - HeadIndex;
463 int secondPartLength = LastIndex - (updateStartIndex + removedLength - 1);
464 int analyzeLength = fisrtPartLength + secondPartLength + insertedLength;
465 Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);
467 return new Tuple<int, int>(HeadIndex, analyzeLength);
470 void RemoveLine(int startRow, int removeCount)
472 for (int i = startRow; i < startRow + removeCount; i++)
473 this.Lines[i].Dispose();
475 this.Lines.RemoveRange(startRow, removeCount);
478 void InsertLine(int startRow, IList<LineToIndexTableData> collection,int removeCount, int deltaLength)
480 int newCount = collection.Count;
481 if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)
483 //stepRowは1か2のうち、大きな方になる
484 // 1.stepRow - (削除された行数 - 挿入された行数)
486 //行が削除や置換された場合、1の処理をしないと正しいIndexが求められない
487 this.stepRow = Math.Max(this.stepRow - (removeCount - newCount), startRow);
489 if (this.stepRow < 0 || this.stepRow > this.Lines.Count + newCount)
491 Debug.WriteLine("step row < 0 or step row >= lines.count");
497 //startRowが挿入した行の開始位置なのであらかじめ引いておく
498 for (int i = 1; i < collection.Count; i++)
500 if (this.stepRow != STEP_ROW_IS_NONE && startRow + i > this.stepRow)
501 collection[i].Index -= deltaLength + this.stepLength;
503 collection[i].Index -= deltaLength;
506 this.Lines.InsertRange(startRow, collection);
511 LineToIndexTableData dummyLine = null;
512 if (this.Lines.Count == 0)
514 dummyLine = new LineToIndexTableData();
515 this.Lines.Add(dummyLine);
519 int lastLineRow = this.Lines.Count > 0 ? this.Lines.Count - 1 : 0;
520 int lastLineHeadIndex = this.GetIndexFromLineNumber(lastLineRow);
521 int lastLineLength = this.GetLengthFromLineNumber(lastLineRow);
523 if (lastLineLength != 0 && this.Document[Document.Length - 1] == Document.NewLine)
525 int realIndex = lastLineHeadIndex + lastLineLength;
526 if (lastLineRow >= this.stepRow)
527 realIndex -= this.stepLength;
528 dummyLine = new LineToIndexTableData(realIndex, 0, true,false, null);
529 this.Lines.Add(dummyLine);
533 void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)
535 if (this.Lines.Count == 0)
537 this.stepRow = STEP_ROW_IS_NONE;
542 if (this.stepRow == STEP_ROW_IS_NONE)
544 this.stepRow = startRow;
545 this.stepLength = deltaLength;
550 if (startRow < this.stepRow)
552 //ドキュメントの後半部分をごっそり削除した場合、this.stepRow >= this.Lines.Countになる可能性がある
553 if (this.stepRow >= this.Lines.Count)
554 this.stepRow = this.Lines.Count - 1;
555 for (int i = this.stepRow; i > startRow; i--)
556 this.Lines[i].Index -= this.stepLength;
558 else if (startRow > this.stepRow)
560 for (int i = this.stepRow + 1; i < startRow; i++)
561 this.Lines[i].Index += this.stepLength;
564 this.stepRow = startRow;
565 this.stepLength += deltaLength;
567 this.ValidateLines();
574 for (int i = 0; i < this.Lines.Count; i++)
576 int lineHeadIndex = this.GetLineHeadIndex(i);
577 if (lineHeadIndex != nextIndex)
579 Debug.WriteLine("Invaild Line");
580 System.Diagnostics.Debugger.Break();
582 nextIndex = lineHeadIndex + this.Lines[i].Length;
590 /// <param name="row">行</param>
591 /// <returns>LineToIndexTableData</returns>
592 /// <remarks>いくつかの値は実態とかけ離れた値を返します。詳しくはLineToIndexTableDataの注意事項を参照すること</remarks>
593 internal LineToIndexTableData GetRaw(int row)
595 return this.Lines[row];
601 /// <param name="row">行番号</param>
602 /// <returns>0から始まるインデックスを返す</returns>
603 public int GetIndexFromLineNumber(int row)
605 if (row < 0 || row > this.Lines.Count)
606 throw new ArgumentOutOfRangeException();
607 return this.GetLineHeadIndex(row);
613 /// <param name="row">行番号</param>
614 /// <returns>行の文字長を返します</returns>
615 public int GetLengthFromLineNumber(int row)
617 if (row < 0 || row > this.Lines.Count)
618 throw new ArgumentOutOfRangeException();
619 return this.Lines[row].Length;
625 /// <param name="row">行番号</param>
626 /// <returns>更新されていれば真。そうでなければ偽</returns>
627 public bool GetDirtyFlag(int row)
629 if (row < 0 || row > this.Lines.Count)
630 throw new ArgumentOutOfRangeException();
631 return this.Lines[row].Dirty;
634 internal ITextLayout GetLayout(int row)
636 if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)
638 this.Lines[row].Layout.Dispose();
639 this.Lines[row].Layout = null;
641 if (this.Lines[row].Layout == null || this.Lines[row].Layout.Disposed)
642 this.Lines[row].Layout = this.CreateLayout(row);
643 return this.Lines[row].Layout;
646 internal event EventHandler<CreateLayoutEventArgs> CreateingLayout;
648 ITextLayout CreateLayout(int row)
651 LineToIndexTableData lineData = this.Lines[row];
652 if (lineData.Length == 0)
654 layout = this.render.CreateLaytout("", null, null, null);
658 int lineHeadIndex = this.GetLineHeadIndex(row);
660 string content = this.Document.ToString(lineHeadIndex, lineData.Length);
662 if (this.CreateingLayout != null)
663 this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));
665 var userMarkerRange = from id in this.Document.Markers.IDs
666 from s in this.Document.Markers.Get(id,lineHeadIndex,lineData.Length)
667 let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
669 var watchdogMarkerRange = from s in this.Document.MarkerPatternSet.GetMarkers(new CreateLayoutEventArgs(lineHeadIndex, lineData.Length, content))
670 let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
672 var markerRange = watchdogMarkerRange.Concat(userMarkerRange);
673 var selectRange = from s in this.Document.Selections.Get(lineHeadIndex, lineData.Length)
674 let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
677 layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange, selectRange);
687 /// <param name="index">インデックス</param>
688 /// <returns>行番号を返します</returns>
689 public int GetLineNumberFromIndex(int index)
692 throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");
694 if (index == 0 && this.Lines.Count > 0)
697 LineToIndexTableData line;
700 if (lastLineNumber < this.Lines.Count - 1)
702 line = this.Lines[lastLineNumber];
703 lineHeadIndex = this.GetLineHeadIndex(lastLineNumber);
704 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)
705 return lastLineNumber;
708 int left = 0, right = this.Lines.Count - 1, mid;
709 while (left <= right)
711 mid = (left + right) / 2;
712 line = this.Lines[mid];
713 lineHeadIndex = this.GetLineHeadIndex(mid);
714 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)
716 lastLineNumber = mid;
719 if (index < lineHeadIndex)
729 int lastRow = this.Lines.Count - 1;
730 line = this.Lines[lastRow];
731 lineHeadIndex = this.GetLineHeadIndex(lastRow);
732 if (index >= lineHeadIndex && index <= lineHeadIndex + line.Length) //最終行長+1までキャレットが移動する可能性があるので
734 lastLineNumber = this.Lines.Count - 1;
735 return lastLineNumber;
738 throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");
742 /// インデックスからテキストポイントに変換します
744 /// <param name="index">インデックス</param>
745 /// <returns>TextPoint構造体を返します</returns>
746 public TextPoint GetTextPointFromIndex(int index)
748 TextPoint tp = new TextPoint();
749 tp.row = GetLineNumberFromIndex(index);
750 tp.col = index - this.GetLineHeadIndex(tp.row);
751 Debug.Assert(tp.row < this.Lines.Count && tp.col <= this.Lines[tp.row].Length);
756 /// テキストポイントからインデックスに変換します
758 /// <param name="tp">TextPoint構造体</param>
759 /// <returns>インデックスを返します</returns>
760 public int GetIndexFromTextPoint(TextPoint tp)
762 if (tp == TextPoint.Null)
763 throw new ArgumentNullException("TextPoint.Null以外の値でなければなりません");
764 if(tp.row < 0 || tp.row > this.Lines.Count)
765 throw new ArgumentOutOfRangeException("tp.rowが設定できる範囲を超えています");
766 if (tp.col < 0 || tp.col > this.Lines[tp.row].Length)
767 throw new ArgumentOutOfRangeException("tp.colが設定できる範囲を超えています");
768 return this.GetLineHeadIndex(tp.row) + tp.col;
774 /// <param name="force">ドキュメントが更新されていなくても再生成する</param>
775 /// <returns>生成された場合は真を返す</returns>
776 /// <remarks>デフォルトではドキュメントが更新されている時にだけ再生成されます</remarks>
777 public bool GenerateFolding(bool force = false)
779 return this._generators[FOLDING_INDEX].Generate(this.Document, this, force);
783 /// フォールディングをすべて削除します
785 public void ClearFolding()
787 this._generators[FOLDING_INDEX].Clear(this);
791 /// すべての行に対しシンタックスハイライトを行います
793 public bool HilightAll(bool force = false)
795 return this._generators[SYNTAX_HIGLITHER_INDEX].Generate(this.Document, this, force);
799 /// ハイライト関連の情報をすべて削除します
801 public void ClearHilight()
803 this._generators[SYNTAX_HIGLITHER_INDEX].Clear(this);
809 internal void Clear()
811 this.ClearLayoutCache();
814 LineToIndexTableData dummy = new LineToIndexTableData();
815 this.Lines.Add(dummy);
816 this.stepRow = STEP_ROW_IS_NONE;
818 Debug.WriteLine("Clear");
821 #region IEnumerable<string> メンバー
824 /// コレクションを反復処理するためのIEnumeratorを返す
826 /// <returns>IEnumeratorオブジェクト</returns>
827 public IEnumerator<string> GetEnumerator()
829 for (int i = 0; i < this.Lines.Count; i++)
830 yield return this[i];
835 #region IEnumerable メンバー
837 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
839 for (int i = 0; i < this.Lines.Count; i++)
840 yield return this[i];