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
52 /// ユーザー側からの処理を担当するクラス。一部を除き、こちらで行われた操作はアンドゥの対象になります
54 internal sealed class Controller
59 public Controller(Document doc, EditView view)
63 this.View.render.ChangedRenderResource += render_ChangedRenderResource;
64 this.View.PageBoundChanged += View_PageBoundChanged;
65 //this.Document.Clear();
68 public Document Document
72 return this._Document;
76 //メモリリークを防ぐためにパンドラーを除く
77 if (this._Document != null)
79 this._Document.Update -= Document_Update;
80 this._Document.StatusUpdate -= Document_StatusChanged;
81 this._Document.SelectionChanged -= Document_SelectionChanged;
82 this._Document.PerformLayouted -= View_LineBreakChanged;
83 this._Document.CaretChanged -= _Document_CaretChanged;
86 this._Document = value;
88 this._Document.Update += new DocumentUpdateEventHandler(Document_Update);
89 this._Document.StatusUpdate += Document_StatusChanged;
90 this._Document.SelectionChanged += Document_SelectionChanged;
91 this._Document.PerformLayouted += View_LineBreakChanged;
92 this._Document.CaretChanged += _Document_CaretChanged;
96 private void _Document_CaretChanged(object sender, EventArgs e)
98 TextPoint pos = this.Document.CaretPostion;
99 this.JumpCaret(pos.row, pos.col);
102 private void Document_SelectionChanged(object sender, EventArgs e)
104 if (this.IsReverseSelect())
106 if (this.Document.SelectGrippers.BottomRight.Enabled)
107 this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart);
108 if (this.Document.SelectGrippers.BottomLeft.Enabled)
109 this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength);
113 if (this.Document.SelectGrippers.BottomLeft.Enabled)
114 this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart);
115 if (this.Document.SelectGrippers.BottomRight.Enabled)
116 this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength);
120 void Document_StatusChanged(object sender,EventArgs e)
126 /// 矩形選択モードなら真を返し、そうでない場合は偽を返す
128 public bool RectSelection
130 get { return this.Document.RectSelection; }
131 set { this.Document.RectSelection = value; }
137 public IndentMode IndentMode
139 get { return this.Document.IndentMode; }
140 set { this.Document.IndentMode = value; }
146 /// <remarks>SelectionLengthが0の場合、キャレット位置を表します</remarks>
147 public int SelectionStart
151 if (this.View.Selections.Count == 0)
152 return this.Document.AnchorIndex;
154 return this.View.Selections.First().start;
161 /// <remarks>矩形選択モードの場合、選択範囲の文字数ではなく、開始位置から終了位置までの長さとなります</remarks>
162 public int SelectionLength
166 if (this.View.Selections.Count == 0)
168 Selection last = this.View.Selections.Last();
169 return last.start + last.length - this.SelectionStart;
177 /// 未選択状態で代入したときは追加され、そうでない場合は選択範囲の文字列と置き換えられます。
179 public string SelectedText
183 if (this.View.LayoutLines.Count == 0 || this.View.Selections.Count == 0)
185 if (this.RectSelection)
186 return GetTextFromRectangleSelectArea(this.View.Selections);
188 return GetTextFromLineSelectArea(this.View.Selections).Replace(Document.NewLine.ToString(), Environment.NewLine);
192 if (this.Document.FireUpdateEvent == false)
193 throw new InvalidOperationException("");
196 this.RepleaceSelectionArea(this.View.Selections, value.Replace(Environment.NewLine,Document.NewLine.ToString()));
201 /// 選択範囲が逆転しているかどうかを判定する
203 /// <returns>逆転しているなら真を返す</returns>
204 public bool IsReverseSelect()
206 int index = this.View.LayoutLines.GetIndexFromTextPoint(this.Document.CaretPostion);
207 return index < this.Document.AnchorIndex;
211 /// 選択範囲内のUTF32コードポイントを文字列に変換します
213 /// <returns>成功した場合は真。そうでない場合は偽を返す</returns>
214 public bool ConvertToChar()
216 if (this.SelectionLength == 0 || this.RectSelection)
218 string str = this.Document.ToString(this.SelectionStart, this.SelectionLength);
219 string[] codes = str.Split(new char[] { ' ' },StringSplitOptions.RemoveEmptyEntries);
220 StringBuilder result = new StringBuilder();
221 foreach (string code in codes)
226 if (Int32.TryParse(code.TrimStart('U'),NumberStyles.HexNumber,null, out utf32_code))
227 result.Append(Char.ConvertFromUtf32(utf32_code));
231 this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString());
236 /// 選択文字列をUTF32のコードポイントに変換します
238 public void ConvertToCodePoint()
240 if (this.SelectionLength == 0 || this.RectSelection)
242 string str = this.Document.ToString(this.SelectionStart, this.SelectionLength);
243 StringInfo info = new StringInfo(str);
244 StringBuilder result = new StringBuilder();
245 for (int i = 0; i < str.Length;)
247 int utf32_code = Char.ConvertToUtf32(str, i);
248 result.Append("U" + Convert.ToString(utf32_code,16));
250 if(Char.IsHighSurrogate(str[i]))
255 this.Document.Replace(this.SelectionStart, this.SelectionLength, result.ToString());
261 public void DeSelectAll()
263 if (this.Document.FireUpdateEvent == false)
264 throw new InvalidOperationException("");
266 this.View.Selections.Clear();
272 /// <param name="tp"></param>
273 /// <param name="type"></param>
274 /// <returns>真ならマーカーがある</returns>
275 public bool IsMarker(TextPoint tp,HilightType type)
277 if (this.Document.FireUpdateEvent == false)
278 throw new InvalidOperationException("");
279 int index = this.View.LayoutLines.GetIndexFromTextPoint(tp);
280 return this.IsMarker(index, type);
286 /// <param name="index"></param>
287 /// <param name="type"></param>
288 /// <returns>真ならマーカーがある</returns>
289 public bool IsMarker(int index, HilightType type)
291 foreach(int id in this.Document.Markers.IDs)
293 foreach (Marker m in this.Document.GetMarkers(id, index))
295 if (m.hilight == type)
305 public void AdjustCaret()
307 int row = this.Document.CaretPostion.row;
308 if (row > this.View.LayoutLines.Count - 1)
309 row = this.View.LayoutLines.Count - 1;
310 int col = this.Document.CaretPostion.col;
311 if (col > 0 && col > this.View.LayoutLines[row].Length)
312 col = this.View.LayoutLines[row].Length;
315 int sel_start = this.SelectionStart;
316 int sel_length = this.SelectionLength;
318 this.JumpCaret(row, col);
320 this.Document.Select(sel_start, sel_length);
324 /// キャレットを指定した位置に移動させる
326 /// <param name="index"></param>
327 /// <param name="autoExpand">折り畳みを展開するなら真</param>
328 public void JumpCaret(int index,bool autoExpand = true)
330 if (index < 0 || index > this.Document.Length)
331 throw new ArgumentOutOfRangeException("indexが設定できる範囲を超えています");
332 TextPoint tp = this.View.GetLayoutLineFromIndex(index);
334 this.JumpCaret(tp.row, tp.col,autoExpand);
338 /// キャレットを指定した位置に移動させる
340 /// <param name="row"></param>
341 /// <param name="col"></param>
342 /// <param name="autoExpand">折り畳みを展開するなら真</param>
343 public void JumpCaret(int row, int col, bool autoExpand = true)
345 if (this.Document.FireUpdateEvent == false)
346 throw new InvalidOperationException("");
348 this.View.JumpCaret(row, col,autoExpand);
350 this.View.AdjustCaretAndSrc();
352 this.SelectWithMoveCaret(false);
358 /// <param name="row">行</param>
359 /// <param name="isSelected">選択状態にするかどうか</param>
360 public void JumpToLineHead(int row,bool isSelected)
362 this.View.JumpCaret(row, 0);
363 this.View.AdjustCaretAndSrc();
364 this.SelectWithMoveCaret(isSelected);
370 /// <param name="row">行</param>
371 /// <param name="isSelected">選択状態にするかどうか</param>
372 public void JumpToLineEnd(int row, bool isSelected)
374 this.View.JumpCaret(row, this.View.LayoutLines[row].Length - 1);
375 this.View.AdjustCaretAndSrc();
376 this.SelectWithMoveCaret(isSelected);
382 /// <param name="isSelected"></param>
383 public void JumpToHead(bool isSelected)
385 if (this.View.TryScroll(0, 0))
387 this.View.JumpCaret(0, 0);
388 this.View.AdjustCaretAndSrc();
389 this.SelectWithMoveCaret(isSelected);
395 /// <param name="isSelected"></param>
396 public void JumpToEnd(bool isSelected)
398 int srcRow = this.View.LayoutLines.Count - this.View.LineCountOnScreen - 1;
401 if (this.View.TryScroll(0, srcRow))
403 this.View.JumpCaret(this.View.LayoutLines.Count - 1, 0);
404 this.View.AdjustCaretAndSrc();
405 this.SelectWithMoveCaret(isSelected);
409 public void ScrollByPixel(ScrollDirection dir,int delta, bool isSelected, bool withCaret)
411 if (this.Document.FireUpdateEvent == false)
412 throw new InvalidOperationException("");
414 if (dir == ScrollDirection.Left || dir == ScrollDirection.Right)
416 this.Scroll(dir, delta, isSelected, withCaret);
420 if(dir == ScrollDirection.Up || dir == ScrollDirection.Down)
424 if (noti < this.View.render.emSize.Height)
427 int delta_row = (int)(noti / this.View.render.emSize.Height + 1.0);
431 this.Scroll(dir, delta_row, isSelected, withCaret);
438 /// <param name="dir">方向を指定する</param>
439 /// <param name="delta">スクロールする量。ScrollDirectionの値がUpやDownなら行数。LeftやRightならピクセル単位の値となる</param>
440 /// <param name="isSelected">選択状態にするなら真</param>
441 /// <param name="withCaret">同時にキャレットを移動させるなら真</param>
442 public void Scroll(ScrollDirection dir, int delta, bool isSelected,bool withCaret)
444 if (this.Document.FireUpdateEvent == false)
445 throw new InvalidOperationException("");
446 int toRow = this.View.Src.Row;
447 double toX = this.View.Src.X;
450 case ScrollDirection.Up:
451 toRow = Math.Max(0, this.View.Src.Row - delta);
452 toRow = this.View.AdjustRow(toRow, false);
454 case ScrollDirection.Down:
455 toRow = Math.Min(this.View.Src.Row + delta, this.View.LayoutLines.Count - 1);
456 toRow = this.View.AdjustRow(toRow, true);
458 case ScrollDirection.Left:
461 case ScrollDirection.Right:
465 throw new ArgumentOutOfRangeException();
467 this.Scroll(toX, toRow, isSelected, withCaret);
473 /// <param name="toX">スクロール先の座標</param>
474 /// <param name="toRow">スクロール先の行</param>
475 /// <param name="isSelected">選択状態にするなら真</param>
476 /// <param name="withCaret">同時にキャレットを移動させるなら真</param>
477 public void Scroll(double toX, int toRow, bool isSelected, bool withCaret)
481 this.View.Scroll(toX, toRow);
482 this.View.JumpCaret(toRow, 0);
483 this.View.AdjustCaretAndSrc();
484 this.SelectWithMoveCaret(isSelected);
488 this.View.Scroll(toX, toRow);
491 this.Document.SelectGrippers.BottomLeft.MoveByIndex(this.View, this.SelectionStart);
492 this.Document.SelectGrippers.BottomRight.MoveByIndex(this.View, this.SelectionStart + this.SelectionLength);
498 /// <returns>移動できない場合は真を返す</returns>
499 /// <param name="realLength">負の値なら左側へ、そうでないなら右側へ移動する</param>
500 /// <param name="isSelected">選択範囲とするなら真。そうでないなら偽</param>
501 /// <param name="alignWord">単語単位で移動するなら真。そうでないなら偽</param>
502 public void MoveCaretHorizontical(int realLength, bool isSelected,bool alignWord = false)
504 for (int i = Math.Abs(realLength); i > 0; i--)
506 bool MoveFlow = realLength > 0;
507 if (this.Document.RightToLeft)
508 MoveFlow = !MoveFlow;
509 this.MoveCaretHorizontical(MoveFlow);
512 this.AlignNearestWord(MoveFlow);
514 this.View.AdjustCaretAndSrc(AdjustFlow.Col);
515 this.SelectWithMoveCaret(isSelected);
518 void AlignNearestWord(bool MoveFlow)
520 string str = this.View.LayoutLines[this.Document.CaretPostion.row];
521 while (this.Document.CaretPostion.col > 0 &&
522 this.Document.CaretPostion.col < str.Length &&
523 str[this.Document.CaretPostion.col] != Document.NewLine)
525 if (!Util.IsWordSeparator(str[this.Document.CaretPostion.col]))
527 this.MoveCaretHorizontical(MoveFlow);
532 this.MoveCaretHorizontical(MoveFlow);
541 /// <returns>再描写する必要があるなら真を返す</returns>
542 /// <param name="deltarow">移動量</param>
543 /// <param name="isSelected"></param>
544 public void MoveCaretVertical(int deltarow,bool isSelected)
546 for (int i = Math.Abs(deltarow); i > 0; i--)
547 this.MoveCaretVertical(deltarow > 0);
548 this.View.AdjustCaretAndSrc(AdjustFlow.Both);
549 this.SelectWithMoveCaret(isSelected);
553 /// キャレット位置の文字を一文字削除する
555 public void DoDeleteAction()
557 if (this.SelectionLength != 0)
559 this.SelectedText = "";
563 if (this.Document.FireUpdateEvent == false)
564 throw new InvalidOperationException("");
566 TextPoint CaretPostion = this.Document.CaretPostion;
567 int index = this.View.GetIndexFromLayoutLine(CaretPostion);
569 if (index == this.Document.Length)
572 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPostion.row);
573 int next = this.View.LayoutLines.GetLayout(CaretPostion.row).AlignIndexToNearestCluster(CaretPostion.col, AlignDirection.Forward) + lineHeadIndex;
575 if (this.Document[index] == Document.NewLine)
578 this.Document.Replace(index, next - index, "", true);
581 public bool IsRectInsertMode()
583 if (!this.RectSelection || this.View.Selections.Count == 0)
585 foreach(Selection sel in this.View.Selections)
594 /// キャレット位置の文字を一文字削除し、キャレット位置を後ろにずらす
596 public void DoBackSpaceAction()
598 if (this.IsRectInsertMode())
600 this.ReplaceBeforeSelectionArea(this.View.Selections, 1, "");
603 else if (this.SelectionLength > 0)
605 this.SelectedText = "";
609 if (this.Document.FireUpdateEvent == false)
610 throw new InvalidOperationException("");
612 TextPoint CurrentPostion = this.Document.CaretPostion;
614 if (CurrentPostion.row == 0 && CurrentPostion.col == 0)
617 int oldIndex = this.View.GetIndexFromLayoutLine(CurrentPostion);
619 int newCol, newIndex;
620 if (CurrentPostion.col > 0)
622 newCol = this.View.LayoutLines.GetLayout(CurrentPostion.row).AlignIndexToNearestCluster(CurrentPostion.col - 1, AlignDirection.Back);
623 newIndex = this.View.GetIndexFromLayoutLine(new TextPoint(CurrentPostion.row, newCol));
627 newIndex = this.View.GetIndexFromLayoutLine(CurrentPostion);
631 this.Document.Replace(newIndex, oldIndex - newIndex, "", true);
637 public void DoEnterAction()
639 this.DoInputChar('\n');
643 /// キャレット位置に文字を入力し、その分だけキャレットを進める。isInsertModeの値により動作が変わります
645 /// <param name="ch"></param>
646 public void DoInputChar(char ch)
648 this.DoInputString(ch.ToString());
651 string GetIndentSpace(int col_index)
653 int space_count = this.Document.TabStops - (col_index % this.Document.TabStops);
654 return new string(Enumerable.Repeat(' ',space_count).ToArray());
658 /// キャレット位置に文字列を挿入し、その分だけキャレットを進める。isInsertModeの値により動作が変わります
660 /// <param name="str">挿入したい文字列</param>
661 /// <param name="fromTip">真の場合、矩形選択の幅にかかわらず矩形編集モードとして動作します。そうでない場合は選択領域を文字列で置き換えます</param>
662 public void DoInputString(string str,bool fromTip = false)
664 TextPoint CaretPos = this.Document.CaretPostion;
666 if (str == "\t" && this.IndentMode == IndentMode.Space)
667 str = this.GetIndentSpace(CaretPos.col);
669 if (this.IsRectInsertMode())
671 this.ReplaceBeforeSelectionArea(this.View.Selections, 0, str);
674 else if (this.SelectionLength != 0)
676 this.RepleaceSelectionArea(this.View.Selections, str, fromTip);
680 if (this.Document.FireUpdateEvent == false)
681 throw new InvalidOperationException("");
683 int index = this.View.GetIndexFromLayoutLine(this.Document.CaretPostion);
685 if (this.View.InsertMode == false && index < this.Document.Length && this.Document[index] != Document.NewLine)
687 string lineString = this.View.LayoutLines[CaretPos.row];
688 int end = this.View.LayoutLines.GetLayout(CaretPos.row).AlignIndexToNearestCluster(CaretPos.col + str.Length - 1, AlignDirection.Forward);
689 if (end > lineString.Length - 1)
690 end = lineString.Length - 1;
691 end += this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row);
692 length = end - index;
694 if (str == Document.NewLine.ToString())
696 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(CaretPos.row);
697 int lineLength = this.View.LayoutLines.GetLengthFromLineNumber(CaretPos.row);
698 FoldingItem foldingData = this.View.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineHeadIndex, lineLength);
699 if (foldingData != null && !foldingData.Expand && index > foldingData.Start && index <= foldingData.End)
700 index = foldingData.End + 1;
702 this.Document.Replace(index, length, str, true);
706 /// キャレットの移動に合わせて選択する
708 /// <param name="isSelected">選択状態にするかどうか</param>
710 /// キャレットを移動後、このメソッドを呼び出さない場合、Select()メソッドは正常に機能しません
712 void SelectWithMoveCaret(bool isSelected)
714 if (this.Document.CaretPostion.col < 0 || this.Document.CaretPostion.row < 0)
717 if (this.Document.FireUpdateEvent == false)
718 throw new InvalidOperationException("");
720 int CaretPostion = this.View.GetIndexFromLayoutLine(this.Document.CaretPostion);
722 SelectCollection Selections = this.View.Selections;
725 this.Document.Select(this.Document.AnchorIndex, CaretPostion - this.Document.AnchorIndex);
727 this.Document.AnchorIndex = CaretPostion;
728 this.Document.Select(CaretPostion, 0);
733 /// JumpCaretで移動した位置からキャレットを移動し、選択状態にする
735 /// <param name="tp"></param>
736 /// <param name="alignWord">単語単位で選択するかどうか</param>
737 public void MoveCaretAndSelect(TextPoint tp,bool alignWord = false)
739 TextPoint endSelectPostion = tp;
740 int CaretPostion = this.View.GetIndexFromLayoutLine(tp);
743 if (this.IsReverseSelect())
744 while (CaretPostion >= 0 && CaretPostion < this.Document.Length && !Util.IsWordSeparator(this.Document[CaretPostion])) CaretPostion--;
746 while (CaretPostion < this.Document.Length && !Util.IsWordSeparator(this.Document[CaretPostion])) CaretPostion++;
747 if (CaretPostion < 0)
749 endSelectPostion = this.View.LayoutLines.GetTextPointFromIndex(CaretPostion);
751 this.Document.Select(this.Document.AnchorIndex, CaretPostion - this.Document.AnchorIndex);
752 this.View.JumpCaret(endSelectPostion.row, endSelectPostion.col);
753 this.View.AdjustCaretAndSrc();
757 /// グリッパーとキャレットを同時に移動する
759 /// <param name="p">ポインターの座標</param>
760 /// <param name="hittedGripper">動かす対象となるグリッパー</param>
761 /// <returns>移動できた場合は真を返す。そうでなければ偽を返す</returns>
762 /// <remarks>グリッパー内にポインターが存在しない場合、グリッパーはポインターの座標近くの行に移動する</remarks>
763 public bool MoveCaretAndGripper(Point p, Gripper hittedGripper)
765 bool HittedCaret = false;
766 TextPoint tp = this.View.GetTextPointFromPostion(p);
767 if (tp == this.Document.CaretPostion)
772 if (HittedCaret || hittedGripper != null)
774 TextPointSearchRange searchRange;
775 if (this.View.HitTextArea(p.X, p.Y))
776 searchRange = TextPointSearchRange.TextAreaOnly;
777 else if (this.SelectionLength > 0)
778 searchRange = TextPointSearchRange.Full;
782 if (hittedGripper != null)
784 tp = this.View.GetTextPointFromPostion(hittedGripper.AdjustPoint(p), searchRange);
785 if (tp == TextPoint.Null)
787 if (Object.ReferenceEquals(hittedGripper, this.Document.SelectGrippers.BottomRight))
788 this.MoveCaretAndSelect(tp);
789 else if(Object.ReferenceEquals(hittedGripper, this.Document.SelectGrippers.BottomLeft))
790 this.MoveSelectBefore(tp);
794 tp = this.View.GetTextPointFromPostion(p, searchRange);
795 if (tp != TextPoint.Null)
797 this.MoveCaretAndSelect(tp);
804 this.Document.SelectGrippers.BottomLeft.Enabled = this.SelectionLength != 0;
810 public void MoveSelectBefore(TextPoint tp)
814 if (this.IsReverseSelect())
816 NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp);
817 SelectionLength = this.SelectionLength + NewAnchorIndex - this.Document.AnchorIndex;
818 this.Document.Select(this.SelectionStart, SelectionLength);
822 NewAnchorIndex = this.View.GetIndexFromLayoutLine(tp);
823 SelectionLength = this.SelectionLength + this.Document.AnchorIndex - NewAnchorIndex;
824 this.Document.Select(NewAnchorIndex, SelectionLength);
826 this.Document.AnchorIndex = NewAnchorIndex;
832 public void ResetCaretPostion()
838 /// 行単位で移動後のキャレット位置を取得する
840 /// <param name="count">移動量</param>
841 /// <param name="current">現在のキャレット位置</param>
842 /// <returns>移動後のキャレット位置</returns>
843 public TextPoint GetTextPointAfterMoveLine(int count, TextPoint current)
845 int row = current.row + count;
849 else if (row >= this.View.LayoutLines.Count)
850 row = this.View.LayoutLines.Count - 1;
852 row = this.View.AdjustRow(row, count > 0);
854 double colpos = this.View.GetColPostionFromIndex(current.row, current.col);
855 int col = this.View.GetIndexFromColPostion(row, colpos);
857 return new TextPoint(row, col);
861 /// 選択文字列のインデントを一つ増やす
863 public void UpIndent()
865 if (this.RectSelection || this.SelectionLength == 0)
867 int selectionStart = this.SelectionStart;
868 string insertStr = this.IndentMode == IndentMode.Space ? this.GetIndentSpace(0) : "\t";
869 string text = this.InsertLineHead(GetTextFromLineSelectArea(this.View.Selections), insertStr);
870 this.RepleaceSelectionArea(this.View.Selections,text);
871 this.Document.Select(selectionStart, text.Length);
875 /// 選択文字列のインデントを一つ減らす
877 public void DownIndent()
879 if (this.RectSelection || this.SelectionLength == 0)
881 int selectionStart = this.SelectionStart;
882 string insertStr = this.IndentMode == IndentMode.Space ? this.GetIndentSpace(0) : "\t";
883 string text = this.RemoveLineHead(GetTextFromLineSelectArea(this.View.Selections), insertStr);
884 this.RepleaceSelectionArea(this.View.Selections, text);
885 this.Document.Select(selectionStart, text.Length);
888 string InsertLineHead(string s, string str)
890 string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.None);
891 StringBuilder output = new StringBuilder();
892 for (int i = 0; i < lines.Length; i++)
894 if(lines[i].Length > 0)
895 output.Append(str + lines[i] + Document.NewLine);
896 else if(i < lines.Length - 1)
897 output.Append(lines[i] + Document.NewLine);
899 return output.ToString();
902 public string RemoveLineHead(string s, string str)
904 string[] lines = s.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.None);
905 StringBuilder output = new StringBuilder();
906 for (int i = 0; i < lines.Length; i++)
908 if (lines[i].StartsWith(str))
909 output.Append(lines[i].Substring(1) + Document.NewLine);
910 else if (i < lines.Length - 1)
911 output.Append(lines[i] + Document.NewLine);
913 return output.ToString();
919 /// <param name="isMoveNext">真なら1文字すすめ、そうでなければ戻す</param>
920 /// <remarks>このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります</remarks>
921 void MoveCaretHorizontical(bool isMoveNext)
923 if (this.Document.FireUpdateEvent == false)
924 throw new InvalidOperationException("");
925 int delta = isMoveNext ? 0 : -1;
926 int prevcol = this.Document.CaretPostion.col;
927 int col = this.Document.CaretPostion.col + delta;
928 string lineString = this.View.LayoutLines[this.Document.CaretPostion.row];
929 if (col < 0 || this.Document.CaretPostion.row >= this.View.LayoutLines.Count)
931 if (this.Document.CaretPostion.row == 0)
936 this.MoveCaretVertical(false);
937 this.View.AdjustCaretAndSrc(AdjustFlow.Row); //この段階で調整しないとスクロールされない
938 col = this.View.LayoutLines.GetLengthFromLineNumber(this.Document.CaretPostion.row) - 1; //最終行以外はすべて改行コードが付くはず
940 else if (col >= lineString.Length || lineString[col] == Document.NewLine)
942 if (this.Document.CaretPostion.row < this.View.LayoutLines.Count - 1)
944 this.MoveCaretVertical(true);
945 this.View.AdjustCaretAndSrc(AdjustFlow.Row); //この段階で調整しないとスクロールされない
951 AlignDirection direction = isMoveNext ? AlignDirection.Forward : AlignDirection.Back;
952 col = this.View.LayoutLines.GetLayout(this.Document.CaretPostion.row).AlignIndexToNearestCluster(col, direction);
955 this.View.JumpCaret(this.Document.CaretPostion.row, col,false);
961 /// <param name="isMoveNext">プラス方向に移動するなら真</param>
962 /// <remarks>このメソッドを呼び出した後でScrollToCaretメソッドとSelectWithMoveCaretメソッドを呼び出す必要があります</remarks>
963 void MoveCaretVertical(bool isMoveNext)
965 if (this.Document.FireUpdateEvent == false)
966 throw new InvalidOperationException("");
968 TextPoint nextPoint = this.GetTextPointAfterMoveLine(isMoveNext ? 1 : -1, this.Document.CaretPostion);
970 this.View.JumpCaret(nextPoint.row, nextPoint.col,false);
973 private void ReplaceBeforeSelectionArea(SelectCollection Selections, int removeLength, string insertStr)
975 if (removeLength == 0 && insertStr.Length == 0)
978 if (this.RectSelection == false || this.Document.FireUpdateEvent == false)
979 throw new InvalidOperationException();
981 SelectCollection temp = this.View.Selections;
982 int selectStart = temp.First().start;
983 int selectEnd = temp.Last().start + temp.Last().length;
985 //ドキュメント操作後に行うとうまくいかないので、あらかじめ取得しておく
986 TextPoint start = this.View.LayoutLines.GetTextPointFromIndex(selectStart);
987 TextPoint end = this.View.LayoutLines.GetTextPointFromIndex(selectEnd);
989 bool reverse = temp.First().start > temp.Last().start;
991 int lineHeadIndex = this.View.LayoutLines.GetIndexFromLineNumber(this.View.LayoutLines.GetLineNumberFromIndex(selectStart));
992 if (selectStart - removeLength < lineHeadIndex)
995 this.Document.UndoManager.BeginUndoGroup();
996 this.Document.FireUpdateEvent = false;
1000 for (int i = 0; i < temp.Count; i++)
1002 this.ReplaceBeforeSelection(temp[i], removeLength, insertStr);
1007 for (int i = temp.Count - 1; i >= 0; i--)
1009 this.ReplaceBeforeSelection(temp[i], removeLength, insertStr);
1013 this.Document.FireUpdateEvent = true;
1014 this.Document.UndoManager.EndUndoGroup();
1016 int delta = insertStr.Length - removeLength;
1021 this.JumpCaret(start.row, start.col);
1023 this.JumpCaret(end.row, end.col);
1025 this.Document.Select(start, 0, end.row - start.row);
1028 private void ReplaceBeforeSelection(Selection sel, int removeLength, string insertStr)
1030 sel = Util.NormalizeIMaker<Selection>(sel);
1031 this.Document.Replace(sel.start - removeLength, removeLength, insertStr);
1034 private void RepleaceSelectionArea(SelectCollection Selections, string value,bool updateInsertPoint = false)
1039 if (this.RectSelection == false)
1041 Selection sel = Selection.Create(this.Document.AnchorIndex, 0);
1042 if (Selections.Count > 0)
1043 sel = Util.NormalizeIMaker<Selection>(this.View.Selections.First());
1045 this.Document.Replace(sel.start, sel.length, value);
1049 if (this.Document.FireUpdateEvent == false)
1050 throw new InvalidOperationException("");
1052 int StartIndex = this.SelectionStart;
1054 SelectCollection newInsertPoint = new SelectCollection();
1056 if (this.SelectionLength == 0)
1060 this.Document.UndoManager.BeginUndoGroup();
1062 this.Document.FireUpdateEvent = false;
1064 string[] line = value.Split(new string[] { Document.NewLine.ToString() }, StringSplitOptions.RemoveEmptyEntries);
1066 TextPoint Current = this.View.GetLayoutLineFromIndex(this.SelectionStart);
1068 for (i = 0; i < line.Length && Current.row < this.View.LayoutLines.Count; i++, Current.row++)
1070 if (Current.col > this.View.LayoutLines[Current.row].Length)
1071 Current.col = this.View.LayoutLines[Current.row].Length;
1072 StartIndex = this.View.GetIndexFromLayoutLine(Current);
1073 this.Document.Replace(StartIndex, 0, line[i]);
1074 StartIndex += line[i].Length;
1077 for (; i < line.Length; i++)
1079 StartIndex = this.Document.Length;
1080 string str = Document.NewLine + line[i];
1081 this.Document.Replace(StartIndex, 0, str);
1082 StartIndex += str.Length;
1085 this.Document.FireUpdateEvent = true;
1087 this.Document.UndoManager.EndUndoGroup();
1091 SelectCollection temp = new SelectCollection(this.View.Selections); //コピーしないとReplaceCommandを呼び出した段階で書き換えられてしまう
1093 this.Document.UndoManager.BeginUndoGroup();
1095 this.Document.FireUpdateEvent = false;
1097 if (temp.First().start < temp.Last().start)
1099 for (int i = temp.Count - 1; i >= 0; i--)
1101 Selection sel = Util.NormalizeIMaker<Selection>(temp[i]);
1103 StartIndex = sel.start;
1105 this.Document.Replace(sel.start, sel.length, value);
1107 newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i,0));
1112 for (int i = 0; i < temp.Count; i++)
1114 Selection sel = Util.NormalizeIMaker<Selection>(temp[i]);
1116 StartIndex = sel.start;
1118 this.Document.Replace(sel.start, sel.length, value);
1120 newInsertPoint.Add(Selection.Create(sel.start + (value.Length - sel.length) * i, 0));
1124 this.Document.FireUpdateEvent = true;
1126 this.Document.UndoManager.EndUndoGroup();
1128 this.JumpCaret(StartIndex);
1129 if (updateInsertPoint && newInsertPoint.Count > 0)
1130 this.View.Selections = newInsertPoint;
1133 private string GetTextFromLineSelectArea(SelectCollection Selections)
1135 Selection sel = Util.NormalizeIMaker<Selection>(Selections.First());
1137 string str = this.Document.ToString(sel.start, sel.length);
1142 string GetTextFromRectangleSelectArea(SelectCollection Selections)
1144 StringBuilder temp = new StringBuilder();
1145 if (Selections.First().start < Selections.Last().start)
1147 for (int i = 0; i < this.View.Selections.Count; i++)
1149 Selection sel = Util.NormalizeIMaker<Selection>(Selections[i]);
1151 string str = this.Document.ToString(sel.start, sel.length);
1152 if (str.IndexOf(Environment.NewLine) == -1)
1153 temp.AppendLine(str);
1160 for (int i = this.View.Selections.Count - 1; i >= 0; i--)
1162 Selection sel = Util.NormalizeIMaker<Selection>(Selections[i]);
1164 string str = this.Document.ToString(sel.start, sel.length).Replace(Document.NewLine.ToString(), Environment.NewLine);
1165 if (str.IndexOf(Environment.NewLine) == -1)
1166 temp.AppendLine(str);
1171 return temp.ToString();
1174 void View_LineBreakChanged(object sender, EventArgs e)
1180 void View_PageBoundChanged(object sender, EventArgs e)
1182 if (this.Document.LineBreak == LineBreakMethod.PageBound && this.View.PageBound.Width - this.View.LineBreakingMarginWidth > 0)
1183 this.Document.PerformLayout();
1187 void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e)
1189 if (e.type == ResourceType.Font)
1191 if (this.Document.LineBreak == LineBreakMethod.PageBound)
1192 this.Document.PerformLayout();
1195 if (e.type == ResourceType.InlineChar)
1197 int oldLineCountOnScreen = this.View.LineCountOnScreen;
1198 this.View.CalculateLineCountOnScreen();
1199 if(this.View.LineCountOnScreen != oldLineCountOnScreen)
1204 void Document_Update(object sender, DocumentUpdateEventArgs e)
1208 case UpdateType.Replace:
1209 this.JumpCaret(e.startIndex + e.insertLength,true);
1211 case UpdateType.Clear:
1212 this.JumpCaret(0,0, false);