2 * Copyright (C) 2013 FooProject
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
4 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
6 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
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/>.
12 using System.Globalization;
15 using System.Diagnostics;
16 using System.Text.RegularExpressions;
21 namespace FooEditEngine
23 internal enum MoveFlow
28 internal enum ScrollDirection
39 public enum IndentMode
46 /// ユーザー側からの処理を担当するクラス。一部を除き、こちらで行われた操作はアンドゥの対象になります
48 internal sealed class Controller
54 public Controller(Document doc, EditView view)
57 this.Document.Update += new DocumentUpdateEventHandler(Document_Update);
58 this.Document.RightToLeftChanged += (s, e) =>
63 this.View.render.ChangedRenderResource += render_ChangedRenderResource;
64 this.View.PerformLayouted += View_LineBreakChanged;
65 this.View.PageBoundChanged += View_PageBoundChanged;
66 this.SelectionChanged += new EventHandler((s, e) => { });
67 this.Document.Clear();
73 public event EventHandler SelectionChanged;
76 /// 矩形選択モードなら真を返し、そうでない場合は偽を返す
78 public bool RectSelection
80 get { return this.Document.RectSelection; }
81 set { this.Document.RectSelection = value; }
87 public IndentMode IndentMode
89 get { return this.Document.IndentMode; }
90 set { this.Document.IndentMode = value; }
96 /// <remarks>SelectionLengthが0の場合、キャレット位置を表します</remarks>
97 public int SelectionStart
101 if (this.View.Selections.Count == 0)
102 return this.AnchorIndex;
104 return this.View.Selections.First().start;
111 /// <remarks>矩形選択モードの場合、選択範囲の文字数ではなく、開始位置から終了位置までの長さとなります</remarks>
112 public int SelectionLength
116 if (this.View.Selections.Count == 0)
118 Selection last = this.View.Selections.Last();
119 return last.start + last.length - this.SelectionStart;
127 /// 未選択状態で代入したときは追加され、そうでない場合は選択範囲の文字列と置き換えられます。
129 public string SelectedText
133 if (this.View.LayoutLines.Count == 0 || this.View.Selections.Count == 0)
135 if (this.RectSelection)
136 return GetTextFromRectangleSelectArea(this.View.Selections);
138 return GetTextFromLineSelectArea(this.View.Selections).Replace(Document.NewLine.ToString(), Environment.NewLine);
142 if (this.Document.FireUpdateEvent == false)
143 throw new InvalidOperationException("");
146 this.RepleaceSelectionArea(this.View.Selections, value.Replace(Environment.NewLine,Document.NewLine.ToString()));
151 /// 選択範囲が逆転しているかどうかを判定する
153 /// <returns>逆転しているなら真を返す</returns>
154 public bool IsReverseSelect()
156 int index = this.View.LayoutLines.GetIndexFromTextPoint(this.View.CaretPostion);
157 return index < this.AnchorIndex;
163 /// <param name="start"></param>
164 /// <param name="length"></param>
165 /// <remarks>RectSelectionの値によって動作が変わります。真の場合は矩形選択モードに、そうでない場合は行ごとに選択されます</remarks>
166 public void Select(int start, int length)
168 if (this.Document.FireUpdateEvent == false)
169 throw new InvalidOperationException("");
170 if (start < 0 || start + length < 0 || start + length > this.Document.Length)
171 throw new ArgumentOutOfRangeException("startかendが指定できる範囲を超えてます");
172 this.View.Selections.Clear();
175 int oldStart = start;
177 length = oldStart - start;
179 if (this.RectSelection && length != 0)
181 TextPoint startTextPoint = this.View.GetLayoutLineFromIndex(start);
182 TextPoint endTextPoint = this.View.GetLayoutLineFromIndex(start + length);
183 this.SelectByRectangle(new TextRectangle(startTextPoint, endTextPoint));
187 this.View.Selections.Add(Selection.Create(start, length));
189 this.SelectionChanged(this, null);
192 public void Select(TextPoint tp, int width, int height)
194 if (this.Document.FireUpdateEvent == false || !this.RectSelection)
195 throw new InvalidOperationException("");
198 end.row = tp.row + height;
199 end.col = tp.col + width;
201 if (end.row > this.View.LayoutLines.Count - 1)
202 throw new ArgumentOutOfRangeException("");
204 this.View.Selections.Clear();
206 this.SelectByRectangle(new TextRectangle(tp,end));
208 this.SelectionChanged(this, null);
211 private void SelectByRectangle(TextRectangle rect)
213 if (this.Document.FireUpdateEvent == false)
214 throw new InvalidOperationException("");
215 if (rect.TopLeft <= rect.BottomRight)
217 for (int i = rect.TopLeft.row; i <= rect.BottomLeft.row; i++)
219 int length = this.View.LayoutLines.GetLengthFromLineNumber(i);
220 int leftCol = rect.TopLeft.col, rightCol = rect.TopRight.col, lastCol = length;
221 if(length > 0 && this.View.LayoutLines[i][length - 1] == Document.NewLine)
222 lastCol = length - 1;
225 if (rect.TopLeft.col > lastCol)
227 if (rect.TopRight.col > lastCol)
230 int StartIndex = this.View.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, leftCol));
231 int EndIndex = this.View.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, rightCol));
234 sel = Selection.Create(StartIndex, EndIndex - StartIndex);
236 this.View.Selections.Add(sel);
244 /// <param name="index">探索を開始するインデックス</param>
245 /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
246 public void SelectWord(int index, bool changeAnchor = false)
248 if (this.Document.FireUpdateEvent == false)
249 throw new InvalidOperationException("");
251 if (this.Document.Length <= 0 || index >= this.Document.Length)
254 Document str = this.Document;
257 while (start > 0 && !Util.IsWordSeparator(str[start]))
260 if (Util.IsWordSeparator(str[start]))
264 while (end < this.Document.Length && !Util.IsWordSeparator(str[end]))
267 this.Select(start, end - start);
270 this.AnchorIndex = start;
274 /// 選択範囲内のUTF32コードポイントを文字列に変換します
276 /// <returns>成功した場合は真。そうでない場合は偽を返す</returns>
277 public bool ConvertToChar()
279 if (this.SelectionLength == 0 || this.RectSelection)
281 string str = this.Document.ToString(this.SelectionStart, this.SelectionLength);
282 string[] codes = str.Split(new char[] { ' ' },StringSplitOptions.RemoveEmptyEntries);
283 StringBuilder result = new StringBuilder();
284 foreach (string code in codes)
289 if (Int32.TryParse(code.TrimStart('U'),NumberStyles.HexNumber,null, out utf32_code))
290 result.Append(Char.ConvertFromUtf32(utf32_code));
294 this.Document.Lock();
295 this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString());
296 this.Document.UnLock();
301 /// 選択文字列をUTF32のコードポイントに変換します
303 public void ConvertToCodePoint()
305 if (this.SelectionLength == 0 || this.RectSelection)
307 string str = this.Document.ToString(this.SelectionStart, this.SelectionLength);
308 StringInfo info = new StringInfo(str);
309 StringBuilder result = new StringBuilder();
310 for (int i = 0; i < str.Length;)
312 int utf32_code = Char.ConvertToUtf32(str, i);
313 result.Append("U" + Convert.ToString(utf32_code,16));
315 if(Char.IsHighSurrogate(str[i]))
320 this.Document.Lock();
321 this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString());
322 this.Document.UnLock();
328 public void DeSelectAll()
330 if (this.Document.FireUpdateEvent == false)
331 throw new InvalidOperationException("");
333 this.View.Selections.Clear();
339 /// <param name="tp"></param>
340 /// <param name="type"></param>
341 /// <returns>真ならマーカーがある</returns>
342 public bool IsMarker(TextPoint tp,HilightType type)
344 if (this.Document.FireUpdateEvent == false)
345 throw new InvalidOperationException("");
346 int index = this.View.LayoutLines.GetIndexFromTextPoint(tp);
347 return this.IsMarker(index, type);
353 /// <param name="index"></param>
354 /// <param name="type"></param>
355 /// <returns>真ならマーカーがある</returns>
356 public bool IsMarker(int index, HilightType type)
358 foreach(int id in this.Document.Markers.IDs)
360 foreach (Marker m in this.Document.GetMarkers(index, id))
362 if (m.hilight == type)
372 public void AdjustCaret()
374 int row = this.View.CaretPostion.row;
375 if (row > this.View.LayoutLines.Count - 1)
376 row = this.View.LayoutLines.Count - 1;
377 int col = this.View.CaretPostion.col;
378 if (col > 0 && col > this.View.LayoutLines[row].Length)
379 col = this.View.LayoutLines[row].Length;
380 this.JumpCaret(row, col);
384 /// キャレットを指定した位置に移動させる
386 /// <param name="index"></param>
387 /// <param name="autoExpand">折り畳みを展開するなら真</param>
388 public void JumpCaret(int index,bool autoExpand = true)
390 if (index < 0 || index > this.Document.Length)
391 throw new ArgumentOutOfRangeException("indexが設定できる範囲を超えています");
392 TextPoint tp = this.View.GetLayoutLineFromIndex(index);
394 this.JumpCaret(tp.row, tp.col,autoExpand);
398 /// キャレットを指定した位置に移動させる
400 /// <param name="row"></param>
401 /// <param name="col"></param>
402 /// <param name="autoExpand">折り畳みを展開するなら真</param>
403 public void JumpCaret(int row, int col, bool autoExpand = true)
405 if (this.Document.FireUpdateEvent == false)
406 throw new InvalidOperationException("");
408 this.View.JumpCaret(row, col,autoExpand);
410 this.View.AdjustCaretAndSrc();
412 this.SelectWithMoveCaret(false);
418 /// <param name="row">行</param>
419 /// <param name="isSelected">選択状態にするかどうか</param>
420 public void JumpToLineHead(int row,bool isSelected)
422 this.View.JumpCaret(row, 0);
423 this.View.AdjustCaretAndSrc();
424 this.SelectWithMoveCaret(isSelected);
430 /// <param name="row">行</param>
431 /// <param name="isSelected">選択状態にするかどうか</param>
432 public void JumpToLineEnd(int row, bool isSelected)
434 this.View.JumpCaret(row, this.View.LayoutLines[row].Length - 1);
435 this.View.AdjustCaretAndSrc();
436 this.SelectWithMoveCaret(isSelected);
442 /// <param name="isSelected"></param>
443 public void JumpToHead(bool isSelected)
445 if (this.View.TryScroll(0, 0))
447 this.View.JumpCaret(0, 0);
448 this.View.AdjustCaretAndSrc();
449 this.SelectWithMoveCaret(isSelected);
455 /// <param name="isSelected"></param>
456 public void JumpToEnd(bool isSelected)
458 int srcRow = this.View.LayoutLines.Count - this.View.LineCountOnScreen - 1;
461 if (this.View.TryScroll(0, srcRow))
463 this.View.JumpCaret(this.View.LayoutLines.Count - 1, 0);
464 this.View.AdjustCaretAndSrc();
465 this.SelectWithMoveCaret(isSelected);
471 /// <param name="dir">方向を指定する</param>
472 /// <param name="delta">スクロールする量。ScrollDirectionの値がUpやDownなら行数。LeftやRightならピクセル単位の値となる</param>
473 /// <param name="isSelected">選択状態にするなら真</param>
474 /// <param name="withCaret">同時にキャレットを移動させるなら真</param>
475 public void Scroll(ScrollDirection dir, int delta, bool isSelected,bool withCaret)
477 if (this.Document.FireUpdateEvent == false)
478 throw new InvalidOperationException("");
479 int toRow = this.View.Src.Row;
480 double toX = this.View.Src.X;
483 case ScrollDirection.Up:
484 toRow = Math.Max(0, this.View.Src.Row - delta);
485 toRow = this.View.AdjustRow(toRow, false);
487 case ScrollDirection.Down:
488 toRow = Math.Min(this.View.Src.Row + delta, this.View.LayoutLines.Count - 1);
489 toRow = this.View.AdjustRow(toRow, true);
491 case ScrollDirection.Left:
494 case ScrollDirection.Right:
498 throw new ArgumentOutOfRangeException();
500 this.Scroll(toX, toRow, isSelected, withCaret);
506 /// <param name="toX">スクロール先の座標</param>
507 /// <param name="toRow">スクロール先の行</param>
508 /// <param name="isSelected">選択状態にするなら真</param>
509 /// <param name="withCaret">同時にキャレットを移動させるなら真</param>
510 public void Scroll(double toX, int toRow, bool isSelected, bool withCaret)
514 this.View.Scroll(toX, toRow);
515 this.View.JumpCaret(toRow, 0);
516 this.View.AdjustCaretAndSrc();
517 this.SelectWithMoveCaret(isSelected);
521 this.View.Scroll(toX, toRow);
522 this.View.IsFocused = false;
529 /// <returns>移動できない場合は真を返す</returns>
530 /// <param name="realLength">負の値なら左側へ、そうでないなら右側へ移動する</param>
531 /// <param name="isSelected">選択範囲とするなら真。そうでないなら偽</param>
532 /// <param name="alignWord">単語単位で移動するなら真。そうでないなら偽</param>
533 public void MoveCaretHorizontical(int realLength, bool isSelected,bool alignWord = false)
535 for (int i = Math.Abs(realLength); i > 0; i--)
537 bool MoveFlow = realLength > 0;
538 if (this.Document.RightToLeft)
539 MoveFlow = !MoveFlow;
540 this.MoveCaretHorizontical(MoveFlow);
543 this.AlignNearestWord(MoveFlow);
545 this.View.AdjustCaretAndSrc(AdjustFlow.Col);
546 this.SelectWithMoveCaret(isSelected);
549 void AlignNearestWord(bool MoveFlow)
551 string str = this.View.LayoutLines[this.View.CaretPostion.row];
552 while (this.View.CaretPostion.col > 0 &&
553 this.View.CaretPostion.col < str.Length &&
554 str[this.View.CaretPostion.col] != Document.NewLine)
556 if (!Util.IsWordSeparator(str[this.View.CaretPostion.col]))
558 this.MoveCaretHorizontical(MoveFlow);
563 this.MoveCaretHorizontical(MoveFlow);
572 /// <returns>再描写する必要があるなら真を返す</returns>
573 /// <param name="deltarow">移動量</param>
574 /// <param name="isSelected"></param>
575 public void MoveCaretVertical(int deltarow,bool isSelected)
577 for (int i = Math.Abs(deltarow); i > 0; i--)
578 this.MoveCaretVertical(deltarow > 0);
579 this.View.AdjustCaretAndSrc(AdjustFlow.Both);
580 this.SelectWithMoveCaret(isSelected);
584 /// キャレット位置の文字を一文字削除する
586 public void DoDeleteAction()
588 if (this.SelectionLength != 0)
590 this.SelectedText = "";
594 if (this.Document.FireUpdateEvent == false)
595 throw new InvalidOperationException("");
597 TextPoint CaretPostion = this.View.CaretPostion;
598 int index = this.View.GetIndexFromLayoutLine(CaretPostion);
600 if (index == this.Document.Length)
603 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPostion.row);
604 int next = this.View.LayoutLines.GetLayout(CaretPostion.row).AlignIndexToNearestCluster(CaretPostion.col, AlignDirection.Forward) + lineHeadIndex;
606 if (this.Document[index] == Document.NewLine)
609 this.Document.Lock();
610 this.Document.Replace(index, next - index, "");
611 this.Document.UnLock();
614 public bool IsRectInsertMode()
616 if (!this.RectSelection || this.View.Selections.Count == 0)
618 foreach(Selection sel in this.View.Selections)
627 /// キャレット位置の文字を一文字削除し、キャレット位置を後ろにずらす
629 public void DoBackSpaceAction()
631 if (this.IsRectInsertMode())
633 this.ReplaceBeforeSelectionArea(this.View.Selections, 1, "");
636 else if (this.SelectionLength > 0)
638 this.SelectedText = "";
642 if (this.Document.FireUpdateEvent == false)
643 throw new InvalidOperationException("");
645 TextPoint CurrentPostion = this.View.CaretPostion;
647 if (CurrentPostion.row == 0 && CurrentPostion.col == 0)
650 int oldIndex = this.View.GetIndexFromLayoutLine(CurrentPostion);
652 int newCol, newIndex;
653 if (CurrentPostion.col > 0)
655 newCol = this.View.LayoutLines.GetLayout(CurrentPostion.row).AlignIndexToNearestCluster(CurrentPostion.col - 1, AlignDirection.Back);
656 newIndex = this.View.GetIndexFromLayoutLine(new TextPoint(CurrentPostion.row, newCol));
660 newIndex = this.View.GetIndexFromLayoutLine(CurrentPostion);
664 this.Document.Lock();
665 this.Document.Replace(newIndex, oldIndex - newIndex, "");
666 this.Document.UnLock();
672 public void DoEnterAction()
674 this.DoInputChar('\n');
678 /// キャレット位置に文字を入力し、その分だけキャレットを進める。isInsertModeの値により動作が変わります
680 /// <param name="ch"></param>
681 public void DoInputChar(char ch)
683 this.DoInputString(ch.ToString());
686 string GetIndentSpace(int col_index)
688 int space_count = this.View.TabStops - (col_index % this.View.TabStops);
689 return new string(Enumerable.Repeat(' ',space_count).ToArray());
693 /// キャレット位置に文字列を挿入し、その分だけキャレットを進める。isInsertModeの値により動作が変わります
695 /// <param name="str"></param>
696 /// <param name="fromTip"></param>
697 public void DoInputString(string str,bool fromTip = false)
699 TextPoint CaretPos = this.View.CaretPostion;
701 if (str == "\t" && this.IndentMode == IndentMode.Space)
702 str = this.GetIndentSpace(CaretPos.col);
704 if (this.IsRectInsertMode())
706 this.ReplaceBeforeSelectionArea(this.View.Selections, 0, str);
709 else if (this.SelectionLength != 0)
711 this.RepleaceSelectionArea(this.View.Selections, str, fromTip);
715 if (this.Document.FireUpdateEvent == false)
716 throw new InvalidOperationException("");
718 int index = this.View.GetIndexFromLayoutLine(this.View.CaretPostion);
720 if (this.View.InsertMode == false && index < this.Document.Length && this.Document[index] != Document.NewLine)
722 string lineString = this.View.LayoutLines[CaretPos.row];
723 int end = this.View.LayoutLines.GetLayout(CaretPos.row).AlignIndexToNearestCluster(CaretPos.col + str.Length - 1, AlignDirection.Forward);
724 if (end > lineString.Length - 1)
725 end = lineString.Length - 1;
726 end += this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row);
727 length = end - index;
729 if (str == Document.NewLine.ToString())
731 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row);
732 int lineLength = this.View.LayoutLines.GetLengthFromLineNumber(CaretPos.row);
733 FoldingItem foldingData = this.View.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineHeadIndex, lineLength);
734 if (foldingData != null && !foldingData.Expand && index > foldingData.Start && index <= foldingData.End)
735 index = foldingData.End + 1;
737 this.Document.Lock();
738 this.Document.Replace(index, length, str);
739 this.Document.UnLock();
743 /// キャレットの移動に合わせて選択する
745 /// <param name="isSelected">選択状態にするかどうか</param>
747 /// キャレットを移動後、このメソッドを呼び出さない場合、Select()メソッドは正常に機能しません
749 void SelectWithMoveCaret(bool isSelected)
751 if (this.View.CaretPostion.col < 0 || this.View.CaretPostion.row < 0)
754 if (this.Document.FireUpdateEvent == false)
755 throw new InvalidOperationException("");
757 int CaretPostion = this.View.GetIndexFromLayoutLine(this.View.CaretPostion);
759 SelectCollection Selections = this.View.Selections;
762 this.Select(this.AnchorIndex, CaretPostion - this.AnchorIndex);
764 this.AnchorIndex = CaretPostion;
765 this.Select(CaretPostion, 0);
770 /// JumpCaretで移動した位置からキャレットを移動し、選択状態にする
772 /// <param name="tp"></param>
773 public void MoveCaretAndSelect(TextPoint tp)
775 int CaretPostion = this.View.GetIndexFromLayoutLine(tp);
776 this.Select(this.AnchorIndex, CaretPostion - this.AnchorIndex);
777 this.View.JumpCaret(tp.row, tp.col);
778 this.View.AdjustCaretAndSrc();
782 /// グリッパーとキャレットを同時に移動する
784 /// <param name="p">ポインターの座標</param>
785 /// <param name="hittedGripper">動かす対象となるグリッパー</param>
786 /// <returns>移動できた場合は真を返す。そうでなければ偽を返す</returns>
787 /// <remarks>グリッパー内にポインターが存在しない場合、グリッパーはポインターの座標近くの行に移動する</remarks>
788 public bool MoveCaretAndGripper(Point p, GripperView hittedGripper)
790 bool HittedCaret = false;
791 TextPoint tp = this.View.GetTextPointFromPostion(p);
792 if (tp == this.View.CaretPostion)
797 if (HittedCaret || hittedGripper != null)
799 if (hittedGripper != null)
801 tp = this.View.GetTextPointFromPostion(hittedGripper.AdjustPoint(p));
802 if (this.IsReverseSelect())
804 if (Object.ReferenceEquals(hittedGripper, this.View.SelectGrippers.BottomRight))
805 this.MoveSelectBefore(tp);
807 this.MoveCaretAndSelect(tp);
811 if (Object.ReferenceEquals(hittedGripper, this.View.SelectGrippers.BottomLeft))
812 this.MoveSelectBefore(tp);
814 this.MoveCaretAndSelect(tp);
816 hittedGripper.Move(this.View, tp);
820 tp = this.View.GetTextPointFromPostion(p);
821 if (tp != TextPoint.Null)
822 this.MoveCaretAndSelect(tp);
824 this.View.SelectGrippers.BottomLeft.Enabled = this.SelectionLength != 0;
830 public void MoveSelectBefore(TextPoint tp)
834 if (this.IsReverseSelect())
836 NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp);
837 SelectionLength = this.SelectionLength + NewAnchorIndex - this.AnchorIndex;
838 this.Select(this.SelectionStart, SelectionLength);
842 NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp);
843 SelectionLength = this.SelectionLength + this.AnchorIndex - NewAnchorIndex;
844 this.Select(NewAnchorIndex, SelectionLength);
846 this.AnchorIndex = NewAnchorIndex;
852 public void ResetCaretPostion()
858 /// 行単位で移動後のキャレット位置を取得する
860 /// <param name="count">移動量</param>
861 /// <param name="current">現在のキャレット位置</param>
862 /// <returns>移動後のキャレット位置</returns>
863 public TextPoint GetTextPointAfterMoveLine(int count, TextPoint current)
865 int row = current.row + count;
869 else if (row >= this.View.LayoutLines.Count)
870 row = this.View.LayoutLines.Count - 1;
872 row = this.View.AdjustRow(row, count > 0);
874 double colpos = this.View.GetColPostionFromIndex(current.row, current.col);
875 int col = this.View.GetIndexFromColPostion(row, colpos);
877 return new TextPoint(row, col);
881 /// 選択文字列のインデントを一つ増やす
883 public void UpIndent()
885 if (this.RectSelection || this.SelectionLength == 0)
887 int selectionStart = this.SelectionStart;
888 string insertStr = this.IndentMode == IndentMode.Space ? this.GetIndentSpace(0) : "\t";
889 string text = this.InsertLineHead(GetTextFromLineSelectArea(this.View.Selections), insertStr);
890 this.RepleaceSelectionArea(this.View.Selections,text);
891 this.Select(selectionStart, text.Length);
895 /// 選択文字列のインデントを一つ減らす
897 public void DownIndent()
899 if (this.RectSelection || this.SelectionLength == 0)
901 int selectionStart = this.SelectionStart;
902 string insertStr = this.IndentMode == IndentMode.Space ? this.GetIndentSpace(0) : "\t";
903 string text = this.RemoveLineHead(GetTextFromLineSelectArea(this.View.Selections), insertStr);
904 this.RepleaceSelectionArea(this.View.Selections, text);
905 this.Select(selectionStart, text.Length);
908 string InsertLineHead(string s, string str)
910 string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.None);
911 StringBuilder output = new StringBuilder();
912 for (int i = 0; i < lines.Length; i++)
914 if(lines[i].Length > 0)
915 output.Append(str + lines[i] + Document.NewLine);
916 else if(i < lines.Length - 1)
917 output.Append(lines[i] + Document.NewLine);
919 return output.ToString();
922 public string RemoveLineHead(string s, string str)
924 string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.None);
925 StringBuilder output = new StringBuilder();
926 for (int i = 0; i < lines.Length; i++)
928 if (lines[i].StartsWith(str))
929 output.Append(lines[i].Substring(1) + Document.NewLine);
930 else if (i < lines.Length - 1)
931 output.Append(lines[i] + Document.NewLine);
933 return output.ToString();
939 /// <param name="isMoveNext">真なら1文字すすめ、そうでなければ戻す</param>
940 /// <remarks>このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります</remarks>
941 void MoveCaretHorizontical(bool isMoveNext)
943 if (this.Document.FireUpdateEvent == false)
944 throw new InvalidOperationException("");
945 int delta = isMoveNext ? 0 : -1;
946 int prevcol = this.View.CaretPostion.col;
947 int col = this.View.CaretPostion.col + delta;
948 string lineString = this.View.LayoutLines[this.View.CaretPostion.row];
949 if (col < 0 || this.View.CaretPostion.row >= this.View.LayoutLines.Count)
951 if (this.View.CaretPostion.row == 0)
956 this.MoveCaretVertical(false);
957 this.View.AdjustCaretAndSrc(AdjustFlow.Row); //この段階で調整しないとスクロールされない
958 col = this.View.LayoutLines.GetLengthFromLineNumber(this.View.CaretPostion.row) - 1; //最終行以外はすべて改行コードが付くはず
960 else if (col >= lineString.Length || lineString[col] == Document.NewLine)
962 if (this.View.CaretPostion.row < this.View.LayoutLines.Count - 1)
964 this.MoveCaretVertical(true);
965 this.View.AdjustCaretAndSrc(AdjustFlow.Row); //この段階で調整しないとスクロールされない
971 AlignDirection direction = isMoveNext ? AlignDirection.Forward : AlignDirection.Back;
972 col = this.View.LayoutLines.GetLayout(this.View.CaretPostion.row).AlignIndexToNearestCluster(col, direction);
975 this.View.JumpCaret(this.View.CaretPostion.row, col,false);
981 /// <param name="isMoveNext">プラス方向に移動するなら真</param>
982 /// <remarks>このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります</remarks>
983 void MoveCaretVertical(bool isMoveNext)
985 if (this.Document.FireUpdateEvent == false)
986 throw new InvalidOperationException("");
988 TextPoint nextPoint = this.GetTextPointAfterMoveLine(isMoveNext ? 1 : -1, this.View.CaretPostion);
990 this.View.JumpCaret(nextPoint.row, nextPoint.col,false);
993 private void ReplaceBeforeSelectionArea(SelectCollection Selections, int removeLength, string insertStr)
995 if (removeLength == 0 && insertStr.Length == 0)
998 if (this.RectSelection == false || this.Document.FireUpdateEvent == false)
999 throw new InvalidOperationException();
1001 SelectCollection temp = this.View.Selections;
1002 int selectStart = temp.First().start;
1003 int selectEnd = temp.Last().start + temp.Last().length;
1005 //ドキュメント操作後に行うとうまくいかないので、あらかじめ取得しておく
1006 TextPoint start = this.View.LayoutLines.GetTextPointFromIndex(selectStart);
1007 TextPoint end = this.View.LayoutLines.GetTextPointFromIndex(selectEnd);
1009 bool reverse = temp.First().start > temp.Last().start;
1011 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(this.View.LayoutLines.GetLineNumberFromIndex(selectStart));
1012 if (selectStart - removeLength < lineHeadIndex)
1015 this.Document.UndoManager.BeginUndoGroup();
1016 this.Document.FireUpdateEvent = false;
1020 for (int i = 0; i < temp.Count; i++)
1022 this.ReplaceBeforeSelection(temp[i], removeLength, insertStr);
1027 for (int i = temp.Count - 1; i >= 0; i--)
1029 this.ReplaceBeforeSelection(temp[i], removeLength, insertStr);
1033 this.Document.FireUpdateEvent = true;
1034 this.Document.UndoManager.EndUndoGroup();
1036 int delta = insertStr.Length - removeLength;
1041 this.JumpCaret(start.row, start.col);
1043 this.JumpCaret(end.row, end.col);
1045 this.Select(start, 0, end.row - start.row);
1048 private void ReplaceBeforeSelection(Selection sel, int removeLength, string insertStr)
1050 sel = Util.NormalizeIMaker<Selection>(sel);
1051 this.Document.Lock();
1052 this.Document.Replace(sel.start - removeLength, removeLength, insertStr);
1053 this.Document.UnLock();
1056 private void RepleaceSelectionArea(SelectCollection Selections, string value,bool updateInsertPoint = false)
1061 if (this.RectSelection == false)
1063 Selection sel = Selection.Create(this.AnchorIndex, 0);
1064 if (Selections.Count > 0)
1065 sel = Util.NormalizeIMaker<Selection>(this.View.Selections.First());
1067 this.Document.Lock();
1068 this.Document.Replace(sel.start, sel.length, value);
1069 this.Document.UnLock();
1073 if (this.Document.FireUpdateEvent == false)
1074 throw new InvalidOperationException("");
1076 int StartIndex = this.SelectionStart;
1078 SelectCollection newInsertPoint = new SelectCollection();
1080 if (this.SelectionLength == 0)
1084 this.Document.Lock();
1086 this.Document.UndoManager.BeginUndoGroup();
1088 this.Document.FireUpdateEvent = false;
1090 string[] line = value.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.RemoveEmptyEntries);
1092 TextPoint Current = this.View.GetLayoutLineFromIndex(this.SelectionStart);
1094 for (i = 0; i < line.Length && Current.row < this.View.LayoutLines.Count; i++, Current.row++)
1096 if (Current.col > this.View.LayoutLines[Current.row].Length)
1097 Current.col = this.View.LayoutLines[Current.row].Length;
1098 StartIndex = this.View.GetIndexFromLayoutLine(Current);
1099 this.Document.Replace(StartIndex, 0, line[i]);
1100 StartIndex += line[i].Length;
1103 for (; i < line.Length; i++)
1105 StartIndex = this.Document.Length;
1106 string str = Document.NewLine + line[i];
1107 this.Document.Replace(StartIndex, 0, str);
1108 StartIndex += str.Length;
1111 this.Document.FireUpdateEvent = true;
1113 this.Document.UndoManager.EndUndoGroup();
1115 this.Document.UnLock();
1119 SelectCollection temp = new SelectCollection(this.View.Selections); //コピーしないとReplaceCommandを呼び出した段階で書き換えられてしまう
1121 this.Document.Lock();
1123 this.Document.UndoManager.BeginUndoGroup();
1125 this.Document.FireUpdateEvent = false;
1127 if (temp.First().start < temp.Last().start)
1129 for (int i = temp.Count - 1; i >= 0; i--)
1131 Selection sel = Util.NormalizeIMaker<Selection>(temp[i]);
1133 StartIndex = sel.start;
1135 this.Document.Replace(sel.start, sel.length, value);
1137 newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i,0));
1142 for (int i = 0; i < temp.Count; i++)
1144 Selection sel = Util.NormalizeIMaker<Selection>(temp[i]);
1146 StartIndex = sel.start;
1148 this.Document.Replace(sel.start, sel.length, value);
1150 newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i, 0));
1154 this.Document.FireUpdateEvent = true;
1156 this.Document.UndoManager.EndUndoGroup();
1158 this.Document.UnLock();
1160 this.JumpCaret(StartIndex);
1161 if (updateInsertPoint && newInsertPoint.Count > 0)
1162 this.View.Selections = newInsertPoint;
1165 private string GetTextFromLineSelectArea(SelectCollection Selections)
1167 Selection sel = Util.NormalizeIMaker<Selection>(Selections.First());
1169 string str = this.Document.ToString(sel.start, sel.length);
1174 string GetTextFromRectangleSelectArea(SelectCollection Selections)
1176 StringBuilder temp = new StringBuilder();
1177 if (Selections.First().start < Selections.Last().start)
1179 for (int i = 0; i < this.View.Selections.Count; i++)
1181 Selection sel = Util.NormalizeIMaker<Selection>(Selections[i]);
1183 string str = this.Document.ToString(sel.start, sel.length);
1184 if (str.IndexOf(Environment.NewLine) == -1)
1185 temp.AppendLine(str);
1192 for (int i = this.View.Selections.Count - 1; i >= 0; i--)
1194 Selection sel = Util.NormalizeIMaker<Selection>(Selections[i]);
1196 string str = this.Document.ToString(sel.start, sel.length).Replace(Document.NewLine.ToString(), Environment.NewLine);
1197 if (str.IndexOf(Environment.NewLine) == -1)
1198 temp.AppendLine(str);
1203 return temp.ToString();
1206 void View_LineBreakChanged(object sender, EventArgs e)
1212 void View_PageBoundChanged(object sender, EventArgs e)
1214 if (this.View.LineBreak == LineBreakMethod.PageBound && this.View.PageBound.Width - this.View.LineBreakingMarginWidth > 0)
1215 this.View.PerfomLayouts();
1219 void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e)
1221 if (e.type == ResourceType.Font)
1223 if (this.View.LineBreak == LineBreakMethod.PageBound)
1224 this.View.PerfomLayouts();
1227 if (e.type == ResourceType.InlineChar)
1229 int oldLineCountOnScreen = this.View.LineCountOnScreen;
1230 this.View.CalculateLineCountOnScreen();
1231 if(this.View.LineCountOnScreen != oldLineCountOnScreen)
1236 void Document_Update(object sender, DocumentUpdateEventArgs e)
1240 case UpdateType.Replace:
1241 if(e.startIndex < this.Document.Length && this.Document[e.startIndex] == Document.NewLine)
1242 this.View.CalculateLineCountOnScreen();
1243 this.JumpCaret(e.startIndex + e.insertLength,true);
1245 case UpdateType.Clear:
1246 this.JumpCaret(0,0, false);