Monaco-editor: Disable suggestions filter

Created on 27 Mar 2020  路  11Comments  路  Source: microsoft/monaco-editor

I want to have autocompletion that doesn't necessarily require the completion item to start with the same letter as the word so far, e.g. I type foo and my CompletionItemProvider provides bar and baz. Since neither of these start foo no suggestions appear for the user. Basically I recognized that since the user typed foo they most likely meant either bar or baz.

help wanted suggest

All 11 comments

@shmish111 That should be possible.

See the attached code to be run in the Monaco Editor Playground. Even though the character is y, all suggestions show up (and none of them have the character y in them).

function createDependencyProposals(range) {
    return [
        {
            label: '"lodash"',
            kind: monaco.languages.CompletionItemKind.Function,
            documentation: "The Lodash library exported as Node.js modules.",
            insertText: '"lodash": "*"',
            range: range
        },
        {
            label: '"express"',
            kind: monaco.languages.CompletionItemKind.Function,
            documentation: "Fast, unopinionated, minimalist web framework",
            insertText: '"express": "*"',
            range: range
        },
        {
            label: '"mkdirp"',
            kind: monaco.languages.CompletionItemKind.Function,
            documentation: "Recursively mkdir, like <code>mkdir -p</code>",
            insertText: '"mkdirp": "*"',
            range: range
        }
    ];
}


monaco.languages.registerCompletionItemProvider('json', {
    provideCompletionItems: function(model, position) {
        var range = {
            startLineNumber: position.lineNumber,
            endLineNumber: position.lineNumber,
            startColumn: position.column,
            endColumn: position.column,
        };
        return {
            suggestions: createDependencyProposals(range)
        };
    }
});

monaco.editor.create(document.getElementById("container"), {
    value: "y",
    language: "json"
});

Ah ok, so the problem I have is that I had set the range to be the replacement range I wanted. So in this case y gets replaced with y"express": "*" but really you want it to be replaced with "express": "*" however if you set the range startColumn: range.startColumn - 1 then the suggestion does not show up:

function createDependencyProposals(range) {
  return [
    {
      label: '"lodash"',
      kind: monaco.languages.CompletionItemKind.Function,
      documentation: "The Lodash library exported as Node.js modules.",
      insertText: '"lodash": "*"',
      range: range
    },
    {
      label: '"express"',
      kind: monaco.languages.CompletionItemKind.Function,
      documentation: "Fast, unopinionated, minimalist web framework",
      insertText: '"express": "*"',
      range: range
    },
    {
      label: '"mkdirp"',
      kind: monaco.languages.CompletionItemKind.Function,
      documentation: "Recursively mkdir, like <code>mkdir -p</code>",
      insertText: '"mkdirp": "*"',
      range: {startLineNumber: range.startLineNumber, startColumn: range.startColumn - 1, endLineNumber: range.endLineNumber, endColumn: range.endColumn}
    }
  ];
}

i.e. in the playground this does not show the mkdirp suggestion. This seems a bit strange since in the example you gave the completion item will break the code you are writing.

Since this is closed I presume it won't be a feature coming any time soon? I appreciate it's probably not something many people would ever want, just me 馃槩

@shmish111 Sorry, maybe I missed something. What is it that you cannot achieve? Have you checked the documention for CompletionItem ?

    /**
     * A completion item represents a text snippet that is
     * proposed to complete text that is being typed.
     */
    export interface CompletionItem {
        /**
         * The label of this completion item. By default
         * this is also the text that is inserted when selecting
         * this completion.
         */
        label: string | CompletionItemLabel;
        /**
         * The kind of this completion item. Based on the kind
         * an icon is chosen by the editor.
         */
        kind: CompletionItemKind;
        /**
         * A modifier to the `kind` which affect how the item
         * is rendered, e.g. Deprecated is rendered with a strikeout
         */
        tags?: ReadonlyArray<CompletionItemTag>;
        /**
         * A human-readable string with additional information
         * about this item, like type or symbol information.
         */
        detail?: string;
        /**
         * A human-readable string that represents a doc-comment.
         */
        documentation?: string | IMarkdownString;
        /**
         * A string that should be used when comparing this item
         * with other items. When `falsy` the [label](#CompletionItem.label)
         * is used.
         */
        sortText?: string;
        /**
         * A string that should be used when filtering a set of
         * completion items. When `falsy` the [label](#CompletionItem.label)
         * is used.
         */
        filterText?: string;
        /**
         * Select this item when showing. *Note* that only one completion item can be selected and
         * that the editor decides which item that is. The rule is that the *first* item of those
         * that match best is selected.
         */
        preselect?: boolean;
        /**
         * A string or snippet that should be inserted in a document when selecting
         * this completion.
         * is used.
         */
        insertText: string;
        /**
         * Addition rules (as bitmask) that should be applied when inserting
         * this completion.
         */
        insertTextRules?: CompletionItemInsertTextRule;
        /**
         * A range of text that should be replaced by this completion item.
         *
         * Defaults to a range from the start of the [current word](#TextDocument.getWordRangeAtPosition) to the
         * current position.
         *
         * *Note:* The range must be a [single line](#Range.isSingleLine) and it must
         * [contain](#Range.contains) the position at which completion has been [requested](#CompletionItemProvider.provideCompletionItems).
         */
        range: IRange | {
            insert: IRange;
            replace: IRange;
        };
        /**
         * An optional set of characters that when pressed while this completion is active will accept it first and
         * then type that character. *Note* that all commit characters should have `length=1` and that superfluous
         * characters will be ignored.
         */
        commitCharacters?: string[];
        /**
         * An optional array of additional text edits that are applied when
         * selecting this completion. Edits must not overlap with the main edit
         * nor with themselves.
         */
        additionalTextEdits?: editor.ISingleEditOperation[];
        /**
         * A command that should be run upon acceptance of this item.
         */
        command?: Command;
    }

