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);
79 /// 座標に対応するインデックスを取得する
81 /// <param name="x">桁方向の座標</param>
82 /// <param name="y">行方向の座標</param>
83 /// <returns>インデックス</returns>
84 /// <remarks>行番号の幅は考慮されてないのでView以外のクラスは呼び出さないでください</remarks>
85 int GetIndexFromPostion(double x, double y);
90 /// <param name="index">インデックス</param>
91 /// <returns>行方向と桁方向の相対座標</returns>
92 /// <remarks>行頭にEOFが含まれている場合、0が返ります</remarks>
93 Point GetPostionFromIndex(int index);
98 /// <param name="index">インデックス</param>
99 /// <param name="flow">真の場合は隣接するクラスターを指すように調整し、
100 /// そうでない場合は対応するクラスターの先頭を指すように調整します</param>
101 /// <returns>調整後のインデックス</returns>
102 int AlignIndexToNearestCluster(int index, AlignDirection flow);
105 internal class SpilitStringEventArgs : EventArgs
107 public Document buffer;
111 public SpilitStringEventArgs(Document buf, int index, int length,int row)
115 this.length = length;
120 internal struct SyntaxInfo
122 public TokenType type;
125 public SyntaxInfo(int index, int length, TokenType type)
129 this.length = length;
133 internal enum EncloserType
141 interface ILineInfoGenerator
143 void Update(Document doc, int startIndex, int insertLength, int removeLength);
144 void Clear(LineToIndexTable lti);
145 bool Generate(Document doc, LineToIndexTable lti, bool force = true);
148 internal class LineToIndexTableData : IDisposable
151 /// 行の先頭。正しい行の先頭位置を取得するにはGetLineHeadIndex()を使用してください
162 public SyntaxInfo[] Syntax;
163 public EncloserType EncloserType;
164 internal ITextLayout Layout;
165 public bool Dirty = false;
168 /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
170 public LineToIndexTableData()
175 /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
177 public LineToIndexTableData(int index, int length, bool lineend,bool dirty, SyntaxInfo[] syntax)
180 this.Length = length;
181 this.LineEnd = lineend;
182 this.Syntax = syntax;
183 this.EncloserType = EncloserType.None;
187 public void Dispose()
189 if(this.Layout != null)
190 this.Layout.Dispose();
194 internal delegate IList<LineToIndexTableData> SpilitStringEventHandler(object sender, SpilitStringEventArgs e);
196 internal sealed class CreateLayoutEventArgs
217 public string Content
222 public CreateLayoutEventArgs(int index, int length,string content)
225 this.Length = length;
226 this.Content = content;
231 /// 行番号とインデックスを相互変換するためのクラス
233 public sealed class LineToIndexTable : IEnumerable<string>
235 const int MaxEntries = 100;
236 GapBuffer<LineToIndexTableData> Lines = new GapBuffer<LineToIndexTableData>();
239 int stepRow = -1,stepLength = 0;
240 const int STEP_ROW_IS_NONE = -1;
242 const int FOLDING_INDEX = 0;
243 const int SYNTAX_HIGLITHER_INDEX = 1;
244 ILineInfoGenerator[] _generators = new ILineInfoGenerator[2];
246 internal LineToIndexTable(Document buf)
249 this.Document.Markers.Updated += Markers_Updated;
250 this._generators[FOLDING_INDEX] = new FoldingGenerator();
251 this._generators[SYNTAX_HIGLITHER_INDEX] = new SyntaxHilightGenerator();
252 this.WrapWidth = NONE_BREAK_LINE;
253 #if DEBUG && !NETFX_CORE
254 if (!Debugger.IsAttached)
256 Guid guid = Guid.NewGuid();
257 string path = string.Format("{0}\\footextbox_lti_debug_{1}.log", System.IO.Path.GetTempPath(), guid);
258 Debug.Listeners.Add(new TextWriterTraceListener(path));
259 Debug.AutoFlush = true;
264 void Markers_Updated(object sender, EventArgs e)
266 this.ClearLayoutCache();
270 /// ITextRenderインターフェイスのインスタンス。必ずセットすること
272 internal ITextRender Render
274 get { return this.render; }
281 internal SpilitStringEventHandler SpilitString;
288 get { return this.Lines.Count; }
292 /// 折り畳み関係の情報を収めたコレクション
294 public FoldingCollection FoldingCollection
298 return ((FoldingGenerator)this._generators[FOLDING_INDEX]).FoldingCollection;
308 public IHilighter Hilighter
312 return ((SyntaxHilightGenerator)this._generators[SYNTAX_HIGLITHER_INDEX]).Hilighter;
316 ((SyntaxHilightGenerator)this._generators[SYNTAX_HIGLITHER_INDEX]).Hilighter = value;
318 this._generators[FOLDING_INDEX].Clear(this);
325 public IFoldingStrategy FoldingStrategy
329 return ((FoldingGenerator)this._generators[FOLDING_INDEX]).FoldingStrategy;
333 ((FoldingGenerator)this._generators[FOLDING_INDEX]).FoldingStrategy = value;
335 this._generators[FOLDING_INDEX].Clear(this);
342 public double WrapWidth
351 public const double NONE_BREAK_LINE = -1;
354 /// 保持しているレイアウトキャッシュをクリアーする
356 public void ClearLayoutCache()
358 foreach (LineToIndexTableData data in this.Lines)
365 /// 保持しているレイアウトキャッシュをクリアーする
367 public void ClearLayoutCache(int index,int length)
369 if (index >= this.Document.Length)
371 int startRow = this.GetLineNumberFromIndex(index);
372 int lastIndex = Math.Min(index + length - 1, this.Document.Length - 1);
375 int endRow = this.GetLineNumberFromIndex(lastIndex);
376 for (int i = startRow; i <= endRow; i++)
377 this.Lines[i].Dispose();
383 /// <param name="n"></param>
384 /// <returns></returns>
385 public string this[int n]
389 LineToIndexTableData data = this.Lines[n];
390 string str = this.Document.ToString(this.GetLineHeadIndex(n), data.Length);
399 public bool IsFrozneDirtyFlag
405 int GetLineHeadIndex(int row)
407 if (this.Lines.Count == 0)
409 if (this.stepRow != STEP_ROW_IS_NONE && row > this.stepRow)
410 return this.Lines[row].Index + this.stepLength;
412 return this.Lines[row].Index;
415 internal LineToIndexTableData CreateLineToIndexTableData(int index, int length, bool lineend, SyntaxInfo[] syntax)
417 LineToIndexTableData result = new LineToIndexTableData(index, length, lineend,this.IsFrozneDirtyFlag == false, syntax);
421 internal void UpdateLineAsReplace(int row,int removedLength, int insertedLength)
423 int deltaLength = insertedLength - removedLength;
425 this.Lines[row] = new LineToIndexTableData(this.GetLineHeadIndex(row), this.GetLengthFromLineNumber(row) + deltaLength, true, true, null);
428 this.UpdateLineHeadIndex(deltaLength, row, 1);
430 foreach(var generator in this._generators)
431 generator.Update(this.Document, this.GetLineHeadIndex(row), insertedLength, removedLength);
434 internal void UpdateAsReplace(int index, int removedLength, int insertedLength)
437 Debug.WriteLine("Replaced Index:{0} RemoveLength:{1} InsertLength:{2}", index, removedLength, insertedLength);
439 int startRow, endRow;
440 GetRemoveRange(index, removedLength, out startRow, out endRow);
442 int deltaLength = insertedLength - removedLength;
444 var result = GetAnalyzeLength(startRow, endRow, index, removedLength, insertedLength);
445 int HeadIndex = result.Item1;
446 int analyzeLength = result.Item2;
448 //挿入範囲内のドキュメントから行を生成する
449 SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);
450 IList<LineToIndexTableData> newLines = SpilitString(this, e);
452 //消すべき行が複数ある場合は消すが、そうでない場合は最適化のため長さを変えるだけにとどめておく
453 int removeCount = endRow - startRow + 1;
454 if (removeCount == 1 && newLines.Count == 1)
456 this.Lines[startRow] = newLines.First();
460 this.RemoveLine(startRow, removeCount);
463 this.InsertLine(startRow, newLines, removeCount, deltaLength);
467 this.UpdateLineHeadIndex(deltaLength, startRow, newLines.Count);
471 foreach (var generator in this._generators)
472 generator.Update(this.Document, index, insertedLength, removedLength);
475 void GetRemoveRange(int index,int length,out int startRow,out int endRow)
477 startRow = this.GetLineNumberFromIndex(index);
478 while (startRow > 0 && this.Lines[startRow - 1].LineEnd == false)
481 endRow = this.GetLineNumberFromIndex(index + length);
482 while (endRow < this.Lines.Count && this.Lines[endRow].LineEnd == false)
484 if (endRow >= this.Lines.Count)
485 endRow = this.Lines.Count - 1;
488 Tuple<int,int> GetAnalyzeLength(int startRow,int endRow,int updateStartIndex,int removedLength,int insertedLength)
490 int HeadIndex = this.GetIndexFromLineNumber(startRow);
491 int LastIndex = this.GetIndexFromLineNumber(endRow) + this.GetLengthFromLineNumber(endRow) - 1;
493 //SpilitStringの対象となる範囲を求める
494 int fisrtPartLength = updateStartIndex - HeadIndex;
495 int secondPartLength = LastIndex - (updateStartIndex + removedLength - 1);
496 int analyzeLength = fisrtPartLength + secondPartLength + insertedLength;
497 Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);
499 return new Tuple<int, int>(HeadIndex, analyzeLength);
502 void RemoveLine(int startRow, int removeCount)
504 for (int i = startRow; i < startRow + removeCount; i++)
505 this.Lines[i].Dispose();
507 this.Lines.RemoveRange(startRow, removeCount);
510 void InsertLine(int startRow, IList<LineToIndexTableData> collection,int removeCount, int deltaLength)
512 int newCount = collection.Count;
513 if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)
515 //stepRowは1か2のうち、大きな方になる
516 // 1.stepRow - (削除された行数 - 挿入された行数)
518 //行が削除や置換された場合、1の処理をしないと正しいIndexが求められない
519 this.stepRow = Math.Max(this.stepRow - (removeCount - newCount), startRow);
521 if (this.stepRow < 0 || this.stepRow > this.Lines.Count + newCount)
523 Debug.WriteLine("step row < 0 or step row >= lines.count");
529 //startRowが挿入した行の開始位置なのであらかじめ引いておく
530 for (int i = 1; i < collection.Count; i++)
532 if (this.stepRow != STEP_ROW_IS_NONE && startRow + i > this.stepRow)
533 collection[i].Index -= deltaLength + this.stepLength;
535 collection[i].Index -= deltaLength;
538 this.Lines.InsertRange(startRow, collection);
543 LineToIndexTableData dummyLine = null;
544 if (this.Lines.Count == 0)
546 dummyLine = new LineToIndexTableData();
547 this.Lines.Add(dummyLine);
551 int lastLineRow = this.Lines.Count > 0 ? this.Lines.Count - 1 : 0;
552 int lastLineHeadIndex = this.GetIndexFromLineNumber(lastLineRow);
553 int lastLineLength = this.GetLengthFromLineNumber(lastLineRow);
555 if (lastLineLength != 0 && this.Document[Document.Length - 1] == Document.NewLine)
557 int realIndex = lastLineHeadIndex + lastLineLength;
558 if (lastLineRow >= this.stepRow)
559 realIndex -= this.stepLength;
560 dummyLine = new LineToIndexTableData(realIndex, 0, true,false, null);
561 this.Lines.Add(dummyLine);
565 void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)
567 if (this.Lines.Count == 0)
569 this.stepRow = STEP_ROW_IS_NONE;
574 if (this.stepRow == STEP_ROW_IS_NONE)
576 this.stepRow = startRow;
577 this.stepLength = deltaLength;
582 if (startRow < this.stepRow)
584 //ドキュメントの後半部分をごっそり削除した場合、this.stepRow >= this.Lines.Countになる可能性がある
585 if (this.stepRow >= this.Lines.Count)
586 this.stepRow = this.Lines.Count - 1;
587 for (int i = this.stepRow; i > startRow; i--)
588 this.Lines[i].Index -= this.stepLength;
590 else if (startRow > this.stepRow)
592 for (int i = this.stepRow + 1; i < startRow; i++)
593 this.Lines[i].Index += this.stepLength;
596 this.stepRow = startRow;
597 this.stepLength += deltaLength;
599 this.ValidateLines();
606 for (int i = 0; i < this.Lines.Count; i++)
608 int lineHeadIndex = this.GetLineHeadIndex(i);
609 if (lineHeadIndex != nextIndex)
611 Debug.WriteLine("Invaild Line");
612 System.Diagnostics.Debugger.Break();
614 nextIndex = lineHeadIndex + this.Lines[i].Length;
622 /// <param name="row">行</param>
623 /// <returns>LineToIndexTableData</returns>
624 /// <remarks>いくつかの値は実態とかけ離れた値を返します。詳しくはLineToIndexTableDataの注意事項を参照すること</remarks>
625 internal LineToIndexTableData GetRaw(int row)
627 return this.Lines[row];
633 /// <param name="row">行番号</param>
634 /// <returns>0から始まるインデックスを返す</returns>
635 public int GetIndexFromLineNumber(int row)
637 if (row < 0 || row > this.Lines.Count)
638 throw new ArgumentOutOfRangeException();
639 return this.GetLineHeadIndex(row);
645 /// <param name="row">行番号</param>
646 /// <returns>行の文字長を返します</returns>
647 public int GetLengthFromLineNumber(int row)
649 if (row < 0 || row > this.Lines.Count)
650 throw new ArgumentOutOfRangeException();
651 return this.Lines[row].Length;
657 /// <param name="row">行番号</param>
658 /// <returns>更新されていれば真。そうでなければ偽</returns>
659 public bool GetDirtyFlag(int row)
661 if (row < 0 || row > this.Lines.Count)
662 throw new ArgumentOutOfRangeException();
663 return this.Lines[row].Dirty;
666 internal ITextLayout GetLayout(int row)
668 if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)
670 this.Lines[row].Layout.Dispose();
671 this.Lines[row].Layout = null;
673 if (this.Lines[row].Layout == null || this.Lines[row].Layout.Disposed)
674 this.Lines[row].Layout = this.CreateLayout(row);
675 return this.Lines[row].Layout;
678 internal event EventHandler<CreateLayoutEventArgs> CreateingLayout;
680 ITextLayout CreateLayout(int row)
683 LineToIndexTableData lineData = this.Lines[row];
684 if (lineData.Length == 0)
686 layout = this.render.CreateLaytout("", null, null, null,this.WrapWidth);
690 int lineHeadIndex = this.GetLineHeadIndex(row);
692 string content = this.Document.ToString(lineHeadIndex, lineData.Length);
694 if (this.CreateingLayout != null)
695 this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));
697 var userMarkerRange = from id in this.Document.Markers.IDs
698 from s in this.Document.Markers.Get(id,lineHeadIndex,lineData.Length)
699 let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
701 var watchdogMarkerRange = from s in this.Document.MarkerPatternSet.GetMarkers(new CreateLayoutEventArgs(lineHeadIndex, lineData.Length, content))
702 let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
704 var markerRange = watchdogMarkerRange.Concat(userMarkerRange);
705 var selectRange = from s in this.Document.Selections.Get(lineHeadIndex, lineData.Length)
706 let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
709 layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange, selectRange,this.WrapWidth);
719 /// <param name="index">インデックス</param>
720 /// <returns>行番号を返します</returns>
721 public int GetLineNumberFromIndex(int index)
724 throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");
726 if (index == 0 && this.Lines.Count > 0)
729 LineToIndexTableData line;
732 if (lastLineNumber < this.Lines.Count - 1)
734 line = this.Lines[lastLineNumber];
735 lineHeadIndex = this.GetLineHeadIndex(lastLineNumber);
736 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)
737 return lastLineNumber;
740 int left = 0, right = this.Lines.Count - 1, mid;
741 while (left <= right)
743 mid = (left + right) / 2;
744 line = this.Lines[mid];
745 lineHeadIndex = this.GetLineHeadIndex(mid);
746 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)
748 lastLineNumber = mid;
751 if (index < lineHeadIndex)
761 int lastRow = this.Lines.Count - 1;
762 line = this.Lines[lastRow];
763 lineHeadIndex = this.GetLineHeadIndex(lastRow);
764 if (index >= lineHeadIndex && index <= lineHeadIndex + line.Length) //最終行長+1までキャレットが移動する可能性があるので
766 lastLineNumber = this.Lines.Count - 1;
767 return lastLineNumber;
770 throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");
774 /// インデックスからテキストポイントに変換します
776 /// <param name="index">インデックス</param>
777 /// <returns>TextPoint構造体を返します</returns>
778 public TextPoint GetTextPointFromIndex(int index)
780 TextPoint tp = new TextPoint();
781 tp.row = GetLineNumberFromIndex(index);
782 tp.col = index - this.GetLineHeadIndex(tp.row);
783 Debug.Assert(tp.row < this.Lines.Count && tp.col <= this.Lines[tp.row].Length);
788 /// テキストポイントからインデックスに変換します
790 /// <param name="tp">TextPoint構造体</param>
791 /// <returns>インデックスを返します</returns>
792 public int GetIndexFromTextPoint(TextPoint tp)
794 if (tp == TextPoint.Null)
795 throw new ArgumentNullException("TextPoint.Null以外の値でなければなりません");
796 if(tp.row < 0 || tp.row > this.Lines.Count)
797 throw new ArgumentOutOfRangeException("tp.rowが設定できる範囲を超えています");
798 if (tp.col < 0 || tp.col > this.Lines[tp.row].Length)
799 throw new ArgumentOutOfRangeException("tp.colが設定できる範囲を超えています");
800 return this.GetLineHeadIndex(tp.row) + tp.col;
806 /// <param name="force">ドキュメントが更新されていなくても再生成する</param>
807 /// <returns>生成された場合は真を返す</returns>
808 /// <remarks>デフォルトではドキュメントが更新されている時にだけ再生成されます</remarks>
809 public bool GenerateFolding(bool force = false)
811 return this._generators[FOLDING_INDEX].Generate(this.Document, this, force);
815 /// フォールディングをすべて削除します
817 public void ClearFolding()
819 this._generators[FOLDING_INDEX].Clear(this);
823 /// すべての行に対しシンタックスハイライトを行います
825 public bool HilightAll(bool force = false)
827 return this._generators[SYNTAX_HIGLITHER_INDEX].Generate(this.Document, this, force);
831 /// ハイライト関連の情報をすべて削除します
833 public void ClearHilight()
835 this._generators[SYNTAX_HIGLITHER_INDEX].Clear(this);
841 internal void Clear()
843 this.ClearLayoutCache();
846 LineToIndexTableData dummy = new LineToIndexTableData();
847 this.Lines.Add(dummy);
848 this.stepRow = STEP_ROW_IS_NONE;
850 Debug.WriteLine("Clear");
853 #region IEnumerable<string> メンバー
856 /// コレクションを反復処理するためのIEnumeratorを返す
858 /// <returns>IEnumeratorオブジェクト</returns>
859 public IEnumerator<string> GetEnumerator()
861 for (int i = 0; i < this.Lines.Count; i++)
862 yield return this[i];
867 #region IEnumerable メンバー
869 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
871 for (int i = 0; i < this.Lines.Count; i++)
872 yield return this[i];