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
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
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
12 using System.Text.RegularExpressions;
\r
13 using System.Threading;
\r
15 using System.Collections.Generic;
\r
16 using System.Diagnostics;
\r
17 using Slusser.Collections.Generic;
\r
19 namespace FooEditEngine
\r
21 internal interface ITextLayout : IDisposable
\r
40 /// Disposeされているなら真を返す
\r
48 /// 破棄すべきなら真。そうでなければ偽
\r
56 /// 桁方向の座標に対応するインデックスを得る
\r
58 /// <param name="colpos">桁方向の座標</param>
\r
59 /// <returns>インデックス</returns>
\r
60 /// <remarks>行番号の幅は考慮されてないのでView以外のクラスは呼び出さないでください</remarks>
\r
61 int GetIndexFromColPostion(double colpos);
\r
64 /// インデックスに対応する文字の幅を得る
\r
66 /// <param name="index">インデックス</param>
\r
67 /// <returns>文字の幅</returns>
\r
68 double GetWidthFromIndex(int index);
\r
71 /// インデックスに対応する桁方向の座標を得る
\r
73 /// <param name="index">インデックス</param>
\r
74 /// <returns>桁方向の座標</returns>
\r
75 /// <remarks>行頭にEOFが含まれている場合、0が返ります</remarks>
\r
76 double GetColPostionFromIndex(int index);
\r
79 /// 適切な位置にインデックスを調整する
\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
88 internal class SpilitStringEventArgs : EventArgs
\r
90 public Document buffer;
\r
94 public SpilitStringEventArgs(Document buf, int index, int length,int row)
\r
98 this.length = length;
\r
103 internal struct SyntaxInfo
\r
105 public TokenType type;
\r
108 public SyntaxInfo(int index, int length, TokenType type)
\r
111 this.index = index;
\r
112 this.length = length;
\r
116 internal enum EncloserType
\r
124 internal class LineToIndexTableData : IDisposable
\r
127 /// 行の先頭。正しい行の先頭位置を取得するにはGetLineHeadIndex()を使用してください
\r
135 /// 改行マークかEOFなら真を返す
\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
144 /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
\r
146 public LineToIndexTableData()
\r
151 /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
\r
153 public LineToIndexTableData(int index, int length, bool lineend,bool dirty, SyntaxInfo[] syntax)
\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
163 public void Dispose()
\r
165 if(this.Layout != null)
\r
166 this.Layout.Dispose();
\r
170 internal delegate IList<LineToIndexTableData> SpilitStringEventHandler(object sender, SpilitStringEventArgs e);
\r
172 internal sealed class CreateLayoutEventArgs
\r
193 public string Content
\r
198 public CreateLayoutEventArgs(int index, int length,string content)
\r
200 this.Index = index;
\r
201 this.Length = length;
\r
202 this.Content = content;
\r
207 /// 行番号とインデックスを相互変換するためのクラス
\r
209 public sealed class LineToIndexTable : IEnumerable<string>
\r
211 const int MaxEntries = 100;
\r
212 Queue<ITextLayout> CacheEntries = new Queue<ITextLayout>();
\r
213 GapBuffer<LineToIndexTableData> Lines = new GapBuffer<LineToIndexTableData>();
\r
216 ITextRender render;
\r
217 int stepRow = -1,stepLength = 0;
\r
218 const int STEP_ROW_IS_NONE = -1;
\r
220 internal LineToIndexTable(Document buf)
\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
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
237 void Markers_Updated(object sender, EventArgs e)
\r
239 this.ClearLayoutCache();
\r
243 /// ITextRenderインターフェイスのインスタンス。必ずセットすること
\r
245 internal ITextRender Render
\r
247 get { return this.render; }
\r
250 this.render = value;
\r
254 internal SpilitStringEventHandler SpilitString;
\r
261 get { return this.Lines.Count; }
\r
265 /// 折り畳み関係の情報を収めたコレクション
\r
267 public FoldingCollection FoldingCollection
\r
276 internal IHilighter Hilighter { get; set; }
\r
278 internal IFoldingStrategy FoldingStrategy { get; set; }
\r
281 /// 保持しているレイアウトキャッシュをクリアーする
\r
283 public void ClearLayoutCache()
\r
285 foreach (ITextLayout data in this.CacheEntries)
\r
289 this.CacheEntries.Clear();
\r
293 /// 行番号に対応する文字列を返します
\r
295 /// <param name="n"></param>
\r
296 /// <returns></returns>
\r
297 public string this[int n]
\r
301 LineToIndexTableData data = this.Lines[n];
\r
302 string str = this.Document.ToString(this.GetLineHeadIndex(n), data.Length);
\r
311 public bool IsFrozneDirtyFlag
\r
317 int GetLineHeadIndex(int row)
\r
319 if (this.Lines.Count == 0)
\r
321 if (this.stepRow != STEP_ROW_IS_NONE && row > this.stepRow)
\r
322 return this.Lines[row].Index + this.stepLength;
\r
324 return this.Lines[row].Index;
\r
327 internal LineToIndexTableData CreateLineToIndexTableData(int index, int length, bool lineend, SyntaxInfo[] syntax)
\r
329 LineToIndexTableData result = new LineToIndexTableData(index, length, lineend,this.IsFrozneDirtyFlag == false, syntax);
\r
333 internal void UpdateAsReplace(int index, int removedLength, int insertedLength)
\r
336 Debug.WriteLine("Replaced Index:{0} RemoveLength:{1} InsertLength:{2}", index, removedLength, insertedLength);
\r
339 int startRow = this.GetLineNumberFromIndex(index);
\r
340 while(startRow > 0 && this.Lines[startRow - 1].LineEnd == false)
\r
344 int endRow = this.GetLineNumberFromIndex(index + removedLength);
\r
345 while (endRow < this.Lines.Count && this.Lines[endRow].LineEnd == false)
\r
347 if (endRow >= this.Lines.Count)
\r
348 endRow = this.Lines.Count - 1;
\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
359 int removeCount = endRow - startRow + 1;
\r
360 this.RemoveLine(startRow, removeCount);
\r
362 //挿入範囲内のドキュメントから行を生成する
\r
363 SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);
\r
364 IList<LineToIndexTableData> newLines = SpilitString(this, e);
\r
367 int newCount = newLines.Count;
\r
368 if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)
\r
370 this.stepRow = Math.Max(this.stepRow - (removeCount - newCount),startRow);
\r
372 if (this.stepRow < 0 || this.stepRow > this.Lines.Count + newCount)
\r
374 Debug.WriteLine("step row < 0 or step row >= lines.count");
\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
384 this.FoldingCollection.UpdateData(this.Document, index, insertedLength, removedLength);
\r
385 this.FoldingCollection.CollectEmptyFolding(index, Document.Length - 1);
\r
387 this.Hilight(startRow, newLines.Count);
\r
389 this._IsSync = false;
\r
392 void RemoveLine(int startRow, int removeCount)
\r
394 for (int i = startRow; i < startRow + removeCount; i++)
\r
395 this.Lines[i].Dispose();
\r
397 this.Lines.RemoveRange(startRow, removeCount);
\r
400 void InsertLine(int startRow, IList<LineToIndexTableData> collection, int deltaLength)
\r
402 //startRowが挿入した行の開始位置なのであらかじめ引いておく
\r
403 for (int i = 1; i < collection.Count; i++)
\r
405 if (this.stepRow != STEP_ROW_IS_NONE && startRow + i > this.stepRow)
\r
406 collection[i].Index -= deltaLength + this.stepLength;
\r
408 collection[i].Index -= deltaLength;
\r
411 this.Lines.InsertRange(startRow, collection);
\r
414 void AddDummyLine()
\r
416 LineToIndexTableData dummyLine = null;
\r
417 if (this.Lines.Count == 0)
\r
419 dummyLine = new LineToIndexTableData();
\r
420 this.Lines.Add(dummyLine);
\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
428 if (lastLineLength != 0 && this.Document[Document.Length - 1] == Document.NewLine)
\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
438 void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)
\r
440 if (this.Lines.Count == 0)
\r
442 this.stepRow = STEP_ROW_IS_NONE;
\r
443 this.stepLength = 0;
\r
447 if (this.stepRow == STEP_ROW_IS_NONE)
\r
449 this.stepRow = startRow;
\r
450 this.stepLength = deltaLength;
\r
455 if (startRow < this.stepRow)
\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
463 else if (startRow > this.stepRow)
\r
465 for (int i = this.stepRow + 1; i < startRow; i++)
\r
466 this.Lines[i].Index += this.stepLength;
\r
469 this.stepRow = startRow;
\r
470 this.stepLength += deltaLength;
\r
472 this.ValidateLines();
\r
475 void ValidateLines()
\r
479 for (int i = 0; i < this.Lines.Count; i++)
\r
481 int lineHeadIndex = this.GetLineHeadIndex(i);
\r
482 if (lineHeadIndex != nextIndex)
\r
484 Debug.WriteLine("Invaild Line");
\r
485 System.Diagnostics.Debugger.Break();
\r
487 nextIndex = lineHeadIndex + this.Lines[i].Length;
\r
493 /// 行番号をインデックスに変換します
\r
495 /// <param name="row">行番号</param>
\r
496 /// <returns>0から始まるインデックスを返す</returns>
\r
497 public int GetIndexFromLineNumber(int row)
\r
499 if (row < 0 || row > this.Lines.Count)
\r
500 throw new ArgumentOutOfRangeException();
\r
501 return this.GetLineHeadIndex(row);
\r
507 /// <param name="row">行番号</param>
\r
508 /// <returns>行の文字長を返します</returns>
\r
509 public int GetLengthFromLineNumber(int row)
\r
511 if (row < 0 || row > this.Lines.Count)
\r
512 throw new ArgumentOutOfRangeException();
\r
513 return this.Lines[row].Length;
\r
519 /// <param name="row">行番号</param>
\r
520 /// <returns>更新されていれば真。そうでなければ偽</returns>
\r
521 public bool GetDirtyFlag(int row)
\r
523 if (row < 0 || row > this.Lines.Count)
\r
524 throw new ArgumentOutOfRangeException();
\r
525 return this.Lines[row].Dirty;
\r
528 internal ITextLayout GetLayout(int row)
\r
530 if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)
\r
532 this.Lines[row].Layout.Dispose();
\r
533 this.Lines[row].Layout = null;
\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
540 internal event EventHandler<CreateLayoutEventArgs> CreateingLayout;
\r
542 ITextLayout CreateLayout(int row)
\r
544 ITextLayout layout;
\r
545 LineToIndexTableData lineData = this.Lines[row];
\r
546 if (lineData.Length == 0)
\r
548 layout = this.render.CreateLaytout("", null, null);
\r
552 int lineHeadIndex = this.GetLineHeadIndex(row);
\r
554 string content = this.Document.ToString(lineHeadIndex, lineData.Length);
\r
556 if (this.CreateingLayout != null)
\r
557 this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));
\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
563 layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange);
\r
566 if (this.CacheEntries.Count > MaxEntries)
\r
568 ITextLayout oldItem = this.CacheEntries.Dequeue();
\r
571 this.CacheEntries.Enqueue(layout);
\r
576 int lastLineNumber;
\r
578 /// インデックスを行番号に変換します
\r
580 /// <param name="index">インデックス</param>
\r
581 /// <returns>行番号を返します</returns>
\r
582 public int GetLineNumberFromIndex(int index)
\r
585 throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");
\r
587 if (index == 0 && this.Lines.Count > 0)
\r
590 LineToIndexTableData line;
\r
593 if (lastLineNumber < this.Lines.Count - 1)
\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
601 int left = 0, right = this.Lines.Count - 1, mid;
\r
602 while (left <= right)
\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
609 lastLineNumber = mid;
\r
612 if (index < lineHeadIndex)
\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
627 lastLineNumber = this.Lines.Count - 1;
\r
628 return lastLineNumber;
\r
631 throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");
\r
635 /// インデックスからテキストポイントに変換します
\r
637 /// <param name="index">インデックス</param>
\r
638 /// <returns>TextPoint構造体を返します</returns>
\r
639 public TextPoint GetTextPointFromIndex(int index)
\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
649 /// テキストポイントからインデックスに変換します
\r
651 /// <param name="tp">TextPoint構造体</param>
\r
652 /// <returns>インデックスを返します</returns>
\r
653 public int GetIndexFromTextPoint(TextPoint tp)
\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
665 /// フォールディングを再生成します
\r
667 /// <param name="force">ドキュメントが更新されていなくても再生成する</param>
\r
668 /// <returns>生成された場合は真を返す</returns>
\r
669 /// <remarks>デフォルトではドキュメントが更新されている時にだけ再生成されます</remarks>
\r
670 public bool GenerateFolding(bool force = false)
\r
673 this._IsSync = false;
\r
674 if (this.Document.Length == 0 || this._IsSync)
\r
676 this.GenerateFolding(0, this.Document.Length - 1);
\r
677 this._IsSync = true;
\r
681 void GenerateFolding(int start, int end)
\r
684 throw new ArgumentException("start <= endである必要があります");
\r
685 if (this.FoldingStrategy != null)
\r
687 var items = this.FoldingStrategy.AnalyzeDocument(this.Document, start, end)
\r
690 int startRow = this.GetLineNumberFromIndex(item.Start);
\r
691 int endRow = this.GetLineNumberFromIndex(item.End);
\r
692 return startRow != endRow;
\r
694 .Select((item) => item);
\r
695 this.FoldingCollection.AddRange(items);
\r
700 /// フォールディングをすべて削除します
\r
702 public void ClearFolding()
\r
704 this.FoldingCollection.Clear();
\r
705 this._IsSync = false;
\r
709 /// すべての行に対しシンタックスハイライトを行います
\r
711 public void HilightAll()
\r
713 this.Hilight(0, this.Lines.Count);
\r
714 this.ClearLayoutCache();
\r
718 /// ハイライト関連の情報をすべて削除します
\r
720 public void ClearHilight()
\r
722 foreach (LineToIndexTableData line in this.Lines)
\r
723 line.Syntax = null;
\r
724 this.ClearLayoutCache();
\r
730 internal void Clear()
\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
742 void Hilight(int row, int rowCount)
\r
744 if (this.Hilighter == null || rowCount == 0)
\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
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
768 bool hasBeginEncloser = false;
\r
770 for (i = startRow; i <= endRow; i++)
\r
772 this.HilightLine(i, ref hasBeginEncloser);
\r
775 if (hasBeginEncloser) //終了エンクロージャーが見つかったかどうか
\r
777 for (; i < this.Lines.Count; i++)
\r
779 if (this.HilightLine(i, ref hasBeginEncloser) < 0)
\r
784 this.Hilighter.Reset();
\r
787 private int HilightLine(int row, ref bool hasBeginEncloser)
\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
794 if (s.type == TokenType.None || s.type == TokenType.Control)
\r
796 if (str[s.index + s.length - 1] == Document.NewLine)
\r
798 syntax.Add(new SyntaxInfo(s.index, s.length, s.type));
\r
801 LineToIndexTableData lineData = this.Lines[row];
\r
802 lineData.Syntax = syntax.ToArray();
\r
804 if (level > 0 && hasBeginEncloser == false) //開始エンクロージャー
\r
806 lineData.EncloserType = EncloserType.Begin;
\r
807 hasBeginEncloser = true;
\r
809 else if (level < 0) //終了エンクロージャー
\r
811 lineData.EncloserType = EncloserType.End;
\r
812 hasBeginEncloser = false;
\r
814 else if (hasBeginEncloser) //エンクロージャーの範囲内
\r
815 lineData.EncloserType = EncloserType.Now;
\r
817 lineData.EncloserType = EncloserType.None;
\r
822 private int SearchStartRow(int startRow)
\r
824 for (startRow--; startRow >= 0; startRow--)
\r
826 EncloserType type = this.Lines[startRow].EncloserType;
\r
827 if (type == EncloserType.Begin || type == EncloserType.None)
\r
833 private int SearchEndRow(int startRow)
\r
835 for (startRow++ ; startRow < this.Lines.Count; startRow++)
\r
837 EncloserType type = this.Lines[startRow].EncloserType;
\r
838 if (type == EncloserType.End)
\r
841 return this.Lines.Count - 1;
\r
844 #region IEnumerable<string> メンバー
\r
847 /// コレクションを反復処理するためのIEnumeratorを返す
\r
849 /// <returns>IEnumeratorオブジェクト</returns>
\r
850 public IEnumerator<string> GetEnumerator()
\r
852 for (int i = 0; i < this.Lines.Count; i++)
\r
853 yield return this[i];
\r
858 #region IEnumerable メンバー
\r
860 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
\r
862 for (int i = 0; i < this.Lines.Count; i++)
\r
863 yield return this[i];
\r