gotoLineQuickAccess.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
  6. import { getCodeEditor } from '../../browser/editorBrowser.js';
  7. import { AbstractEditorNavigationQuickAccessProvider } from './editorNavigationQuickAccess.js';
  8. import { localize } from '../../../nls.js';
  9. export class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider {
  10. constructor() {
  11. super({ canAcceptInBackground: true });
  12. }
  13. provideWithoutTextEditor(picker) {
  14. const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line.");
  15. picker.items = [{ label }];
  16. picker.ariaLabel = label;
  17. return Disposable.None;
  18. }
  19. provideWithTextEditor(context, picker, token) {
  20. const editor = context.editor;
  21. const disposables = new DisposableStore();
  22. // Goto line once picked
  23. disposables.add(picker.onDidAccept(event => {
  24. const [item] = picker.selectedItems;
  25. if (item) {
  26. if (!this.isValidLineNumber(editor, item.lineNumber)) {
  27. return;
  28. }
  29. this.gotoLocation(context, { range: this.toRange(item.lineNumber, item.column), keyMods: picker.keyMods, preserveFocus: event.inBackground });
  30. if (!event.inBackground) {
  31. picker.hide();
  32. }
  33. }
  34. }));
  35. // React to picker changes
  36. const updatePickerAndEditor = () => {
  37. const position = this.parsePosition(editor, picker.value.trim().substr(AbstractGotoLineQuickAccessProvider.PREFIX.length));
  38. const label = this.getPickLabel(editor, position.lineNumber, position.column);
  39. // Picker
  40. picker.items = [{
  41. lineNumber: position.lineNumber,
  42. column: position.column,
  43. label
  44. }];
  45. // ARIA Label
  46. picker.ariaLabel = label;
  47. // Clear decorations for invalid range
  48. if (!this.isValidLineNumber(editor, position.lineNumber)) {
  49. this.clearDecorations(editor);
  50. return;
  51. }
  52. // Reveal
  53. const range = this.toRange(position.lineNumber, position.column);
  54. editor.revealRangeInCenter(range, 0 /* Smooth */);
  55. // Decorate
  56. this.addDecorations(editor, range);
  57. };
  58. updatePickerAndEditor();
  59. disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor()));
  60. // Adjust line number visibility as needed
  61. const codeEditor = getCodeEditor(editor);
  62. if (codeEditor) {
  63. const options = codeEditor.getOptions();
  64. const lineNumbers = options.get(59 /* lineNumbers */);
  65. if (lineNumbers.renderType === 2 /* Relative */) {
  66. codeEditor.updateOptions({ lineNumbers: 'on' });
  67. disposables.add(toDisposable(() => codeEditor.updateOptions({ lineNumbers: 'relative' })));
  68. }
  69. }
  70. return disposables;
  71. }
  72. toRange(lineNumber = 1, column = 1) {
  73. return {
  74. startLineNumber: lineNumber,
  75. startColumn: column,
  76. endLineNumber: lineNumber,
  77. endColumn: column
  78. };
  79. }
  80. parsePosition(editor, value) {
  81. // Support line-col formats of `line,col`, `line:col`, `line#col`
  82. const numbers = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part));
  83. const endLine = this.lineCount(editor) + 1;
  84. return {
  85. lineNumber: numbers[0] > 0 ? numbers[0] : endLine + numbers[0],
  86. column: numbers[1]
  87. };
  88. }
  89. getPickLabel(editor, lineNumber, column) {
  90. // Location valid: indicate this as picker label
  91. if (this.isValidLineNumber(editor, lineNumber)) {
  92. if (this.isValidColumn(editor, lineNumber, column)) {
  93. return localize('gotoLineColumnLabel', "Go to line {0} and character {1}.", lineNumber, column);
  94. }
  95. return localize('gotoLineLabel', "Go to line {0}.", lineNumber);
  96. }
  97. // Location invalid: show generic label
  98. const position = editor.getPosition() || { lineNumber: 1, column: 1 };
  99. const lineCount = this.lineCount(editor);
  100. if (lineCount > 1) {
  101. return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Character: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount);
  102. }
  103. return localize('gotoLineLabelEmpty', "Current Line: {0}, Character: {1}. Type a line number to navigate to.", position.lineNumber, position.column);
  104. }
  105. isValidLineNumber(editor, lineNumber) {
  106. if (!lineNumber || typeof lineNumber !== 'number') {
  107. return false;
  108. }
  109. return lineNumber > 0 && lineNumber <= this.lineCount(editor);
  110. }
  111. isValidColumn(editor, lineNumber, column) {
  112. if (!column || typeof column !== 'number') {
  113. return false;
  114. }
  115. const model = this.getModel(editor);
  116. if (!model) {
  117. return false;
  118. }
  119. const positionCandidate = { lineNumber, column };
  120. return model.validatePosition(positionCandidate).equals(positionCandidate);
  121. }
  122. lineCount(editor) {
  123. var _a, _b;
  124. return (_b = (_a = this.getModel(editor)) === null || _a === void 0 ? void 0 : _a.getLineCount()) !== null && _b !== void 0 ? _b : 0;
  125. }
  126. }
  127. AbstractGotoLineQuickAccessProvider.PREFIX = ':';