codelensController.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  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. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  15. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  16. return new (P || (P = Promise))(function (resolve, reject) {
  17. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  18. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  19. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  20. step((generator = generator.apply(thisArg, _arguments || [])).next());
  21. });
  22. };
  23. import * as dom from '../../../base/browser/dom.js';
  24. import { createCancelablePromise, disposableTimeout, RunOnceScheduler } from '../../../base/common/async.js';
  25. import { onUnexpectedError, onUnexpectedExternalError } from '../../../base/common/errors.js';
  26. import { hash } from '../../../base/common/hash.js';
  27. import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
  28. import { StableEditorScrollState } from '../../browser/core/editorState.js';
  29. import { EditorAction, registerEditorAction, registerEditorContribution } from '../../browser/editorExtensions.js';
  30. import { EDITOR_FONT_DEFAULTS } from '../../common/config/editorOptions.js';
  31. import { EditorContextKeys } from '../../common/editorContextKeys.js';
  32. import { CodeLensProviderRegistry } from '../../common/modes.js';
  33. import { LanguageFeatureRequestDelays } from '../../common/modes/languageFeatureRegistry.js';
  34. import { getCodeLensModel } from './codelens.js';
  35. import { ICodeLensCache } from './codeLensCache.js';
  36. import { CodeLensHelper, CodeLensWidget } from './codelensWidget.js';
  37. import { localize } from '../../../nls.js';
  38. import { ICommandService } from '../../../platform/commands/common/commands.js';
  39. import { INotificationService } from '../../../platform/notification/common/notification.js';
  40. import { IQuickInputService } from '../../../platform/quickinput/common/quickInput.js';
  41. let CodeLensContribution = class CodeLensContribution {
  42. constructor(_editor, _commandService, _notificationService, _codeLensCache) {
  43. this._editor = _editor;
  44. this._commandService = _commandService;
  45. this._notificationService = _notificationService;
  46. this._codeLensCache = _codeLensCache;
  47. this._disposables = new DisposableStore();
  48. this._localToDispose = new DisposableStore();
  49. this._lenses = [];
  50. this._getCodeLensModelDelays = new LanguageFeatureRequestDelays(CodeLensProviderRegistry, 250, 2500);
  51. this._oldCodeLensModels = new DisposableStore();
  52. this._resolveCodeLensesDelays = new LanguageFeatureRequestDelays(CodeLensProviderRegistry, 250, 2500);
  53. this._resolveCodeLensesScheduler = new RunOnceScheduler(() => this._resolveCodeLensesInViewport(), this._resolveCodeLensesDelays.min);
  54. this._disposables.add(this._editor.onDidChangeModel(() => this._onModelChange()));
  55. this._disposables.add(this._editor.onDidChangeModelLanguage(() => this._onModelChange()));
  56. this._disposables.add(this._editor.onDidChangeConfiguration((e) => {
  57. if (e.hasChanged(43 /* fontInfo */) || e.hasChanged(16 /* codeLensFontSize */) || e.hasChanged(15 /* codeLensFontFamily */)) {
  58. this._updateLensStyle();
  59. }
  60. if (e.hasChanged(14 /* codeLens */)) {
  61. this._onModelChange();
  62. }
  63. }));
  64. this._disposables.add(CodeLensProviderRegistry.onDidChange(this._onModelChange, this));
  65. this._onModelChange();
  66. this._styleClassName = '_' + hash(this._editor.getId()).toString(16);
  67. this._styleElement = dom.createStyleSheet(dom.isInShadowDOM(this._editor.getContainerDomNode())
  68. ? this._editor.getContainerDomNode()
  69. : undefined);
  70. this._updateLensStyle();
  71. }
  72. dispose() {
  73. var _a;
  74. this._localDispose();
  75. this._disposables.dispose();
  76. this._oldCodeLensModels.dispose();
  77. (_a = this._currentCodeLensModel) === null || _a === void 0 ? void 0 : _a.dispose();
  78. this._styleElement.remove();
  79. }
  80. _getLayoutInfo() {
  81. let fontSize = this._editor.getOption(16 /* codeLensFontSize */);
  82. let codeLensHeight;
  83. if (!fontSize || fontSize < 5) {
  84. fontSize = (this._editor.getOption(45 /* fontSize */) * .9) | 0;
  85. codeLensHeight = this._editor.getOption(58 /* lineHeight */);
  86. }
  87. else {
  88. codeLensHeight = (fontSize * Math.max(1.3, this._editor.getOption(58 /* lineHeight */) / this._editor.getOption(45 /* fontSize */))) | 0;
  89. }
  90. return { codeLensHeight, fontSize };
  91. }
  92. _updateLensStyle() {
  93. const { codeLensHeight, fontSize } = this._getLayoutInfo();
  94. const fontFamily = this._editor.getOption(15 /* codeLensFontFamily */);
  95. const editorFontInfo = this._editor.getOption(43 /* fontInfo */);
  96. const fontFamilyVar = `--codelens-font-family${this._styleClassName}`;
  97. const fontFeaturesVar = `--codelens-font-features${this._styleClassName}`;
  98. let newStyle = `
  99. .monaco-editor .codelens-decoration.${this._styleClassName} { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; padding-right: ${Math.round(fontSize * 0.5)}px; font-feature-settings: var(${fontFeaturesVar}) }
  100. .monaco-editor .codelens-decoration.${this._styleClassName} span.codicon { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; }
  101. `;
  102. if (fontFamily) {
  103. newStyle += `.monaco-editor .codelens-decoration.${this._styleClassName} { font-family: var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}}`;
  104. }
  105. this._styleElement.textContent = newStyle;
  106. this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily !== null && fontFamily !== void 0 ? fontFamily : 'inherit');
  107. this._editor.getContainerDomNode().style.setProperty(fontFeaturesVar, editorFontInfo.fontFeatureSettings);
  108. //
  109. this._editor.changeViewZones(accessor => {
  110. for (let lens of this._lenses) {
  111. lens.updateHeight(codeLensHeight, accessor);
  112. }
  113. });
  114. }
  115. _localDispose() {
  116. var _a, _b, _c;
  117. (_a = this._getCodeLensModelPromise) === null || _a === void 0 ? void 0 : _a.cancel();
  118. this._getCodeLensModelPromise = undefined;
  119. (_b = this._resolveCodeLensesPromise) === null || _b === void 0 ? void 0 : _b.cancel();
  120. this._resolveCodeLensesPromise = undefined;
  121. this._localToDispose.clear();
  122. this._oldCodeLensModels.clear();
  123. (_c = this._currentCodeLensModel) === null || _c === void 0 ? void 0 : _c.dispose();
  124. }
  125. _onModelChange() {
  126. this._localDispose();
  127. const model = this._editor.getModel();
  128. if (!model) {
  129. return;
  130. }
  131. if (!this._editor.getOption(14 /* codeLens */)) {
  132. return;
  133. }
  134. const cachedLenses = this._codeLensCache.get(model);
  135. if (cachedLenses) {
  136. this._renderCodeLensSymbols(cachedLenses);
  137. }
  138. if (!CodeLensProviderRegistry.has(model)) {
  139. // no provider -> return but check with
  140. // cached lenses. they expire after 30 seconds
  141. if (cachedLenses) {
  142. this._localToDispose.add(disposableTimeout(() => {
  143. const cachedLensesNow = this._codeLensCache.get(model);
  144. if (cachedLenses === cachedLensesNow) {
  145. this._codeLensCache.delete(model);
  146. this._onModelChange();
  147. }
  148. }, 30 * 1000));
  149. }
  150. return;
  151. }
  152. for (const provider of CodeLensProviderRegistry.all(model)) {
  153. if (typeof provider.onDidChange === 'function') {
  154. let registration = provider.onDidChange(() => scheduler.schedule());
  155. this._localToDispose.add(registration);
  156. }
  157. }
  158. const scheduler = new RunOnceScheduler(() => {
  159. var _a;
  160. const t1 = Date.now();
  161. (_a = this._getCodeLensModelPromise) === null || _a === void 0 ? void 0 : _a.cancel();
  162. this._getCodeLensModelPromise = createCancelablePromise(token => getCodeLensModel(model, token));
  163. this._getCodeLensModelPromise.then(result => {
  164. if (this._currentCodeLensModel) {
  165. this._oldCodeLensModels.add(this._currentCodeLensModel);
  166. }
  167. this._currentCodeLensModel = result;
  168. // cache model to reduce flicker
  169. this._codeLensCache.put(model, result);
  170. // update moving average
  171. const newDelay = this._getCodeLensModelDelays.update(model, Date.now() - t1);
  172. scheduler.delay = newDelay;
  173. // render lenses
  174. this._renderCodeLensSymbols(result);
  175. // dom.scheduleAtNextAnimationFrame(() => this._resolveCodeLensesInViewport());
  176. this._resolveCodeLensesInViewportSoon();
  177. }, onUnexpectedError);
  178. }, this._getCodeLensModelDelays.get(model));
  179. this._localToDispose.add(scheduler);
  180. this._localToDispose.add(toDisposable(() => this._resolveCodeLensesScheduler.cancel()));
  181. this._localToDispose.add(this._editor.onDidChangeModelContent(() => {
  182. this._editor.changeDecorations(decorationsAccessor => {
  183. this._editor.changeViewZones(viewZonesAccessor => {
  184. let toDispose = [];
  185. let lastLensLineNumber = -1;
  186. this._lenses.forEach((lens) => {
  187. if (!lens.isValid() || lastLensLineNumber === lens.getLineNumber()) {
  188. // invalid -> lens collapsed, attach range doesn't exist anymore
  189. // line_number -> lenses should never be on the same line
  190. toDispose.push(lens);
  191. }
  192. else {
  193. lens.update(viewZonesAccessor);
  194. lastLensLineNumber = lens.getLineNumber();
  195. }
  196. });
  197. let helper = new CodeLensHelper();
  198. toDispose.forEach((l) => {
  199. l.dispose(helper, viewZonesAccessor);
  200. this._lenses.splice(this._lenses.indexOf(l), 1);
  201. });
  202. helper.commit(decorationsAccessor);
  203. });
  204. });
  205. // Ask for all references again
  206. scheduler.schedule();
  207. }));
  208. this._localToDispose.add(this._editor.onDidFocusEditorWidget(() => {
  209. scheduler.schedule();
  210. }));
  211. this._localToDispose.add(this._editor.onDidScrollChange(e => {
  212. if (e.scrollTopChanged && this._lenses.length > 0) {
  213. this._resolveCodeLensesInViewportSoon();
  214. }
  215. }));
  216. this._localToDispose.add(this._editor.onDidLayoutChange(() => {
  217. this._resolveCodeLensesInViewportSoon();
  218. }));
  219. this._localToDispose.add(toDisposable(() => {
  220. if (this._editor.getModel()) {
  221. const scrollState = StableEditorScrollState.capture(this._editor);
  222. this._editor.changeDecorations(decorationsAccessor => {
  223. this._editor.changeViewZones(viewZonesAccessor => {
  224. this._disposeAllLenses(decorationsAccessor, viewZonesAccessor);
  225. });
  226. });
  227. scrollState.restore(this._editor);
  228. }
  229. else {
  230. // No accessors available
  231. this._disposeAllLenses(undefined, undefined);
  232. }
  233. }));
  234. this._localToDispose.add(this._editor.onMouseDown(e => {
  235. if (e.target.type !== 9 /* CONTENT_WIDGET */) {
  236. return;
  237. }
  238. let target = e.target.element;
  239. if ((target === null || target === void 0 ? void 0 : target.tagName) === 'SPAN') {
  240. target = target.parentElement;
  241. }
  242. if ((target === null || target === void 0 ? void 0 : target.tagName) === 'A') {
  243. for (const lens of this._lenses) {
  244. let command = lens.getCommand(target);
  245. if (command) {
  246. this._commandService.executeCommand(command.id, ...(command.arguments || [])).catch(err => this._notificationService.error(err));
  247. break;
  248. }
  249. }
  250. }
  251. }));
  252. scheduler.schedule();
  253. }
  254. _disposeAllLenses(decChangeAccessor, viewZoneChangeAccessor) {
  255. const helper = new CodeLensHelper();
  256. for (const lens of this._lenses) {
  257. lens.dispose(helper, viewZoneChangeAccessor);
  258. }
  259. if (decChangeAccessor) {
  260. helper.commit(decChangeAccessor);
  261. }
  262. this._lenses.length = 0;
  263. }
  264. _renderCodeLensSymbols(symbols) {
  265. if (!this._editor.hasModel()) {
  266. return;
  267. }
  268. let maxLineNumber = this._editor.getModel().getLineCount();
  269. let groups = [];
  270. let lastGroup;
  271. for (let symbol of symbols.lenses) {
  272. let line = symbol.symbol.range.startLineNumber;
  273. if (line < 1 || line > maxLineNumber) {
  274. // invalid code lens
  275. continue;
  276. }
  277. else if (lastGroup && lastGroup[lastGroup.length - 1].symbol.range.startLineNumber === line) {
  278. // on same line as previous
  279. lastGroup.push(symbol);
  280. }
  281. else {
  282. // on later line as previous
  283. lastGroup = [symbol];
  284. groups.push(lastGroup);
  285. }
  286. }
  287. const scrollState = StableEditorScrollState.capture(this._editor);
  288. const layoutInfo = this._getLayoutInfo();
  289. this._editor.changeDecorations(decorationsAccessor => {
  290. this._editor.changeViewZones(viewZoneAccessor => {
  291. const helper = new CodeLensHelper();
  292. let codeLensIndex = 0;
  293. let groupsIndex = 0;
  294. while (groupsIndex < groups.length && codeLensIndex < this._lenses.length) {
  295. let symbolsLineNumber = groups[groupsIndex][0].symbol.range.startLineNumber;
  296. let codeLensLineNumber = this._lenses[codeLensIndex].getLineNumber();
  297. if (codeLensLineNumber < symbolsLineNumber) {
  298. this._lenses[codeLensIndex].dispose(helper, viewZoneAccessor);
  299. this._lenses.splice(codeLensIndex, 1);
  300. }
  301. else if (codeLensLineNumber === symbolsLineNumber) {
  302. this._lenses[codeLensIndex].updateCodeLensSymbols(groups[groupsIndex], helper);
  303. groupsIndex++;
  304. codeLensIndex++;
  305. }
  306. else {
  307. this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, layoutInfo.codeLensHeight, () => this._resolveCodeLensesInViewportSoon()));
  308. codeLensIndex++;
  309. groupsIndex++;
  310. }
  311. }
  312. // Delete extra code lenses
  313. while (codeLensIndex < this._lenses.length) {
  314. this._lenses[codeLensIndex].dispose(helper, viewZoneAccessor);
  315. this._lenses.splice(codeLensIndex, 1);
  316. }
  317. // Create extra symbols
  318. while (groupsIndex < groups.length) {
  319. this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, layoutInfo.codeLensHeight, () => this._resolveCodeLensesInViewportSoon()));
  320. groupsIndex++;
  321. }
  322. helper.commit(decorationsAccessor);
  323. });
  324. });
  325. scrollState.restore(this._editor);
  326. }
  327. _resolveCodeLensesInViewportSoon() {
  328. const model = this._editor.getModel();
  329. if (model) {
  330. this._resolveCodeLensesScheduler.schedule();
  331. }
  332. }
  333. _resolveCodeLensesInViewport() {
  334. var _a;
  335. (_a = this._resolveCodeLensesPromise) === null || _a === void 0 ? void 0 : _a.cancel();
  336. this._resolveCodeLensesPromise = undefined;
  337. const model = this._editor.getModel();
  338. if (!model) {
  339. return;
  340. }
  341. const toResolve = [];
  342. const lenses = [];
  343. this._lenses.forEach((lens) => {
  344. const request = lens.computeIfNecessary(model);
  345. if (request) {
  346. toResolve.push(request);
  347. lenses.push(lens);
  348. }
  349. });
  350. if (toResolve.length === 0) {
  351. return;
  352. }
  353. const t1 = Date.now();
  354. const resolvePromise = createCancelablePromise(token => {
  355. const promises = toResolve.map((request, i) => {
  356. const resolvedSymbols = new Array(request.length);
  357. const promises = request.map((request, i) => {
  358. if (!request.symbol.command && typeof request.provider.resolveCodeLens === 'function') {
  359. return Promise.resolve(request.provider.resolveCodeLens(model, request.symbol, token)).then(symbol => {
  360. resolvedSymbols[i] = symbol;
  361. }, onUnexpectedExternalError);
  362. }
  363. else {
  364. resolvedSymbols[i] = request.symbol;
  365. return Promise.resolve(undefined);
  366. }
  367. });
  368. return Promise.all(promises).then(() => {
  369. if (!token.isCancellationRequested && !lenses[i].isDisposed()) {
  370. lenses[i].updateCommands(resolvedSymbols);
  371. }
  372. });
  373. });
  374. return Promise.all(promises);
  375. });
  376. this._resolveCodeLensesPromise = resolvePromise;
  377. this._resolveCodeLensesPromise.then(() => {
  378. // update moving average
  379. const newDelay = this._resolveCodeLensesDelays.update(model, Date.now() - t1);
  380. this._resolveCodeLensesScheduler.delay = newDelay;
  381. if (this._currentCodeLensModel) { // update the cached state with new resolved items
  382. this._codeLensCache.put(model, this._currentCodeLensModel);
  383. }
  384. this._oldCodeLensModels.clear(); // dispose old models once we have updated the UI with the current model
  385. if (resolvePromise === this._resolveCodeLensesPromise) {
  386. this._resolveCodeLensesPromise = undefined;
  387. }
  388. }, err => {
  389. onUnexpectedError(err); // can also be cancellation!
  390. if (resolvePromise === this._resolveCodeLensesPromise) {
  391. this._resolveCodeLensesPromise = undefined;
  392. }
  393. });
  394. }
  395. getLenses() {
  396. return this._lenses;
  397. }
  398. };
  399. CodeLensContribution.ID = 'css.editor.codeLens';
  400. CodeLensContribution = __decorate([
  401. __param(1, ICommandService),
  402. __param(2, INotificationService),
  403. __param(3, ICodeLensCache)
  404. ], CodeLensContribution);
  405. export { CodeLensContribution };
  406. registerEditorContribution(CodeLensContribution.ID, CodeLensContribution);
  407. registerEditorAction(class ShowLensesInCurrentLine extends EditorAction {
  408. constructor() {
  409. super({
  410. id: 'codelens.showLensesInCurrentLine',
  411. precondition: EditorContextKeys.hasCodeLensProvider,
  412. label: localize('showLensOnLine', "Show CodeLens Commands For Current Line"),
  413. alias: 'Show CodeLens Commands For Current Line',
  414. });
  415. }
  416. run(accessor, editor) {
  417. return __awaiter(this, void 0, void 0, function* () {
  418. if (!editor.hasModel()) {
  419. return;
  420. }
  421. const quickInputService = accessor.get(IQuickInputService);
  422. const commandService = accessor.get(ICommandService);
  423. const notificationService = accessor.get(INotificationService);
  424. const lineNumber = editor.getSelection().positionLineNumber;
  425. const codelensController = editor.getContribution(CodeLensContribution.ID);
  426. const items = [];
  427. for (let lens of codelensController.getLenses()) {
  428. if (lens.getLineNumber() === lineNumber) {
  429. for (let item of lens.getItems()) {
  430. const { command } = item.symbol;
  431. if (command) {
  432. items.push({
  433. label: command.title,
  434. command: command
  435. });
  436. }
  437. }
  438. }
  439. }
  440. if (items.length === 0) {
  441. // We dont want an empty picker
  442. return;
  443. }
  444. const item = yield quickInputService.pick(items, { canPickMany: false });
  445. if (!item) {
  446. // Nothing picked
  447. return;
  448. }
  449. try {
  450. yield commandService.executeCommand(item.command.id, ...(item.command.arguments || []));
  451. }
  452. catch (err) {
  453. notificationService.error(err);
  454. }
  455. });
  456. }
  457. });