TextView.Navigation.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. namespace Terminal.Gui.Views;
  2. /// <summary>Navigation functionality - cursor movement and scrolling</summary>
  3. public partial class TextView
  4. {
  5. #region Public Navigation Methods
  6. /// <summary>Will scroll the <see cref="TextView"/> to the last line and position the cursor there.</summary>
  7. public void MoveEnd ()
  8. {
  9. CurrentRow = _model.Count - 1;
  10. List<Cell> line = GetCurrentLine ();
  11. CurrentColumn = line.Count;
  12. TrackColumn ();
  13. DoNeededAction ();
  14. }
  15. /// <summary>Will scroll the <see cref="TextView"/> to the first line and position the cursor there.</summary>
  16. public void MoveHome ()
  17. {
  18. CurrentRow = 0;
  19. _topRow = 0;
  20. CurrentColumn = 0;
  21. _leftColumn = 0;
  22. TrackColumn ();
  23. DoNeededAction ();
  24. }
  25. /// <summary>
  26. /// Will scroll the <see cref="TextView"/> to display the specified row at the top if <paramref name="isRow"/> is
  27. /// true or will scroll the <see cref="TextView"/> to display the specified column at the left if
  28. /// <paramref name="isRow"/> is false.
  29. /// </summary>
  30. /// <param name="idx">
  31. /// Row that should be displayed at the top or Column that should be displayed at the left, if the value
  32. /// is negative it will be reset to zero
  33. /// </param>
  34. /// <param name="isRow">If true (default) the <paramref name="idx"/> is a row, column otherwise.</param>
  35. public void ScrollTo (int idx, bool isRow = true)
  36. {
  37. if (idx < 0)
  38. {
  39. idx = 0;
  40. }
  41. if (isRow)
  42. {
  43. _topRow = Math.Max (idx > _model.Count - 1 ? _model.Count - 1 : idx, 0);
  44. if (IsInitialized && Viewport.Y != _topRow)
  45. {
  46. Viewport = Viewport with { Y = _topRow };
  47. }
  48. }
  49. else if (!_wordWrap)
  50. {
  51. int maxlength = _model.GetMaxVisibleLine (_topRow, _topRow + Viewport.Height, TabWidth);
  52. _leftColumn = Math.Max (!_wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0);
  53. if (IsInitialized && Viewport.X != _leftColumn)
  54. {
  55. Viewport = Viewport with { X = _leftColumn };
  56. }
  57. }
  58. SetNeedsDraw ();
  59. }
  60. #endregion
  61. #region Private Navigation Methods
  62. private void MoveBottomEnd ()
  63. {
  64. ResetAllTrack ();
  65. if (_shiftSelecting && IsSelecting)
  66. {
  67. StopSelecting ();
  68. }
  69. MoveEnd ();
  70. }
  71. private void MoveBottomEndExtend ()
  72. {
  73. ResetAllTrack ();
  74. StartSelecting ();
  75. MoveEnd ();
  76. }
  77. private bool MoveDown ()
  78. {
  79. if (CurrentRow + 1 < _model.Count)
  80. {
  81. if (_columnTrack == -1)
  82. {
  83. _columnTrack = CurrentColumn;
  84. }
  85. CurrentRow++;
  86. if (CurrentRow >= _topRow + Viewport.Height)
  87. {
  88. _topRow++;
  89. SetNeedsDraw ();
  90. }
  91. TrackColumn ();
  92. PositionCursor ();
  93. }
  94. else if (CurrentRow > Viewport.Height)
  95. {
  96. Adjust ();
  97. }
  98. else
  99. {
  100. return false;
  101. }
  102. DoNeededAction ();
  103. return true;
  104. }
  105. private void MoveEndOfLine ()
  106. {
  107. List<Cell> currentLine = GetCurrentLine ();
  108. CurrentColumn = currentLine.Count;
  109. DoNeededAction ();
  110. }
  111. private bool MoveLeft ()
  112. {
  113. if (CurrentColumn > 0)
  114. {
  115. CurrentColumn--;
  116. }
  117. else
  118. {
  119. if (CurrentRow > 0)
  120. {
  121. CurrentRow--;
  122. if (CurrentRow < _topRow)
  123. {
  124. _topRow--;
  125. SetNeedsDraw ();
  126. }
  127. List<Cell> currentLine = GetCurrentLine ();
  128. CurrentColumn = Math.Max (currentLine.Count - (ReadOnly ? 1 : 0), 0);
  129. }
  130. else
  131. {
  132. return false;
  133. }
  134. }
  135. DoNeededAction ();
  136. return true;
  137. }
  138. private void MovePageDown ()
  139. {
  140. int nPageDnShift = Viewport.Height - 1;
  141. if (CurrentRow >= 0 && CurrentRow < _model.Count)
  142. {
  143. if (_columnTrack == -1)
  144. {
  145. _columnTrack = CurrentColumn;
  146. }
  147. CurrentRow = CurrentRow + nPageDnShift > _model.Count
  148. ? _model.Count > 0 ? _model.Count - 1 : 0
  149. : CurrentRow + nPageDnShift;
  150. if (_topRow < CurrentRow - nPageDnShift)
  151. {
  152. _topRow = CurrentRow >= _model.Count
  153. ? CurrentRow - nPageDnShift
  154. : _topRow + nPageDnShift;
  155. SetNeedsDraw ();
  156. }
  157. TrackColumn ();
  158. PositionCursor ();
  159. }
  160. DoNeededAction ();
  161. }
  162. private void MovePageUp ()
  163. {
  164. int nPageUpShift = Viewport.Height - 1;
  165. if (CurrentRow > 0)
  166. {
  167. if (_columnTrack == -1)
  168. {
  169. _columnTrack = CurrentColumn;
  170. }
  171. CurrentRow = CurrentRow - nPageUpShift < 0 ? 0 : CurrentRow - nPageUpShift;
  172. if (CurrentRow < _topRow)
  173. {
  174. _topRow = _topRow - nPageUpShift < 0 ? 0 : _topRow - nPageUpShift;
  175. SetNeedsDraw ();
  176. }
  177. TrackColumn ();
  178. PositionCursor ();
  179. }
  180. DoNeededAction ();
  181. }
  182. private bool MoveRight ()
  183. {
  184. List<Cell> currentLine = GetCurrentLine ();
  185. if ((ReadOnly ? CurrentColumn + 1 : CurrentColumn) < currentLine.Count)
  186. {
  187. CurrentColumn++;
  188. }
  189. else
  190. {
  191. if (CurrentRow + 1 < _model.Count)
  192. {
  193. CurrentRow++;
  194. CurrentColumn = 0;
  195. if (CurrentRow >= _topRow + Viewport.Height)
  196. {
  197. _topRow++;
  198. SetNeedsDraw ();
  199. }
  200. }
  201. else
  202. {
  203. return false;
  204. }
  205. }
  206. DoNeededAction ();
  207. return true;
  208. }
  209. private void MoveLeftStart ()
  210. {
  211. if (_leftColumn > 0)
  212. {
  213. SetNeedsDraw ();
  214. }
  215. CurrentColumn = 0;
  216. _leftColumn = 0;
  217. DoNeededAction ();
  218. }
  219. private void MoveTopHome ()
  220. {
  221. ResetAllTrack ();
  222. if (_shiftSelecting && IsSelecting)
  223. {
  224. StopSelecting ();
  225. }
  226. MoveHome ();
  227. }
  228. private void MoveTopHomeExtend ()
  229. {
  230. ResetColumnTrack ();
  231. StartSelecting ();
  232. MoveHome ();
  233. }
  234. private bool MoveUp ()
  235. {
  236. if (CurrentRow > 0)
  237. {
  238. if (_columnTrack == -1)
  239. {
  240. _columnTrack = CurrentColumn;
  241. }
  242. CurrentRow--;
  243. if (CurrentRow < _topRow)
  244. {
  245. _topRow--;
  246. SetNeedsDraw ();
  247. }
  248. TrackColumn ();
  249. PositionCursor ();
  250. }
  251. else
  252. {
  253. return false;
  254. }
  255. DoNeededAction ();
  256. return true;
  257. }
  258. private void MoveWordBackward ()
  259. {
  260. (int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
  261. if (newPos.HasValue)
  262. {
  263. CurrentColumn = newPos.Value.col;
  264. CurrentRow = newPos.Value.row;
  265. }
  266. DoNeededAction ();
  267. }
  268. private void MoveWordForward ()
  269. {
  270. (int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
  271. if (newPos.HasValue)
  272. {
  273. CurrentColumn = newPos.Value.col;
  274. CurrentRow = newPos.Value.row;
  275. }
  276. DoNeededAction ();
  277. }
  278. #endregion
  279. #region Process Navigation Methods
  280. private bool ProcessMoveDown ()
  281. {
  282. ResetContinuousFindTrack ();
  283. if (_shiftSelecting && IsSelecting)
  284. {
  285. StopSelecting ();
  286. }
  287. return MoveDown ();
  288. }
  289. private void ProcessMoveDownExtend ()
  290. {
  291. ResetColumnTrack ();
  292. StartSelecting ();
  293. MoveDown ();
  294. }
  295. private void ProcessMoveEndOfLine ()
  296. {
  297. ResetAllTrack ();
  298. if (_shiftSelecting && IsSelecting)
  299. {
  300. StopSelecting ();
  301. }
  302. MoveEndOfLine ();
  303. }
  304. private void ProcessMoveRightEndExtend ()
  305. {
  306. ResetAllTrack ();
  307. StartSelecting ();
  308. MoveEndOfLine ();
  309. }
  310. private bool ProcessMoveLeft ()
  311. {
  312. // if the user presses Left (without any control keys) and they are at the start of the text
  313. if (CurrentColumn == 0 && CurrentRow == 0)
  314. {
  315. if (IsSelecting)
  316. {
  317. StopSelecting ();
  318. return true;
  319. }
  320. // do not respond (this lets the key press fall through to navigation system - which usually changes focus backward)
  321. return false;
  322. }
  323. ResetAllTrack ();
  324. if (_shiftSelecting && IsSelecting)
  325. {
  326. StopSelecting ();
  327. }
  328. MoveLeft ();
  329. return true;
  330. }
  331. private void ProcessMoveLeftExtend ()
  332. {
  333. ResetAllTrack ();
  334. StartSelecting ();
  335. MoveLeft ();
  336. }
  337. private bool ProcessMoveRight ()
  338. {
  339. // if the user presses Right (without any control keys)
  340. // determine where the last cursor position in the text is
  341. int lastRow = _model.Count - 1;
  342. int lastCol = _model.GetLine (lastRow).Count;
  343. // if they are at the very end of all the text do not respond (this lets the key press fall through to navigation system - which usually changes focus forward)
  344. if (CurrentColumn == lastCol && CurrentRow == lastRow)
  345. {
  346. // Unless they have text selected
  347. if (IsSelecting)
  348. {
  349. // In which case clear
  350. StopSelecting ();
  351. return true;
  352. }
  353. return false;
  354. }
  355. ResetAllTrack ();
  356. if (_shiftSelecting && IsSelecting)
  357. {
  358. StopSelecting ();
  359. }
  360. MoveRight ();
  361. return true;
  362. }
  363. private void ProcessMoveRightExtend ()
  364. {
  365. ResetAllTrack ();
  366. StartSelecting ();
  367. MoveRight ();
  368. }
  369. private void ProcessMoveLeftStart ()
  370. {
  371. ResetAllTrack ();
  372. if (_shiftSelecting && IsSelecting)
  373. {
  374. StopSelecting ();
  375. }
  376. MoveLeftStart ();
  377. }
  378. private void ProcessMoveLeftStartExtend ()
  379. {
  380. ResetAllTrack ();
  381. StartSelecting ();
  382. MoveLeftStart ();
  383. }
  384. private bool ProcessMoveUp ()
  385. {
  386. ResetContinuousFindTrack ();
  387. if (_shiftSelecting && IsSelecting)
  388. {
  389. StopSelecting ();
  390. }
  391. return MoveUp ();
  392. }
  393. private void ProcessMoveUpExtend ()
  394. {
  395. ResetColumnTrack ();
  396. StartSelecting ();
  397. MoveUp ();
  398. }
  399. private void ProcessMoveWordBackward ()
  400. {
  401. ResetAllTrack ();
  402. if (_shiftSelecting && IsSelecting)
  403. {
  404. StopSelecting ();
  405. }
  406. MoveWordBackward ();
  407. }
  408. private void ProcessMoveWordBackwardExtend ()
  409. {
  410. ResetAllTrack ();
  411. StartSelecting ();
  412. MoveWordBackward ();
  413. }
  414. private void ProcessMoveWordForward ()
  415. {
  416. ResetAllTrack ();
  417. if (_shiftSelecting && IsSelecting)
  418. {
  419. StopSelecting ();
  420. }
  421. MoveWordForward ();
  422. }
  423. private void ProcessMoveWordForwardExtend ()
  424. {
  425. ResetAllTrack ();
  426. StartSelecting ();
  427. MoveWordForward ();
  428. }
  429. private void ProcessPageDown ()
  430. {
  431. ResetColumnTrack ();
  432. if (_shiftSelecting && IsSelecting)
  433. {
  434. StopSelecting ();
  435. }
  436. MovePageDown ();
  437. }
  438. private void ProcessPageDownExtend ()
  439. {
  440. ResetColumnTrack ();
  441. StartSelecting ();
  442. MovePageDown ();
  443. }
  444. private void ProcessPageUp ()
  445. {
  446. ResetColumnTrack ();
  447. if (_shiftSelecting && IsSelecting)
  448. {
  449. StopSelecting ();
  450. }
  451. MovePageUp ();
  452. }
  453. private void ProcessPageUpExtend ()
  454. {
  455. ResetColumnTrack ();
  456. StartSelecting ();
  457. MovePageUp ();
  458. }
  459. #endregion
  460. #region Column Tracking
  461. // Tries to snap the cursor to the tracking column
  462. private void TrackColumn ()
  463. {
  464. // Now track the column
  465. List<Cell> line = GetCurrentLine ();
  466. if (line.Count < _columnTrack)
  467. {
  468. CurrentColumn = line.Count;
  469. }
  470. else if (_columnTrack != -1)
  471. {
  472. CurrentColumn = _columnTrack;
  473. }
  474. else if (CurrentColumn > line.Count)
  475. {
  476. CurrentColumn = line.Count;
  477. }
  478. Adjust ();
  479. }
  480. #endregion
  481. }