cab25047c8961112b62e1308920e47e1c5557c3494a8dc1be0df36583ed96a834193ee6db8d55bd3cd3524cdae67ef11281a34ec8b5f279f6cfd54cbd68b08 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  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 { alert } from '../../../../base/browser/ui/aria/aria.js';
  15. import { isNonEmptyArray } from '../../../../base/common/arrays.js';
  16. import { IdleValue } from '../../../../base/common/async.js';
  17. import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
  18. import { onUnexpectedError } from '../../../../base/common/errors.js';
  19. import { Event } from '../../../../base/common/event.js';
  20. import { SimpleKeybinding } from '../../../../base/common/keybindings.js';
  21. import { DisposableStore, dispose, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
  22. import * as platform from '../../../../base/common/platform.js';
  23. import { StopWatch } from '../../../../base/common/stopwatch.js';
  24. import { assertType, isObject } from '../../../../base/common/types.js';
  25. import { StableEditorScrollState } from '../../../browser/stableEditorScroll.js';
  26. import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution } from '../../../browser/editorExtensions.js';
  27. import { EditOperation } from '../../../common/core/editOperation.js';
  28. import { Position } from '../../../common/core/position.js';
  29. import { Range } from '../../../common/core/range.js';
  30. import { EditorContextKeys } from '../../../common/editorContextKeys.js';
  31. import { SnippetController2 } from '../../snippet/browser/snippetController2.js';
  32. import { SnippetParser } from '../../snippet/browser/snippetParser.js';
  33. import { ISuggestMemoryService } from './suggestMemory.js';
  34. import { WordContextKey } from './wordContextKey.js';
  35. import * as nls from '../../../../nls.js';
  36. import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js';
  37. import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
  38. import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
  39. import { ILogService } from '../../../../platform/log/common/log.js';
  40. import { Context as SuggestContext, suggestWidgetStatusbarMenu } from './suggest.js';
  41. import { SuggestAlternatives } from './suggestAlternatives.js';
  42. import { CommitCharacterController } from './suggestCommitCharacters.js';
  43. import { SuggestModel } from './suggestModel.js';
  44. import { OvertypingCapturer } from './suggestOvertypingCapturer.js';
  45. import { SuggestWidget } from './suggestWidget.js';
  46. import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
  47. import { basename, extname } from '../../../../base/common/resources.js';
  48. import { hash } from '../../../../base/common/hash.js';
  49. // sticky suggest widget which doesn't disappear on focus out and such
  50. const _sticky = false;
  51. // _sticky = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this
  52. class LineSuffix {
  53. constructor(_model, _position) {
  54. this._model = _model;
  55. this._position = _position;
  56. // spy on what's happening right of the cursor. two cases:
  57. // 1. end of line -> check that it's still end of line
  58. // 2. mid of line -> add a marker and compute the delta
  59. const maxColumn = _model.getLineMaxColumn(_position.lineNumber);
  60. if (maxColumn !== _position.column) {
  61. const offset = _model.getOffsetAt(_position);
  62. const end = _model.getPositionAt(offset + 1);
  63. this._marker = _model.deltaDecorations([], [{
  64. range: Range.fromPositions(_position, end),
  65. options: { description: 'suggest-line-suffix', stickiness: 1 /* TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges */ }
  66. }]);
  67. }
  68. }
  69. dispose() {
  70. if (this._marker && !this._model.isDisposed()) {
  71. this._model.deltaDecorations(this._marker, []);
  72. }
  73. }
  74. delta(position) {
  75. if (this._model.isDisposed() || this._position.lineNumber !== position.lineNumber) {
  76. // bail out early if things seems fishy
  77. return 0;
  78. }
  79. // read the marker (in case suggest was triggered at line end) or compare
  80. // the cursor to the line end.
  81. if (this._marker) {
  82. const range = this._model.getDecorationRange(this._marker[0]);
  83. const end = this._model.getOffsetAt(range.getStartPosition());
  84. return end - this._model.getOffsetAt(position);
  85. }
  86. else {
  87. return this._model.getLineMaxColumn(position.lineNumber) - position.column;
  88. }
  89. }
  90. }
  91. let SuggestController = class SuggestController {
  92. constructor(editor, _memoryService, _commandService, _contextKeyService, _instantiationService, _logService, _telemetryService) {
  93. this._memoryService = _memoryService;
  94. this._commandService = _commandService;
  95. this._contextKeyService = _contextKeyService;
  96. this._instantiationService = _instantiationService;
  97. this._logService = _logService;
  98. this._telemetryService = _telemetryService;
  99. this._lineSuffix = new MutableDisposable();
  100. this._toDispose = new DisposableStore();
  101. this._selectors = new PriorityRegistry(s => s.priority);
  102. this._telemetryGate = 0;
  103. this.editor = editor;
  104. this.model = _instantiationService.createInstance(SuggestModel, this.editor);
  105. // context key: update insert/replace mode
  106. const ctxInsertMode = SuggestContext.InsertMode.bindTo(_contextKeyService);
  107. ctxInsertMode.set(editor.getOption(108 /* EditorOption.suggest */).insertMode);
  108. this.model.onDidTrigger(() => ctxInsertMode.set(editor.getOption(108 /* EditorOption.suggest */).insertMode));
  109. this.widget = this._toDispose.add(new IdleValue(() => {
  110. const widget = this._instantiationService.createInstance(SuggestWidget, this.editor);
  111. this._toDispose.add(widget);
  112. this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, 0), this));
  113. // Wire up logic to accept a suggestion on certain characters
  114. const commitCharacterController = new CommitCharacterController(this.editor, widget, item => this._insertSuggestion(item, 2 /* InsertFlags.NoAfterUndoStop */));
  115. this._toDispose.add(commitCharacterController);
  116. this._toDispose.add(this.model.onDidSuggest(e => {
  117. if (e.completionModel.items.length === 0) {
  118. commitCharacterController.reset();
  119. }
  120. }));
  121. // Wire up makes text edit context key
  122. const ctxMakesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
  123. const ctxHasInsertAndReplace = SuggestContext.HasInsertAndReplaceRange.bindTo(this._contextKeyService);
  124. const ctxCanResolve = SuggestContext.CanResolve.bindTo(this._contextKeyService);
  125. this._toDispose.add(toDisposable(() => {
  126. ctxMakesTextEdit.reset();
  127. ctxHasInsertAndReplace.reset();
  128. ctxCanResolve.reset();
  129. }));
  130. this._toDispose.add(widget.onDidFocus(({ item }) => {
  131. // (ctx: makesTextEdit)
  132. const position = this.editor.getPosition();
  133. const startColumn = item.editStart.column;
  134. const endColumn = position.column;
  135. let value = true;
  136. if (this.editor.getOption(1 /* EditorOption.acceptSuggestionOnEnter */) === 'smart'
  137. && this.model.state === 2 /* State.Auto */
  138. && !item.completion.additionalTextEdits
  139. && !(item.completion.insertTextRules & 4 /* CompletionItemInsertTextRule.InsertAsSnippet */)
  140. && endColumn - startColumn === item.completion.insertText.length) {
  141. const oldText = this.editor.getModel().getValueInRange({
  142. startLineNumber: position.lineNumber,
  143. startColumn,
  144. endLineNumber: position.lineNumber,
  145. endColumn
  146. });
  147. value = oldText !== item.completion.insertText;
  148. }
  149. ctxMakesTextEdit.set(value);
  150. // (ctx: hasInsertAndReplaceRange)
  151. ctxHasInsertAndReplace.set(!Position.equals(item.editInsertEnd, item.editReplaceEnd));
  152. // (ctx: canResolve)
  153. ctxCanResolve.set(Boolean(item.provider.resolveCompletionItem) || Boolean(item.completion.documentation) || item.completion.detail !== item.completion.label);
  154. }));
  155. this._toDispose.add(widget.onDetailsKeyDown(e => {
  156. // cmd + c on macOS, ctrl + c on Win / Linux
  157. if (e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, 33 /* KeyCode.KeyC */)) ||
  158. (platform.isMacintosh && e.toKeybinding().equals(new SimpleKeybinding(false, false, false, true, 33 /* KeyCode.KeyC */)))) {
  159. e.stopPropagation();
  160. return;
  161. }
  162. if (!e.toKeybinding().isModifierKey()) {
  163. this.editor.focus();
  164. }
  165. }));
  166. return widget;
  167. }));
  168. // Wire up text overtyping capture
  169. this._overtypingCapturer = this._toDispose.add(new IdleValue(() => {
  170. return this._toDispose.add(new OvertypingCapturer(this.editor, this.model));
  171. }));
  172. this._alternatives = this._toDispose.add(new IdleValue(() => {
  173. return this._toDispose.add(new SuggestAlternatives(this.editor, this._contextKeyService));
  174. }));
  175. this._toDispose.add(_instantiationService.createInstance(WordContextKey, editor));
  176. this._toDispose.add(this.model.onDidTrigger(e => {
  177. this.widget.value.showTriggered(e.auto, e.shy ? 250 : 50);
  178. this._lineSuffix.value = new LineSuffix(this.editor.getModel(), e.position);
  179. }));
  180. this._toDispose.add(this.model.onDidSuggest(e => {
  181. if (e.shy) {
  182. return;
  183. }
  184. let index = -1;
  185. if (!e.noSelect) {
  186. for (const selector of this._selectors.itemsOrderedByPriorityDesc) {
  187. index = selector.select(this.editor.getModel(), this.editor.getPosition(), e.completionModel.items);
  188. if (index !== -1) {
  189. break;
  190. }
  191. }
  192. if (index === -1) {
  193. index = this._memoryService.select(this.editor.getModel(), this.editor.getPosition(), e.completionModel.items);
  194. }
  195. }
  196. this.widget.value.showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
  197. }));
  198. this._toDispose.add(this.model.onDidCancel(e => {
  199. if (!e.retrigger) {
  200. this.widget.value.hideWidget();
  201. }
  202. }));
  203. this._toDispose.add(this.editor.onDidBlurEditorWidget(() => {
  204. if (!_sticky) {
  205. this.model.cancel();
  206. this.model.clear();
  207. }
  208. }));
  209. // Manage the acceptSuggestionsOnEnter context key
  210. const acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
  211. const updateFromConfig = () => {
  212. const acceptSuggestionOnEnter = this.editor.getOption(1 /* EditorOption.acceptSuggestionOnEnter */);
  213. acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart');
  214. };
  215. this._toDispose.add(this.editor.onDidChangeConfiguration(() => updateFromConfig()));
  216. updateFromConfig();
  217. }
  218. static get(editor) {
  219. return editor.getContribution(SuggestController.ID);
  220. }
  221. dispose() {
  222. this._alternatives.dispose();
  223. this._toDispose.dispose();
  224. this.widget.dispose();
  225. this.model.dispose();
  226. this._lineSuffix.dispose();
  227. }
  228. _insertSuggestion(event, flags) {
  229. if (!event || !event.item) {
  230. this._alternatives.value.reset();
  231. this.model.cancel();
  232. this.model.clear();
  233. return;
  234. }
  235. if (!this.editor.hasModel()) {
  236. return;
  237. }
  238. const snippetController = SnippetController2.get(this.editor);
  239. if (!snippetController) {
  240. return;
  241. }
  242. const model = this.editor.getModel();
  243. const modelVersionNow = model.getAlternativeVersionId();
  244. const { item } = event;
  245. //
  246. const tasks = [];
  247. const cts = new CancellationTokenSource();
  248. // pushing undo stops *before* additional text edits and
  249. // *after* the main edit
  250. if (!(flags & 1 /* InsertFlags.NoBeforeUndoStop */)) {
  251. this.editor.pushUndoStop();
  252. }
  253. // compute overwrite[Before|After] deltas BEFORE applying extra edits
  254. const info = this.getOverwriteInfo(item, Boolean(flags & 8 /* InsertFlags.AlternativeOverwriteConfig */));
  255. // keep item in memory
  256. this._memoryService.memorize(model, this.editor.getPosition(), item);
  257. if (Array.isArray(item.completion.additionalTextEdits)) {
  258. // sync additional edits
  259. const scrollState = StableEditorScrollState.capture(this.editor);
  260. this.editor.executeEdits('suggestController.additionalTextEdits.sync', item.completion.additionalTextEdits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)));
  261. scrollState.restoreRelativeVerticalPositionOfCursor(this.editor);
  262. }
  263. else if (!item.isResolved) {
  264. // async additional edits
  265. const sw = new StopWatch(true);
  266. let position;
  267. const docListener = model.onDidChangeContent(e => {
  268. if (e.isFlush) {
  269. cts.cancel();
  270. docListener.dispose();
  271. return;
  272. }
  273. for (const change of e.changes) {
  274. const thisPosition = Range.getEndPosition(change.range);
  275. if (!position || Position.isBefore(thisPosition, position)) {
  276. position = thisPosition;
  277. }
  278. }
  279. });
  280. const oldFlags = flags;
  281. flags |= 2 /* InsertFlags.NoAfterUndoStop */;
  282. let didType = false;
  283. const typeListener = this.editor.onWillType(() => {
  284. typeListener.dispose();
  285. didType = true;
  286. if (!(oldFlags & 2 /* InsertFlags.NoAfterUndoStop */)) {
  287. this.editor.pushUndoStop();
  288. }
  289. });
  290. tasks.push(item.resolve(cts.token).then(() => {
  291. if (!item.completion.additionalTextEdits || cts.token.isCancellationRequested) {
  292. return false;
  293. }
  294. if (position && item.completion.additionalTextEdits.some(edit => Position.isBefore(position, Range.getStartPosition(edit.range)))) {
  295. return false;
  296. }
  297. if (didType) {
  298. this.editor.pushUndoStop();
  299. }
  300. const scrollState = StableEditorScrollState.capture(this.editor);
  301. this.editor.executeEdits('suggestController.additionalTextEdits.async', item.completion.additionalTextEdits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)));
  302. scrollState.restoreRelativeVerticalPositionOfCursor(this.editor);
  303. if (didType || !(oldFlags & 2 /* InsertFlags.NoAfterUndoStop */)) {
  304. this.editor.pushUndoStop();
  305. }
  306. return true;
  307. }).then(applied => {
  308. this._logService.trace('[suggest] async resolving of edits DONE (ms, applied?)', sw.elapsed(), applied);
  309. docListener.dispose();
  310. typeListener.dispose();
  311. }));
  312. }
  313. let { insertText } = item.completion;
  314. if (!(item.completion.insertTextRules & 4 /* CompletionItemInsertTextRule.InsertAsSnippet */)) {
  315. insertText = SnippetParser.escape(insertText);
  316. }
  317. snippetController.insert(insertText, {
  318. overwriteBefore: info.overwriteBefore,
  319. overwriteAfter: info.overwriteAfter,
  320. undoStopBefore: false,
  321. undoStopAfter: false,
  322. adjustWhitespace: !(item.completion.insertTextRules & 1 /* CompletionItemInsertTextRule.KeepWhitespace */),
  323. clipboardText: event.model.clipboardText,
  324. overtypingCapturer: this._overtypingCapturer.value
  325. });
  326. if (!(flags & 2 /* InsertFlags.NoAfterUndoStop */)) {
  327. this.editor.pushUndoStop();
  328. }
  329. if (!item.completion.command) {
  330. // done
  331. this.model.cancel();
  332. }
  333. else if (item.completion.command.id === TriggerSuggestAction.id) {
  334. // retigger
  335. this.model.trigger({ auto: true, shy: false, noSelect: false }, true);
  336. }
  337. else {
  338. // exec command, done
  339. tasks.push(this._commandService.executeCommand(item.completion.command.id, ...(item.completion.command.arguments ? [...item.completion.command.arguments] : [])).catch(onUnexpectedError));
  340. this.model.cancel();
  341. }
  342. if (flags & 4 /* InsertFlags.KeepAlternativeSuggestions */) {
  343. this._alternatives.value.set(event, next => {
  344. // cancel resolving of additional edits
  345. cts.cancel();
  346. // this is not so pretty. when inserting the 'next'
  347. // suggestion we undo until we are at the state at
  348. // which we were before inserting the previous suggestion...
  349. while (model.canUndo()) {
  350. if (modelVersionNow !== model.getAlternativeVersionId()) {
  351. model.undo();
  352. }
  353. this._insertSuggestion(next, 1 /* InsertFlags.NoBeforeUndoStop */ | 2 /* InsertFlags.NoAfterUndoStop */ | (flags & 8 /* InsertFlags.AlternativeOverwriteConfig */ ? 8 /* InsertFlags.AlternativeOverwriteConfig */ : 0));
  354. break;
  355. }
  356. });
  357. }
  358. this._alertCompletionItem(item);
  359. // clear only now - after all tasks are done
  360. Promise.all(tasks).finally(() => {
  361. this._reportSuggestionAcceptedTelemetry(item, model, event);
  362. this.model.clear();
  363. cts.dispose();
  364. });
  365. }
  366. _reportSuggestionAcceptedTelemetry(item, model, acceptedSuggestion) {
  367. var _a;
  368. if (this._telemetryGate++ % 100 !== 0) {
  369. return;
  370. }
  371. // _debugDisplayName looks like `vscode.css-language-features(/-:)`, where the last bit is the trigger chars
  372. // normalize it to just the extension ID and lowercase
  373. const providerId = item.extensionId ? item.extensionId.value : ((_a = acceptedSuggestion.item.provider._debugDisplayName) !== null && _a !== void 0 ? _a : 'unknown').split('(', 1)[0].toLowerCase();
  374. this._telemetryService.publicLog2('suggest.acceptedSuggestion', {
  375. providerId,
  376. kind: item.completion.kind,
  377. basenameHash: hash(basename(model.uri)).toString(16),
  378. languageId: model.getLanguageId(),
  379. fileExtension: extname(model.uri),
  380. });
  381. }
  382. getOverwriteInfo(item, toggleMode) {
  383. assertType(this.editor.hasModel());
  384. let replace = this.editor.getOption(108 /* EditorOption.suggest */).insertMode === 'replace';
  385. if (toggleMode) {
  386. replace = !replace;
  387. }
  388. const overwriteBefore = item.position.column - item.editStart.column;
  389. const overwriteAfter = (replace ? item.editReplaceEnd.column : item.editInsertEnd.column) - item.position.column;
  390. const columnDelta = this.editor.getPosition().column - item.position.column;
  391. const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this.editor.getPosition()) : 0;
  392. return {
  393. overwriteBefore: overwriteBefore + columnDelta,
  394. overwriteAfter: overwriteAfter + suffixDelta
  395. };
  396. }
  397. _alertCompletionItem(item) {
  398. if (isNonEmptyArray(item.completion.additionalTextEdits)) {
  399. const msg = nls.localize('aria.alert.snippet', "Accepting '{0}' made {1} additional edits", item.textLabel, item.completion.additionalTextEdits.length);
  400. alert(msg);
  401. }
  402. }
  403. triggerSuggest(onlyFrom, auto, noFilter, noSelect) {
  404. if (this.editor.hasModel()) {
  405. this.model.trigger({ auto: auto !== null && auto !== void 0 ? auto : false, shy: false, noSelect: noSelect !== null && noSelect !== void 0 ? noSelect : false }, false, onlyFrom, undefined, noFilter);
  406. this.editor.revealPosition(this.editor.getPosition(), 0 /* ScrollType.Smooth */);
  407. this.editor.focus();
  408. }
  409. }
  410. triggerSuggestAndAcceptBest(arg) {
  411. if (!this.editor.hasModel()) {
  412. return;
  413. }
  414. const positionNow = this.editor.getPosition();
  415. const fallback = () => {
  416. if (positionNow.equals(this.editor.getPosition())) {
  417. this._commandService.executeCommand(arg.fallback);
  418. }
  419. };
  420. const makesTextEdit = (item) => {
  421. if (item.completion.insertTextRules & 4 /* CompletionItemInsertTextRule.InsertAsSnippet */ || item.completion.additionalTextEdits) {
  422. // snippet, other editor -> makes edit
  423. return true;
  424. }
  425. const position = this.editor.getPosition();
  426. const startColumn = item.editStart.column;
  427. const endColumn = position.column;
  428. if (endColumn - startColumn !== item.completion.insertText.length) {
  429. // unequal lengths -> makes edit
  430. return true;
  431. }
  432. const textNow = this.editor.getModel().getValueInRange({
  433. startLineNumber: position.lineNumber,
  434. startColumn,
  435. endLineNumber: position.lineNumber,
  436. endColumn
  437. });
  438. // unequal text -> makes edit
  439. return textNow !== item.completion.insertText;
  440. };
  441. Event.once(this.model.onDidTrigger)(_ => {
  442. // wait for trigger because only then the cancel-event is trustworthy
  443. const listener = [];
  444. Event.any(this.model.onDidTrigger, this.model.onDidCancel)(() => {
  445. // retrigger or cancel -> try to type default text
  446. dispose(listener);
  447. fallback();
  448. }, undefined, listener);
  449. this.model.onDidSuggest(({ completionModel }) => {
  450. dispose(listener);
  451. if (completionModel.items.length === 0) {
  452. fallback();
  453. return;
  454. }
  455. const index = this._memoryService.select(this.editor.getModel(), this.editor.getPosition(), completionModel.items);
  456. const item = completionModel.items[index];
  457. if (!makesTextEdit(item)) {
  458. fallback();
  459. return;
  460. }
  461. this.editor.pushUndoStop();
  462. this._insertSuggestion({ index, item, model: completionModel }, 4 /* InsertFlags.KeepAlternativeSuggestions */ | 1 /* InsertFlags.NoBeforeUndoStop */ | 2 /* InsertFlags.NoAfterUndoStop */);
  463. }, undefined, listener);
  464. });
  465. this.model.trigger({ auto: false, shy: true, noSelect: false });
  466. this.editor.revealPosition(positionNow, 0 /* ScrollType.Smooth */);
  467. this.editor.focus();
  468. }
  469. acceptSelectedSuggestion(keepAlternativeSuggestions, alternativeOverwriteConfig) {
  470. const item = this.widget.value.getFocusedItem();
  471. let flags = 0;
  472. if (keepAlternativeSuggestions) {
  473. flags |= 4 /* InsertFlags.KeepAlternativeSuggestions */;
  474. }
  475. if (alternativeOverwriteConfig) {
  476. flags |= 8 /* InsertFlags.AlternativeOverwriteConfig */;
  477. }
  478. this._insertSuggestion(item, flags);
  479. }
  480. acceptNextSuggestion() {
  481. this._alternatives.value.next();
  482. }
  483. acceptPrevSuggestion() {
  484. this._alternatives.value.prev();
  485. }
  486. cancelSuggestWidget() {
  487. this.model.cancel();
  488. this.model.clear();
  489. this.widget.value.hideWidget();
  490. }
  491. selectNextSuggestion() {
  492. this.widget.value.selectNext();
  493. }
  494. selectNextPageSuggestion() {
  495. this.widget.value.selectNextPage();
  496. }
  497. selectLastSuggestion() {
  498. this.widget.value.selectLast();
  499. }
  500. selectPrevSuggestion() {
  501. this.widget.value.selectPrevious();
  502. }
  503. selectPrevPageSuggestion() {
  504. this.widget.value.selectPreviousPage();
  505. }
  506. selectFirstSuggestion() {
  507. this.widget.value.selectFirst();
  508. }
  509. toggleSuggestionDetails() {
  510. this.widget.value.toggleDetails();
  511. }
  512. toggleExplainMode() {
  513. this.widget.value.toggleExplainMode();
  514. }
  515. toggleSuggestionFocus() {
  516. this.widget.value.toggleDetailsFocus();
  517. }
  518. resetWidgetSize() {
  519. this.widget.value.resetPersistedSize();
  520. }
  521. forceRenderingAbove() {
  522. this.widget.value.forceRenderingAbove();
  523. }
  524. stopForceRenderingAbove() {
  525. if (!this.widget.isInitialized) {
  526. // This method has no effect if the widget is not initialized yet.
  527. return;
  528. }
  529. this.widget.value.stopForceRenderingAbove();
  530. }
  531. registerSelector(selector) {
  532. return this._selectors.register(selector);
  533. }
  534. };
  535. SuggestController.ID = 'editor.contrib.suggestController';
  536. SuggestController = __decorate([
  537. __param(1, ISuggestMemoryService),
  538. __param(2, ICommandService),
  539. __param(3, IContextKeyService),
  540. __param(4, IInstantiationService),
  541. __param(5, ILogService),
  542. __param(6, ITelemetryService)
  543. ], SuggestController);
  544. export { SuggestController };
  545. class PriorityRegistry {
  546. constructor(prioritySelector) {
  547. this.prioritySelector = prioritySelector;
  548. this._items = new Array();
  549. }
  550. register(value) {
  551. if (this._items.indexOf(value) !== -1) {
  552. throw new Error('Value is already registered');
  553. }
  554. this._items.push(value);
  555. this._items.sort((s1, s2) => this.prioritySelector(s2) - this.prioritySelector(s1));
  556. return {
  557. dispose: () => {
  558. const idx = this._items.indexOf(value);
  559. if (idx >= 0) {
  560. this._items.splice(idx, 1);
  561. }
  562. }
  563. };
  564. }
  565. get itemsOrderedByPriorityDesc() {
  566. return this._items;
  567. }
  568. }
  569. export class TriggerSuggestAction extends EditorAction {
  570. constructor() {
  571. super({
  572. id: TriggerSuggestAction.id,
  573. label: nls.localize('suggest.trigger.label', "Trigger Suggest"),
  574. alias: 'Trigger Suggest',
  575. precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCompletionItemProvider),
  576. kbOpts: {
  577. kbExpr: EditorContextKeys.textInputFocus,
  578. primary: 2048 /* KeyMod.CtrlCmd */ | 10 /* KeyCode.Space */,
  579. secondary: [2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */],
  580. mac: { primary: 256 /* KeyMod.WinCtrl */ | 10 /* KeyCode.Space */, secondary: [512 /* KeyMod.Alt */ | 9 /* KeyCode.Escape */, 2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */] },
  581. weight: 100 /* KeybindingWeight.EditorContrib */
  582. }
  583. });
  584. }
  585. run(_accessor, editor, args) {
  586. const controller = SuggestController.get(editor);
  587. if (!controller) {
  588. return;
  589. }
  590. let auto;
  591. let noSelect;
  592. if (args && typeof args === 'object') {
  593. if (args.auto === true) {
  594. auto = true;
  595. }
  596. if (args.noSelection === true) {
  597. noSelect = true;
  598. }
  599. }
  600. controller.triggerSuggest(undefined, auto, undefined, noSelect);
  601. }
  602. }
  603. TriggerSuggestAction.id = 'editor.action.triggerSuggest';
  604. registerEditorContribution(SuggestController.ID, SuggestController);
  605. registerEditorAction(TriggerSuggestAction);
  606. const weight = 100 /* KeybindingWeight.EditorContrib */ + 90;
  607. const SuggestCommand = EditorCommand.bindToContribution(SuggestController.get);
  608. registerEditorCommand(new SuggestCommand({
  609. id: 'acceptSelectedSuggestion',
  610. precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.HasFocusedSuggestion),
  611. handler(x) {
  612. x.acceptSelectedSuggestion(true, false);
  613. },
  614. kbOpts: [{
  615. // normal tab
  616. primary: 2 /* KeyCode.Tab */,
  617. kbExpr: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus),
  618. weight,
  619. }, {
  620. // accept on enter has special rules
  621. primary: 3 /* KeyCode.Enter */,
  622. kbExpr: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.AcceptSuggestionsOnEnter, SuggestContext.MakesTextEdit),
  623. weight,
  624. }],
  625. menuOpts: [{
  626. menuId: suggestWidgetStatusbarMenu,
  627. title: nls.localize('accept.insert', "Insert"),
  628. group: 'left',
  629. order: 1,
  630. when: SuggestContext.HasInsertAndReplaceRange.toNegated()
  631. }, {
  632. menuId: suggestWidgetStatusbarMenu,
  633. title: nls.localize('accept.insert', "Insert"),
  634. group: 'left',
  635. order: 1,
  636. when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, SuggestContext.InsertMode.isEqualTo('insert'))
  637. }, {
  638. menuId: suggestWidgetStatusbarMenu,
  639. title: nls.localize('accept.replace', "Replace"),
  640. group: 'left',
  641. order: 1,
  642. when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, SuggestContext.InsertMode.isEqualTo('replace'))
  643. }]
  644. }));
  645. registerEditorCommand(new SuggestCommand({
  646. id: 'acceptAlternativeSelectedSuggestion',
  647. precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.HasFocusedSuggestion),
  648. kbOpts: {
  649. weight: weight,
  650. kbExpr: EditorContextKeys.textInputFocus,
  651. primary: 1024 /* KeyMod.Shift */ | 3 /* KeyCode.Enter */,
  652. secondary: [1024 /* KeyMod.Shift */ | 2 /* KeyCode.Tab */],
  653. },
  654. handler(x) {
  655. x.acceptSelectedSuggestion(false, true);
  656. },
  657. menuOpts: [{
  658. menuId: suggestWidgetStatusbarMenu,
  659. group: 'left',
  660. order: 2,
  661. when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, SuggestContext.InsertMode.isEqualTo('insert')),
  662. title: nls.localize('accept.replace', "Replace")
  663. }, {
  664. menuId: suggestWidgetStatusbarMenu,
  665. group: 'left',
  666. order: 2,
  667. when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, SuggestContext.InsertMode.isEqualTo('replace')),
  668. title: nls.localize('accept.insert', "Insert")
  669. }]
  670. }));
  671. // continue to support the old command
  672. CommandsRegistry.registerCommandAlias('acceptSelectedSuggestionOnEnter', 'acceptSelectedSuggestion');
  673. registerEditorCommand(new SuggestCommand({
  674. id: 'hideSuggestWidget',
  675. precondition: SuggestContext.Visible,
  676. handler: x => x.cancelSuggestWidget(),
  677. kbOpts: {
  678. weight: weight,
  679. kbExpr: EditorContextKeys.textInputFocus,
  680. primary: 9 /* KeyCode.Escape */,
  681. secondary: [1024 /* KeyMod.Shift */ | 9 /* KeyCode.Escape */]
  682. }
  683. }));
  684. registerEditorCommand(new SuggestCommand({
  685. id: 'selectNextSuggestion',
  686. precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
  687. handler: c => c.selectNextSuggestion(),
  688. kbOpts: {
  689. weight: weight,
  690. kbExpr: EditorContextKeys.textInputFocus,
  691. primary: 18 /* KeyCode.DownArrow */,
  692. secondary: [2048 /* KeyMod.CtrlCmd */ | 18 /* KeyCode.DownArrow */],
  693. mac: { primary: 18 /* KeyCode.DownArrow */, secondary: [2048 /* KeyMod.CtrlCmd */ | 18 /* KeyCode.DownArrow */, 256 /* KeyMod.WinCtrl */ | 44 /* KeyCode.KeyN */] }
  694. }
  695. }));
  696. registerEditorCommand(new SuggestCommand({
  697. id: 'selectNextPageSuggestion',
  698. precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
  699. handler: c => c.selectNextPageSuggestion(),
  700. kbOpts: {
  701. weight: weight,
  702. kbExpr: EditorContextKeys.textInputFocus,
  703. primary: 12 /* KeyCode.PageDown */,
  704. secondary: [2048 /* KeyMod.CtrlCmd */ | 12 /* KeyCode.PageDown */]
  705. }
  706. }));
  707. registerEditorCommand(new SuggestCommand({
  708. id: 'selectLastSuggestion',
  709. precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
  710. handler: c => c.selectLastSuggestion()
  711. }));
  712. registerEditorCommand(new SuggestCommand({
  713. id: 'selectPrevSuggestion',
  714. precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
  715. handler: c => c.selectPrevSuggestion(),
  716. kbOpts: {
  717. weight: weight,
  718. kbExpr: EditorContextKeys.textInputFocus,
  719. primary: 16 /* KeyCode.UpArrow */,
  720. secondary: [2048 /* KeyMod.CtrlCmd */ | 16 /* KeyCode.UpArrow */],
  721. mac: { primary: 16 /* KeyCode.UpArrow */, secondary: [2048 /* KeyMod.CtrlCmd */ | 16 /* KeyCode.UpArrow */, 256 /* KeyMod.WinCtrl */ | 46 /* KeyCode.KeyP */] }
  722. }
  723. }));
  724. registerEditorCommand(new SuggestCommand({
  725. id: 'selectPrevPageSuggestion',
  726. precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
  727. handler: c => c.selectPrevPageSuggestion(),
  728. kbOpts: {
  729. weight: weight,
  730. kbExpr: EditorContextKeys.textInputFocus,
  731. primary: 11 /* KeyCode.PageUp */,
  732. secondary: [2048 /* KeyMod.CtrlCmd */ | 11 /* KeyCode.PageUp */]
  733. }
  734. }));
  735. registerEditorCommand(new SuggestCommand({
  736. id: 'selectFirstSuggestion',
  737. precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.MultipleSuggestions),
  738. handler: c => c.selectFirstSuggestion()
  739. }));
  740. registerEditorCommand(new SuggestCommand({
  741. id: 'toggleSuggestionDetails',
  742. precondition: SuggestContext.Visible,
  743. handler: x => x.toggleSuggestionDetails(),
  744. kbOpts: {
  745. weight: weight,
  746. kbExpr: EditorContextKeys.textInputFocus,
  747. primary: 2048 /* KeyMod.CtrlCmd */ | 10 /* KeyCode.Space */,
  748. secondary: [2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */],
  749. mac: { primary: 256 /* KeyMod.WinCtrl */ | 10 /* KeyCode.Space */, secondary: [2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */] }
  750. },
  751. menuOpts: [{
  752. menuId: suggestWidgetStatusbarMenu,
  753. group: 'right',
  754. order: 1,
  755. when: ContextKeyExpr.and(SuggestContext.DetailsVisible, SuggestContext.CanResolve),
  756. title: nls.localize('detail.more', "show less")
  757. }, {
  758. menuId: suggestWidgetStatusbarMenu,
  759. group: 'right',
  760. order: 1,
  761. when: ContextKeyExpr.and(SuggestContext.DetailsVisible.toNegated(), SuggestContext.CanResolve),
  762. title: nls.localize('detail.less', "show more")
  763. }]
  764. }));
  765. registerEditorCommand(new SuggestCommand({
  766. id: 'toggleExplainMode',
  767. precondition: SuggestContext.Visible,
  768. handler: x => x.toggleExplainMode(),
  769. kbOpts: {
  770. weight: 100 /* KeybindingWeight.EditorContrib */,
  771. primary: 2048 /* KeyMod.CtrlCmd */ | 85 /* KeyCode.Slash */,
  772. }
  773. }));
  774. registerEditorCommand(new SuggestCommand({
  775. id: 'toggleSuggestionFocus',
  776. precondition: SuggestContext.Visible,
  777. handler: x => x.toggleSuggestionFocus(),
  778. kbOpts: {
  779. weight: weight,
  780. kbExpr: EditorContextKeys.textInputFocus,
  781. primary: 2048 /* KeyMod.CtrlCmd */ | 512 /* KeyMod.Alt */ | 10 /* KeyCode.Space */,
  782. mac: { primary: 256 /* KeyMod.WinCtrl */ | 512 /* KeyMod.Alt */ | 10 /* KeyCode.Space */ }
  783. }
  784. }));
  785. //#region tab completions
  786. registerEditorCommand(new SuggestCommand({
  787. id: 'insertBestCompletion',
  788. precondition: ContextKeyExpr.and(EditorContextKeys.textInputFocus, ContextKeyExpr.equals('config.editor.tabCompletion', 'on'), WordContextKey.AtEnd, SuggestContext.Visible.toNegated(), SuggestAlternatives.OtherSuggestions.toNegated(), SnippetController2.InSnippetMode.toNegated()),
  789. handler: (x, arg) => {
  790. x.triggerSuggestAndAcceptBest(isObject(arg) ? Object.assign({ fallback: 'tab' }, arg) : { fallback: 'tab' });
  791. },
  792. kbOpts: {
  793. weight,
  794. primary: 2 /* KeyCode.Tab */
  795. }
  796. }));
  797. registerEditorCommand(new SuggestCommand({
  798. id: 'insertNextSuggestion',
  799. precondition: ContextKeyExpr.and(EditorContextKeys.textInputFocus, ContextKeyExpr.equals('config.editor.tabCompletion', 'on'), SuggestAlternatives.OtherSuggestions, SuggestContext.Visible.toNegated(), SnippetController2.InSnippetMode.toNegated()),
  800. handler: x => x.acceptNextSuggestion(),
  801. kbOpts: {
  802. weight: weight,
  803. kbExpr: EditorContextKeys.textInputFocus,
  804. primary: 2 /* KeyCode.Tab */
  805. }
  806. }));
  807. registerEditorCommand(new SuggestCommand({
  808. id: 'insertPrevSuggestion',
  809. precondition: ContextKeyExpr.and(EditorContextKeys.textInputFocus, ContextKeyExpr.equals('config.editor.tabCompletion', 'on'), SuggestAlternatives.OtherSuggestions, SuggestContext.Visible.toNegated(), SnippetController2.InSnippetMode.toNegated()),
  810. handler: x => x.acceptPrevSuggestion(),
  811. kbOpts: {
  812. weight: weight,
  813. kbExpr: EditorContextKeys.textInputFocus,
  814. primary: 1024 /* KeyMod.Shift */ | 2 /* KeyCode.Tab */
  815. }
  816. }));
  817. registerEditorAction(class extends EditorAction {
  818. constructor() {
  819. super({
  820. id: 'editor.action.resetSuggestSize',
  821. label: nls.localize('suggest.reset.label', "Reset Suggest Widget Size"),
  822. alias: 'Reset Suggest Widget Size',
  823. precondition: undefined
  824. });
  825. }
  826. run(_accessor, editor) {
  827. var _a;
  828. (_a = SuggestController.get(editor)) === null || _a === void 0 ? void 0 : _a.resetWidgetSize();
  829. }
  830. });