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.Text;
+using System.Globalization;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
double GetColPostionFromIndex(int index);
/// <summary>
+ /// 座標に対応するインデックスを取得する
+ /// </summary>
+ /// <param name="x">桁方向の座標</param>
+ /// <param name="y">行方向の座標</param>
+ /// <returns>インデックス</returns>
+ /// <remarks>行番号の幅は考慮されてないのでView以外のクラスは呼び出さないでください</remarks>
+ int GetIndexFromPostion(double x, double y);
+
+ /// <summary>
+ /// インデックスに対応する座標を得る
+ /// </summary>
+ /// <param name="index">インデックス</param>
+ /// <returns>行方向と桁方向の相対座標</returns>
+ /// <remarks>行頭にEOFが含まれている場合、0が返ります</remarks>
+ Point GetPostionFromIndex(int index);
+
+ /// <summary>
/// 適切な位置にインデックスを調整する
/// </summary>
/// <param name="index">インデックス</param>
End,
}
+ interface ILineInfoGenerator
+ {
+ void Update(Document doc, int startIndex, int insertLength, int removeLength);
+ void Clear(LineToIndexTable lti);
+ bool Generate(Document doc, LineToIndexTable lti, bool force = true);
+ }
+
internal class LineToIndexTableData : IDisposable
{
/// <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;
+ const int FOLDING_INDEX = 0;
+ const int SYNTAX_HIGLITHER_INDEX = 1;
+ ILineInfoGenerator[] _generators = new ILineInfoGenerator[2];
+
internal LineToIndexTable(Document buf)
{
this.Document = buf;
this.Document.Markers.Updated += Markers_Updated;
- this.FoldingCollection = new FoldingCollection();
- this._IsSync = true;
+ this._generators[FOLDING_INDEX] = new FoldingGenerator();
+ this._generators[SYNTAX_HIGLITHER_INDEX] = new SyntaxHilightGenerator();
+ this.WrapWidth = NONE_BREAK_LINE;
#if DEBUG && !NETFX_CORE
if (!Debugger.IsAttached)
{
/// </summary>
public FoldingCollection FoldingCollection
{
- get;
- private set;
+ get
+ {
+ return ((FoldingGenerator)this._generators[FOLDING_INDEX]).FoldingCollection;
+ }
+ private set
+ {
+ }
}
/// <summary>
/// シンタックスハイライター
/// </summary>
- internal IHilighter Hilighter { get; set; }
+ public IHilighter Hilighter
+ {
+ get
+ {
+ return ((SyntaxHilightGenerator)this._generators[SYNTAX_HIGLITHER_INDEX]).Hilighter;
+ }
+ set
+ {
+ ((SyntaxHilightGenerator)this._generators[SYNTAX_HIGLITHER_INDEX]).Hilighter = value;
+ if (value == null)
+ this._generators[FOLDING_INDEX].Clear(this);
+ }
+ }
+
+ /// <summary>
+ /// 折り畳み
+ /// </summary>
+ public IFoldingStrategy FoldingStrategy
+ {
+ get
+ {
+ return ((FoldingGenerator)this._generators[FOLDING_INDEX]).FoldingStrategy;
+ }
+ set
+ {
+ ((FoldingGenerator)this._generators[FOLDING_INDEX]).FoldingStrategy = value;
+ if (value == null)
+ this._generators[FOLDING_INDEX].Clear(this);
+ }
+ }
- internal IFoldingStrategy FoldingStrategy { get; set; }
+ /// <summary>
+ /// ピクセル単位で折り返すかどうか
+ /// </summary>
+ public double WrapWidth
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// 行を折り返さないことを表す
+ /// </summary>
+ public const double NONE_BREAK_LINE = -1;
/// <summary>
/// 保持しているレイアウトキャッシュをクリアーする
/// </summary>
public void ClearLayoutCache()
{
- foreach (ITextLayout data in this.CacheEntries)
+ foreach (LineToIndexTableData data in this.Lines)
{
data.Dispose();
}
- this.CacheEntries.Clear();
+ }
+
+ /// <summary>
+ /// 保持しているレイアウトキャッシュをクリアーする
+ /// </summary>
+ public void ClearLayoutCache(int index,int length)
+ {
+ if (index >= this.Document.Length)
+ return;
+ int startRow = this.GetLineNumberFromIndex(index);
+ int lastIndex = Math.Min(index + length - 1, this.Document.Length - 1);
+ if (lastIndex < 0)
+ lastIndex = 0;
+ int endRow = this.GetLineNumberFromIndex(lastIndex);
+ for (int i = startRow; i <= endRow; i++)
+ this.Lines[i].Dispose();
}
/// <summary>
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.UpdateLineHeadIndex(deltaLength, row, 1);
- this.FoldingCollection.UpdateData(this.Document, this.GetLineHeadIndex(row), insertedLength, removedLength);
-
- this._IsSync = false;
-
- this.lastUpdateTicks = DateTime.Now.Ticks;
+ foreach(var generator in this._generators)
+ generator.Update(this.Document, this.GetLineHeadIndex(row), insertedLength, removedLength);
}
internal void UpdateAsReplace(int index, int removedLength, int insertedLength)
//挿入範囲内のドキュメントから行を生成する
SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);
- IList<LineToIndexTableData> newLines = SpilitString(this, e);
+ IList<LineToIndexTableData> newLines = this.CreateLineList(e.index, e.length, Document.MaximumLineLength);
//消すべき行が複数ある場合は消すが、そうでない場合は最適化のため長さを変えるだけにとどめておく
int removeCount = endRow - startRow + 1;
this.AddDummyLine();
- this.FoldingCollection.UpdateData(this.Document, index, insertedLength, removedLength);
+ foreach (var generator in this._generators)
+ generator.Update(this.Document, index, insertedLength, removedLength);
+ }
- this._IsSync = false;
+ internal IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
+ {
+ int currentLineHeadIndex = startIndex;
+ int currentLineLength = 0;
- this.lastUpdateTicks = DateTime.Now.Ticks;
+ for (int i = startIndex; i <= endIndex; i++)
+ {
+ currentLineLength++;
+ char c = this.Document[i];
+ if (c == Document.NewLine ||
+ (maxCharCount != -1 && currentLineLength >= maxCharCount))
+ {
+ UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);
+ if (uc != UnicodeCategory.NonSpacingMark &&
+ uc != UnicodeCategory.SpacingCombiningMark &&
+ uc != UnicodeCategory.EnclosingMark &&
+ uc != UnicodeCategory.Surrogate)
+ {
+ yield return new Tuple<int, int>(currentLineHeadIndex, currentLineLength);
+ currentLineHeadIndex += currentLineLength;
+ currentLineLength = 0;
+ }
+ }
+ }
+ if (currentLineLength > 0)
+ yield return new Tuple<int, int>(currentLineHeadIndex, currentLineLength);
+ }
+
+ IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)
+ {
+ int startIndex = index;
+ int endIndex = index + length - 1;
+ List<LineToIndexTableData> output = new List<LineToIndexTableData>();
+
+ foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))
+ {
+ int lineHeadIndex = range.Item1;
+ int lineLength = range.Item2;
+ char c = this.Document[lineHeadIndex + lineLength - 1];
+ bool hasNewLine = c == Document.NewLine;
+ LineToIndexTableData result = new LineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, this.IsFrozneDirtyFlag == false, null);
+ output.Add(result);
+ }
+
+ if (output.Count > 0)
+ output.Last().LineEnd = true;
+
+ return output;
}
void GetRemoveRange(int index,int length,out int startRow,out int endRow)
}
/// <summary>
+ /// 生データを取得します
+ /// </summary>
+ /// <param name="row">行</param>
+ /// <returns>LineToIndexTableData</returns>
+ /// <remarks>いくつかの値は実態とかけ離れた値を返します。詳しくはLineToIndexTableDataの注意事項を参照すること</remarks>
+ internal LineToIndexTableData GetRaw(int row)
+ {
+ return this.Lines[row];
+ }
+
+ /// <summary>
/// 行番号をインデックスに変換します
/// </summary>
/// <param name="row">行番号</param>
return this.Lines[row].Dirty;
}
+ /// <summary>
+ /// 行の高さを返す
+ /// </summary>
+ /// <param name="tp">テキストポイント</param>
+ /// <returns>テキストポイントで指定された行の高さを返します</returns>
+ public double GetLineHeight(TextPoint tp)
+ {
+ return this.render.emSize.Height;
+ }
+
internal ITextLayout GetLayout(int row)
{
if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)
LineToIndexTableData lineData = this.Lines[row];
if (lineData.Length == 0)
{
- layout = this.render.CreateLaytout("", null, null);
+ layout = this.render.CreateLaytout("", null, null, null,this.WrapWidth);
}
else
{
if (this.CreateingLayout != null)
this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));
-
- var markerRange = from id in this.Document.Markers.IDs
+
+ var userMarkerRange = 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);
- }
+ var watchdogMarkerRange = from s in this.Document.MarkerPatternSet.GetMarkers(new CreateLayoutEventArgs(lineHeadIndex, lineData.Length, content))
+ let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
+ select n;
+ var markerRange = watchdogMarkerRange.Concat(userMarkerRange);
+ var selectRange = from s in this.Document.Selections.Get(lineHeadIndex, lineData.Length)
+ let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
+ select n;
- if (this.CacheEntries.Count > MaxEntries)
- {
- ITextLayout oldItem = this.CacheEntries.Dequeue();
- oldItem.Dispose();
+ layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange, selectRange,this.WrapWidth);
}
- this.CacheEntries.Enqueue(layout);
return layout;
}
/// <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);
- }
+ return this._generators[FOLDING_INDEX].Generate(this.Document, this, force);
}
/// <summary>
/// </summary>
public void ClearFolding()
{
- this.FoldingCollection.Clear();
- this._IsSync = false;
+ this._generators[FOLDING_INDEX].Clear(this);
}
/// <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;
+ return this._generators[SYNTAX_HIGLITHER_INDEX].Generate(this.Document, this, force);
}
/// <summary>
/// </summary>
public void ClearHilight()
{
- foreach (LineToIndexTableData line in this.Lines)
- line.Syntax = null;
- this.ClearLayoutCache();
+ this._generators[SYNTAX_HIGLITHER_INDEX].Clear(this);
}
/// <summary>
internal void Clear()
{
this.ClearLayoutCache();
- this.FoldingCollection.Clear();
+ this.ClearFolding();
this.Lines.Clear();
LineToIndexTableData dummy = new LineToIndexTableData();
this.Lines.Add(dummy);
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>