OSDN Git Service

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