PageRenderTime 24ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/main/src/core/Mono.Texteditor/Mono.TextEditor/Caret.cs

https://github.com/Unity-Technologies/monodevelop
C# | 409 lines | 326 code | 41 blank | 42 comment | 79 complexity | 35bad79bd497aa9480a365a4040d152a MD5 | raw file
  1. // Caret.cs
  2. //
  3. // Author:
  4. // Mike Krüger <mkrueger@novell.com>
  5. //
  6. // Copyright (c) 2007 Novell, Inc (http://www.novell.com)
  7. //
  8. // Permission is hereby granted, free of charge, to any person obtaining a copy
  9. // of this software and associated documentation files (the "Software"), to deal
  10. // in the Software without restriction, including without limitation the rights
  11. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. // copies of the Software, and to permit persons to whom the Software is
  13. // furnished to do so, subject to the following conditions:
  14. //
  15. // The above copyright notice and this permission notice shall be included in
  16. // all copies or substantial portions of the Software.
  17. //
  18. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. // THE SOFTWARE.
  25. //
  26. //
  27. using System;
  28. using System.Linq;
  29. using ICSharpCode.NRefactory.Editor;
  30. namespace Mono.TextEditor
  31. {
  32. public class Caret
  33. {
  34. bool isInInsertMode = true;
  35. bool autoScrollToCaret = true;
  36. CaretMode mode;
  37. int line = DocumentLocation.MinLine;
  38. public int Line {
  39. get {
  40. return line;
  41. }
  42. set {
  43. if (line != value) {
  44. if (value < DocumentLocation.MinLine)
  45. throw new ArgumentException ("Line < MinLine");
  46. var old = Location;
  47. line = value;
  48. CheckLine ();
  49. SetColumn ();
  50. UpdateCaretOffset ();
  51. OnPositionChanged (new DocumentLocationEventArgs (old));
  52. }
  53. }
  54. }
  55. int column = DocumentLocation.MinColumn;
  56. public int Column {
  57. get {
  58. return column;
  59. }
  60. set {
  61. if (column != value) {
  62. if (value < DocumentLocation.MinColumn)
  63. throw new ArgumentException ("Column < MinColumn");
  64. var old = Location;
  65. column = value;
  66. CheckColumn ();
  67. SetDesiredColumn ();
  68. UpdateCaretOffset ();
  69. OnPositionChanged (new DocumentLocationEventArgs (old));
  70. }
  71. }
  72. }
  73. public DocumentLocation Location {
  74. get {
  75. return new DocumentLocation (Line, Column);
  76. }
  77. set {
  78. if (Location != value) {
  79. if (value.Line < DocumentLocation.MinLine || value.Column < DocumentLocation.MinColumn)
  80. throw new ArgumentException ("invalid location: " + value);
  81. DocumentLocation old = Location;
  82. line = value.Line;
  83. column = value.Column;
  84. CheckLine ();
  85. CheckColumn ();
  86. SetDesiredColumn ();
  87. UpdateCaretOffset ();
  88. OnPositionChanged (new DocumentLocationEventArgs (old));
  89. }
  90. }
  91. }
  92. ITextSourceVersion offsetVersion;
  93. int caretOffset;
  94. public int Offset {
  95. get {
  96. return caretOffset;
  97. }
  98. set {
  99. if (caretOffset == value)
  100. return;
  101. DocumentLocation old = Location;
  102. caretOffset = value;
  103. offsetVersion = TextEditorData.Document.Version;
  104. line = System.Math.Max (1, TextEditorData.Document.OffsetToLineNumber (value));
  105. var lineSegment = TextEditorData.Document.GetLine (line);
  106. column = lineSegment != null ? value - lineSegment.Offset + 1 : 1;
  107. CheckLine ();
  108. CheckColumn ();
  109. SetDesiredColumn ();
  110. OnPositionChanged (new DocumentLocationEventArgs (old));
  111. }
  112. }
  113. public bool PreserveSelection {
  114. get;
  115. set;
  116. }
  117. public bool IsInInsertMode {
  118. get {
  119. return CaretMode.Insert == mode;
  120. }
  121. set {
  122. mode = value? CaretMode.Insert: CaretMode.Block;
  123. OnModeChanged ();
  124. }
  125. }
  126. /// <summary>
  127. /// The current mode of the caret
  128. /// </summary>
  129. public CaretMode Mode {
  130. get {
  131. return mode;
  132. }
  133. set {
  134. mode = value;
  135. OnModeChanged ();
  136. }
  137. }
  138. public bool AutoScrollToCaret {
  139. get {
  140. return autoScrollToCaret;
  141. }
  142. set {
  143. if (value != autoScrollToCaret) {
  144. autoScrollToCaret = value;
  145. if (autoScrollToCaret)
  146. OnPositionChanged (new DocumentLocationEventArgs (Location));
  147. }
  148. }
  149. }
  150. public bool IsVisible {
  151. get;
  152. set;
  153. }
  154. public bool AllowCaretBehindLineEnd {
  155. get;
  156. set;
  157. }
  158. public int DesiredColumn {
  159. get;
  160. set;
  161. }
  162. public TextEditorData TextEditorData {
  163. get;
  164. private set;
  165. }
  166. /// <summary>
  167. /// Gets or sets a value indicating whether this <see cref="T:Mono.TextEditor.Caret"/> will listen to text chagne events to update the caret position.
  168. /// </summary>
  169. public bool AutoUpdatePosition {
  170. get;
  171. set;
  172. }
  173. public Caret (TextEditorData editor)
  174. {
  175. TextEditorData = editor;
  176. IsVisible = true;
  177. AllowCaretBehindLineEnd = false;
  178. DesiredColumn = DocumentLocation.MinColumn;
  179. AutoUpdatePosition = true;
  180. }
  181. /// <summary>
  182. /// Activates auto scroll to caret on next caret move.
  183. /// </summary>
  184. public void ActivateAutoScrollWithoutMove ()
  185. {
  186. autoScrollToCaret = true;
  187. }
  188. void CheckLine ()
  189. {
  190. if (line > TextEditorData.Document.LineCount) {
  191. line = TextEditorData.Document.LineCount;
  192. UpdateCaretOffset ();
  193. }
  194. }
  195. void CheckColumn ()
  196. {
  197. var curLine = TextEditorData.Document.GetLine (Line);
  198. if (TextEditorData.HasIndentationTracker && TextEditorData.Options.IndentStyle == IndentStyle.Virtual && curLine.Length == 0) {
  199. if (column > DocumentLocation.MinColumn) {
  200. var indentColumn = TextEditorData.GetVirtualIndentationColumn (Location);
  201. if (column < indentColumn) {
  202. column = indentColumn;
  203. UpdateCaretOffset ();
  204. return;
  205. }
  206. if (column == indentColumn)
  207. return;
  208. }
  209. }
  210. if (!AllowCaretBehindLineEnd) {
  211. var max = curLine.Length + 1;
  212. if (column > max) {
  213. column = max;
  214. UpdateCaretOffset ();
  215. }
  216. }
  217. }
  218. public void SetToOffsetWithDesiredColumn (int desiredOffset)
  219. {
  220. DocumentLocation old = Location;
  221. int desiredLineNumber = TextEditorData.Document.OffsetToLineNumber (desiredOffset);
  222. var desiredLine = TextEditorData.Document.GetLine (desiredLineNumber);
  223. int newColumn = desiredOffset - desiredLine.Offset + 1;
  224. if (desiredLine.Length + 1 < Column && newColumn == 1) {
  225. if (TextEditorData.HasIndentationTracker && TextEditorData.Options.IndentStyle == IndentStyle.Virtual)
  226. newColumn = TextEditorData.GetVirtualIndentationColumn (desiredLineNumber, 1);
  227. }
  228. line = desiredLineNumber;
  229. column = newColumn;
  230. var logicalDesiredColumn = desiredLine.GetLogicalColumn (TextEditorData, DesiredColumn);
  231. if (logicalDesiredColumn <= desiredLine.Length + 1) {
  232. int possibleOffset = TextEditorData.LocationToOffset (desiredLineNumber, logicalDesiredColumn);
  233. if (!TextEditorData.Document.GetFoldingsFromOffset (possibleOffset).Any (f => f.IsFolded))
  234. column = logicalDesiredColumn;
  235. } else {
  236. column = System.Math.Max (newColumn, desiredLine.Length + 1);
  237. }
  238. UpdateCaretOffset ();
  239. OnPositionChanged (new DocumentLocationEventArgs (old));
  240. }
  241. void SetDesiredColumn ()
  242. {
  243. var curLine = TextEditorData.Document.GetLine (Line);
  244. if (curLine == null)
  245. return;
  246. DesiredColumn = curLine.GetVisualColumn (TextEditorData, Column);
  247. }
  248. void SetColumn ()
  249. {
  250. var curLine = TextEditorData.Document.GetLine (Line);
  251. if (curLine == null)
  252. return;
  253. column = System.Math.Max (DocumentLocation.MinColumn, Column);
  254. column = curLine.GetLogicalColumn (TextEditorData, DesiredColumn);
  255. if (TextEditorData.HasIndentationTracker && TextEditorData.Options.IndentStyle == IndentStyle.Virtual && curLine.GetVisualColumn (TextEditorData, column) < DesiredColumn) {
  256. column = TextEditorData.GetVirtualIndentationColumn (Line, column);
  257. } else {
  258. if (!AllowCaretBehindLineEnd && Column > curLine.Length + 1)
  259. column = System.Math.Min (curLine.Length + 1, column);
  260. }
  261. }
  262. public void SetToDesiredColumn (int desiredColumn)
  263. {
  264. var old = Location;
  265. DesiredColumn = desiredColumn;
  266. SetColumn ();
  267. OnPositionChanged (new DocumentLocationEventArgs (old));
  268. }
  269. public override string ToString ()
  270. {
  271. return String.Format ("[Caret: Location={0}, IsInInsertMode={1}]",
  272. Location,
  273. isInInsertMode);
  274. }
  275. /// <summary>
  276. /// This method should be called after a fold segment is folded, to ensure
  277. /// that the caret is in a valid state.
  278. /// </summary>
  279. public void MoveCaretBeforeFoldings ()
  280. {
  281. int offset = Offset;
  282. foreach (FoldSegment fold in TextEditorData.Document.GetFoldingsFromOffset (Offset)) {
  283. if (fold.IsFolded)
  284. offset = System.Math.Min (offset, fold.Offset);
  285. }
  286. Offset = offset;
  287. }
  288. protected virtual void OnPositionChanged (DocumentLocationEventArgs args)
  289. {
  290. TextEditorData.Document.EnsureOffsetIsUnfolded (Offset);
  291. if (PositionChanged != null)
  292. PositionChanged (this, args);
  293. }
  294. public event EventHandler<DocumentLocationEventArgs> PositionChanged;
  295. protected virtual void OnModeChanged ()
  296. {
  297. if (ModeChanged != null)
  298. ModeChanged (this, EventArgs.Empty);
  299. }
  300. public event EventHandler ModeChanged;
  301. public void UpdateCaretOffset ()
  302. {
  303. int result = 0;
  304. var doc = TextEditorData.Document;
  305. if (doc == null)
  306. return;
  307. if (Line <= doc.LineCount) {
  308. DocumentLine line = doc.GetLine (Line);
  309. if (line != null) {
  310. result = line.Offset;
  311. result += System.Math.Min (Column - 1, line.Length);
  312. }
  313. }
  314. caretOffset = result;
  315. offsetVersion = doc.Version;
  316. }
  317. internal void UpdateCaretPosition (DocumentChangeEventArgs e)
  318. {
  319. if (e.AnchorMovementType == AnchorMovementType.BeforeInsertion && caretOffset == e.Offset) {
  320. offsetVersion = TextEditorData.Version;
  321. return;
  322. }
  323. var curVersion = TextEditorData.Version;
  324. if (offsetVersion == null) {
  325. offsetVersion = curVersion;
  326. return;
  327. }
  328. var newOffset = offsetVersion.MoveOffsetTo (curVersion, caretOffset);
  329. offsetVersion = curVersion;
  330. if (newOffset == caretOffset || !AutoUpdatePosition)
  331. return;
  332. DocumentLocation old = Location;
  333. var newLocation = TextEditorData.OffsetToLocation (newOffset);
  334. int newColumn = newLocation.Column;
  335. var curLine = TextEditorData.GetLine (newLocation.Line);
  336. if (TextEditorData.HasIndentationTracker && TextEditorData.Options.IndentStyle == IndentStyle.Virtual && curLine.Length == 0) {
  337. var indentColumn = TextEditorData.GetVirtualIndentationColumn (Location);
  338. if (column == indentColumn) {
  339. newColumn = indentColumn;
  340. }
  341. }
  342. if (AllowCaretBehindLineEnd) {
  343. if (curLine != null && column > curLine.Length)
  344. newColumn = column;
  345. }
  346. line = newLocation.Line;
  347. column = newColumn;
  348. SetDesiredColumn ();
  349. UpdateCaretOffset ();
  350. OnPositionChanged (new DocumentLocationEventArgs (old));
  351. }
  352. public void SetDocument (TextDocument doc)
  353. {
  354. line = column = 1;
  355. offsetVersion = doc.Version;
  356. caretOffset = 0;
  357. }
  358. }
  359. /// <summary>
  360. /// Possible visual modes for the caret
  361. /// </summary>
  362. public enum CaretMode
  363. {
  364. Insert,
  365. Block,
  366. Underscore
  367. }
  368. }