OSDN Git Service

全置換えのアルゴリズムを高速化のために変更した
[fooeditengine/FooEditEngine.git] / Common / LineToIndex.cs
1 /*\r
2  * Copyright (C) 2013 FooProject\r
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\r
4  * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\r
5 \r
6  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of \r
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\r
8 \r
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/>.\r
10  */\r
11 using System;\r
12 using System.Text.RegularExpressions;\r
13 using System.Threading;\r
14 using System.Linq;\r
15 using System.Collections.Generic;\r
16 using System.Diagnostics;\r
17 using Slusser.Collections.Generic;\r
18 \r
19 namespace FooEditEngine\r
20 {\r
21     internal interface ITextLayout : IDisposable\r
22     {\r
23         /// <summary>\r
24         /// 文字列の幅\r
25         /// </summary>\r
26         double Width\r
27         {\r
28             get;\r
29         }\r
30 \r
31         /// <summary>\r
32         /// 文字列の高さ\r
33         /// </summary>\r
34         double Height\r
35         {\r
36             get;\r
37         }\r
38 \r
39         /// <summary>\r
40         /// Disposeされているなら真を返す\r
41         /// </summary>\r
42         bool Disposed\r
43         {\r
44             get;\r
45         }\r
46 \r
47         /// <summary>\r
48         /// 破棄すべきなら真。そうでなければ偽\r
49         /// </summary>\r
50         bool Invaild\r
51         {\r
52             get;\r
53         }\r
54 \r
55         /// <summary>\r
56         /// 桁方向の座標に対応するインデックスを得る\r
57         /// </summary>\r
58         /// <param name="colpos">桁方向の座標</param>\r
59         /// <returns>インデックス</returns>\r
60         /// <remarks>行番号の幅は考慮されてないのでView以外のクラスは呼び出さないでください</remarks>\r
61         int GetIndexFromColPostion(double colpos);\r
62 \r
63         /// <summary>\r
64         /// インデックスに対応する文字の幅を得る\r
65         /// </summary>\r
66         /// <param name="index">インデックス</param>\r
67         /// <returns>文字の幅</returns>\r
68         double GetWidthFromIndex(int index);\r
69 \r
70         /// <summary>\r
71         /// インデックスに対応する桁方向の座標を得る\r
72         /// </summary>\r
73         /// <param name="index">インデックス</param>\r
74         /// <returns>桁方向の座標</returns>\r
75         /// <remarks>行頭にEOFが含まれている場合、0が返ります</remarks>\r
76         double GetColPostionFromIndex(int index);\r
77 \r
78         /// <summary>\r
79         /// 適切な位置にインデックスを調整する\r
80         /// </summary>\r
81         /// <param name="index">インデックス</param>\r
82         /// <param name="flow">真の場合は隣接するクラスターを指すように調整し、\r
83         /// そうでない場合は対応するクラスターの先頭を指すように調整します</param>\r
84         /// <returns>調整後のインデックス</returns>\r
85         int AlignIndexToNearestCluster(int index, AlignDirection flow);\r
86     }\r
87 \r
88     internal class SpilitStringEventArgs : EventArgs\r
89     {\r
90         public Document buffer;\r
91         public int index;\r
92         public int length;\r
93         public int row;\r
94         public SpilitStringEventArgs(Document buf, int index, int length,int row)\r
95         {\r
96             this.buffer = buf;\r
97             this.index = index;\r
98             this.length = length;\r
99             this.row = row;\r
100         }\r
101     }\r
102 \r
103     internal struct SyntaxInfo\r
104     {\r
105         public TokenType type;\r
106         public int index;\r
107         public int length;\r
108         public SyntaxInfo(int index, int length, TokenType type)\r
109         {\r
110             this.type = type;\r
111             this.index = index;\r
112             this.length = length;\r
113         }\r
114     }\r
115 \r
116     internal enum EncloserType\r
117     {\r
118         None,\r
119         Begin,\r
120         Now,\r
121         End,\r
122     }\r
123 \r
124     internal class LineToIndexTableData : IDisposable\r
125     {\r
126         /// <summary>\r
127         /// 行の先頭。正しい行の先頭位置を取得するにはGetLineHeadIndex()を使用してください\r
128         /// </summary>\r
129         public int Index;\r
130         /// <summary>\r
131         /// 行の長さ\r
132         /// </summary>\r
133         public int Length;\r
134         /// <summary>\r
135         /// 改行マークかEOFなら真を返す\r
136         /// </summary>\r
137         public bool LineEnd;\r
138         public SyntaxInfo[] Syntax;\r
139         public EncloserType EncloserType;\r
140         internal ITextLayout Layout;\r
141         public bool Dirty = false;\r
142 \r
143         /// <summary>\r
144         /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください\r
145         /// </summary>\r
146         public LineToIndexTableData()\r
147         {\r
148         }\r
149 \r
150         /// <summary>\r
151         /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください\r
152         /// </summary>\r
153         public LineToIndexTableData(int index, int length, bool lineend,bool dirty, SyntaxInfo[] syntax)\r
154         {\r
155             this.Index = index;\r
156             this.Length = length;\r
157             this.LineEnd = lineend;\r
158             this.Syntax = syntax;\r
159             this.EncloserType = EncloserType.None;\r
160             this.Dirty = dirty;\r
161         }\r
162 \r
163         public void Dispose()\r
164         {\r
165             if(this.Layout != null)\r
166                 this.Layout.Dispose();\r
167         }\r
168     }\r
169 \r
170     internal delegate IList<LineToIndexTableData> SpilitStringEventHandler(object sender, SpilitStringEventArgs e);\r
171 \r
172     internal sealed class CreateLayoutEventArgs\r
173     {\r
174         /// <summary>\r
175         /// 開始インデックス\r
176         /// </summary>\r
177         public int Index\r
178         {\r
179             get;\r
180             private set;\r
181         }\r
182         /// <summary>\r
183         /// 長さ\r
184         /// </summary>\r
185         public int Length\r
186         {\r
187             get;\r
188             private set;\r
189         }\r
190         /// <summary>\r
191         /// 文字列\r
192         /// </summary>\r
193         public string Content\r
194         {\r
195             get;\r
196             private set;\r
197         }\r
198         public CreateLayoutEventArgs(int index, int length,string content)\r
199         {\r
200             this.Index = index;\r
201             this.Length = length;\r
202             this.Content = content;\r
203         }\r
204     }\r
205 \r
206     /// <summary>\r
207     /// 行番号とインデックスを相互変換するためのクラス\r
208     /// </summary>\r
209     public sealed class LineToIndexTable : IEnumerable<string>\r
210     {\r
211         const int MaxEntries = 100;\r
212         Queue<ITextLayout> CacheEntries = new Queue<ITextLayout>();\r
213         GapBuffer<LineToIndexTableData> Lines = new GapBuffer<LineToIndexTableData>();\r
214         Document Document;\r
215         long lastUpdateTicks = DateTime.Now.Ticks;\r
216         const long AllowCallTicks = 1000 * 10000;   //see.DateTime.Ticks プロパティ\r
217         bool _IsSync;\r
218         ITextRender render;\r
219         int stepRow = -1,stepLength = 0;\r
220         const int STEP_ROW_IS_NONE = -1;\r
221 \r
222         internal LineToIndexTable(Document buf)\r
223         {\r
224             this.Document = buf;\r
225             this.Document.Markers.Updated += Markers_Updated;\r
226             this.FoldingCollection = new FoldingCollection();\r
227             this._IsSync = true;\r
228 #if DEBUG && !NETFX_CORE\r
229             if (!Debugger.IsAttached)\r
230             {\r
231                 Guid guid = Guid.NewGuid();\r
232                 string path = string.Format("{0}\\footextbox_lti_debug_{1}.log", System.IO.Path.GetTempPath(), guid);\r
233                 Debug.Listeners.Add(new TextWriterTraceListener(path));\r
234                 Debug.AutoFlush = true;\r
235             }\r
236 #endif\r
237         }\r
238 \r
239         void Markers_Updated(object sender, EventArgs e)\r
240         {\r
241             this.ClearLayoutCache();\r
242         }\r
243 \r
244         /// <summary>\r
245         /// ITextRenderインターフェイスのインスタンス。必ずセットすること\r
246         /// </summary>\r
247         internal ITextRender Render\r
248         {\r
249             get { return this.render; }\r
250             set\r
251             {\r
252                 this.render = value;\r
253             }\r
254         }\r
255 \r
256         internal SpilitStringEventHandler SpilitString;\r
257 \r
258         /// <summary>\r
259         /// 行数を返す\r
260         /// </summary>\r
261         public int Count\r
262         {\r
263             get { return this.Lines.Count; }\r
264         }\r
265 \r
266         /// <summary>\r
267         /// 折り畳み関係の情報を収めたコレクション\r
268         /// </summary>\r
269         public FoldingCollection FoldingCollection\r
270         {\r
271             get;\r
272             private set;\r
273         }\r
274 \r
275         /// <summary>\r
276         /// シンタックスハイライター\r
277         /// </summary>\r
278         internal IHilighter Hilighter { get; set; }\r
279 \r
280         internal IFoldingStrategy FoldingStrategy { get; set; }\r
281 \r
282         /// <summary>\r
283         /// 保持しているレイアウトキャッシュをクリアーする\r
284         /// </summary>\r
285         public void ClearLayoutCache()\r
286         {\r
287             foreach (ITextLayout data in this.CacheEntries)\r
288             {\r
289                 data.Dispose();\r
290             }\r
291             this.CacheEntries.Clear();\r
292         }\r
293 \r
294         /// <summary>\r
295         /// 行番号に対応する文字列を返します\r
296         /// </summary>\r
297         /// <param name="n"></param>\r
298         /// <returns></returns>\r
299         public string this[int n]\r
300         {\r
301             get\r
302             {\r
303                 LineToIndexTableData data = this.Lines[n];\r
304                 string str = this.Document.ToString(this.GetLineHeadIndex(n), data.Length);\r
305 \r
306                 return str;\r
307             }\r
308         }\r
309 \r
310         /// <summary>\r
311         /// 更新フラグを更新しないなら真\r
312         /// </summary>\r
313         public bool IsFrozneDirtyFlag\r
314         {\r
315             get;\r
316             set;\r
317         }\r
318 \r
319         int GetLineHeadIndex(int row)\r
320         {\r
321             if (this.Lines.Count == 0)\r
322                 return 0;\r
323             if (this.stepRow != STEP_ROW_IS_NONE && row > this.stepRow)\r
324                 return this.Lines[row].Index + this.stepLength;\r
325             else\r
326                 return this.Lines[row].Index;\r
327         }\r
328 \r
329         internal LineToIndexTableData CreateLineToIndexTableData(int index, int length, bool lineend, SyntaxInfo[] syntax)\r
330         {\r
331             LineToIndexTableData result = new LineToIndexTableData(index, length, lineend,this.IsFrozneDirtyFlag == false, syntax);\r
332             return result;\r
333         }\r
334 \r
335         internal void UpdateLineAsReplace(int row,int removedLength, int insertedLength)\r
336         {\r
337             int deltaLength = insertedLength - removedLength;\r
338 \r
339             this.Lines[row] = new LineToIndexTableData(this.GetLineHeadIndex(row), this.GetLengthFromLineNumber(row) + deltaLength, true, true, null);\r
340 \r
341             //行テーブルを更新する\r
342             this.UpdateLineHeadIndex(deltaLength, row, 1);\r
343 \r
344             this.FoldingCollection.UpdateData(this.Document, this.GetLineHeadIndex(row), insertedLength, removedLength);\r
345 \r
346             this._IsSync = false;\r
347 \r
348             this.lastUpdateTicks = DateTime.Now.Ticks;\r
349         }\r
350 \r
351         internal void UpdateAsReplace(int index, int removedLength, int insertedLength)\r
352         {\r
353 #if DEBUG\r
354             Debug.WriteLine("Replaced Index:{0} RemoveLength:{1} InsertLength:{2}", index, removedLength, insertedLength);\r
355 #endif\r
356             int startRow, endRow;\r
357             GetRemoveRange(index, removedLength, out startRow, out endRow);\r
358 \r
359             int deltaLength = insertedLength - removedLength;\r
360 \r
361             var result = GetAnalyzeLength(startRow, endRow, index, removedLength, insertedLength);\r
362             int HeadIndex = result.Item1;\r
363             int analyzeLength = result.Item2;\r
364 \r
365             //挿入範囲内のドキュメントから行を生成する\r
366             SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);\r
367             IList<LineToIndexTableData> newLines = SpilitString(this, e);\r
368 \r
369             //消すべき行が複数ある場合は消すが、そうでない場合は最適化のため長さを変えるだけにとどめておく\r
370             int removeCount = endRow - startRow + 1;\r
371             if (removeCount == 1 && newLines.Count == 1)\r
372             {\r
373                 this.Lines[startRow] = newLines.First();\r
374             }\r
375             else\r
376             {\r
377                 this.RemoveLine(startRow, removeCount);\r
378 \r
379                 //行を挿入する\r
380                 this.InsertLine(startRow, newLines, removeCount, deltaLength);\r
381             }\r
382 \r
383             //行テーブルを更新する\r
384             this.UpdateLineHeadIndex(deltaLength, startRow, newLines.Count);\r
385 \r
386             this.AddDummyLine();\r
387 \r
388             this.FoldingCollection.UpdateData(this.Document, index, insertedLength, removedLength);            \r
389 \r
390             this._IsSync = false;\r
391 \r
392             this.lastUpdateTicks = DateTime.Now.Ticks;\r
393         }\r
394 \r
395         void GetRemoveRange(int index,int length,out int startRow,out int endRow)\r
396         {\r
397             startRow = this.GetLineNumberFromIndex(index);\r
398             while (startRow > 0 && this.Lines[startRow - 1].LineEnd == false)\r
399                 startRow--;\r
400 \r
401             endRow = this.GetLineNumberFromIndex(index + length);\r
402             while (endRow < this.Lines.Count && this.Lines[endRow].LineEnd == false)\r
403                 endRow++;\r
404             if (endRow >= this.Lines.Count)\r
405                 endRow = this.Lines.Count - 1;\r
406         }\r
407 \r
408         Tuple<int,int> GetAnalyzeLength(int startRow,int endRow,int updateStartIndex,int removedLength,int insertedLength)\r
409         {\r
410             int HeadIndex = this.GetIndexFromLineNumber(startRow);\r
411             int LastIndex = this.GetIndexFromLineNumber(endRow) + this.GetLengthFromLineNumber(endRow) - 1;\r
412 \r
413             //SpilitStringの対象となる範囲を求める\r
414             int fisrtPartLength = updateStartIndex - HeadIndex;\r
415             int secondPartLength = LastIndex - (updateStartIndex + removedLength - 1);\r
416             int analyzeLength = fisrtPartLength + secondPartLength + insertedLength;\r
417             Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);\r
418 \r
419             return new Tuple<int, int>(HeadIndex, analyzeLength);\r
420         }\r
421 \r
422         void RemoveLine(int startRow, int removeCount)\r
423         {\r
424             for (int i = startRow; i < startRow + removeCount; i++)\r
425                 this.Lines[i].Dispose();\r
426 \r
427             this.Lines.RemoveRange(startRow, removeCount);\r
428         }\r
429 \r
430         void InsertLine(int startRow, IList<LineToIndexTableData> collection,int removeCount, int deltaLength)\r
431         {\r
432             int newCount = collection.Count;\r
433             if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)\r
434             {\r
435                 //stepRowは1か2のうち、大きな方になる\r
436                 // 1.stepRow - (削除された行数 - 挿入された行数)\r
437                 // 2.行の挿入箇所\r
438                 //行が削除や置換された場合、1の処理をしないと正しいIndexが求められない\r
439                 this.stepRow = Math.Max(this.stepRow - (removeCount - newCount), startRow);\r
440 #if DEBUG\r
441                 if (this.stepRow < 0 || this.stepRow > this.Lines.Count + newCount)\r
442                 {\r
443                     Debug.WriteLine("step row < 0 or step row >= lines.count");\r
444                     Debugger.Break();\r
445                 }\r
446 #endif\r
447             }\r
448 \r
449             //startRowが挿入した行の開始位置なのであらかじめ引いておく\r
450             for (int i = 1; i < collection.Count; i++)\r
451             {\r
452                 if (this.stepRow != STEP_ROW_IS_NONE && startRow + i > this.stepRow)\r
453                     collection[i].Index -= deltaLength + this.stepLength;\r
454                 else\r
455                     collection[i].Index -= deltaLength;\r
456             }\r
457 \r
458             this.Lines.InsertRange(startRow, collection);\r
459         }\r
460 \r
461         void AddDummyLine()\r
462         {\r
463             LineToIndexTableData dummyLine = null;\r
464             if (this.Lines.Count == 0)\r
465             {\r
466                 dummyLine = new LineToIndexTableData();\r
467                 this.Lines.Add(dummyLine);\r
468                 return;\r
469             }\r
470 \r
471             int lastLineRow = this.Lines.Count > 0 ? this.Lines.Count - 1 : 0;\r
472             int lastLineHeadIndex = this.GetIndexFromLineNumber(lastLineRow);\r
473             int lastLineLength = this.GetLengthFromLineNumber(lastLineRow);\r
474 \r
475             if (lastLineLength != 0 && this.Document[Document.Length - 1] == Document.NewLine)\r
476             {\r
477                 int realIndex = lastLineHeadIndex + lastLineLength;\r
478                 if (lastLineRow >= this.stepRow)\r
479                     realIndex -= this.stepLength;\r
480                 dummyLine = new LineToIndexTableData(realIndex, 0, true,false, null);\r
481                 this.Lines.Add(dummyLine);\r
482             }\r
483         }\r
484 \r
485         void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)\r
486         {\r
487             if (this.Lines.Count == 0)\r
488             {\r
489                 this.stepRow = STEP_ROW_IS_NONE;\r
490                 this.stepLength = 0;\r
491                 return;\r
492             }\r
493 \r
494             if (this.stepRow == STEP_ROW_IS_NONE)\r
495             {\r
496                 this.stepRow = startRow;\r
497                 this.stepLength = deltaLength;\r
498                 return;\r
499             }\r
500 \r
501 \r
502             if (startRow < this.stepRow)\r
503             {\r
504                 //ドキュメントの後半部分をごっそり削除した場合、this.stepRow >= this.Lines.Countになる可能性がある\r
505                 if (this.stepRow >= this.Lines.Count)\r
506                     this.stepRow = this.Lines.Count - 1;\r
507                 for (int i = this.stepRow; i > startRow; i--)\r
508                     this.Lines[i].Index -= this.stepLength;\r
509             }\r
510             else if (startRow > this.stepRow)\r
511             {\r
512                 for (int i = this.stepRow + 1; i < startRow; i++)\r
513                     this.Lines[i].Index += this.stepLength;\r
514             }\r
515 \r
516             this.stepRow = startRow;\r
517             this.stepLength += deltaLength;\r
518 \r
519             this.ValidateLines();\r
520         }\r
521 \r
522         void ValidateLines()\r
523         {\r
524 #if DEBUG\r
525             int nextIndex = 0;\r
526             for (int i = 0; i < this.Lines.Count; i++)\r
527             {\r
528                 int lineHeadIndex = this.GetLineHeadIndex(i);\r
529                 if (lineHeadIndex != nextIndex)\r
530                 {\r
531                     Debug.WriteLine("Invaild Line");\r
532                     System.Diagnostics.Debugger.Break();\r
533                 }\r
534                 nextIndex = lineHeadIndex + this.Lines[i].Length;\r
535             }\r
536 #endif\r
537         }\r
538 \r
539         /// <summary>\r
540         /// 行番号をインデックスに変換します\r
541         /// </summary>\r
542         /// <param name="row">行番号</param>\r
543         /// <returns>0から始まるインデックスを返す</returns>\r
544         public int GetIndexFromLineNumber(int row)\r
545         {\r
546             if (row < 0 || row > this.Lines.Count)\r
547                 throw new ArgumentOutOfRangeException();\r
548             return this.GetLineHeadIndex(row);\r
549         }\r
550 \r
551         /// <summary>\r
552         /// 行の長さを得ます\r
553         /// </summary>\r
554         /// <param name="row">行番号</param>\r
555         /// <returns>行の文字長を返します</returns>\r
556         public int GetLengthFromLineNumber(int row)\r
557         {\r
558             if (row < 0 || row > this.Lines.Count)\r
559                 throw new ArgumentOutOfRangeException();\r
560             return this.Lines[row].Length;\r
561         }\r
562 \r
563         /// <summary>\r
564         /// 更新フラグを取得します\r
565         /// </summary>\r
566         /// <param name="row">行番号</param>\r
567         /// <returns>更新されていれば真。そうでなければ偽</returns>\r
568         public bool GetDirtyFlag(int row)\r
569         {\r
570             if (row < 0 || row > this.Lines.Count)\r
571                 throw new ArgumentOutOfRangeException();\r
572             return this.Lines[row].Dirty;\r
573         }\r
574 \r
575         internal ITextLayout GetLayout(int row)\r
576         {\r
577             if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)\r
578             {\r
579                 this.Lines[row].Layout.Dispose();\r
580                 this.Lines[row].Layout = null;\r
581             }\r
582             if (this.Lines[row].Layout == null || this.Lines[row].Layout.Disposed)\r
583                 this.Lines[row].Layout = this.CreateLayout(row);\r
584             return this.Lines[row].Layout;\r
585         }\r
586 \r
587         internal event EventHandler<CreateLayoutEventArgs> CreateingLayout;\r
588 \r
589         ITextLayout CreateLayout(int row)\r
590         {\r
591             ITextLayout layout;\r
592             LineToIndexTableData lineData = this.Lines[row];\r
593             if (lineData.Length == 0)\r
594             {\r
595                 layout = this.render.CreateLaytout("", null, null);\r
596             }\r
597             else\r
598             {\r
599                 int lineHeadIndex = this.GetLineHeadIndex(row);\r
600 \r
601                 string content = this.Document.ToString(lineHeadIndex, lineData.Length);\r
602 \r
603                 if (this.CreateingLayout != null)\r
604                     this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));\r
605                 \r
606                 var markerRange = from id in this.Document.Markers.IDs\r
607                                   from s in this.Document.Markers.Get(id,lineHeadIndex,lineData.Length)\r
608                                   let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)\r
609                                   select n;\r
610                 layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange);\r
611             }\r
612 \r
613             if (this.CacheEntries.Count > MaxEntries)\r
614             {\r
615                 ITextLayout oldItem = this.CacheEntries.Dequeue();\r
616                 oldItem.Dispose();\r
617             }\r
618             this.CacheEntries.Enqueue(layout);\r
619 \r
620             return layout;\r
621         }\r
622 \r
623         int lastLineNumber;\r
624         /// <summary>\r
625         /// インデックスを行番号に変換します\r
626         /// </summary>\r
627         /// <param name="index">インデックス</param>\r
628         /// <returns>行番号を返します</returns>\r
629         public int GetLineNumberFromIndex(int index)\r
630         {\r
631             if (index < 0)\r
632                 throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");\r
633 \r
634             if (index == 0 && this.Lines.Count > 0)\r
635                 return 0;\r
636 \r
637             LineToIndexTableData line;\r
638             int lineHeadIndex;\r
639 \r
640             if (lastLineNumber < this.Lines.Count - 1)\r
641             {\r
642                 line = this.Lines[lastLineNumber];\r
643                 lineHeadIndex = this.GetLineHeadIndex(lastLineNumber);\r
644                 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
645                     return lastLineNumber;\r
646             }\r
647 \r
648             int left = 0, right = this.Lines.Count - 1, mid;\r
649             while (left <= right)\r
650             {\r
651                 mid = (left + right) / 2;\r
652                 line = this.Lines[mid];\r
653                 lineHeadIndex = this.GetLineHeadIndex(mid);\r
654                 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
655                 {\r
656                     lastLineNumber = mid;\r
657                     return mid;\r
658                 }\r
659                 if (index < lineHeadIndex)\r
660                 {\r
661                     right = mid - 1;\r
662                 }\r
663                 else\r
664                 {\r
665                     left = mid + 1;\r
666                 }\r
667             }\r
668 \r
669             int lastRow = this.Lines.Count - 1;\r
670             line = this.Lines[lastRow];\r
671             lineHeadIndex = this.GetLineHeadIndex(lastRow);\r
672             if (index >= lineHeadIndex && index <= lineHeadIndex + line.Length)   //最終行長+1までキャレットが移動する可能性があるので\r
673             {\r
674                 lastLineNumber = this.Lines.Count - 1;\r
675                 return lastLineNumber;\r
676             }\r
677 \r
678             throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");\r
679         }\r
680 \r
681         /// <summary>\r
682         /// インデックスからテキストポイントに変換します\r
683         /// </summary>\r
684         /// <param name="index">インデックス</param>\r
685         /// <returns>TextPoint構造体を返します</returns>\r
686         public TextPoint GetTextPointFromIndex(int index)\r
687         {\r
688             TextPoint tp = new TextPoint();\r
689             tp.row = GetLineNumberFromIndex(index);\r
690             tp.col = index - this.GetLineHeadIndex(tp.row);\r
691             Debug.Assert(tp.row < this.Lines.Count && tp.col <= this.Lines[tp.row].Length);\r
692             return tp;\r
693         }\r
694 \r
695         /// <summary>\r
696         /// テキストポイントからインデックスに変換します\r
697         /// </summary>\r
698         /// <param name="tp">TextPoint構造体</param>\r
699         /// <returns>インデックスを返します</returns>\r
700         public int GetIndexFromTextPoint(TextPoint tp)\r
701         {\r
702             if (tp == TextPoint.Null)\r
703                 throw new ArgumentNullException("TextPoint.Null以外の値でなければなりません");\r
704             if(tp.row < 0 || tp.row > this.Lines.Count)\r
705                 throw new ArgumentOutOfRangeException("tp.rowが設定できる範囲を超えています");\r
706             if (tp.col < 0 || tp.col > this.Lines[tp.row].Length)\r
707                 throw new ArgumentOutOfRangeException("tp.colが設定できる範囲を超えています");\r
708             return this.GetLineHeadIndex(tp.row) + tp.col;\r
709         }\r
710 \r
711         /// <summary>\r
712         /// フォールディングを再生成します\r
713         /// </summary>\r
714         /// <param name="force">ドキュメントが更新されていなくても再生成する</param>\r
715         /// <returns>生成された場合は真を返す</returns>\r
716         /// <remarks>デフォルトではドキュメントが更新されている時にだけ再生成されます</remarks>\r
717         public bool GenerateFolding(bool force = false)\r
718         {\r
719             if (this.Document.Length == 0 || this.Document.IsLocked)\r
720                 return false;\r
721             long nowTick = DateTime.Now.Ticks;\r
722             bool sync = force || !this._IsSync;\r
723             if (sync || Math.Abs(nowTick - this.lastUpdateTicks) >= AllowCallTicks)\r
724             {\r
725                 this.GenerateFolding(0, this.Document.Length - 1);\r
726                 this.lastUpdateTicks = nowTick;\r
727                 return true;\r
728             }\r
729             return false;\r
730         }\r
731 \r
732         void GenerateFolding(int start, int end)\r
733         {\r
734             if (start > end)\r
735                 throw new ArgumentException("start <= endである必要があります");\r
736             if (this.FoldingStrategy != null)\r
737             {\r
738                 //再生成するとすべて展開状態になってしまうので、閉じてるやつだけを保存しておく\r
739                 FoldingItem[] closed_items =  this.FoldingCollection.Where((e)=> { return !e.Expand; }).ToArray();\r
740 \r
741                 this.FoldingCollection.Clear();\r
742 \r
743                 var items = this.FoldingStrategy.AnalyzeDocument(this.Document, start, end)\r
744                     .Where((item) =>\r
745                     {\r
746                         int startRow = this.GetLineNumberFromIndex(item.Start);\r
747                         int endRow = this.GetLineNumberFromIndex(item.End);\r
748                         return startRow != endRow;\r
749                     })\r
750                     .Select((item) => item);\r
751                 this.FoldingCollection.AddRange(items);\r
752 \r
753                 this.FoldingCollection.ApplyExpandStatus(closed_items);\r
754             }\r
755         }\r
756 \r
757         /// <summary>\r
758         /// フォールディングをすべて削除します\r
759         /// </summary>\r
760         public void ClearFolding()\r
761         {\r
762             this.FoldingCollection.Clear();\r
763             this._IsSync = false;\r
764         }\r
765 \r
766         /// <summary>\r
767         /// すべての行に対しシンタックスハイライトを行います\r
768         /// </summary>\r
769         public bool HilightAll(bool force = false)\r
770         {\r
771             if (this.Hilighter == null || this.Document.IsLocked)\r
772                 return false;\r
773 \r
774             long nowTick = DateTime.Now.Ticks;\r
775             bool sync = force || !this._IsSync;\r
776             if (sync || Math.Abs(nowTick - this.lastUpdateTicks) >= AllowCallTicks)\r
777             {\r
778                 for (int i = 0; i < this.Lines.Count; i++)\r
779                     this.HilightLine(i);\r
780 \r
781                 this.Hilighter.Reset();\r
782                 this.ClearLayoutCache();\r
783 \r
784                 this.lastUpdateTicks = nowTick;\r
785 \r
786                 return true;\r
787             }\r
788             return false;\r
789         }\r
790 \r
791         /// <summary>\r
792         /// ハイライト関連の情報をすべて削除します\r
793         /// </summary>\r
794         public void ClearHilight()\r
795         {\r
796             foreach (LineToIndexTableData line in this.Lines)\r
797                 line.Syntax = null;\r
798             this.ClearLayoutCache();\r
799         }\r
800 \r
801         /// <summary>\r
802         /// すべて削除します\r
803         /// </summary>\r
804         internal void Clear()\r
805         {\r
806             this.ClearLayoutCache();\r
807             this.FoldingCollection.Clear();\r
808             this.Lines.Clear();\r
809             LineToIndexTableData dummy = new LineToIndexTableData();\r
810             this.Lines.Add(dummy);\r
811             this.stepRow = STEP_ROW_IS_NONE;\r
812             this.stepLength = 0;\r
813             Debug.WriteLine("Clear");\r
814         }\r
815 \r
816         private void HilightLine(int row)\r
817         {\r
818             //シンタックスハイライトを行う\r
819             List<SyntaxInfo> syntax = new List<SyntaxInfo>();\r
820             string str = this[row];\r
821             int level = this.Hilighter.DoHilight(str, str.Length, (s) =>\r
822             {\r
823                 if (s.type == TokenType.None || s.type == TokenType.Control)\r
824                     return;\r
825                 if (str[s.index + s.length - 1] == Document.NewLine)\r
826                     s.length--;\r
827                 syntax.Add(new SyntaxInfo(s.index, s.length, s.type));\r
828             });\r
829 \r
830             LineToIndexTableData lineData = this.Lines[row];\r
831             lineData.Syntax = syntax.ToArray();\r
832 \r
833         }\r
834 \r
835         #region IEnumerable<string> メンバー\r
836 \r
837         /// <summary>\r
838         /// コレクションを反復処理するためのIEnumeratorを返す\r
839         /// </summary>\r
840         /// <returns>IEnumeratorオブジェクト</returns>\r
841         public IEnumerator<string> GetEnumerator()\r
842         {\r
843             for (int i = 0; i < this.Lines.Count; i++)\r
844                 yield return this[i];\r
845         }\r
846 \r
847         #endregion\r
848 \r
849         #region IEnumerable メンバー\r
850 \r
851         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()\r
852         {\r
853             for (int i = 0; i < this.Lines.Count; i++)\r
854                 yield return this[i];\r
855         }\r
856 \r
857         #endregion\r
858     }\r
859 \r
860 }\r