f7aef9dc9289cb309de248124cb555365bd0c510cbb65a61eb482baf285008ffc4c7ee41b7304bddfceaf82c88323ac5f09acf92954f3609d05f75f9827f89 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  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 { createCancelablePromise, RunOnceScheduler } from '../../../../base/common/async.js';
  24. import { CancellationToken } from '../../../../base/common/cancellation.js';
  25. import { onUnexpectedError, onUnexpectedExternalError } from '../../../../base/common/errors.js';
  26. import { Emitter } from '../../../../base/common/event.js';
  27. import { Disposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
  28. import { CoreEditingCommands } from '../../../browser/coreCommands.js';
  29. import { EditOperation } from '../../../common/core/editOperation.js';
  30. import { Range } from '../../../common/core/range.js';
  31. import { InlineCompletionTriggerKind } from '../../../common/languages.js';
  32. import { BaseGhostTextWidgetModel, GhostTextReplacement } from './ghostText.js';
  33. import { ICommandService } from '../../../../platform/commands/common/commands.js';
  34. import { inlineSuggestCommitId } from './consts.js';
  35. import { inlineCompletionToGhostText } from './inlineCompletionToGhostText.js';
  36. import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
  37. import { fixBracketsInLine } from '../../../common/model/bracketPairsTextModelPart/fixBrackets.js';
  38. import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
  39. import { ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js';
  40. import { SnippetParser } from '../../snippet/browser/snippetParser.js';
  41. import { SnippetController2 } from '../../snippet/browser/snippetController2.js';
  42. import { assertNever } from '../../../../base/common/types.js';
  43. import { matchesSubString } from '../../../../base/common/filters.js';
  44. import { getReadonlyEmptyArray } from './utils.js';
  45. import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
  46. let InlineCompletionsModel = class InlineCompletionsModel extends Disposable {
  47. constructor(editor, cache, commandService, languageConfigurationService, languageFeaturesService, debounceService, configurationService) {
  48. super();
  49. this.editor = editor;
  50. this.cache = cache;
  51. this.commandService = commandService;
  52. this.languageConfigurationService = languageConfigurationService;
  53. this.languageFeaturesService = languageFeaturesService;
  54. this.debounceService = debounceService;
  55. this.onDidChangeEmitter = new Emitter();
  56. this.onDidChange = this.onDidChangeEmitter.event;
  57. this.completionSession = this._register(new MutableDisposable());
  58. this.active = false;
  59. this.disposed = false;
  60. this.debounceValue = this.debounceService.for(this.languageFeaturesService.inlineCompletionsProvider, 'InlineCompletionsDebounce', { min: 50, max: 50 });
  61. this._register(commandService.onDidExecuteCommand((e) => {
  62. // These commands don't trigger onDidType.
  63. const commands = new Set([
  64. CoreEditingCommands.Tab.id,
  65. CoreEditingCommands.DeleteLeft.id,
  66. CoreEditingCommands.DeleteRight.id,
  67. inlineSuggestCommitId,
  68. 'acceptSelectedSuggestion',
  69. ]);
  70. if (commands.has(e.commandId) && editor.hasTextFocus()) {
  71. this.handleUserInput();
  72. }
  73. }));
  74. this._register(this.editor.onDidType((e) => {
  75. this.handleUserInput();
  76. }));
  77. this._register(this.editor.onDidChangeCursorPosition((e) => {
  78. if (e.reason === 3 /* CursorChangeReason.Explicit */ ||
  79. this.session && !this.session.isValid) {
  80. this.hide();
  81. }
  82. }));
  83. this._register(toDisposable(() => {
  84. this.disposed = true;
  85. }));
  86. this._register(this.editor.onDidBlurEditorWidget(() => {
  87. // This is a hidden setting very useful for debugging
  88. if (configurationService.getValue('editor.inlineSuggest.hideOnBlur')) {
  89. return;
  90. }
  91. this.hide();
  92. }));
  93. }
  94. handleUserInput() {
  95. if (this.session && !this.session.isValid) {
  96. this.hide();
  97. }
  98. setTimeout(() => {
  99. if (this.disposed) {
  100. return;
  101. }
  102. // Wait for the cursor update that happens in the same iteration loop iteration
  103. this.startSessionIfTriggered();
  104. }, 0);
  105. }
  106. get session() {
  107. return this.completionSession.value;
  108. }
  109. get ghostText() {
  110. var _a;
  111. return (_a = this.session) === null || _a === void 0 ? void 0 : _a.ghostText;
  112. }
  113. get minReservedLineCount() {
  114. return this.session ? this.session.minReservedLineCount : 0;
  115. }
  116. setExpanded(expanded) {
  117. var _a;
  118. (_a = this.session) === null || _a === void 0 ? void 0 : _a.setExpanded(expanded);
  119. }
  120. setActive(active) {
  121. var _a;
  122. this.active = active;
  123. if (active) {
  124. (_a = this.session) === null || _a === void 0 ? void 0 : _a.scheduleAutomaticUpdate();
  125. }
  126. }
  127. startSessionIfTriggered() {
  128. const suggestOptions = this.editor.getOption(57 /* EditorOption.inlineSuggest */);
  129. if (!suggestOptions.enabled) {
  130. return;
  131. }
  132. if (this.session && this.session.isValid) {
  133. return;
  134. }
  135. this.trigger(InlineCompletionTriggerKind.Automatic);
  136. }
  137. trigger(triggerKind) {
  138. if (this.completionSession.value) {
  139. if (triggerKind === InlineCompletionTriggerKind.Explicit) {
  140. void this.completionSession.value.ensureUpdateWithExplicitContext();
  141. }
  142. return;
  143. }
  144. this.completionSession.value = new InlineCompletionsSession(this.editor, this.editor.getPosition(), () => this.active, this.commandService, this.cache, triggerKind, this.languageConfigurationService, this.languageFeaturesService.inlineCompletionsProvider, this.debounceValue);
  145. this.completionSession.value.takeOwnership(this.completionSession.value.onDidChange(() => {
  146. this.onDidChangeEmitter.fire();
  147. }));
  148. }
  149. hide() {
  150. this.completionSession.clear();
  151. this.onDidChangeEmitter.fire();
  152. }
  153. commitCurrentSuggestion() {
  154. var _a;
  155. // Don't dispose the session, so that after committing, more suggestions are shown.
  156. (_a = this.session) === null || _a === void 0 ? void 0 : _a.commitCurrentCompletion();
  157. }
  158. showNext() {
  159. var _a;
  160. (_a = this.session) === null || _a === void 0 ? void 0 : _a.showNextInlineCompletion();
  161. }
  162. showPrevious() {
  163. var _a;
  164. (_a = this.session) === null || _a === void 0 ? void 0 : _a.showPreviousInlineCompletion();
  165. }
  166. hasMultipleInlineCompletions() {
  167. var _a;
  168. return __awaiter(this, void 0, void 0, function* () {
  169. const result = yield ((_a = this.session) === null || _a === void 0 ? void 0 : _a.hasMultipleInlineCompletions());
  170. return result !== undefined ? result : false;
  171. });
  172. }
  173. };
  174. InlineCompletionsModel = __decorate([
  175. __param(2, ICommandService),
  176. __param(3, ILanguageConfigurationService),
  177. __param(4, ILanguageFeaturesService),
  178. __param(5, ILanguageFeatureDebounceService),
  179. __param(6, IConfigurationService)
  180. ], InlineCompletionsModel);
  181. export { InlineCompletionsModel };
  182. export class InlineCompletionsSession extends BaseGhostTextWidgetModel {
  183. constructor(editor, triggerPosition, shouldUpdate, commandService, cache, initialTriggerKind, languageConfigurationService, registry, debounce) {
  184. super(editor);
  185. this.triggerPosition = triggerPosition;
  186. this.shouldUpdate = shouldUpdate;
  187. this.commandService = commandService;
  188. this.cache = cache;
  189. this.initialTriggerKind = initialTriggerKind;
  190. this.languageConfigurationService = languageConfigurationService;
  191. this.registry = registry;
  192. this.debounce = debounce;
  193. this.minReservedLineCount = 0;
  194. this.updateOperation = this._register(new MutableDisposable());
  195. this.updateSoon = this._register(new RunOnceScheduler(() => {
  196. const triggerKind = this.initialTriggerKind;
  197. // All subsequent triggers are automatic.
  198. this.initialTriggerKind = InlineCompletionTriggerKind.Automatic;
  199. return this.update(triggerKind);
  200. }, 50));
  201. this.filteredCompletions = [];
  202. //#region Selection
  203. // We use a semantic id to track the selection even if the cache changes.
  204. this.currentlySelectedCompletionId = undefined;
  205. let lastCompletionItem = undefined;
  206. this._register(this.onDidChange(() => {
  207. var _a;
  208. const currentCompletion = this.currentCompletion;
  209. if (currentCompletion && currentCompletion.sourceInlineCompletion !== lastCompletionItem) {
  210. lastCompletionItem = currentCompletion.sourceInlineCompletion;
  211. const provider = currentCompletion.sourceProvider;
  212. (_a = provider.handleItemDidShow) === null || _a === void 0 ? void 0 : _a.call(provider, currentCompletion.sourceInlineCompletions, lastCompletionItem);
  213. }
  214. }));
  215. this._register(toDisposable(() => {
  216. this.cache.clear();
  217. }));
  218. this._register(this.editor.onDidChangeCursorPosition((e) => {
  219. var _a;
  220. if (e.reason === 3 /* CursorChangeReason.Explicit */) {
  221. return;
  222. }
  223. // Ghost text depends on the cursor position
  224. (_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.updateRanges();
  225. if (this.cache.value) {
  226. this.updateFilteredInlineCompletions();
  227. this.onDidChangeEmitter.fire();
  228. }
  229. }));
  230. this._register(this.editor.onDidChangeModelContent((e) => {
  231. var _a;
  232. // Call this in case `onDidChangeModelContent` calls us first.
  233. (_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.updateRanges();
  234. this.updateFilteredInlineCompletions();
  235. this.scheduleAutomaticUpdate();
  236. }));
  237. this._register(this.registry.onDidChange(() => {
  238. this.updateSoon.schedule(this.debounce.get(this.editor.getModel()));
  239. }));
  240. this.scheduleAutomaticUpdate();
  241. }
  242. updateFilteredInlineCompletions() {
  243. if (!this.cache.value) {
  244. this.filteredCompletions = [];
  245. return;
  246. }
  247. const model = this.editor.getModel();
  248. const cursorPosition = model.validatePosition(this.editor.getPosition());
  249. this.filteredCompletions = this.cache.value.completions.filter(c => {
  250. const originalValue = model.getValueInRange(c.synchronizedRange).toLowerCase();
  251. const filterText = c.inlineCompletion.filterText.toLowerCase();
  252. const indent = model.getLineIndentColumn(c.synchronizedRange.startLineNumber);
  253. const cursorPosIndex = Math.max(0, cursorPosition.column - c.synchronizedRange.startColumn);
  254. let filterTextBefore = filterText.substring(0, cursorPosIndex);
  255. let filterTextAfter = filterText.substring(cursorPosIndex);
  256. let originalValueBefore = originalValue.substring(0, cursorPosIndex);
  257. let originalValueAfter = originalValue.substring(cursorPosIndex);
  258. if (c.synchronizedRange.startColumn <= indent) {
  259. // Remove indentation
  260. originalValueBefore = originalValueBefore.trimStart();
  261. if (originalValueBefore.length === 0) {
  262. originalValueAfter = originalValueAfter.trimStart();
  263. }
  264. filterTextBefore = filterTextBefore.trimStart();
  265. if (filterTextBefore.length === 0) {
  266. filterTextAfter = filterTextAfter.trimStart();
  267. }
  268. }
  269. return filterTextBefore.startsWith(originalValueBefore)
  270. && matchesSubString(originalValueAfter, filterTextAfter);
  271. });
  272. }
  273. fixAndGetIndexOfCurrentSelection() {
  274. if (!this.currentlySelectedCompletionId || !this.cache.value) {
  275. return 0;
  276. }
  277. if (this.cache.value.completions.length === 0) {
  278. // don't reset the selection in this case
  279. return 0;
  280. }
  281. const idx = this.filteredCompletions.findIndex(v => v.semanticId === this.currentlySelectedCompletionId);
  282. if (idx === -1) {
  283. // Reset the selection so that the selection does not jump back when it appears again
  284. this.currentlySelectedCompletionId = undefined;
  285. return 0;
  286. }
  287. return idx;
  288. }
  289. get currentCachedCompletion() {
  290. if (!this.cache.value) {
  291. return undefined;
  292. }
  293. return this.filteredCompletions[this.fixAndGetIndexOfCurrentSelection()];
  294. }
  295. showNextInlineCompletion() {
  296. return __awaiter(this, void 0, void 0, function* () {
  297. yield this.ensureUpdateWithExplicitContext();
  298. const completions = this.filteredCompletions || [];
  299. if (completions.length > 0) {
  300. const newIdx = (this.fixAndGetIndexOfCurrentSelection() + 1) % completions.length;
  301. this.currentlySelectedCompletionId = completions[newIdx].semanticId;
  302. }
  303. else {
  304. this.currentlySelectedCompletionId = undefined;
  305. }
  306. this.onDidChangeEmitter.fire();
  307. });
  308. }
  309. showPreviousInlineCompletion() {
  310. return __awaiter(this, void 0, void 0, function* () {
  311. yield this.ensureUpdateWithExplicitContext();
  312. const completions = this.filteredCompletions || [];
  313. if (completions.length > 0) {
  314. const newIdx = (this.fixAndGetIndexOfCurrentSelection() + completions.length - 1) % completions.length;
  315. this.currentlySelectedCompletionId = completions[newIdx].semanticId;
  316. }
  317. else {
  318. this.currentlySelectedCompletionId = undefined;
  319. }
  320. this.onDidChangeEmitter.fire();
  321. });
  322. }
  323. ensureUpdateWithExplicitContext() {
  324. var _a;
  325. return __awaiter(this, void 0, void 0, function* () {
  326. if (this.updateOperation.value) {
  327. // Restart or wait for current update operation
  328. if (this.updateOperation.value.triggerKind === InlineCompletionTriggerKind.Explicit) {
  329. yield this.updateOperation.value.promise;
  330. }
  331. else {
  332. yield this.update(InlineCompletionTriggerKind.Explicit);
  333. }
  334. }
  335. else if (((_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.triggerKind) !== InlineCompletionTriggerKind.Explicit) {
  336. // Refresh cache
  337. yield this.update(InlineCompletionTriggerKind.Explicit);
  338. }
  339. });
  340. }
  341. hasMultipleInlineCompletions() {
  342. var _a;
  343. return __awaiter(this, void 0, void 0, function* () {
  344. yield this.ensureUpdateWithExplicitContext();
  345. return (((_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.completions.length) || 0) > 1;
  346. });
  347. }
  348. //#endregion
  349. get ghostText() {
  350. const currentCompletion = this.currentCompletion;
  351. if (!currentCompletion) {
  352. return undefined;
  353. }
  354. const cursorPosition = this.editor.getPosition();
  355. if (currentCompletion.range.getEndPosition().isBefore(cursorPosition)) {
  356. return undefined;
  357. }
  358. const mode = this.editor.getOptions().get(57 /* EditorOption.inlineSuggest */).mode;
  359. const ghostText = inlineCompletionToGhostText(currentCompletion, this.editor.getModel(), mode, cursorPosition);
  360. if (ghostText) {
  361. if (ghostText.isEmpty()) {
  362. return undefined;
  363. }
  364. return ghostText;
  365. }
  366. return new GhostTextReplacement(currentCompletion.range.startLineNumber, currentCompletion.range.startColumn, currentCompletion.range.endColumn - currentCompletion.range.startColumn, currentCompletion.insertText.split('\n'), 0);
  367. }
  368. get currentCompletion() {
  369. const completion = this.currentCachedCompletion;
  370. if (!completion) {
  371. return undefined;
  372. }
  373. return completion.toLiveInlineCompletion();
  374. }
  375. get isValid() {
  376. return this.editor.getPosition().lineNumber === this.triggerPosition.lineNumber;
  377. }
  378. scheduleAutomaticUpdate() {
  379. // Since updateSoon debounces, starvation can happen.
  380. // To prevent stale cache, we clear the current update operation.
  381. this.updateOperation.clear();
  382. this.updateSoon.schedule(this.debounce.get(this.editor.getModel()));
  383. }
  384. update(triggerKind) {
  385. return __awaiter(this, void 0, void 0, function* () {
  386. if (!this.shouldUpdate()) {
  387. return;
  388. }
  389. const position = this.editor.getPosition();
  390. const startTime = new Date();
  391. const promise = createCancelablePromise((token) => __awaiter(this, void 0, void 0, function* () {
  392. let result;
  393. try {
  394. result = yield provideInlineCompletions(this.registry, position, this.editor.getModel(), { triggerKind, selectedSuggestionInfo: undefined }, token, this.languageConfigurationService);
  395. const endTime = new Date();
  396. this.debounce.update(this.editor.getModel(), endTime.getTime() - startTime.getTime());
  397. }
  398. catch (e) {
  399. onUnexpectedError(e);
  400. return;
  401. }
  402. if (token.isCancellationRequested) {
  403. return;
  404. }
  405. this.cache.setValue(this.editor, result, triggerKind);
  406. this.updateFilteredInlineCompletions();
  407. this.onDidChangeEmitter.fire();
  408. }));
  409. const operation = new UpdateOperation(promise, triggerKind);
  410. this.updateOperation.value = operation;
  411. yield promise;
  412. if (this.updateOperation.value === operation) {
  413. this.updateOperation.clear();
  414. }
  415. });
  416. }
  417. takeOwnership(disposable) {
  418. this._register(disposable);
  419. }
  420. commitCurrentCompletion() {
  421. const ghostText = this.ghostText;
  422. if (!ghostText) {
  423. // No ghost text was shown for this completion.
  424. // Thus, we don't want to commit anything.
  425. return;
  426. }
  427. const completion = this.currentCompletion;
  428. if (completion) {
  429. this.commit(completion);
  430. }
  431. }
  432. commit(completion) {
  433. var _a;
  434. // Mark the cache as stale, but don't dispose it yet,
  435. // otherwise command args might get disposed.
  436. const cache = this.cache.clearAndLeak();
  437. if (completion.snippetInfo) {
  438. this.editor.executeEdits('inlineSuggestion.accept', [
  439. EditOperation.replaceMove(completion.range, ''),
  440. ...completion.additionalTextEdits
  441. ]);
  442. this.editor.setPosition(completion.snippetInfo.range.getStartPosition());
  443. (_a = SnippetController2.get(this.editor)) === null || _a === void 0 ? void 0 : _a.insert(completion.snippetInfo.snippet);
  444. }
  445. else {
  446. this.editor.executeEdits('inlineSuggestion.accept', [
  447. EditOperation.replaceMove(completion.range, completion.insertText),
  448. ...completion.additionalTextEdits
  449. ]);
  450. }
  451. if (completion.command) {
  452. this.commandService
  453. .executeCommand(completion.command.id, ...(completion.command.arguments || []))
  454. .finally(() => {
  455. cache === null || cache === void 0 ? void 0 : cache.dispose();
  456. })
  457. .then(undefined, onUnexpectedExternalError);
  458. }
  459. else {
  460. cache === null || cache === void 0 ? void 0 : cache.dispose();
  461. }
  462. this.onDidChangeEmitter.fire();
  463. }
  464. get commands() {
  465. var _a;
  466. const lists = new Set(((_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.completions.map(c => c.inlineCompletion.sourceInlineCompletions)) || []);
  467. return [...lists].flatMap(l => l.commands || []);
  468. }
  469. }
  470. export class UpdateOperation {
  471. constructor(promise, triggerKind) {
  472. this.promise = promise;
  473. this.triggerKind = triggerKind;
  474. }
  475. dispose() {
  476. this.promise.cancel();
  477. }
  478. }
  479. /**
  480. * The cache keeps itself in sync with the editor.
  481. * It also owns the completions result and disposes it when the cache is diposed.
  482. */
  483. export class SynchronizedInlineCompletionsCache extends Disposable {
  484. constructor(completionsSource, editor, onChange, triggerKind) {
  485. super();
  486. this.editor = editor;
  487. this.onChange = onChange;
  488. this.triggerKind = triggerKind;
  489. this.isDisposing = false;
  490. const decorationIds = editor.changeDecorations((changeAccessor) => {
  491. return changeAccessor.deltaDecorations([], completionsSource.items.map(i => ({
  492. range: i.range,
  493. options: {
  494. description: 'inline-completion-tracking-range'
  495. },
  496. })));
  497. });
  498. this._register(toDisposable(() => {
  499. this.isDisposing = true;
  500. editor.removeDecorations(decorationIds);
  501. }));
  502. this.completions = completionsSource.items.map((c, idx) => new CachedInlineCompletion(c, decorationIds[idx]));
  503. this._register(editor.onDidChangeModelContent(() => {
  504. this.updateRanges();
  505. }));
  506. this._register(completionsSource);
  507. }
  508. updateRanges() {
  509. if (this.isDisposing) {
  510. return;
  511. }
  512. let hasChanged = false;
  513. const model = this.editor.getModel();
  514. for (const c of this.completions) {
  515. const newRange = model.getDecorationRange(c.decorationId);
  516. if (!newRange) {
  517. onUnexpectedError(new Error('Decoration has no range'));
  518. continue;
  519. }
  520. if (!c.synchronizedRange.equalsRange(newRange)) {
  521. hasChanged = true;
  522. c.synchronizedRange = newRange;
  523. }
  524. }
  525. if (hasChanged) {
  526. this.onChange();
  527. }
  528. }
  529. }
  530. class CachedInlineCompletion {
  531. constructor(inlineCompletion, decorationId) {
  532. this.inlineCompletion = inlineCompletion;
  533. this.decorationId = decorationId;
  534. this.semanticId = JSON.stringify({
  535. text: this.inlineCompletion.insertText,
  536. abbreviation: this.inlineCompletion.filterText,
  537. startLine: this.inlineCompletion.range.startLineNumber,
  538. startColumn: this.inlineCompletion.range.startColumn,
  539. command: this.inlineCompletion.command
  540. });
  541. this.synchronizedRange = inlineCompletion.range;
  542. }
  543. toLiveInlineCompletion() {
  544. return {
  545. insertText: this.inlineCompletion.insertText,
  546. range: this.synchronizedRange,
  547. command: this.inlineCompletion.command,
  548. sourceProvider: this.inlineCompletion.sourceProvider,
  549. sourceInlineCompletions: this.inlineCompletion.sourceInlineCompletions,
  550. sourceInlineCompletion: this.inlineCompletion.sourceInlineCompletion,
  551. snippetInfo: this.inlineCompletion.snippetInfo,
  552. filterText: this.inlineCompletion.filterText,
  553. additionalTextEdits: this.inlineCompletion.additionalTextEdits,
  554. };
  555. }
  556. }
  557. export function provideInlineCompletions(registry, position, model, context, token = CancellationToken.None, languageConfigurationService) {
  558. return __awaiter(this, void 0, void 0, function* () {
  559. const defaultReplaceRange = getDefaultRange(position, model);
  560. const providers = registry.all(model);
  561. const results = yield Promise.all(providers.map((provider) => __awaiter(this, void 0, void 0, function* () {
  562. const completions = yield Promise.resolve(provider.provideInlineCompletions(model, position, context, token)).catch(onUnexpectedExternalError);
  563. return ({
  564. completions,
  565. provider,
  566. dispose: () => {
  567. if (completions) {
  568. provider.freeInlineCompletions(completions);
  569. }
  570. }
  571. });
  572. })));
  573. const itemsByHash = new Map();
  574. for (const result of results) {
  575. const completions = result.completions;
  576. if (!completions) {
  577. continue;
  578. }
  579. for (const item of completions.items) {
  580. let range = item.range ? Range.lift(item.range) : defaultReplaceRange;
  581. if (range.startLineNumber !== range.endLineNumber) {
  582. // Ignore invalid ranges.
  583. continue;
  584. }
  585. let insertText;
  586. let snippetInfo;
  587. if (typeof item.insertText === 'string') {
  588. insertText = item.insertText;
  589. if (languageConfigurationService && item.completeBracketPairs) {
  590. insertText = closeBrackets(insertText, range.getStartPosition(), model, languageConfigurationService);
  591. // Modify range depending on if brackets are added or removed
  592. const diff = insertText.length - item.insertText.length;
  593. if (diff !== 0) {
  594. range = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn + diff);
  595. }
  596. }
  597. snippetInfo = undefined;
  598. }
  599. else if ('snippet' in item.insertText) {
  600. const snippet = new SnippetParser().parse(item.insertText.snippet);
  601. insertText = snippet.toString();
  602. snippetInfo = {
  603. snippet: item.insertText.snippet,
  604. range: range
  605. };
  606. }
  607. else {
  608. assertNever(item.insertText);
  609. }
  610. const trackedItem = ({
  611. insertText,
  612. snippetInfo,
  613. range,
  614. command: item.command,
  615. sourceProvider: result.provider,
  616. sourceInlineCompletions: completions,
  617. sourceInlineCompletion: item,
  618. filterText: item.filterText || insertText,
  619. additionalTextEdits: item.additionalTextEdits || getReadonlyEmptyArray()
  620. });
  621. itemsByHash.set(JSON.stringify({ insertText, range: item.range }), trackedItem);
  622. }
  623. }
  624. return {
  625. items: [...itemsByHash.values()],
  626. dispose: () => {
  627. for (const result of results) {
  628. result.dispose();
  629. }
  630. },
  631. };
  632. });
  633. }
  634. function getDefaultRange(position, model) {
  635. const word = model.getWordAtPosition(position);
  636. const maxColumn = model.getLineMaxColumn(position.lineNumber);
  637. // By default, always replace up until the end of the current line.
  638. // This default might be subject to change!
  639. return word
  640. ? new Range(position.lineNumber, word.startColumn, position.lineNumber, maxColumn)
  641. : Range.fromPositions(position, position.with(undefined, maxColumn));
  642. }
  643. function closeBrackets(text, position, model, languageConfigurationService) {
  644. const lineStart = model.getLineContent(position.lineNumber).substring(0, position.column - 1);
  645. const newLine = lineStart + text;
  646. const newTokens = model.tokenization.tokenizeLineWithEdit(position, newLine.length - (position.column - 1), text);
  647. const slicedTokens = newTokens === null || newTokens === void 0 ? void 0 : newTokens.sliceAndInflate(position.column - 1, newLine.length, 0);
  648. if (!slicedTokens) {
  649. return text;
  650. }
  651. const newText = fixBracketsInLine(slicedTokens, languageConfigurationService);
  652. return newText;
  653. }