OSDN Git Service

Documentのコンストラクターを変更した
[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         bool _IsSync;\r
216         ITextRender render;\r
217         int stepRow = -1,stepLength = 0;\r
218         const int STEP_ROW_IS_NONE = -1;\r
219 \r
220         internal LineToIndexTable(Document buf)\r
221         {\r
222             this.Document = buf;\r
223             this.Document.Markers.Updated += Markers_Updated;\r
224             this.FoldingCollection = new FoldingCollection();\r
225             this._IsSync = true;\r
226 #if DEBUG && !NETFX_CORE\r
227             if (!Debugger.IsAttached)\r
228             {\r
229                 Guid guid = Guid.NewGuid();\r
230                 string path = string.Format("{0}\\footextbox_lti_debug_{1}.log", System.IO.Path.GetTempPath(), guid);\r
231                 Debug.Listeners.Add(new TextWriterTraceListener(path));\r
232                 Debug.AutoFlush = true;\r
233             }\r
234 #endif\r
235         }\r
236 \r
237         void Markers_Updated(object sender, EventArgs e)\r
238         {\r
239             this.ClearLayoutCache();\r
240         }\r
241 \r
242         /// <summary>\r
243         /// ITextRenderインターフェイスのインスタンス。必ずセットすること\r
244         /// </summary>\r
245         internal ITextRender Render\r
246         {\r
247             get { return this.render; }\r
248             set\r
249             {\r
250                 this.render = value;\r
251             }\r
252         }\r
253 \r
254         internal SpilitStringEventHandler SpilitString;\r
255 \r
256         /// <summary>\r
257         /// 行数を返す\r
258         /// </summary>\r
259         public int Count\r
260         {\r
261             get { return this.Lines.Count; }\r
262         }\r
263 \r
264         /// <summary>\r
265         /// 折り畳み関係の情報を収めたコレクション\r
266         /// </summary>\r
267         public FoldingCollection FoldingCollection\r
268         {\r
269             get;\r
270             private set;\r
271         }\r
272 \r
273         /// <summary>\r
274         /// シンタックスハイライター\r
275         /// </summary>\r
276         internal IHilighter Hilighter { get; set; }\r
277 \r
278         internal IFoldingStrategy FoldingStrategy { get; set; }\r
279 \r
280         /// <summary>\r
281         /// 保持しているレイアウトキャッシュをクリアーする\r
282         /// </summary>\r
283         public void ClearLayoutCache()\r
284         {\r
285             foreach (ITextLayout data in this.CacheEntries)\r
286             {\r
287                 data.Dispose();\r
288             }\r
289             this.CacheEntries.Clear();\r
290         }\r
291 \r
292         /// <summary>\r
293         /// 行番号に対応する文字列を返します\r
294         /// </summary>\r
295         /// <param name="n"></param>\r
296         /// <returns></returns>\r
297         public string this[int n]\r
298         {\r
299             get\r
300             {\r
301                 LineToIndexTableData data = this.Lines[n];\r
302                 string str = this.Document.ToString(this.GetLineHeadIndex(n), data.Length);\r
303 \r
304                 return str;\r
305             }\r
306         }\r
307 \r
308         /// <summary>\r
309         /// 更新フラグを更新しないなら真\r
310         /// </summary>\r
311         public bool IsFrozneDirtyFlag\r
312         {\r
313             get;\r
314             set;\r
315         }\r
316 \r
317         int GetLineHeadIndex(int row)\r
318         {\r
319             if (this.Lines.Count == 0)\r
320                 return 0;\r
321             if (this.stepRow != STEP_ROW_IS_NONE && row > this.stepRow)\r
322                 return this.Lines[row].Index + this.stepLength;\r
323             else\r
324                 return this.Lines[row].Index;\r
325         }\r
326 \r
327         internal LineToIndexTableData CreateLineToIndexTableData(int index, int length, bool lineend, SyntaxInfo[] syntax)\r
328         {\r
329             LineToIndexTableData result = new LineToIndexTableData(index, length, lineend,this.IsFrozneDirtyFlag == false, syntax);\r
330             return result;\r
331         }\r
332 \r
333         internal void UpdateAsReplace(int index, int removedLength, int insertedLength)\r
334         {\r
335 #if DEBUG\r
336             Debug.WriteLine("Replaced Index:{0} RemoveLength:{1} InsertLength:{2}", index, removedLength, insertedLength);\r
337 #endif\r
338             //削除すべき行の開始位置を求める\r
339             int startRow = this.GetLineNumberFromIndex(index);\r
340             while(startRow > 0 && this.Lines[startRow - 1].LineEnd == false)\r
341                 startRow--;\r
342 \r
343             //削除すべき行の終了位置を求める\r
344             int endRow = this.GetLineNumberFromIndex(index + removedLength);\r
345             while (endRow < this.Lines.Count && this.Lines[endRow].LineEnd == false)\r
346                 endRow++;\r
347             if (endRow >= this.Lines.Count)\r
348                 endRow = this.Lines.Count - 1;\r
349 \r
350             //SpilitStringの対象となる範囲を求める\r
351             int HeadIndex = this.GetIndexFromLineNumber(startRow);\r
352             int LastIndex = this.GetIndexFromLineNumber(endRow) + this.GetLengthFromLineNumber(endRow) - 1;\r
353             int fisrtPartLength = index - HeadIndex;\r
354             int secondPartLength = LastIndex - (index + removedLength - 1);\r
355             int analyzeLength = fisrtPartLength + secondPartLength + insertedLength;\r
356             Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);\r
357 \r
358             //行を削除する\r
359             int removeCount = endRow - startRow + 1;\r
360             this.RemoveLine(startRow, removeCount);\r
361 \r
362             //挿入範囲内のドキュメントから行を生成する\r
363             SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);\r
364             IList<LineToIndexTableData> newLines = SpilitString(this, e);\r
365 \r
366             //行を挿入する\r
367             int newCount = newLines.Count;\r
368             if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)\r
369             {\r
370                 this.stepRow = Math.Max(this.stepRow - (removeCount - newCount),startRow);\r
371 #if DEBUG\r
372                 if (this.stepRow < 0 || this.stepRow > this.Lines.Count + newCount)\r
373                 {\r
374                     Debug.WriteLine("step row < 0 or step row >= lines.count");\r
375                     Debugger.Break();\r
376                 }\r
377 #endif\r
378             }\r
379             int deltaLength = insertedLength - removedLength;\r
380             this.InsertLine(startRow, newLines, deltaLength);\r
381             this.UpdateLineHeadIndex(deltaLength, startRow, newLines.Count);\r
382             this.AddDummyLine();\r
383 \r
384             this.FoldingCollection.UpdateData(this.Document, index, insertedLength, removedLength);            \r
385             this.FoldingCollection.CollectEmptyFolding(index, Document.Length - 1);\r
386 \r
387             this.Hilight(startRow, newLines.Count);\r
388 \r
389             this._IsSync = false;\r
390         }\r
391 \r
392         void RemoveLine(int startRow, int removeCount)\r
393         {\r
394             for (int i = startRow; i < startRow + removeCount; i++)\r
395                 this.Lines[i].Dispose();\r
396 \r
397             this.Lines.RemoveRange(startRow, removeCount);\r
398         }\r
399 \r
400         void InsertLine(int startRow, IList<LineToIndexTableData> collection, int deltaLength)\r
401         {\r
402             //startRowが挿入した行の開始位置なのであらかじめ引いておく\r
403             for (int i = 1; i < collection.Count; i++)\r
404             {\r
405                 if (this.stepRow != STEP_ROW_IS_NONE && startRow + i > this.stepRow)\r
406                     collection[i].Index -= deltaLength + this.stepLength;\r
407                 else\r
408                     collection[i].Index -= deltaLength;\r
409             }\r
410 \r
411             this.Lines.InsertRange(startRow, collection);\r
412         }\r
413 \r
414         void AddDummyLine()\r
415         {\r
416             LineToIndexTableData dummyLine = null;\r
417             if (this.Lines.Count == 0)\r
418             {\r
419                 dummyLine = new LineToIndexTableData();\r
420                 this.Lines.Add(dummyLine);\r
421                 return;\r
422             }\r
423 \r
424             int lastLineRow = this.Lines.Count > 0 ? this.Lines.Count - 1 : 0;\r
425             int lastLineHeadIndex = this.GetIndexFromLineNumber(lastLineRow);\r
426             int lastLineLength = this.GetLengthFromLineNumber(lastLineRow);\r
427 \r
428             if (lastLineLength != 0 && this.Document[Document.Length - 1] == Document.NewLine)\r
429             {\r
430                 int realIndex = lastLineHeadIndex + lastLineLength;\r
431                 if (lastLineRow >= this.stepRow)\r
432                     realIndex -= this.stepLength;\r
433                 dummyLine = new LineToIndexTableData(realIndex, 0, true,false, null);\r
434                 this.Lines.Add(dummyLine);\r
435             }\r
436         }\r
437 \r
438         void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)\r
439         {\r
440             if (this.Lines.Count == 0)\r
441             {\r
442                 this.stepRow = STEP_ROW_IS_NONE;\r
443                 this.stepLength = 0;\r
444                 return;\r
445             }\r
446 \r
447             if (this.stepRow == STEP_ROW_IS_NONE)\r
448             {\r
449                 this.stepRow = startRow;\r
450                 this.stepLength = deltaLength;\r
451                 return;\r
452             }\r
453 \r
454 \r
455             if (startRow < this.stepRow)\r
456             {\r
457                 //ドキュメントの後半部分をごっそり削除した場合、this.stepRow >= this.Lines.Countになる可能性がある\r
458                 if (this.stepRow >= this.Lines.Count)\r
459                     this.stepRow = this.Lines.Count - 1;\r
460                 for (int i = this.stepRow; i > startRow; i--)\r
461                     this.Lines[i].Index -= this.stepLength;\r
462             }\r
463             else if (startRow > this.stepRow)\r
464             {\r
465                 for (int i = this.stepRow + 1; i < startRow; i++)\r
466                     this.Lines[i].Index += this.stepLength;\r
467             }\r
468 \r
469             this.stepRow = startRow;\r
470             this.stepLength += deltaLength;\r
471 \r
472             this.ValidateLines();\r
473         }\r
474 \r
475         void ValidateLines()\r
476         {\r
477 #if DEBUG\r
478             int nextIndex = 0;\r
479             for (int i = 0; i < this.Lines.Count; i++)\r
480             {\r
481                 int lineHeadIndex = this.GetLineHeadIndex(i);\r
482                 if (lineHeadIndex != nextIndex)\r
483                 {\r
484                     Debug.WriteLine("Invaild Line");\r
485                     System.Diagnostics.Debugger.Break();\r
486                 }\r
487                 nextIndex = lineHeadIndex + this.Lines[i].Length;\r
488             }\r
489 #endif\r
490         }\r
491 \r
492         /// <summary>\r
493         /// 行番号をインデックスに変換します\r
494         /// </summary>\r
495         /// <param name="row">行番号</param>\r
496         /// <returns>0から始まるインデックスを返す</returns>\r
497         public int GetIndexFromLineNumber(int row)\r
498         {\r
499             if (row < 0 || row > this.Lines.Count)\r
500                 throw new ArgumentOutOfRangeException();\r
501             return this.GetLineHeadIndex(row);\r
502         }\r
503 \r
504         /// <summary>\r
505         /// 行の長さを得ます\r
506         /// </summary>\r
507         /// <param name="row">行番号</param>\r
508         /// <returns>行の文字長を返します</returns>\r
509         public int GetLengthFromLineNumber(int row)\r
510         {\r
511             if (row < 0 || row > this.Lines.Count)\r
512                 throw new ArgumentOutOfRangeException();\r
513             return this.Lines[row].Length;\r
514         }\r
515 \r
516         /// <summary>\r
517         /// 更新フラグを取得します\r
518         /// </summary>\r
519         /// <param name="row">行番号</param>\r
520         /// <returns>更新されていれば真。そうでなければ偽</returns>\r
521         public bool GetDirtyFlag(int row)\r
522         {\r
523             if (row < 0 || row > this.Lines.Count)\r
524                 throw new ArgumentOutOfRangeException();\r
525             return this.Lines[row].Dirty;\r
526         }\r
527 \r
528         internal ITextLayout GetLayout(int row)\r
529         {\r
530             if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)\r
531             {\r
532                 this.Lines[row].Layout.Dispose();\r
533                 this.Lines[row].Layout = null;\r
534             }\r
535             if (this.Lines[row].Layout == null || this.Lines[row].Layout.Disposed)\r
536                 this.Lines[row].Layout = this.CreateLayout(row);\r
537             return this.Lines[row].Layout;\r
538         }\r
539 \r
540         internal event EventHandler<CreateLayoutEventArgs> CreateingLayout;\r
541 \r
542         ITextLayout CreateLayout(int row)\r
543         {\r
544             ITextLayout layout;\r
545             LineToIndexTableData lineData = this.Lines[row];\r
546             if (lineData.Length == 0)\r
547             {\r
548                 layout = this.render.CreateLaytout("", null, null);\r
549             }\r
550             else\r
551             {\r
552                 int lineHeadIndex = this.GetLineHeadIndex(row);\r
553 \r
554                 string content = this.Document.ToString(lineHeadIndex, lineData.Length);\r
555 \r
556                 if (this.CreateingLayout != null)\r
557                     this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));\r
558                 \r
559                 var markerRange = from id in this.Document.Markers.IDs\r
560                                   from s in this.Document.Markers.Get(id,lineHeadIndex,lineData.Length)\r
561                                   let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)\r
562                                   select n;\r
563                 layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange);\r
564             }\r
565 \r
566             if (this.CacheEntries.Count > MaxEntries)\r
567             {\r
568                 ITextLayout oldItem = this.CacheEntries.Dequeue();\r
569                 oldItem.Dispose();\r
570             }\r
571             this.CacheEntries.Enqueue(layout);\r
572 \r
573             return layout;\r
574         }\r
575 \r
576         int lastLineNumber;\r
577         /// <summary>\r
578         /// インデックスを行番号に変換します\r
579         /// </summary>\r
580         /// <param name="index">インデックス</param>\r
581         /// <returns>行番号を返します</returns>\r
582         public int GetLineNumberFromIndex(int index)\r
583         {\r
584             if (index < 0)\r
585                 throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");\r
586 \r
587             if (index == 0 && this.Lines.Count > 0)\r
588                 return 0;\r
589 \r
590             LineToIndexTableData line;\r
591             int lineHeadIndex;\r
592 \r
593             if (lastLineNumber < this.Lines.Count - 1)\r
594             {\r
595                 line = this.Lines[lastLineNumber];\r
596                 lineHeadIndex = this.GetLineHeadIndex(lastLineNumber);\r
597                 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
598                     return lastLineNumber;\r
599             }\r
600 \r
601             int left = 0, right = this.Lines.Count - 1, mid;\r
602             while (left <= right)\r
603             {\r
604                 mid = (left + right) / 2;\r
605                 line = this.Lines[mid];\r
606                 lineHeadIndex = this.GetLineHeadIndex(mid);\r
607                 if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)\r
608                 {\r
609                     lastLineNumber = mid;\r
610                     return mid;\r
611                 }\r
612                 if (index < lineHeadIndex)\r
613                 {\r
614                     right = mid - 1;\r
615                 }\r
616                 else\r
617                 {\r
618                     left = mid + 1;\r
619                 }\r
620             }\r
621 \r
622             int lastRow = this.Lines.Count - 1;\r
623             line = this.Lines[lastRow];\r
624             lineHeadIndex = this.GetLineHeadIndex(lastRow);\r
625             if (index >= lineHeadIndex && index <= lineHeadIndex + line.Length)   //最終行長+1までキャレットが移動する可能性があるので\r
626             {\r
627                 lastLineNumber = this.Lines.Count - 1;\r
628                 return lastLineNumber;\r
629             }\r
630 \r
631             throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");\r
632         }\r
633 \r
634         /// <summary>\r
635         /// インデックスからテキストポイントに変換します\r
636         /// </summary>\r
637         /// <param name="index">インデックス</param>\r
638         /// <returns>TextPoint構造体を返します</returns>\r
639         public TextPoint GetTextPointFromIndex(int index)\r
640         {\r
641             TextPoint tp = new TextPoint();\r
642             tp.row = GetLineNumberFromIndex(index);\r
643             tp.col = index - this.GetLineHeadIndex(tp.row);\r
644             Debug.Assert(tp.row < this.Lines.Count && tp.col <= this.Lines[tp.row].Length);\r
645             return tp;\r
646         }\r
647 \r
648         /// <summary>\r
649         /// テキストポイントからインデックスに変換します\r
650         /// </summary>\r
651         /// <param name="tp">TextPoint構造体</param>\r
652         /// <returns>インデックスを返します</returns>\r
653         public int GetIndexFromTextPoint(TextPoint tp)\r
654         {\r
655             if (tp == TextPoint.Null)\r
656                 throw new ArgumentNullException("TextPoint.Null以外の値でなければなりません");\r
657             if(tp.row < 0 || tp.row > this.Lines.Count)\r
658                 throw new ArgumentOutOfRangeException("tp.rowが設定できる範囲を超えています");\r
659             if (tp.col < 0 || tp.col > this.Lines[tp.row].Length)\r
660                 throw new ArgumentOutOfRangeException("tp.colが設定できる範囲を超えています");\r
661             return this.GetLineHeadIndex(tp.row) + tp.col;\r
662         }\r
663 \r
664         /// <summary>\r
665         /// フォールディングを再生成します\r
666         /// </summary>\r
667         /// <param name="force">ドキュメントが更新されていなくても再生成する</param>\r
668         /// <returns>生成された場合は真を返す</returns>\r
669         /// <remarks>デフォルトではドキュメントが更新されている時にだけ再生成されます</remarks>\r
670         public bool GenerateFolding(bool force = false)\r
671         {\r
672             if (force)\r
673                 this._IsSync = false;\r
674             if (this.Document.Length == 0 || this._IsSync)\r
675                 return false;\r
676             this.GenerateFolding(0, this.Document.Length - 1);\r
677             this._IsSync = true;\r
678             return true;\r
679         }\r
680 \r
681         void GenerateFolding(int start, int end)\r
682         {\r
683             if (start > end)\r
684                 throw new ArgumentException("start <= endである必要があります");\r
685             if (this.FoldingStrategy != null)\r
686             {\r
687                 var items = this.FoldingStrategy.AnalyzeDocument(this.Document, start, end)\r
688                     .Where((item) =>\r
689                     {\r
690                         int startRow = this.GetLineNumberFromIndex(item.Start);\r
691                         int endRow = this.GetLineNumberFromIndex(item.End);\r
692                         return startRow != endRow;\r
693                     })\r
694                     .Select((item) => item);\r
695                 this.FoldingCollection.AddRange(items);\r
696             }\r
697         }\r
698 \r
699         /// <summary>\r
700         /// フォールディングをすべて削除します\r
701         /// </summary>\r
702         public void ClearFolding()\r
703         {\r
704             this.FoldingCollection.Clear();\r
705             this._IsSync = false;\r
706         }\r
707 \r
708         /// <summary>\r
709         /// すべての行に対しシンタックスハイライトを行います\r
710         /// </summary>\r
711         public void HilightAll()\r
712         {\r
713             this.Hilight(0, this.Lines.Count);\r
714             this.ClearLayoutCache();\r
715         }\r
716 \r
717         /// <summary>\r
718         /// ハイライト関連の情報をすべて削除します\r
719         /// </summary>\r
720         public void ClearHilight()\r
721         {\r
722             foreach (LineToIndexTableData line in this.Lines)\r
723                 line.Syntax = null;\r
724             this.ClearLayoutCache();\r
725         }\r
726 \r
727         /// <summary>\r
728         /// すべて削除します\r
729         /// </summary>\r
730         internal void Clear()\r
731         {\r
732             this.ClearLayoutCache();\r
733             this.FoldingCollection.Clear();\r
734             this.Lines.Clear();\r
735             LineToIndexTableData dummy = new LineToIndexTableData();\r
736             this.Lines.Add(dummy);\r
737             this.stepRow = STEP_ROW_IS_NONE;\r
738             this.stepLength = 0;\r
739             Debug.WriteLine("Clear");\r
740         }\r
741 \r
742         void Hilight(int row, int rowCount)\r
743         {\r
744             if (this.Hilighter == null || rowCount == 0)\r
745                 return;\r
746 \r
747             //シンタックスハイライトの開始行を求める\r
748             int startRow = row;\r
749             EncloserType type = this.Lines[startRow].EncloserType;\r
750             EncloserType prevLineType = startRow > 0 ? this.Lines[startRow - 1].EncloserType : EncloserType.None;\r
751             if (type == EncloserType.Now || type == EncloserType.End ||\r
752                 prevLineType == EncloserType.Now || prevLineType == EncloserType.End)\r
753                 startRow = SearchStartRow(startRow);\r
754             else if (prevLineType == EncloserType.Begin)\r
755                 startRow = startRow - 1;\r
756 \r
757             //シンタックスハイライトの終了行を求める\r
758             int endRow = row + rowCount - 1;\r
759             type = this.Lines[endRow].EncloserType;\r
760             prevLineType = endRow > 0 ? this.Lines[endRow - 1].EncloserType : EncloserType.None;\r
761             if (type == EncloserType.Begin || type == EncloserType.Now ||\r
762                 prevLineType == EncloserType.Begin || prevLineType == EncloserType.Now)\r
763                 endRow = SearchEndRow(endRow);\r
764             else if (endRow + 1 <= this.Lines.Count - 1 && this.Lines[endRow + 1].EncloserType == EncloserType.Now)\r
765                 endRow = SearchEndRow(endRow + 1);\r
766 \r
767             //シンタックスハイライトを行う\r
768             bool hasBeginEncloser = false;\r
769             int i;\r
770             for (i = startRow; i <= endRow; i++)\r
771             {\r
772                 this.HilightLine(i, ref hasBeginEncloser);\r
773             }\r
774 \r
775             if (hasBeginEncloser)   //終了エンクロージャーが見つかったかどうか\r
776             {\r
777                 for (; i < this.Lines.Count; i++)\r
778                 {\r
779                     if (this.HilightLine(i, ref hasBeginEncloser) < 0)\r
780                         break;\r
781                 }\r
782             }\r
783 \r
784             this.Hilighter.Reset();\r
785         }\r
786 \r
787         private int HilightLine(int row, ref bool hasBeginEncloser)\r
788         {\r
789             //シンタックスハイライトを行う\r
790             List<SyntaxInfo> syntax = new List<SyntaxInfo>();\r
791             string str = this[row];\r
792             int level = this.Hilighter.DoHilight(str, str.Length, (s) =>\r
793             {\r
794                 if (s.type == TokenType.None || s.type == TokenType.Control)\r
795                     return;\r
796                 if (str[s.index + s.length - 1] == Document.NewLine)\r
797                     s.length--;\r
798                 syntax.Add(new SyntaxInfo(s.index, s.length, s.type));\r
799             });\r
800 \r
801             LineToIndexTableData lineData = this.Lines[row];\r
802             lineData.Syntax = syntax.ToArray();\r
803 \r
804             if (level > 0 && hasBeginEncloser == false)  //開始エンクロージャー\r
805             {\r
806                 lineData.EncloserType = EncloserType.Begin;\r
807                 hasBeginEncloser = true;\r
808             }\r
809             else if (level < 0) //終了エンクロージャー\r
810             {\r
811                 lineData.EncloserType = EncloserType.End;\r
812                 hasBeginEncloser = false;\r
813             }\r
814             else if (hasBeginEncloser) //エンクロージャーの範囲内\r
815                 lineData.EncloserType = EncloserType.Now;\r
816             else\r
817                 lineData.EncloserType = EncloserType.None;\r
818 \r
819             return level;\r
820         }\r
821 \r
822         private int SearchStartRow(int startRow)\r
823         {\r
824             for (startRow--; startRow >= 0; startRow--)\r
825             {\r
826                 EncloserType type = this.Lines[startRow].EncloserType;\r
827                 if (type == EncloserType.Begin || type == EncloserType.None)\r
828                     return startRow;\r
829             }\r
830             return 0;\r
831         }\r
832 \r
833         private int SearchEndRow(int startRow)\r
834         {\r
835             for (startRow++ ; startRow < this.Lines.Count; startRow++)\r
836             {\r
837                 EncloserType type = this.Lines[startRow].EncloserType;\r
838                 if (type == EncloserType.End)\r
839                     return startRow;\r
840             }\r
841             return this.Lines.Count - 1;\r
842         }\r
843 \r
844         #region IEnumerable<string> メンバー\r
845 \r
846         /// <summary>\r
847         /// コレクションを反復処理するためのIEnumeratorを返す\r
848         /// </summary>\r
849         /// <returns>IEnumeratorオブジェクト</returns>\r
850         public IEnumerator<string> GetEnumerator()\r
851         {\r
852             for (int i = 0; i < this.Lines.Count; i++)\r
853                 yield return this[i];\r
854         }\r
855 \r
856         #endregion\r
857 \r
858         #region IEnumerable メンバー\r
859 \r
860         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()\r
861         {\r
862             for (int i = 0; i < this.Lines.Count; i++)\r
863                 yield return this[i];\r
864         }\r
865 \r
866         #endregion\r
867     }\r
868 \r
869 }\r