b3155dd518899f04747c332472182b059adc38e23df8315b1cc38053b4fb6ce1f2e1a75a9131c18d6ac55baadea02b23d0a756a6f6c315e73b130d8fac9c28 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. import { createCancelablePromise } from '../../../../../base/common/async.js';
  15. import { onUnexpectedError } from '../../../../../base/common/errors.js';
  16. import { MarkdownString } from '../../../../../base/common/htmlContent.js';
  17. import { DisposableStore } from '../../../../../base/common/lifecycle.js';
  18. import { withNullAsUndefined } from '../../../../../base/common/types.js';
  19. import './goToDefinitionAtPosition.css';
  20. import { EditorState } from '../../../editorState/browser/editorState.js';
  21. import { registerEditorContribution } from '../../../../browser/editorExtensions.js';
  22. import { Range } from '../../../../common/core/range.js';
  23. import { ILanguageService } from '../../../../common/languages/language.js';
  24. import { ITextModelService } from '../../../../common/services/resolverService.js';
  25. import { ClickLinkGesture } from './clickLinkGesture.js';
  26. import { PeekContext } from '../../../peekView/browser/peekView.js';
  27. import * as nls from '../../../../../nls.js';
  28. import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
  29. import { editorActiveLinkForeground } from '../../../../../platform/theme/common/colorRegistry.js';
  30. import { registerThemingParticipant } from '../../../../../platform/theme/common/themeService.js';
  31. import { DefinitionAction } from '../goToCommands.js';
  32. import { getDefinitionsAtPosition } from '../goToSymbol.js';
  33. import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';
  34. let GotoDefinitionAtPositionEditorContribution = class GotoDefinitionAtPositionEditorContribution {
  35. constructor(editor, textModelResolverService, languageService, languageFeaturesService) {
  36. this.textModelResolverService = textModelResolverService;
  37. this.languageService = languageService;
  38. this.languageFeaturesService = languageFeaturesService;
  39. this.toUnhook = new DisposableStore();
  40. this.toUnhookForKeyboard = new DisposableStore();
  41. this.currentWordAtPosition = null;
  42. this.previousPromise = null;
  43. this.editor = editor;
  44. this.linkDecorations = this.editor.createDecorationsCollection();
  45. const linkGesture = new ClickLinkGesture(editor);
  46. this.toUnhook.add(linkGesture);
  47. this.toUnhook.add(linkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => {
  48. this.startFindDefinitionFromMouse(mouseEvent, withNullAsUndefined(keyboardEvent));
  49. }));
  50. this.toUnhook.add(linkGesture.onExecute((mouseEvent) => {
  51. if (this.isEnabled(mouseEvent)) {
  52. this.gotoDefinition(mouseEvent.target.position, mouseEvent.hasSideBySideModifier).then(() => {
  53. this.removeLinkDecorations();
  54. }, (error) => {
  55. this.removeLinkDecorations();
  56. onUnexpectedError(error);
  57. });
  58. }
  59. }));
  60. this.toUnhook.add(linkGesture.onCancel(() => {
  61. this.removeLinkDecorations();
  62. this.currentWordAtPosition = null;
  63. }));
  64. }
  65. static get(editor) {
  66. return editor.getContribution(GotoDefinitionAtPositionEditorContribution.ID);
  67. }
  68. startFindDefinitionFromCursor(position) {
  69. // For issue: https://github.com/microsoft/vscode/issues/46257
  70. // equivalent to mouse move with meta/ctrl key
  71. // First find the definition and add decorations
  72. // to the editor to be shown with the content hover widget
  73. return this.startFindDefinition(position).then(() => {
  74. // Add listeners for editor cursor move and key down events
  75. // Dismiss the "extended" editor decorations when the user hides
  76. // the hover widget. There is no event for the widget itself so these
  77. // serve as a best effort. After removing the link decorations, the hover
  78. // widget is clean and will only show declarations per next request.
  79. this.toUnhookForKeyboard.add(this.editor.onDidChangeCursorPosition(() => {
  80. this.currentWordAtPosition = null;
  81. this.removeLinkDecorations();
  82. this.toUnhookForKeyboard.clear();
  83. }));
  84. this.toUnhookForKeyboard.add(this.editor.onKeyDown((e) => {
  85. if (e) {
  86. this.currentWordAtPosition = null;
  87. this.removeLinkDecorations();
  88. this.toUnhookForKeyboard.clear();
  89. }
  90. }));
  91. });
  92. }
  93. startFindDefinitionFromMouse(mouseEvent, withKey) {
  94. // check if we are active and on a content widget
  95. if (mouseEvent.target.type === 9 /* MouseTargetType.CONTENT_WIDGET */ && this.linkDecorations.length > 0) {
  96. return;
  97. }
  98. if (!this.editor.hasModel() || !this.isEnabled(mouseEvent, withKey)) {
  99. this.currentWordAtPosition = null;
  100. this.removeLinkDecorations();
  101. return;
  102. }
  103. const position = mouseEvent.target.position;
  104. this.startFindDefinition(position);
  105. }
  106. startFindDefinition(position) {
  107. var _a;
  108. // Dispose listeners for updating decorations when using keyboard to show definition hover
  109. this.toUnhookForKeyboard.clear();
  110. // Find word at mouse position
  111. const word = position ? (_a = this.editor.getModel()) === null || _a === void 0 ? void 0 : _a.getWordAtPosition(position) : null;
  112. if (!word) {
  113. this.currentWordAtPosition = null;
  114. this.removeLinkDecorations();
  115. return Promise.resolve(0);
  116. }
  117. // Return early if word at position is still the same
  118. if (this.currentWordAtPosition && this.currentWordAtPosition.startColumn === word.startColumn && this.currentWordAtPosition.endColumn === word.endColumn && this.currentWordAtPosition.word === word.word) {
  119. return Promise.resolve(0);
  120. }
  121. this.currentWordAtPosition = word;
  122. // Find definition and decorate word if found
  123. const state = new EditorState(this.editor, 4 /* CodeEditorStateFlag.Position */ | 1 /* CodeEditorStateFlag.Value */ | 2 /* CodeEditorStateFlag.Selection */ | 8 /* CodeEditorStateFlag.Scroll */);
  124. if (this.previousPromise) {
  125. this.previousPromise.cancel();
  126. this.previousPromise = null;
  127. }
  128. this.previousPromise = createCancelablePromise(token => this.findDefinition(position, token));
  129. return this.previousPromise.then(results => {
  130. if (!results || !results.length || !state.validate(this.editor)) {
  131. this.removeLinkDecorations();
  132. return;
  133. }
  134. // Multiple results
  135. if (results.length > 1) {
  136. this.addDecoration(new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), new MarkdownString().appendText(nls.localize('multipleResults', "Click to show {0} definitions.", results.length)));
  137. }
  138. // Single result
  139. else {
  140. const result = results[0];
  141. if (!result.uri) {
  142. return;
  143. }
  144. this.textModelResolverService.createModelReference(result.uri).then(ref => {
  145. if (!ref.object || !ref.object.textEditorModel) {
  146. ref.dispose();
  147. return;
  148. }
  149. const { object: { textEditorModel } } = ref;
  150. const { startLineNumber } = result.range;
  151. if (startLineNumber < 1 || startLineNumber > textEditorModel.getLineCount()) {
  152. // invalid range
  153. ref.dispose();
  154. return;
  155. }
  156. const previewValue = this.getPreviewValue(textEditorModel, startLineNumber, result);
  157. let wordRange;
  158. if (result.originSelectionRange) {
  159. wordRange = Range.lift(result.originSelectionRange);
  160. }
  161. else {
  162. wordRange = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
  163. }
  164. const languageId = this.languageService.guessLanguageIdByFilepathOrFirstLine(textEditorModel.uri);
  165. this.addDecoration(wordRange, new MarkdownString().appendCodeblock(languageId ? languageId : '', previewValue));
  166. ref.dispose();
  167. });
  168. }
  169. }).then(undefined, onUnexpectedError);
  170. }
  171. getPreviewValue(textEditorModel, startLineNumber, result) {
  172. let rangeToUse = result.range;
  173. const numberOfLinesInRange = rangeToUse.endLineNumber - rangeToUse.startLineNumber;
  174. if (numberOfLinesInRange >= GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES) {
  175. rangeToUse = this.getPreviewRangeBasedOnIndentation(textEditorModel, startLineNumber);
  176. }
  177. const previewValue = this.stripIndentationFromPreviewRange(textEditorModel, startLineNumber, rangeToUse);
  178. return previewValue;
  179. }
  180. stripIndentationFromPreviewRange(textEditorModel, startLineNumber, previewRange) {
  181. const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber);
  182. let minIndent = startIndent;
  183. for (let endLineNumber = startLineNumber + 1; endLineNumber < previewRange.endLineNumber; endLineNumber++) {
  184. const endIndent = textEditorModel.getLineFirstNonWhitespaceColumn(endLineNumber);
  185. minIndent = Math.min(minIndent, endIndent);
  186. }
  187. const previewValue = textEditorModel.getValueInRange(previewRange).replace(new RegExp(`^\\s{${minIndent - 1}}`, 'gm'), '').trim();
  188. return previewValue;
  189. }
  190. getPreviewRangeBasedOnIndentation(textEditorModel, startLineNumber) {
  191. const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber);
  192. const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES);
  193. let endLineNumber = startLineNumber + 1;
  194. for (; endLineNumber < maxLineNumber; endLineNumber++) {
  195. const endIndent = textEditorModel.getLineFirstNonWhitespaceColumn(endLineNumber);
  196. if (startIndent === endIndent) {
  197. break;
  198. }
  199. }
  200. return new Range(startLineNumber, 1, endLineNumber + 1, 1);
  201. }
  202. addDecoration(range, hoverMessage) {
  203. const newDecorations = {
  204. range: range,
  205. options: {
  206. description: 'goto-definition-link',
  207. inlineClassName: 'goto-definition-link',
  208. hoverMessage
  209. }
  210. };
  211. this.linkDecorations.set([newDecorations]);
  212. }
  213. removeLinkDecorations() {
  214. this.linkDecorations.clear();
  215. }
  216. isEnabled(mouseEvent, withKey) {
  217. return this.editor.hasModel() &&
  218. mouseEvent.isNoneOrSingleMouseDown &&
  219. (mouseEvent.target.type === 6 /* MouseTargetType.CONTENT_TEXT */) &&
  220. (mouseEvent.hasTriggerModifier || (withKey ? withKey.keyCodeIsTriggerKey : false)) &&
  221. this.languageFeaturesService.definitionProvider.has(this.editor.getModel());
  222. }
  223. findDefinition(position, token) {
  224. const model = this.editor.getModel();
  225. if (!model) {
  226. return Promise.resolve(null);
  227. }
  228. return getDefinitionsAtPosition(this.languageFeaturesService.definitionProvider, model, position, token);
  229. }
  230. gotoDefinition(position, openToSide) {
  231. this.editor.setPosition(position);
  232. return this.editor.invokeWithinContext((accessor) => {
  233. const canPeek = !openToSide && this.editor.getOption(80 /* EditorOption.definitionLinkOpensInPeek */) && !this.isInPeekEditor(accessor);
  234. const action = new DefinitionAction({ openToSide, openInPeek: canPeek, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined });
  235. return action.run(accessor, this.editor);
  236. });
  237. }
  238. isInPeekEditor(accessor) {
  239. const contextKeyService = accessor.get(IContextKeyService);
  240. return PeekContext.inPeekEditor.getValue(contextKeyService);
  241. }
  242. dispose() {
  243. this.toUnhook.dispose();
  244. }
  245. };
  246. GotoDefinitionAtPositionEditorContribution.ID = 'editor.contrib.gotodefinitionatposition';
  247. GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES = 8;
  248. GotoDefinitionAtPositionEditorContribution = __decorate([
  249. __param(1, ITextModelService),
  250. __param(2, ILanguageService),
  251. __param(3, ILanguageFeaturesService)
  252. ], GotoDefinitionAtPositionEditorContribution);
  253. export { GotoDefinitionAtPositionEditorContribution };
  254. registerEditorContribution(GotoDefinitionAtPositionEditorContribution.ID, GotoDefinitionAtPositionEditorContribution);
  255. registerThemingParticipant((theme, collector) => {
  256. const activeLinkForeground = theme.getColor(editorActiveLinkForeground);
  257. if (activeLinkForeground) {
  258. collector.addRule(`.monaco-editor .goto-definition-link { color: ${activeLinkForeground} !important; }`);
  259. }
  260. });