monaco-editor-core
Version:
A browser based code editor
931 lines (930 loc) • 45.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var SuggestController_1;
import { alert } from '../../../../base/browser/ui/aria/aria.js';
import { isNonEmptyArray } from '../../../../base/common/arrays.js';
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { onUnexpectedError, onUnexpectedExternalError } from '../../../../base/common/errors.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { KeyCodeChord } from '../../../../base/common/keybindings.js';
import { DisposableStore, dispose, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
import * as platform from '../../../../base/common/platform.js';
import { StopWatch } from '../../../../base/common/stopwatch.js';
import { assertType, isObject } from '../../../../base/common/types.js';
import { StableEditorScrollState } from '../../../browser/stableEditorScroll.js';
import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution } from '../../../browser/editorExtensions.js';
import { EditOperation } from '../../../common/core/editOperation.js';
import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
import { SnippetController2 } from '../../snippet/browser/snippetController2.js';
import { SnippetParser } from '../../snippet/browser/snippetParser.js';
import { ISuggestMemoryService } from './suggestMemory.js';
import { WordContextKey } from './wordContextKey.js';
import * as nls from '../../../../nls.js';
import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js';
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { Context as SuggestContext, suggestWidgetStatusbarMenu } from './suggest.js';
import { SuggestAlternatives } from './suggestAlternatives.js';
import { CommitCharacterController } from './suggestCommitCharacters.js';
import { SuggestModel } from './suggestModel.js';
import { OvertypingCapturer } from './suggestOvertypingCapturer.js';
import { SuggestWidget } from './suggestWidget.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { basename, extname } from '../../../../base/common/resources.js';
import { hash } from '../../../../base/common/hash.js';
import { WindowIdleValue, getWindow } from '../../../../base/browser/dom.js';
import { ModelDecorationOptions } from '../../../common/model/textModel.js';
// sticky suggest widget which doesn't disappear on focus out and such
const _sticky = false;
class LineSuffix {
constructor(_model, _position) {
this._model = _model;
this._position = _position;
this._decorationOptions = ModelDecorationOptions.register({
description: 'suggest-line-suffix',
stickiness: 1 /* TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges */
});
// spy on what's happening right of the cursor. two cases:
// 1. end of line -> check that it's still end of line
// 2. mid of line -> add a marker and compute the delta
const maxColumn = _model.getLineMaxColumn(_position.lineNumber);
if (maxColumn !== _position.column) {
const offset = _model.getOffsetAt(_position);
const end = _model.getPositionAt(offset + 1);
_model.changeDecorations(accessor => {
if (this._marker) {
accessor.removeDecoration(this._marker);
}
this._marker = accessor.addDecoration(Range.fromPositions(_position, end), this._decorationOptions);
});
}
}
dispose() {
if (this._marker && !this._model.isDisposed()) {
this._model.changeDecorations(accessor => {
accessor.removeDecoration(this._marker);
this._marker = undefined;
});
}
}
delta(position) {
if (this._model.isDisposed() || this._position.lineNumber !== position.lineNumber) {
// bail out early if things seems fishy
return 0;
}
// read the marker (in case suggest was triggered at line end) or compare
// the cursor to the line end.
if (this._marker) {
const range = this._model.getDecorationRange(this._marker);
const end = this._model.getOffsetAt(range.getStartPosition());
return end - this._model.getOffsetAt(position);
}
else {
return this._model.getLineMaxColumn(position.lineNumber) - position.column;
}
}
}
let SuggestController = class SuggestController {
static { SuggestController_1 = this; }
static { this.ID = 'editor.contrib.suggestController'; }
static get(editor) {
return editor.getContribution(SuggestController_1.ID);
}
constructor(editor, _memoryService, _commandService, _contextKeyService, _instantiationService, _logService, _telemetryService) {
this._memoryService = _memoryService;
this._commandService = _commandService;
this._contextKeyService = _contextKeyService;
this._instantiationService = _instantiationService;
this._logService = _logService;
this._telemetryService = _telemetryService;
this._lineSuffix = new MutableDisposable();
this._toDispose = new DisposableStore();
this._selectors = new PriorityRegistry(s => s.priority);
this._onWillInsertSuggestItem = new Emitter();
this.onWillInsertSuggestItem = this._onWillInsertSuggestItem.event;
this.editor = editor;
this.model = _instantiationService.createInstance(SuggestModel, this.editor);
// default selector
this._selectors.register({
priority: 0,
select: (model, pos, items) => this._memoryService.select(model, pos, items)
});
// context key: update insert/replace mode
const ctxInsertMode = SuggestContext.InsertMode.bindTo(_contextKeyService);
ctxInsertMode.set(editor.getOption(119 /* EditorOption.suggest */).insertMode);
this._toDispose.add(this.model.onDidTrigger(() => ctxInsertMode.set(editor.getOption(119 /* EditorOption.suggest */).insertMode)));
this.widget = this._toDispose.add(new WindowIdleValue(getWindow(editor.getDomNode()), () => {
const widget = this._instantiationService.createInstance(SuggestWidget, this.editor);
this._toDispose.add(widget);
this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, 0 /* InsertFlags.None */), this));
// Wire up logic to accept a suggestion on certain characters
const commitCharacterController = new CommitCharacterController(this.editor, widget, this.model, item => this._insertSuggestion(item, 2 /* InsertFlags.NoAfterUndoStop */));
this._toDispose.add(commitCharacterController);
// Wire up makes text edit context key
const ctxMakesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
const ctxHasInsertAndReplace = SuggestContext.HasInsertAndReplaceRange.bindTo(this._contextKeyService);
const ctxCanResolve = SuggestContext.CanResolve.bindTo(this._contextKeyService);
this._toDispose.add(toDisposable(() => {
ctxMakesTextEdit.reset();
ctxHasInsertAndReplace.reset();
ctxCanResolve.reset();
}));
this._toDispose.add(widget.onDidFocus(({ item }) => {
// (ctx: makesTextEdit)
const position = this.editor.getPosition();
const startColumn = item.editStart.column;
const endColumn = position.column;
let value = true;
if (this.editor.getOption(1 /* EditorOption.acceptSuggestionOnEnter */) === 'smart'
&& this.model.state === 2 /* State.Auto */
&& !item.completion.additionalTextEdits
&& !(item.completion.insertTextRules & 4 /* CompletionItemInsertTextRule.InsertAsSnippet */)
&& endColumn - startColumn === item.completion.insertText.length) {
const oldText = this.editor.getModel().getValueInRange({
startLineNumber: position.lineNumber,
startColumn,
endLineNumber: position.lineNumber,
endColumn
});
value = oldText !== item.completion.insertText;
}
ctxMakesTextEdit.set(value);
// (ctx: hasInsertAndReplaceRange)
ctxHasInsertAndReplace.set(!Position.equals(item.editInsertEnd, item.editReplaceEnd));
// (ctx: canResolve)
ctxCanResolve.set(Boolean(item.provider.resolveCompletionItem) || Boolean(item.completion.documentation) || item.completion.detail !== item.completion.label);
}));
this._toDispose.add(widget.onDetailsKeyDown(e => {
// cmd + c on macOS, ctrl + c on Win / Linux
if (e.toKeyCodeChord().equals(new KeyCodeChord(true, false, false, false, 33 /* KeyCode.KeyC */)) ||
(platform.isMacintosh && e.toKeyCodeChord().equals(new KeyCodeChord(false, false, false, true, 33 /* KeyCode.KeyC */)))) {
e.stopPropagation();
return;
}
if (!e.toKeyCodeChord().isModifierKey()) {
this.editor.focus();
}
}));
return widget;
}));
// Wire up text overtyping capture
this._overtypingCapturer = this._toDispose.add(new WindowIdleValue(getWindow(editor.getDomNode()), () => {
return this._toDispose.add(new OvertypingCapturer(this.editor, this.model));
}));
this._alternatives = this._toDispose.add(new WindowIdleValue(getWindow(editor.getDomNode()), () => {
return this._toDispose.add(new SuggestAlternatives(this.editor, this._contextKeyService));
}));
this._toDispose.add(_instantiationService.createInstance(WordContextKey, editor));
this._toDispose.add(this.model.onDidTrigger(e => {
this.widget.value.showTriggered(e.auto, e.shy ? 250 : 50);
this._lineSuffix.value = new LineSuffix(this.editor.getModel(), e.position);
}));
this._toDispose.add(this.model.onDidSuggest(e => {
if (e.triggerOptions.shy) {
return;
}
let index = -1;
for (const selector of this._selectors.itemsOrderedByPriorityDesc) {
index = selector.select(this.editor.getModel(), this.editor.getPosition(), e.completionModel.items);
if (index !== -1) {
break;
}
}
if (index === -1) {
index = 0;
}
if (this.model.state === 0 /* State.Idle */) {
// selecting an item can "pump" out selection/cursor change events
// which can cancel suggest halfway through this function. therefore
// we need to check again and bail if the session has been canceled
return;
}
let noFocus = false;
if (e.triggerOptions.auto) {
// don't "focus" item when configured to do
const options = this.editor.getOption(119 /* EditorOption.suggest */);
if (options.selectionMode === 'never' || options.selectionMode === 'always') {
// simple: always or never
noFocus = options.selectionMode === 'never';
}
else if (options.selectionMode === 'whenTriggerCharacter') {
// on with trigger character
noFocus = e.triggerOptions.triggerKind !== 1 /* CompletionTriggerKind.TriggerCharacter */;
}
else if (options.selectionMode === 'whenQuickSuggestion') {
// without trigger character or when refiltering
noFocus = e.triggerOptions.triggerKind === 1 /* CompletionTriggerKind.TriggerCharacter */ && !e.triggerOptions.refilter;
}
}
this.widget.value.showSuggestions(e.completionModel, index, e.isFrozen, e.triggerOptions.auto, noFocus);
}));
this._toDispose.add(this.model.onDidCancel(e => {
if (!e.retrigger) {
this.widget.value.hideWidget();
}
}));
this._toDispose.add(this.editor.onDidBlurEditorWidget(() => {
if (!_sticky) {
this.model.cancel();
this.model.clear();
}
}));
// Manage the acceptSuggestionsOnEnter context key
const acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
const updateFromConfig = () => {
const acceptSuggestionOnEnter = this.editor.getOption(1 /* EditorOption.acceptSuggestionOnEnter */);
acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart');
};
this._toDispose.add(this.editor.onDidChangeConfiguration(() => updateFromConfig()));
updateFromConfig();
}
dispose() {
this._alternatives.dispose();
this._toDispose.dispose();
this.widget.dispose();
this.model.dispose();
this._lineSuffix.dispose();
this._onWillInsertSuggestItem.dispose();
}
_insertSuggestion(event, flags) {
if (!event || !event.item) {
this._alternatives.value.reset();
this.model.cancel();
this.model.clear();
return;
}
if (!this.editor.hasModel()) {
return;
}
const snippetController = SnippetController2.get(this.editor);
if (!snippetController) {
return;
}
this._onWillInsertSuggestItem.fire({ item: event.item });
const model = this.editor.getModel();
const modelVersionNow = model.getAlternativeVersionId();
const { item } = event;
//
const tasks = [];
const cts = new CancellationTokenSource();
// pushing undo stops *before* additional text edits and
// *after* the main edit
if (!(flags & 1 /* InsertFlags.NoBeforeUndoStop */)) {
this.editor.pushUndoStop();
}
// compute overwrite[Before|After] deltas BEFORE applying extra edits
const info = this.getOverwriteInfo(item, Boolean(flags & 8 /* InsertFlags.AlternativeOverwriteConfig */));
// keep item in memory
this._memoryService.memorize(model, this.editor.getPosition(), item);
const isResolved = item.isResolved;
// telemetry data points: duration of command execution, info about async additional edits (-1=n/a, -2=none, 1=success, 0=failed)
let _commandExectionDuration = -1;
let _additionalEditsAppliedAsync = -1;
if (Array.isArray(item.completion.additionalTextEdits)) {
// cancel -> stops all listening and closes widget
this.model.cancel();
// sync additional edits
const scrollState = StableEditorScrollState.capture(this.editor);
this.editor.executeEdits('suggestController.additionalTextEdits.sync', item.completion.additionalTextEdits.map(edit => {
let range = Range.lift(edit.range);
if (range.startLineNumber === item.position.lineNumber && range.startColumn > item.position.column) {
// shift additional edit when it is "after" the completion insertion position
const columnDelta = this.editor.getPosition().column - item.position.column;
const startColumnDelta = columnDelta;
const endColumnDelta = Range.spansMultipleLines(range) ? 0 : columnDelta;
range = new Range(range.startLineNumber, range.startColumn + startColumnDelta, range.endLineNumber, range.endColumn + endColumnDelta);
}
return EditOperation.replaceMove(range, edit.text);
}));
scrollState.restoreRelativeVerticalPositionOfCursor(this.editor);
}
else if (!isResolved) {
// async additional edits
const sw = new StopWatch();
let position;
const docListener = model.onDidChangeContent(e => {
if (e.isFlush) {
cts.cancel();
docListener.dispose();
return;
}
for (const change of e.changes) {
const thisPosition = Range.getEndPosition(change.range);
if (!position || Position.isBefore(thisPosition, position)) {
position = thisPosition;
}
}
});
const oldFlags = flags;
flags |= 2 /* InsertFlags.NoAfterUndoStop */;
let didType = false;
const typeListener = this.editor.onWillType(() => {
typeListener.dispose();
didType = true;
if (!(oldFlags & 2 /* InsertFlags.NoAfterUndoStop */)) {
this.editor.pushUndoStop();
}
});
tasks.push(item.resolve(cts.token).then(() => {
if (!item.completion.additionalTextEdits || cts.token.isCancellationRequested) {
return undefined;
}
if (position && item.completion.additionalTextEdits.some(edit => Position.isBefore(position, Range.getStartPosition(edit.range)))) {
return false;
}
if (didType) {
this.editor.pushUndoStop();
}
const scrollState = StableEditorScrollState.capture(this.editor);
this.editor.executeEdits('suggestController.additionalTextEdits.async', item.completion.additionalTextEdits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)));
scrollState.restoreRelativeVerticalPositionOfCursor(this.editor);
if (didType || !(oldFlags & 2 /* InsertFlags.NoAfterUndoStop */)) {
this.editor.pushUndoStop();
}
return true;
}).then(applied => {
this._logService.trace('[suggest] async resolving of edits DONE (ms, applied?)', sw.elapsed(), applied);
_additionalEditsAppliedAsync = applied === true ? 1 : applied === false ? 0 : -2;
}).finally(() => {
docListener.dispose();
typeListener.dispose();
}));
}
let { insertText } = item.completion;
if (!(item.completion.insertTextRules & 4 /* CompletionItemInsertTextRule.InsertAsSnippet */)) {
insertText = SnippetParser.escape(insertText);
}
// cancel -> stops all listening and closes widget
this.model.cancel();
snippetController.insert(insertText, {
overwriteBefore: info.overwriteBefore,
overwriteAfter: info.overwriteAfter,
undoStopBefore: false,
undoStopAfter: false,
adjustWhitespace: !(item.completion.insertTextRules & 1 /* CompletionItemInsertTextRule.KeepWhitespace */),
clipboardText: event.model.clipboardText,
overtypingCapturer: this._overtypingCapturer.value
});
if (!(flags & 2 /* InsertFlags.NoAfterUndoStop */)) {
this.editor.pushUndoStop();
}
if (item.completion.command) {
if (item.completion.command.id === TriggerSuggestAction.id) {
// retigger
this.model.trigger({ auto: true, retrigger: true });
}
else {
// exec command, done
const sw = new StopWatch();
tasks.push(this._commandService.executeCommand(item.completion.command.id, ...(item.completion.command.arguments ? [...item.completion.command.arguments] : [])).catch(e => {
if (item.completion.extensionId) {
onUnexpectedExternalError(e);
}
else {
onUnexpectedError(e);
}
}).finally(() => {
_commandExectionDuration = sw.elapsed();
}));
}
}
if (flags & 4 /* InsertFlags.KeepAlternativeSuggestions */) {
this._alternatives.value.set(event, next => {
// cancel resolving of additional edits
cts.cancel();
// this is not so pretty. when inserting the 'next'
// suggestion we undo until we are at the state at
// which we were before inserting the previous suggestion...
while (model.canUndo()) {
if (modelVersionNow !== model.getAlternativeVersionId()) {
model.undo();
}
this._insertSuggestion(next, 1 /* InsertFlags.NoBeforeUndoStop */ | 2 /* InsertFlags.NoAfterUndoStop */ | (flags & 8 /* InsertFlags.AlternativeOverwriteConfig */ ? 8 /* InsertFlags.AlternativeOverwriteConfig */ : 0));
break;
}
});
}
this._alertCompletionItem(item);
// clear only now - after all tasks are done
Promise.all(tasks).finally(() => {
this._reportSuggestionAcceptedTelemetry(item, model, isResolved, _commandExectionDuration, _additionalEditsAppliedAsync, event.index, event.model.items);
this.model.clear();
cts.dispose();
});
}
_reportSuggestionAcceptedTelemetry(item, model, itemResolved, commandExectionDuration, additionalEditsAppliedAsync, index, completionItems) {
if (Math.floor(Math.random() * 100) === 0) {
// throttle telemetry event because accepting completions happens a lot
return;
}
const labelMap = new Map();
for (let i = 0; i < Math.min(30, completionItems.length); i++) {
const label = completionItems[i].textLabel;
if (labelMap.has(label)) {
labelMap.get(label).push(i);
}
else {
labelMap.set(label, [i]);
}
}
const firstIndexArray = labelMap.get(item.textLabel);
const hasDuplicates = firstIndexArray && firstIndexArray.length > 1;
const firstIndex = hasDuplicates ? firstIndexArray[0] : -1;
this._telemetryService.publicLog2('suggest.acceptedSuggestion', {
extensionId: item.extensionId?.value ?? 'unknown',
providerId: item.provider._debugDisplayName ?? 'unknown',
kind: item.completion.kind,
basenameHash: hash(basename(model.uri)).toString(16),
languageId: model.getLanguageId(),
fileExtension: extname(model.uri),
resolveInfo: !item.provider.resolveCompletionItem ? -1 : itemResolved ? 1 : 0,
resolveDuration: item.resolveDuration,
commandDuration: commandExectionDuration,
additionalEditsAsync: additionalEditsAppliedAsync,
index,
firstIndex,
});
}
getOverwriteInfo(item, toggleMode) {
assertType(this.editor.hasModel());
let replace = this.editor.getOption(119 /* EditorOption.suggest */).insertMode === 'replace';
if (toggleMode) {
replace = !replace;
}
const overwriteBefore = item.position.column - item.editStart.column;
const overwriteAfter = (replace ? item.editReplaceEnd.column : item.editInsertEnd.column) - item.position.column;
const columnDelta = this.editor.getPosition().column - item.position.column;
const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this.editor.getPosition()) : 0;
return {
overwriteBefore: overwriteBefore + columnDelta,
overwriteAfter: overwriteAfter + suffixDelta
};
}
_alertCompletionItem(item) {
if (isNonEmptyArray(item.completion.additionalTextEdits)) {
const msg = nls.localize('aria.alert.snippet', "Accepting '{0}' made {1} additional edits", item.textLabel, item.completion.additionalTextEdits.length);
alert(msg);
}
}
triggerSuggest(onlyFrom, auto, noFilter) {
if (this.editor.hasModel()) {
this.model.trigger({
auto: auto ?? false,
completionOptions: { providerFilter: onlyFrom, kindFilter: noFilter ? new Set() : undefined }
});
this.editor.revealPosition(this.editor.getPosition(), 0 /* ScrollType.Smooth */);
this.editor.focus();
}
}
triggerSuggestAndAcceptBest(arg) {
if (!this.editor.hasModel()) {
return;
}
const positionNow = this.editor.getPosition();
const fallback = () => {
if (positionNow.equals(this.editor.getPosition())) {
this._commandService.executeCommand(arg.fallback);
}
};
const makesTextEdit = (item) => {
if (item.completion.insertTextRules & 4 /* CompletionItemInsertTextRule.InsertAsSnippet */ || item.completion.additionalTextEdits) {
// snippet, other editor -> makes edit
return true;
}
const position = this.editor.getPosition();
const startColumn = item.editStart.column;
const endColumn = position.column;
if (endColumn - startColumn !== item.completion.insertText.length) {
// unequal lengths -> makes edit
return true;
}
const textNow = this.editor.getModel().getValueInRange({
startLineNumber: position.lineNumber,
startColumn,
endLineNumber: position.lineNumber,
endColumn
});
// unequal text -> makes edit
return textNow !== item.completion.insertText;
};
Event.once(this.model.onDidTrigger)(_ => {
// wait for trigger because only then the cancel-event is trustworthy
const listener = [];
Event.any(this.model.onDidTrigger, this.model.onDidCancel)(() => {
// retrigger or cancel -> try to type default text
dispose(listener);
fallback();
}, undefined, listener);
this.model.onDidSuggest(({ completionModel }) => {
dispose(listener);
if (completionModel.items.length === 0) {
fallback();
return;
}
const index = this._memoryService.select(this.editor.getModel(), this.editor.getPosition(), completionModel.items);
const item = completionModel.items[index];
if (!makesTextEdit(item)) {
fallback();
return;
}
this.editor.pushUndoStop();
this._insertSuggestion({ index, item, model: completionModel }, 4 /* InsertFlags.KeepAlternativeSuggestions */ | 1 /* InsertFlags.NoBeforeUndoStop */ | 2 /* InsertFlags.NoAfterUndoStop */);
}, undefined, listener);
});
this.model.trigger({ auto: false, shy: true });
this.editor.revealPosition(positionNow, 0 /* ScrollType.Smooth */);
this.editor.focus();
}
acceptSelectedSuggestion(keepAlternativeSuggestions, alternativeOverwriteConfig) {
const item = this.widget.value.getFocusedItem();
let flags = 0;
if (keepAlternativeSuggestions) {
flags |= 4 /* InsertFlags.KeepAlternativeSuggestions */;
}
if (alternativeOverwriteConfig) {
flags |= 8 /* InsertFlags.AlternativeOverwriteConfig */;
}
this._insertSuggestion(item, flags);
}
acceptNextSuggestion() {
this._alternatives.value.next();
}
acceptPrevSuggestion() {
this._alternatives.value.prev();
}
cancelSuggestWidget() {
this.model.cancel();
this.model.clear();
this.widget.value.hideWidget();
}
focusSuggestion() {
this.widget.value.focusSelected();
}
selectNextSuggestion() {
this.widget.value.selectNext();
}
selectNextPageSuggestion() {
this.widget.value.selectNextPage();
}
selectLastSuggestion() {
this.widget.value.selectLast();
}
selectPrevSuggestion() {
this.widget.value.selectPrevious();
}
selectPrevPageSuggestion() {
this.widget.value.selectPreviousPage();
}
selectFirstSuggestion() {
this.widget.value.selectFirst();
}
toggleSuggestionDetails() {
this.widget.value.toggleDetails();
}
toggleExplainMode() {
this.widget.value.toggleExplainMode();
}
toggleSuggestionFocus() {
this.widget.value.toggleDetailsFocus();
}
resetWidgetSize() {
this.widget.value.resetPersistedSize();
}
forceRenderingAbove() {
this.widget.value.forceRenderingAbove();
}
stopForceRenderingAbove() {
if (!this.widget.isInitialized) {
// This method has no effect if the widget is not initialized yet.
return;
}
this.widget.value.stopForceRenderingAbove();
}
registerSelector(selector) {
return this._selectors.register(selector);
}
};
SuggestController = SuggestController_1 = __decorate([
__param(1, ISuggestMemoryService),
__param(2, ICommandService),
__param(3, IContextKeyService),
__param(4, IInstantiationService),
__param(5, ILogService),
__param(6, ITelemetryService)
], SuggestController);
export { SuggestController };
class PriorityRegistry {
constructor(prioritySelector) {
this.prioritySelector = prioritySelector;
this._items = new Array();
}
register(value) {
if (this._items.indexOf(value) !== -1) {
throw new Error('Value is already registered');
}
this._items.push(value);
this._items.sort((s1, s2) => this.prioritySelector(s2) - this.prioritySelector(s1));
return {
dispose: () => {
const idx = this._items.indexOf(value);
if (idx >= 0) {
this._items.splice(idx, 1);
}
}
};
}
get itemsOrderedByPriorityDesc() {
return this._items;
}
}
export class TriggerSuggestAction extends EditorAction {
static { this.id = 'editor.action.triggerSuggest'; }
constructor() {
super({
id: TriggerSuggestAction.id,
label: nls.localize('suggest.trigger.label', "Trigger Suggest"),
alias: 'Trigger Suggest',
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCompletionItemProvider, SuggestContext.Visible.toNegated()),
kbOpts: {
kbExpr: EditorContextKeys.textInputFocus,
primary: 2048 /* KeyMod.CtrlCmd */ | 10 /* KeyCode.Space */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */],
mac: { primary: 256 /* KeyMod.WinCtrl */ | 10 /* KeyCode.Space */, secondary: [512 /* KeyMod.Alt */ | 9 /* KeyCode.Escape */, 2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */] },
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
run(_accessor, editor, args) {
const controller = SuggestController.get(editor);
if (!controller) {
return;
}
let auto;
if (args && typeof args === 'object') {
if (args.auto === true) {
auto = true;
}
}
controller.triggerSuggest(undefined, auto, undefined);
}
}
registerEditorContribution(SuggestController.ID, SuggestController, 2 /* EditorContributionInstantiation.BeforeFirstInteraction */);
registerEditorAction(TriggerSuggestAction);
const weight = 100 /* KeybindingWeight.EditorContrib */ + 90;
const SuggestCommand = EditorCommand.bindToContribution(SuggestController.get);
registerEditorCommand(new SuggestCommand({
id: 'acceptSelectedSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.HasFocusedSuggestion),
handler(x) {
x.acceptSelectedSuggestion(true, false);
},
kbOpts: [{
// normal tab
primary: 2 /* KeyCode.Tab */,
kbExpr: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus),
weight,
}, {
// accept on enter has special rules
primary: 3 /* KeyCode.Enter */,
kbExpr: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.AcceptSuggestionsOnEnter, SuggestContext.MakesTextEdit),
weight,
}],
menuOpts: [{
menuId: suggestWidgetStatusbarMenu,
title: nls.localize('accept.insert', "Insert"),
group: 'left',
order: 1,
when: SuggestContext.HasInsertAndReplaceRange.toNegated()
}, {
menuId: suggestWidgetStatusbarMenu,
title: nls.localize('accept.insert', "Insert"),
group: 'left',
order: 1,
when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, SuggestContext.InsertMode.isEqualTo('insert'))
}, {
menuId: suggestWidgetStatusbarMenu,
title: nls.localize('accept.replace', "Replace"),
group: 'left',
order: 1,
when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, SuggestContext.InsertMode.isEqualTo('replace'))
}]
}));
registerEditorCommand(new SuggestCommand({
id: 'acceptAlternativeSelectedSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.HasFocusedSuggestion),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 1024 /* KeyMod.Shift */ | 3 /* KeyCode.Enter */,
secondary: [1024 /* KeyMod.Shift */ | 2 /* KeyCode.Tab */],
},
handler(x) {
x.acceptSelectedSuggestion(false, true);
},
menuOpts: [{
menuId: suggestWidgetStatusbarMenu,
group: 'left',
order: 2,
when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, SuggestContext.InsertMode.isEqualTo('insert')),
title: nls.localize('accept.replace', "Replace")
}, {
menuId: suggestWidgetStatusbarMenu,
group: 'left',
order: 2,
when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, SuggestContext.InsertMode.isEqualTo('replace')),
title: nls.localize('accept.insert', "Insert")
}]
}));
// continue to support the old command
CommandsRegistry.registerCommandAlias('acceptSelectedSuggestionOnEnter', 'acceptSelectedSuggestion');
registerEditorCommand(new SuggestCommand({
id: 'hideSuggestWidget',
precondition: SuggestContext.Visible,
handler: x => x.cancelSuggestWidget(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 9 /* KeyCode.Escape */,
secondary: [1024 /* KeyMod.Shift */ | 9 /* KeyCode.Escape */]
}
}));
registerEditorCommand(new SuggestCommand({
id: 'selectNextSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, ContextKeyExpr.or(SuggestContext.MultipleSuggestions, SuggestContext.HasFocusedSuggestion.negate())),
handler: c => c.selectNextSuggestion(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 18 /* KeyCode.DownArrow */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 18 /* KeyCode.DownArrow */],
mac: { primary: 18 /* KeyCode.DownArrow */, secondary: [2048 /* KeyMod.CtrlCmd */ | 18 /* KeyCode.DownArrow */, 256 /* KeyMod.WinCtrl */ | 44 /* KeyCode.KeyN */] }
}
}));
registerEditorCommand(new SuggestCommand({
id: 'selectNextPageSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, ContextKeyExpr.or(SuggestContext.MultipleSuggestions, SuggestContext.HasFocusedSuggestion.negate())),
handler: c => c.selectNextPageSuggestion(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 12 /* KeyCode.PageDown */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 12 /* KeyCode.PageDown */]
}
}));
registerEditorCommand(new SuggestCommand({
id: 'selectLastSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, ContextKeyExpr.or(SuggestContext.MultipleSuggestions, SuggestContext.HasFocusedSuggestion.negate())),
handler: c => c.selectLastSuggestion()
}));
registerEditorCommand(new SuggestCommand({
id: 'selectPrevSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, ContextKeyExpr.or(SuggestContext.MultipleSuggestions, SuggestContext.HasFocusedSuggestion.negate())),
handler: c => c.selectPrevSuggestion(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 16 /* KeyCode.UpArrow */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 16 /* KeyCode.UpArrow */],
mac: { primary: 16 /* KeyCode.UpArrow */, secondary: [2048 /* KeyMod.CtrlCmd */ | 16 /* KeyCode.UpArrow */, 256 /* KeyMod.WinCtrl */ | 46 /* KeyCode.KeyP */] }
}
}));
registerEditorCommand(new SuggestCommand({
id: 'selectPrevPageSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, ContextKeyExpr.or(SuggestContext.MultipleSuggestions, SuggestContext.HasFocusedSuggestion.negate())),
handler: c => c.selectPrevPageSuggestion(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 11 /* KeyCode.PageUp */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 11 /* KeyCode.PageUp */]
}
}));
registerEditorCommand(new SuggestCommand({
id: 'selectFirstSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, ContextKeyExpr.or(SuggestContext.MultipleSuggestions, SuggestContext.HasFocusedSuggestion.negate())),
handler: c => c.selectFirstSuggestion()
}));
registerEditorCommand(new SuggestCommand({
id: 'focusSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.HasFocusedSuggestion.negate()),
handler: x => x.focusSuggestion(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 2048 /* KeyMod.CtrlCmd */ | 10 /* KeyCode.Space */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */],
mac: { primary: 256 /* KeyMod.WinCtrl */ | 10 /* KeyCode.Space */, secondary: [2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */] }
},
}));
registerEditorCommand(new SuggestCommand({
id: 'focusAndAcceptSuggestion',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.HasFocusedSuggestion.negate()),
handler: c => {
c.focusSuggestion();
c.acceptSelectedSuggestion(true, false);
}
}));
registerEditorCommand(new SuggestCommand({
id: 'toggleSuggestionDetails',
precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.HasFocusedSuggestion),
handler: x => x.toggleSuggestionDetails(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 2048 /* KeyMod.CtrlCmd */ | 10 /* KeyCode.Space */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */],
mac: { primary: 256 /* KeyMod.WinCtrl */ | 10 /* KeyCode.Space */, secondary: [2048 /* KeyMod.CtrlCmd */ | 39 /* KeyCode.KeyI */] }
},
menuOpts: [{
menuId: suggestWidgetStatusbarMenu,
group: 'right',
order: 1,
when: ContextKeyExpr.and(SuggestContext.DetailsVisible, SuggestContext.CanResolve),
title: nls.localize('detail.more', "Show Less")
}, {
menuId: suggestWidgetStatusbarMenu,
group: 'right',
order: 1,
when: ContextKeyExpr.and(SuggestContext.DetailsVisible.toNegated(), SuggestContext.CanResolve),
title: nls.localize('detail.less', "Show More")
}]
}));
registerEditorCommand(new SuggestCommand({
id: 'toggleExplainMode',
precondition: SuggestContext.Visible,
handler: x => x.toggleExplainMode(),
kbOpts: {
weight: 100 /* KeybindingWeight.EditorContrib */,
primary: 2048 /* KeyMod.CtrlCmd */ | 90 /* KeyCode.Slash */,
}
}));
registerEditorCommand(new SuggestCommand({
id: 'toggleSuggestionFocus',
precondition: SuggestContext.Visible,
handler: x => x.toggleSuggestionFocus(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 2048 /* KeyMod.CtrlCmd */ | 512 /* KeyMod.Alt */ | 10 /* KeyCode.Space */,
mac: { primary: 256 /* KeyMod.WinCtrl */ | 512 /* KeyMod.Alt */ | 10 /* KeyCode.Space */ }
}
}));
//#region tab completions
registerEditorCommand(new SuggestCommand({
id: 'insertBestCompletion',
precondition: ContextKeyExpr.and(EditorContextKeys.textInputFocus, ContextKeyExpr.equals('config.editor.tabCompletion', 'on'), WordContextKey.AtEnd, SuggestContext.Visible.toNegated(), SuggestAlternatives.OtherSuggestions.toNegated(), SnippetController2.InSnippetMode.toNegated()),
handler: (x, arg) => {
x.triggerSuggestAndAcceptBest(isObject(arg) ? { fallback: 'tab', ...arg } : { fallback: 'tab' });
},
kbOpts: {
weight,
primary: 2 /* KeyCode.Tab */
}
}));
registerEditorCommand(new SuggestCommand({
id: 'insertNextSuggestion',
precondition: ContextKeyExpr.and(EditorContextKeys.textInputFocus, ContextKeyExpr.equals('config.editor.tabCompletion', 'on'), SuggestAlternatives.OtherSuggestions, SuggestContext.Visible.toNegated(), SnippetController2.InSnippetMode.toNegated()),
handler: x => x.acceptNextSuggestion(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 2 /* KeyCode.Tab */
}
}));
registerEditorCommand(new SuggestCommand({
id: 'insertPrevSuggestion',
precondition: ContextKeyExpr.and(EditorContextKeys.textInputFocus, ContextKeyExpr.equals('config.editor.tabCompletion', 'on'), SuggestAlternatives.OtherSuggestions, SuggestContext.Visible.toNegated(), SnippetController2.InSnippetMode.toNegated()),
handler: x => x.acceptPrevSuggestion(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
primary: 1024 /* KeyMod.Shift */ | 2 /* KeyCode.Tab */
}
}));
registerEditorAction(class extends EditorAction {
constructor() {
super({
id: 'editor.action.resetSuggestSize',
label: nls.localize('suggest.reset.label', "Reset Suggest Widget Size"),
alias: 'Reset Suggest Widget Size',
precondition: undefined
});
}
run(_accessor, editor) {
SuggestController.get(editor)?.resetWidgetSize();
}
});