UNPKG

monaco-editor

Version:
186 lines (185 loc) • 9.14 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { SelectedSuggestionInfo } from '../../../common/languages.js'; import { SnippetParser } from '../../snippet/browser/snippetParser.js'; import { SnippetSession } from '../../snippet/browser/snippetSession.js'; import { SuggestController } from '../../suggest/browser/suggestController.js'; import { observableValue, transaction } from '../../../../base/common/observable.js'; import { SingleTextEdit } from './singleTextEdit.js'; import { compareBy, numberComparator } from '../../../../base/common/arrays.js'; import { findFirstMaxBy } from '../../../../base/common/arraysFind.js'; export class SuggestWidgetAdaptor extends Disposable { get selectedItem() { return this._selectedItem; } constructor(editor, suggestControllerPreselector, checkModelVersion, onWillAccept) { super(); this.editor = editor; this.suggestControllerPreselector = suggestControllerPreselector; this.checkModelVersion = checkModelVersion; this.onWillAccept = onWillAccept; this.isSuggestWidgetVisible = false; this.isShiftKeyPressed = false; this._isActive = false; this._currentSuggestItemInfo = undefined; this._selectedItem = observableValue(this, undefined); // See the command acceptAlternativeSelectedSuggestion that is bound to shift+tab this._register(editor.onKeyDown(e => { if (e.shiftKey && !this.isShiftKeyPressed) { this.isShiftKeyPressed = true; this.update(this._isActive); } })); this._register(editor.onKeyUp(e => { if (e.shiftKey && this.isShiftKeyPressed) { this.isShiftKeyPressed = false; this.update(this._isActive); } })); const suggestController = SuggestController.get(this.editor); if (suggestController) { this._register(suggestController.registerSelector({ priority: 100, select: (model, pos, suggestItems) => { var _a; transaction(tx => this.checkModelVersion(tx)); const textModel = this.editor.getModel(); if (!textModel) { // Should not happen return -1; } const itemToPreselect = (_a = this.suggestControllerPreselector()) === null || _a === void 0 ? void 0 : _a.removeCommonPrefix(textModel); if (!itemToPreselect) { return -1; } const position = Position.lift(pos); const candidates = suggestItems .map((suggestItem, index) => { const suggestItemInfo = SuggestItemInfo.fromSuggestion(suggestController, textModel, position, suggestItem, this.isShiftKeyPressed); const suggestItemTextEdit = suggestItemInfo.toSingleTextEdit().removeCommonPrefix(textModel); const valid = itemToPreselect.augments(suggestItemTextEdit); return { index, valid, prefixLength: suggestItemTextEdit.text.length, suggestItem }; }) .filter(item => item && item.valid && item.prefixLength > 0); const result = findFirstMaxBy(candidates, compareBy(s => s.prefixLength, numberComparator)); return result ? result.index : -1; } })); let isBoundToSuggestWidget = false; const bindToSuggestWidget = () => { if (isBoundToSuggestWidget) { return; } isBoundToSuggestWidget = true; this._register(suggestController.widget.value.onDidShow(() => { this.isSuggestWidgetVisible = true; this.update(true); })); this._register(suggestController.widget.value.onDidHide(() => { this.isSuggestWidgetVisible = false; this.update(false); })); this._register(suggestController.widget.value.onDidFocus(() => { this.isSuggestWidgetVisible = true; this.update(true); })); }; this._register(Event.once(suggestController.model.onDidTrigger)(e => { bindToSuggestWidget(); })); this._register(suggestController.onWillInsertSuggestItem(e => { const position = this.editor.getPosition(); const model = this.editor.getModel(); if (!position || !model) { return undefined; } const suggestItemInfo = SuggestItemInfo.fromSuggestion(suggestController, model, position, e.item, this.isShiftKeyPressed); this.onWillAccept(suggestItemInfo); })); } this.update(this._isActive); } update(newActive) { const newInlineCompletion = this.getSuggestItemInfo(); if (this._isActive !== newActive || !suggestItemInfoEquals(this._currentSuggestItemInfo, newInlineCompletion)) { this._isActive = newActive; this._currentSuggestItemInfo = newInlineCompletion; transaction(tx => { /** @description Update state from suggest widget */ this.checkModelVersion(tx); this._selectedItem.set(this._isActive ? this._currentSuggestItemInfo : undefined, tx); }); } } getSuggestItemInfo() { const suggestController = SuggestController.get(this.editor); if (!suggestController || !this.isSuggestWidgetVisible) { return undefined; } const focusedItem = suggestController.widget.value.getFocusedItem(); const position = this.editor.getPosition(); const model = this.editor.getModel(); if (!focusedItem || !position || !model) { return undefined; } return SuggestItemInfo.fromSuggestion(suggestController, model, position, focusedItem.item, this.isShiftKeyPressed); } stopForceRenderingAbove() { const suggestController = SuggestController.get(this.editor); suggestController === null || suggestController === void 0 ? void 0 : suggestController.stopForceRenderingAbove(); } forceRenderingAbove() { const suggestController = SuggestController.get(this.editor); suggestController === null || suggestController === void 0 ? void 0 : suggestController.forceRenderingAbove(); } } export class SuggestItemInfo { static fromSuggestion(suggestController, model, position, item, toggleMode) { let { insertText } = item.completion; let isSnippetText = false; if (item.completion.insertTextRules & 4 /* CompletionItemInsertTextRule.InsertAsSnippet */) { const snippet = new SnippetParser().parse(insertText); if (snippet.children.length < 100) { // Adjust whitespace is expensive. SnippetSession.adjustWhitespace(model, position, true, snippet); } insertText = snippet.toString(); isSnippetText = true; } const info = suggestController.getOverwriteInfo(item, toggleMode); return new SuggestItemInfo(Range.fromPositions(position.delta(0, -info.overwriteBefore), position.delta(0, Math.max(info.overwriteAfter, 0))), insertText, item.completion.kind, isSnippetText); } constructor(range, insertText, completionItemKind, isSnippetText) { this.range = range; this.insertText = insertText; this.completionItemKind = completionItemKind; this.isSnippetText = isSnippetText; } equals(other) { return this.range.equalsRange(other.range) && this.insertText === other.insertText && this.completionItemKind === other.completionItemKind && this.isSnippetText === other.isSnippetText; } toSelectedSuggestionInfo() { return new SelectedSuggestionInfo(this.range, this.insertText, this.completionItemKind, this.isSnippetText); } toSingleTextEdit() { return new SingleTextEdit(this.range, this.insertText); } } function suggestItemInfoEquals(a, b) { if (a === b) { return true; } if (!a || !b) { return false; } return a.equals(b); }