I _think_ I understand it correctly and it says *Note:* The range must be a [single line] and it must [contain] the position at which completion has been which seems not to be quite true. What is actually happening is that the range must start from the column that the position is currently at, you cannot set a range that starts before the position and finishes after. So in your example, if you change the range to the following you get no results:

        var range = {
            startLineNumber: position.lineNumber,
            endLineNumber: position.lineNumber,
            startColumn: (position.column - 1), // this starts before the cursor
            endColumn: position.column,
        };

Actually, given the documentation this seems like a bug to me.

@shmish111 Unfortunately, I am not the author of that code, it just gets extracted from VS Code and ships here as well. My personal recommendation to get a more meaningful answer is to create a small VS Code extension that uses the completion provider API and demonstrates a problem and then create an issue against VS Code at https://github.com/microsoft/vscode . That will end up with an issue that will go to the author of that code.

Awesome, thanks @alexdima , I'll try and get that done and post a link to it here, however that's quite an undertaking for me as I have not used the VSCode API.

@shmish111 I got exactly the same issue.

In case it would help you, here the workaround I used:

Monaco extracts the string matching your CompletionItem.range and keeps the suggestion only if its CompletionItem.label or CompletionItem.filterText match it.
As I wanted to keep the label, I just defined CompletionItem.filterText attribute with the string matching CompletionItem.range.

With your previous example, we have:

{
      label: '"express"',
      kind: monaco.languages.CompletionItemKind.Function,
      insertText: '"express": "*"',
      range: {
            startLineNumber: position.lineNumber,
            endLineNumber: position.lineNumber,
            startColumn: (position.column - 1), // this starts before the cursor
            endColumn: position.column,
      },
      filterItem: "y"
}

Hope it will help!

thanks @yoanthiebault I managed to get things working with your suggestion. I also used sortText to sort things however weirdly it sorts the wrong way round, i.e. the suggestion item that matches what you typed goes at the bottom if you put sortText: whatyoutyped so I had to put sortText: whatyoutyped for all items _except_ the one I wanted at the top.

Closing this since there is a work around, although I still think it's a bug due to what the docs suggest.

@yoanthiebault @shmish111 I've been trying something similar, i.e. when a user enters the word "route" , I have to show him the suggestions as below, but once he/she clicks on a suggestion, the word "route" should be replaced. But I am not able to do it, the suggestion is being appended to the word "route" . ( example from below :- routehandle() ).

Code to test on monaco editor is below :- Any help here is appreciated.

const foundSuggestions = [
    {
        label: 'route',
        description: 'Handling route action.',
        values: [
            {
                label: 'handle',
                kind: monaco.languages.CompletionItemKind.Function,
                insertText: 'handle()',
                description: 'handle()'
            },
            {
                label: 'route',
                kind: monaco.languages.CompletionItemKind.Function,
                insertText: 'route()',
                description: "route",
                insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
            },
            {
                label: 'noRoute',
                kind: monaco.languages.CompletionItemKind.Function,
                insertText: 'noRoute()',
                description: 'noRoute()',
            },
            {
                label: 'submitAfter',
                kind: monaco.languages.CompletionItemKind.Function,
                insertText: 'submitAfter(${1:condition}, ${2:condition})',
                description: 'submitAfter(int minutesOffset, int secondsOffset)',
                insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
            }
        ]
    },];

const x = ["route"];

monaco.languages.registerCompletionItemProvider('java', {
    provideCompletionItems: function(model, position) {
        var textUntilPosition = model.getValueInRange({startLineNumber: position.lineNumber, startColumn: position.startColumn, endLineNumber: position.lineNumber, endColumn: position.column});

       const keyWord = textUntilPosition.trim();

       // check if the text entered matches with the word "route" 

        if(x[0] === keyWord){

    // iterate over the suggestions and apply the labels, details, etc to display when the word "route" is entered. 

    const suggestions = foundSuggestions[0].values.map(x => ({
                label: x.label,
                kind: monaco.languages.CompletionItemKind.Function,
                                insertText: ` ${x.label}`,
                documentation: x.description,
                                insertText: x.insertText,
                               detail: x.description,
                               insertTextRules: x.insertTextRules ? x.insertTextRules : '',
                range: {
                    startLineNumber: position.lineNumber,
                    startColumn: position.column,
                    endLineNumber: position.lineNumber,
                    endColumn: position.column,
                },
      }));
            return {suggestions:suggestions};
    } else {
        return {suggestions: [{label: ''}]};  // to disable editor's inbuilt suggestions.
        // return {suggestions: []};
    }
    }
});

monaco.editor.create(document.getElementById("container"), {
    value: "hello",
    language: "java"
});
Was this page helpful?
0 / 5 - 0 ratings

Related issues

brandalorian picture brandalorian  路  3Comments

PinkyJie picture PinkyJie  路  3Comments

andreymarchenko picture andreymarchenko  路  3Comments

robclive picture robclive  路  3Comments

zeegin picture zeegin  路  3Comments