suggestWidgetPreviewModel.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  6. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  7. return new (P || (P = Promise))(function (resolve, reject) {
  8. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  9. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  10. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  11. step((generator = generator.apply(thisArg, _arguments || [])).next());
  12. });
  13. };
  14. import { createCancelablePromise, RunOnceScheduler } from '../../../base/common/async.js';
  15. import { onUnexpectedError } from '../../../base/common/errors.js';
  16. import { MutableDisposable, toDisposable } from '../../../base/common/lifecycle.js';
  17. import { InlineCompletionTriggerKind } from '../../common/modes.js';
  18. import { BaseGhostTextWidgetModel, GhostText } from './ghostText.js';
  19. import { minimizeInlineCompletion, provideInlineCompletions, UpdateOperation } from './inlineCompletionsModel.js';
  20. import { inlineCompletionToGhostText } from './inlineCompletionToGhostText.js';
  21. import { SuggestWidgetInlineCompletionProvider } from './suggestWidgetInlineCompletionProvider.js';
  22. export class SuggestWidgetPreviewModel extends BaseGhostTextWidgetModel {
  23. constructor(editor, cache) {
  24. super(editor);
  25. this.cache = cache;
  26. this.suggestionInlineCompletionSource = this._register(new SuggestWidgetInlineCompletionProvider(this.editor,
  27. // Use the first cache item (if any) as preselection.
  28. () => { var _a, _b; return (_b = (_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.completions[0]) === null || _b === void 0 ? void 0 : _b.toLiveInlineCompletion(); }));
  29. this.updateOperation = this._register(new MutableDisposable());
  30. this.updateCacheSoon = this._register(new RunOnceScheduler(() => this.updateCache(), 50));
  31. this.minReservedLineCount = 0;
  32. this._register(this.suggestionInlineCompletionSource.onDidChange(() => {
  33. this.updateCacheSoon.schedule();
  34. const suggestWidgetState = this.suggestionInlineCompletionSource.state;
  35. if (!suggestWidgetState) {
  36. this.minReservedLineCount = 0;
  37. }
  38. const newGhostText = this.ghostText;
  39. if (newGhostText) {
  40. this.minReservedLineCount = Math.max(this.minReservedLineCount, sum(newGhostText.parts.map(p => p.lines.length - 1)));
  41. }
  42. if (this.minReservedLineCount >= 1) {
  43. this.suggestionInlineCompletionSource.forceRenderingAbove();
  44. }
  45. else {
  46. this.suggestionInlineCompletionSource.stopForceRenderingAbove();
  47. }
  48. this.onDidChangeEmitter.fire();
  49. }));
  50. this._register(this.cache.onDidChange(() => {
  51. this.onDidChangeEmitter.fire();
  52. }));
  53. this._register(this.editor.onDidChangeCursorPosition((e) => {
  54. this.minReservedLineCount = 0;
  55. this.updateCacheSoon.schedule();
  56. this.onDidChangeEmitter.fire();
  57. }));
  58. this._register(toDisposable(() => this.suggestionInlineCompletionSource.stopForceRenderingAbove()));
  59. }
  60. get isActive() {
  61. return this.suggestionInlineCompletionSource.state !== undefined;
  62. }
  63. isSuggestionPreviewEnabled() {
  64. const suggestOptions = this.editor.getOption(105 /* suggest */);
  65. return suggestOptions.preview;
  66. }
  67. updateCache() {
  68. return __awaiter(this, void 0, void 0, function* () {
  69. const state = this.suggestionInlineCompletionSource.state;
  70. if (!state || !state.selectedItem) {
  71. return;
  72. }
  73. const info = {
  74. text: state.selectedItem.normalizedInlineCompletion.text,
  75. range: state.selectedItem.normalizedInlineCompletion.range,
  76. isSnippetText: state.selectedItem.isSnippetText,
  77. completionKind: state.selectedItem.completionItemKind,
  78. };
  79. const position = this.editor.getPosition();
  80. const promise = createCancelablePromise((token) => __awaiter(this, void 0, void 0, function* () {
  81. let result;
  82. try {
  83. result = yield provideInlineCompletions(position, this.editor.getModel(), { triggerKind: InlineCompletionTriggerKind.Automatic, selectedSuggestionInfo: info }, token);
  84. }
  85. catch (e) {
  86. onUnexpectedError(e);
  87. return;
  88. }
  89. if (token.isCancellationRequested) {
  90. return;
  91. }
  92. this.cache.setValue(this.editor, result, InlineCompletionTriggerKind.Automatic);
  93. this.onDidChangeEmitter.fire();
  94. }));
  95. const operation = new UpdateOperation(promise, InlineCompletionTriggerKind.Automatic);
  96. this.updateOperation.value = operation;
  97. yield promise;
  98. if (this.updateOperation.value === operation) {
  99. this.updateOperation.clear();
  100. }
  101. });
  102. }
  103. get ghostText() {
  104. var _a, _b, _c;
  105. const isSuggestionPreviewEnabled = this.isSuggestionPreviewEnabled();
  106. const augmentedCompletion = minimizeInlineCompletion(this.editor.getModel(), (_b = (_a = this.cache.value) === null || _a === void 0 ? void 0 : _a.completions[0]) === null || _b === void 0 ? void 0 : _b.toLiveInlineCompletion());
  107. const suggestWidgetState = this.suggestionInlineCompletionSource.state;
  108. const suggestInlineCompletion = minimizeInlineCompletion(this.editor.getModel(), (_c = suggestWidgetState === null || suggestWidgetState === void 0 ? void 0 : suggestWidgetState.selectedItem) === null || _c === void 0 ? void 0 : _c.normalizedInlineCompletion);
  109. const isAugmentedCompletionValid = augmentedCompletion
  110. && suggestInlineCompletion
  111. && augmentedCompletion.text.startsWith(suggestInlineCompletion.text)
  112. && augmentedCompletion.range.equalsRange(suggestInlineCompletion.range);
  113. if (!isSuggestionPreviewEnabled && !isAugmentedCompletionValid) {
  114. return undefined;
  115. }
  116. // If the augmented completion is not valid and there is no suggest inline completion, we still show the augmented completion.
  117. const finalCompletion = isAugmentedCompletionValid ? augmentedCompletion : (suggestInlineCompletion || augmentedCompletion);
  118. const inlineCompletionPreviewLength = isAugmentedCompletionValid ? finalCompletion.text.length - suggestInlineCompletion.text.length : 0;
  119. const newGhostText = this.toGhostText(finalCompletion, inlineCompletionPreviewLength);
  120. return newGhostText;
  121. }
  122. toGhostText(completion, inlineCompletionPreviewLength) {
  123. const mode = this.editor.getOptions().get(105 /* suggest */).previewMode;
  124. return completion
  125. ? (inlineCompletionToGhostText(completion, this.editor.getModel(), mode, this.editor.getPosition(), inlineCompletionPreviewLength) ||
  126. // Show an invisible ghost text to reserve space
  127. new GhostText(completion.range.endLineNumber, [], this.minReservedLineCount))
  128. : undefined;
  129. }
  130. }
  131. function sum(arr) {
  132. return arr.reduce((a, b) => a + b, 0);
  133. }