OSDN Git Service

再構築するのでCollectEmptyFoldingを呼び出す必要がなくなった
[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 UpdateAsReplace(int index, int removedLength, int insertedLength)\r
336         {\r
337 #if DEBUG\r
338             Debug.WriteLine("Replaced Index:{0} RemoveLength:{1} InsertLength:{2}", index, removedLength, insertedLength);\r
339 #endif\r
340             int startRow, endRow;\r
341             GetRemoveRange(index, removedLength, out startRow, out endRow);\r
342 \r
343             var result = GetAnalyzeLength(startRow, endRow, index, removedLength, insertedLength);\r
344             int HeadIndex = result.Item1;\r
345             int analyzeLength = result.Item2;\r
346 \r
347             //挿入範囲内のドキュメントから行を生成する\r
348             SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);\r
349             IList<LineToIndexTableData> newLines = SpilitString(this, e);\r
350 \r
351             //消すべき行が複数ある場合は消すが、そうでない場合は最適化のため長さを変えるだけにとどめておく\r
352             int removeCount = endRow - startRow + 1;\r
353             int deltaLength = insertedLength - removedLength;\r
354             if(removeCount == 1 && newLines.Count == 1)\r
355             {\r
356                 this.Lines[startRow] = newLines.First();\r
357             }\r
358             else\r
359             {\r
360                 this.RemoveLine(startRow, removeCount);\r
361 \r
362                 //行を挿入する\r
363                 this.InsertLine(startRow, newLines, removeCount, deltaLength);\r
364             }\r
365 \r
366             //行テーブルを更新する\r
367             this.UpdateLineHeadIndex(deltaLength, startRow, newLines.Count);\r
368 \r
369             this.AddDummyLine();\r
370 \r
371             this.FoldingCollection.UpdateData(this.Document, index, insertedLength, removedLength);            \r
372 \r
373             this._IsSync = false;\r
374 \r
375             this.lastUpdateTicks = DateTime.Now.Ticks;\r
376         }\r
377 \r
378         void GetRemoveRange(int index,int length,out int startRow,out int endRow)\r
379         {\r
380             startRow = this.GetLineNumberFromIndex(index);\r
381             while (startRow > 0 && this.Lines[startRow - 1].LineEnd == false)\r
382                 startRow--;\r
383 \r
384             endRow = this.GetLineNumberFromIndex(index + length);\r
385             while (endRow < this.Lines.Count && this.Lines[endRow].LineEnd == false)\r
386                 endRow++;\r
387             if (endRow >= this.Lines.Count)\r
388                 endRow = this.Lines.Count - 1;\r
389         }\r
390 \r
391         Tuple<int,int> GetAnalyzeLength(int startRow,int endRow,int updateStartIndex,int removedLength,int insertedLength)\r
392         {\r
393             int HeadIndex = this.GetIndexFromLineNumber(startRow);\r
394             int LastIndex = this.GetIndexFromLineNumber(endRow) + this.GetLengthFromLineNumber(endRow) - 1;\r
395 \r
396             //SpilitStringの対象となる範囲を求める\r
397             int fisrtPartLength = updateStartIndex - HeadIndex;\r
398             int secondPartLength = LastIndex - (updateStartIndex + removedLength - 1);\r
399             int analyzeLength = fisrtPartLength + secondPartLength + insertedLength;\r
400             Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);\r
401 \r
402             return new Tuple<int, int>(HeadIndex, analyzeLength);\r
403         }\r
404 \r
405         void RemoveLine(int startRow, int removeCount)\r
406         {\r
407             for (int i = startRow; i < startRow + removeCount; i++)\r
408                 this.Lines[i].Dispose();\r
409 \r
410             this.Lines.RemoveRange(startRow, removeCount);\r
411         }\r
412 \r
413         void InsertLine(int startRow, IList<LineToIndexTableData> collection,int removeCount, int deltaLength)\r
414         {\r
415             int newCount = collection.Count;\r
416             if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)\r
417             {\r
418                 //stepRowは1か2のうち、大きな方になる\r
419                 // 1.stepRow - (削除された行数 - 挿入された行数)\r
420                 // 2.行の挿入箇所\r
421                 //行が削除や置換された場合、1の処理をしないと正しいIndexが求められない\r
422                 this.stepRow = Math.Max(this.stepRow - (removeCount - newCount), startRow);\r
423 #if DEBUG\r
424                 if (this.stepRow < 0 || this.stepRow > this.Lines.Count + newCount)\r
425                 {\r
426                     Debug.WriteLine("step row < 0 or step row >= lines.count");\r
427                     Debugger.Break();\r
428                 }\r
429 #endif\r
430             }\r
431 \r
432             //startRowが挿入した行の開始位置なのであらかじめ引いておく\r
433             for (int i = 1; i < collection.Count; i++)\r
434             {\r
435                 if (this.stepRow != STEP_ROW_IS_NONE && startRow + i > this.stepRow)\r
436                     collection[i].Index -= deltaLength + this.stepLength;\r
437                 else\r
438                     collection[i].Index -= deltaLength;\r
439             }\r
440 \r
441             this.Lines.InsertRange(startRow, collection);\r
442         }\r
443 \r
444         void AddDummyLine()\r
445         {\r
446             LineToIndexTableData dummyLine = null;\r
447             if (this.Lines.Count == 0)\r
448             {\r
449                 dummyLine = new LineToIndexTableData();\r
450                 this.Lines.Add(dummyLine);\r
451                 return;\r
452             }\r
453 \r
454             int lastLineRow = this.Lines.Count > 0 ? this.Lines.Count - 1 : 0;\r
455             int lastLineHeadIndex = this.GetIndexFromLineNumber(lastLineRow);\r
456             int lastLineLength = this.GetLengthFromLineNumber(lastLineRow);\r
457 \r
458             if (lastLineLength != 0 && this.Document[Document.Length - 1] == Document.NewLine)\r
459             {\r
460                 int realIndex = lastLineHeadIndex + lastLineLength;\r
461                 if (lastLineRow >= this.stepRow)\r
462                     realIndex -= this.stepLength;\r
463                 dummyLine = new LineToIndexTableData(realIndex, 0, true,false, null);\r
464                 this.Lines.Add(dummyLine);\r
465             }\r
466         }\r
467 \r
468         void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)\r
469         {\r
470             if (this.Lines.Count == 0)\r
471             {\r
472                 this.stepRow = STEP_ROW_IS_NONE;\r
473                 this.stepLength = 0;\r
474                 return;\r
475             }\r
476 \r
477             if (this.stepRow == STEP_ROW_IS_NONE)\r
478             {\r
479                 this.stepRow = startRow;\r
480                 this.stepLength = deltaLength;\r
481                 return;\r
482             }\r
483 \r
484 \r
485             if (startRow < this.stepRow)\r
486             {\r
487                 //ドキュメントの後半部分をごっそり削除した場合、this.stepRow >= this.Lines.Countになる可能性がある\r
488                 if (this.stepRow >= this.Lines.Count)\r
489                     this.stepRow = this.Lines.Count - 1;\r
490                 for (int i = this.stepRow; i > startRow; i--)\r
491                     this.Lines[i].Index -= this.stepLength;\r
492             }\r
493             else if (startRow > this.stepRow)\r
494             {\r
495                 for (int i = this.stepRow + 1; i < startRow; i++)\r
496                     this.Lines[i].Index += this.stepLength;\r
497             }\r
498 \r
499             this.stepRow = startRow;\r
500             this.stepLength += deltaLength;\r
501 \r
502             this.ValidateLines();\r
503         }\r
504 \r
505         void ValidateLines()\r
506         {\r
507 #if DEBUG\r
508             int nextIndex = 0;\r
509             for (int i = 0; i < this.Lines.Count; i++)\r
510             {\r
511                 int lineHeadIndex = this.GetLineHeadIndex(i);\r
512                 if (lineHeadIndex != nextIndex)\r
513                 {\r
514                     Debug.WriteLine("Invaild Line");\r
515                     System.Diagnostics.Debugger.Break();\r
516                 }\r
517                 nextIndex = lineHeadIndex + this.Lines[i].Length;\r
518             }\r
519 #endif\r
520         }\r
521 \r
522         /// <summary>\r
523         /// 行番号をインデックスに変換します\r
524         /// </summary>\r
525         /// <param name="row">行番号</param>\r
526         /// <returns>0から始まるインデックスを返す</returns>\r
527         public int GetIndexFromLineNumber(int row)\r
528         {\r
529             if (row < 0 || row > this.Lines.Count)\r
530                 throw new ArgumentOutOfRangeException();\r
531             return this.GetLineHeadIndex(row);\r
532         }\r
533 \r
534         /// <summary>\r
535         /// 行の長さを得ます\r
536         /// </summary>\r
537         /// <param name="row">行番号</param>\r
538         /// <returns>行の文字長を返します</returns>\r
539         public int GetLengthFromLineNumber(int row)\r
540         {\r
541             if (row < 0 || row > this.Lines.Count)\r
542                 throw new ArgumentOutOfRangeException();\r
543             return this.Lines[row].Length;\r
544         }\r
545 \r
546         /// <summary>\r
547         /// 更新フラグを取得します\r
548         /// </summary>\r
549         /// <param name="row">行番号</param>\r
550         /// <returns>更新されていれば真。そうでなければ偽</returns>\r
551         public bool GetDirtyFlag(int row)\r
552         {\r
553             if (row < 0 || row > this.Lines.Count)\r
554                 throw new ArgumentOutOfRangeException();\r
555             return this.Lines[row].Dirty;\r
556         }\r
557 \r
558         internal ITextLayout GetLayout(int row)\r
559         {\r
560             if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)\r
561             {\r
562                 this.Lines[row].Layout.Dispose();\r
563                 this.Lines[row].Layout = null;\r
564             }\r
565             if (this.Lines[row].Layout == null || this.Lines[row].Layout.Disposed)\r
566                 this.Lines[row].Layout = this.CreateLayout(row);\r
567             return this.Lines[row].Layout;\r
568         }\r
569 \r
570         internal event EventHandler<CreateLayoutEventArgs> CreateingLayout;\r
571 \r
572         ITextLayout CreateLayout(int row)\r
573         {\r
574             ITextLayout layout;\r
575             LineToIndexTableData lineData = this.Lines[row];\r
576             if (lineData.Length == 0)\r
577             {\r
578                 layout = this.render.CreateLaytout("", null, null);\r
579             }\r
580             else\r
581             {\r
582                 int lineHeadIndex = this.GetLineHeadIndex(row);\r
583 \r
584                 string content = this.Document.ToString(lineHeadIndex, lineData.Length);\r
585 \r
586                 if (this.CreateingLayout != null)\r
587                     this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));\r
588                 \r
589                 var markerRange = from id in this.Document.Markers.IDs\r
590                                   from s in this.Document.Markers.Get(id,lineHeadIndex,lineData.Length)\r
591                                   let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)\r
592                                   select n;\r
593                 layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange);\r
594             }\r
595 \r
596             if (this.CacheEntries.Count > MaxEntries)\r
597             {\r
598                 ITextLayout oldItem = this.CacheEntries.Dequeue();\r
599                 oldItem.Dispose();\r
600             }\r
601             this.CacheEntries.Enqueue(layout);\r
602 \r
603             return layout;\r
604         }\r
605 \r
606         int lastLineNumber;\r
607         /// <summary>\r
608         /// インデックスを行番号に変換します\r
609         /// </summary>\r
610         /// <param name="index">インデックス</param>\r
611         /// <returns>行番号を返します</returns>\r
612         public int GetLineNumberFromIndex(int index)\r
613         {\r
614             if (index < 0)\r
615                 throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");\r
616 \r
617             if (index == 0 && this.Lines.Count > 0)\r
618                 return 0;\r
619 \r
620             LineToIndexTableData line;\r
621             int lineHeadIndex;\r
622 \r
623             if (lastLineNumber < this.Lines.Count - 1)\r
624             {\r
625                 line = this.Lines[lastLineNumber];\r
626                 lineHeadIndex = this.GetLineHeadIndex(lastLineNumber);\r
627                 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
628                     return lastLineNumber;\r
629             }\r
630 \r
631             int left = 0, right = this.Lines.Count - 1, mid;\r
632             while (left <= right)\r
633             {\r
634                 mid = (left + right) / 2;\r
635                 line = this.Lines[mid];\r
636                 lineHeadIndex = this.GetLineHeadIndex(mid);\r
637                 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
638                 {\r
639                     lastLineNumber = mid;\r
640                     return mid;\r
641                 }\r
642                 if (index < lineHeadIndex)\r
643                 {\r
644                     right = mid - 1;\r
645                 }\r
646                 else\r
647                 {\r
648                     left = mid + 1;\r
649                 }\r
650             }\r
651 \r
652             int lastRow = this.Lines.Count - 1;\r
653             line = this.Lines[lastRow];\r
654             lineHeadIndex = this.GetLineHeadIndex(lastRow);\r
655             if (index >= lineHeadIndex && index <= lineHeadIndex + line.Length)   //最終行長+1までキャレットが移動する可能性があるので\r
656             {\r
657                 lastLineNumber = this.Lines.Count - 1;\r
658                 return lastLineNumber;\r
659             }\r
660 \r
661             throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");\r
662         }\r
663 \r
664         /// <summary>\r
665         /// インデックスからテキストポイントに変換します\r
666         /// </summary>\r
667         /// <param name="index">インデックス</param>\r
668         /// <returns>TextPoint構造体を返します</returns>\r
669         public TextPoint GetTextPointFromIndex(int index)\r
670         {\r
671             TextPoint tp = new TextPoint();\r
672             tp.row = GetLineNumberFromIndex(index);\r
673             tp.col = index - this.GetLineHeadIndex(tp.row);\r
674             Debug.Assert(tp.row < this.Lines.Count && tp.col <= this.Lines[tp.row].Length);\r
675             return tp;\r
676         }\r
677 \r
678         /// <summary>\r
679         /// テキストポイントからインデックスに変換します\r
680         /// </summary>\r
681         /// <param name="tp">TextPoint構造体</param>\r
682         /// <returns>インデックスを返します</returns>\r
683         public int GetIndexFromTextPoint(TextPoint tp)\r
684         {\r
685             if (tp == TextPoint.Null)\r
686                 throw new ArgumentNullException("TextPoint.Null以外の値でなければなりません");\r
687             if(tp.row < 0 || tp.row > this.Lines.Count)\r
688                 throw new ArgumentOutOfRangeException("tp.rowが設定できる範囲を超えています");\r
689             if (tp.col < 0 || tp.col > this.Lines[tp.row].Length)\r
690                 throw new ArgumentOutOfRangeException("tp.colが設定できる範囲を超えています");\r
691             return this.GetLineHeadIndex(tp.row) + tp.col;\r
692         }\r
693 \r
694         /// <summary>\r
695         /// フォールディングを再生成します\r
696         /// </summary>\r
697         /// <param name="force">ドキュメントが更新されていなくても再生成する</param>\r
698         /// <returns>生成された場合は真を返す</returns>\r
699         /// <remarks>デフォルトではドキュメントが更新されている時にだけ再生成されます</remarks>\r
700         public bool GenerateFolding(bool force = false)\r
701         {\r
702             if (this.Document.Length == 0 || this.Document.IsLocked)\r
703                 return false;\r
704             long nowTick = DateTime.Now.Ticks;\r
705             if (force || Math.Abs(nowTick - this.lastUpdateTicks) >= AllowCallTicks)\r
706             {\r
707                 this.GenerateFolding(0, this.Document.Length - 1);\r
708                 this.lastUpdateTicks = nowTick;\r
709                 return true;\r
710             }\r
711             return false;\r
712         }\r
713 \r
714         void GenerateFolding(int start, int end)\r
715         {\r
716             if (start > end)\r
717                 throw new ArgumentException("start <= endである必要があります");\r
718             if (this.FoldingStrategy != null)\r
719             {\r
720                 //再生成するとすべて展開状態になってしまうので、閉じてるやつだけを保存しておく\r
721                 FoldingItem[] closed_items =  this.FoldingCollection.Where((e)=> { return !e.Expand; }).ToArray();\r
722 \r
723                 this.FoldingCollection.Clear();\r
724 \r
725                 var items = this.FoldingStrategy.AnalyzeDocument(this.Document, start, end)\r
726                     .Where((item) =>\r
727                     {\r
728                         int startRow = this.GetLineNumberFromIndex(item.Start);\r
729                         int endRow = this.GetLineNumberFromIndex(item.End);\r
730                         return startRow != endRow;\r
731                     })\r
732                     .Select((item) => item);\r
733                 this.FoldingCollection.AddRange(items);\r
734 \r
735                 this.FoldingCollection.ApplyExpandStatus(closed_items);\r
736             }\r
737         }\r
738 \r
739         /// <summary>\r
740         /// フォールディングをすべて削除します\r
741         /// </summary>\r
742         public void ClearFolding()\r
743         {\r
744             this.FoldingCollection.Clear();\r
745             this._IsSync = false;\r
746         }\r
747 \r
748         /// <summary>\r
749         /// すべての行に対しシンタックスハイライトを行います\r
750         /// </summary>\r
751         public bool HilightAll(bool force = false)\r
752         {\r
753             if (this.Hilighter == null || this.Document.IsLocked)\r
754                 return false;\r
755 \r
756             long nowTick = DateTime.Now.Ticks;\r
757             if (force || Math.Abs(nowTick - this.lastUpdateTicks) >= AllowCallTicks)\r
758             {\r
759                 for (int i = 0; i < this.Lines.Count; i++)\r
760                     this.HilightLine(i);\r
761 \r
762                 this.Hilighter.Reset();\r
763                 this.ClearLayoutCache();\r
764 \r
765                 this.lastUpdateTicks = nowTick;\r
766 \r
767                 return true;\r
768             }\r
769             return false;\r
770         }\r
771 \r
772         /// <summary>\r
773         /// ハイライト関連の情報をすべて削除します\r
774         /// </summary>\r
775         public void ClearHilight()\r
776         {\r
777             foreach (LineToIndexTableData line in this.Lines)\r
778                 line.Syntax = null;\r
779             this.ClearLayoutCache();\r
780         }\r
781 \r
782         /// <summary>\r
783         /// すべて削除します\r
784         /// </summary>\r
785         internal void Clear()\r
786         {\r
787             this.ClearLayoutCache();\r
788             this.FoldingCollection.Clear();\r
789             this.Lines.Clear();\r
790             LineToIndexTableData dummy = new LineToIndexTableData();\r
791             this.Lines.Add(dummy);\r
792             this.stepRow = STEP_ROW_IS_NONE;\r
793             this.stepLength = 0;\r
794             Debug.WriteLine("Clear");\r
795         }\r
796 \r
797         private void HilightLine(int row)\r
798         {\r
799             //シンタックスハイライトを行う\r
800             List<SyntaxInfo> syntax = new List<SyntaxInfo>();\r
801             string str = this[row];\r
802             int level = this.Hilighter.DoHilight(str, str.Length, (s) =>\r
803             {\r
804                 if (s.type == TokenType.None || s.type == TokenType.Control)\r
805                     return;\r
806                 if (str[s.index + s.length - 1] == Document.NewLine)\r
807                     s.length--;\r
808                 syntax.Add(new SyntaxInfo(s.index, s.length, s.type));\r
809             });\r
810 \r
811             LineToIndexTableData lineData = this.Lines[row];\r
812             lineData.Syntax = syntax.ToArray();\r
813 \r
814         }\r
815 \r
816         #region IEnumerable<string> メンバー\r
817 \r
818         /// <summary>\r
819         /// コレクションを反復処理するためのIEnumeratorを返す\r
820         /// </summary>\r
821         /// <returns>IEnumeratorオブジェクト</returns>\r
822         public IEnumerator<string> GetEnumerator()\r
823         {\r
824             for (int i = 0; i < this.Lines.Count; i++)\r
825                 yield return this[i];\r
826         }\r
827 \r
828         #endregion\r
829 \r
830         #region IEnumerable メンバー\r
831 \r
832         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()\r
833         {\r
834             for (int i = 0; i < this.Lines.Count; i++)\r
835                 yield return this[i];\r
836         }\r
837 \r
838         #endregion\r
839     }\r
840 \r
841 }\r