diff --git a/LSP.sublime-settings b/LSP.sublime-settings index d472caf52..c3b8e9ecd 100644 --- a/LSP.sublime-settings +++ b/LSP.sublime-settings @@ -355,6 +355,11 @@ // Resolve completions and apply snippet if received. "resolve_completion_for_snippets": false, + // When presenting completions, prefer the "label" over the "filterText" key + // in the CompletionItem. By default, the "filterText" is chosen over the + // "label". If the "filterText" is not present, fall back to the "label". + "prefer_label_over_filter_text": false, + // Show symbol references in Sublime's quick panel instead of the bottom panel. "show_references_in_quick_panel": false, diff --git a/docs/index.md b/docs/index.md index 83fe04a7e..6e23d5baf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,6 +10,7 @@ Global plugin settings and settings defined at project level are merged together * `only_show_lsp_completions` `false` *disable sublime word completion and snippets from autocomplete lists* * `completion_hint_type` `"auto"` *override automatic completion hints with "detail", "kind" or "none"* * `resolve_completion_for_snippets` `false` *resolve completions and apply snippet if received* +* `prefer_label_over_filter_text` `false` *always use the "label" key instead of the "filterText" key in CompletionItems* * `show_references_in_quick_panel` `false` *show symbol references in Sublime's quick panel instead of the bottom panel* * `show_status_messages` `true` *show messages in the status bar for a few seconds* * `show_view_status` `true` *show permanent language server status in the status bar* diff --git a/plugin/completion.py b/plugin/completion.py index beb3b9ac0..00bcb7388 100644 --- a/plugin/completion.py +++ b/plugin/completion.py @@ -258,7 +258,10 @@ def do_request(self, prefix: str, locations: 'List[int]'): def format_completion(self, item: dict) -> 'Tuple[str, str]': # Sublime handles snippets automatically, so we don't have to care about insertTextFormat. - label = item["label"] + if settings.prefer_label_over_filter_text: + trigger = item["label"] + else: + trigger = item.get("filterText", item["label"]) # choose hint based on availability and user preference hint = None if settings.completion_hint_type == "auto": @@ -274,12 +277,11 @@ def format_completion(self, item: dict) -> 'Tuple[str, str]': if kind: hint = completion_item_kind_names.get(kind) # label is an alternative for insertText if neither textEdit nor insertText is provided - insert_text = self.text_edit_text(item) or item.get("insertText") or label - trigger = insert_text - if len(insert_text) > 0 and insert_text[0] == '$': # sublime needs leading '$' escaped. - insert_text = '\\$' + insert_text[1:] + replacement = self.text_edit_text(item) or item.get("insertText") or trigger + if len(replacement) > 0 and replacement[0] == '$': # sublime needs leading '$' escaped. + replacement = '\\$' + replacement[1:] # only return trigger with a hint if available - return "\t ".join((trigger, hint)) if hint else trigger, insert_text + return "\t ".join((trigger, hint)) if hint else trigger, replacement def text_edit_text(self, item) -> 'Optional[str]': text_edit = item.get("textEdit") diff --git a/plugin/core/settings.py b/plugin/core/settings.py index fc477e3ea..f7d9b5610 100644 --- a/plugin/core/settings.py +++ b/plugin/core/settings.py @@ -64,6 +64,7 @@ def update_settings(settings: Settings, settings_obj: sublime.Settings): settings.complete_all_chars = read_bool_setting(settings_obj, "complete_all_chars", True) settings.completion_hint_type = read_str_setting(settings_obj, "completion_hint_type", "auto") settings.resolve_completion_for_snippets = read_bool_setting(settings_obj, "resolve_completion_for_snippets", False) + settings.prefer_label_over_filter_text = read_bool_setting(settings_obj, "prefer_label_over_filter_text", False) settings.show_references_in_quick_panel = read_bool_setting(settings_obj, "show_references_in_quick_panel", False) settings.log_debug = read_bool_setting(settings_obj, "log_debug", False) settings.log_server = read_bool_setting(settings_obj, "log_server", True) diff --git a/plugin/core/types.py b/plugin/core/types.py index 2e01061ee..2bea5dcbb 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -34,6 +34,7 @@ def __init__(self) -> None: self.complete_all_chars = False self.completion_hint_type = "auto" self.resolve_completion_for_snippets = False + self.prefer_label_over_filter_text = False self.show_references_in_quick_panel = False self.log_debug = True self.log_server = True diff --git a/tests/test_completion.py b/tests/test_completion.py index 96c04718e..0dfc7eab0 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -7,6 +7,12 @@ from LSP.plugin.core.settings import client_configs, ClientConfig from os.path import dirname, join +try: + from typing import Dict, Optional + assert Dict and Optional +except ImportError: + pass + def load_completion_sample(name: str) -> 'Dict': return json.load(open(join(dirname(__file__), name + ".json"))) @@ -17,7 +23,7 @@ def load_completion_sample(name: str) -> 'Dict': intelephense_completion_sample = load_completion_sample("intelephense_completion_sample") -def create_completion_item(item: str, insert_text: 'Optional[str]'=None) -> dict: +def create_completion_item(item: str, insert_text: 'Optional[str]' = None) -> dict: return { "label": item, "insertText": insert_text @@ -144,7 +150,7 @@ def test_prefers_insert_text(self): handler = CompletionHandler(self.view) result = handler.format_completion(create_completion_item("asdf", "Asdf")) self.assertEqual(len(result), 2) - self.assertEqual("Asdf", result[0]) + self.assertEqual("asdf", result[0]) self.assertEqual("Asdf", result[1]) def test_ignores_text_edit(self): @@ -165,7 +171,7 @@ def test_ignores_text_edit(self): result = handler.format_completion(item) self.assertEqual(len(result), 2) - self.assertEqual("$true", result[0]) + self.assertEqual("true", result[0]) self.assertEqual("\\$true", result[1]) def test_ignore_label(self): @@ -235,27 +241,24 @@ def test_text_edit_clangd(self): [ ('argc\t int', 'argc'), ('argv\t const char **', 'argv'), - ('alignas(${1:expression})\t Snippet', 'alignas(${1:expression})'), - ('alignof(${1:type})\t size_t', 'alignof(${1:type})'), + ('alignas\t Snippet', 'alignas(${1:expression})'), + ('alignof\t size_t', 'alignof(${1:type})'), ('auto\t Keyword', 'auto'), - ('static_assert(${1:expression}, ${2:message})\t Snippet', - 'static_assert(${1:expression}, ${2:message})'), - ('a64l(${1:const char *__s})\t long', 'a64l(${1:const char *__s})'), - ('abort()\t void', 'abort()'), - ('abs(${1:int __x})\t int', 'abs(${1:int __x})'), - ('aligned_alloc(${1:size_t __alignment}, ${2:size_t __size})\t void *', - 'aligned_alloc(${1:size_t __alignment}, ${2:size_t __size})'), - ('alloca(${1:size_t __size})\t void *', 'alloca(${1:size_t __size})'), - ('asctime(${1:const struct tm *__tp})\t char *', 'asctime(${1:const struct tm *__tp})'), - ('asctime_r(${1:const struct tm *__restrict __tp}, ${2:char *__restrict __buf})\t char *', - 'asctime_r(${1:const struct tm *__restrict __tp}, ${2:char *__restrict __buf})'), - ('asprintf(${1:char **__restrict __ptr}, ${2:const char *__restrict __fmt, ...})\t int', - 'asprintf(${1:char **__restrict __ptr}, ${2:const char *__restrict __fmt, ...})'), - ('at_quick_exit(${1:void (*__func)()})\t int', 'at_quick_exit(${1:void (*__func)()})'), - ('atexit(${1:void (*__func)()})\t int', 'atexit(${1:void (*__func)()})'), - ('atof(${1:const char *__nptr})\t double', 'atof(${1:const char *__nptr})'), - ('atoi(${1:const char *__nptr})\t int', 'atoi(${1:const char *__nptr})'), - ('atol(${1:const char *__nptr})\t long', 'atol(${1:const char *__nptr})') + ('static_assert\t Snippet', 'static_assert(${1:expression}, ${2:message})'), + ('a64l\t long', 'a64l(${1:const char *__s})'), + ('abort\t void', 'abort()'), + ('abs\t int', 'abs(${1:int __x})'), + ('aligned_alloc\t void *', 'aligned_alloc(${1:size_t __alignment}, ${2:size_t __size})'), + ('alloca\t void *', 'alloca(${1:size_t __size})'), + ('asctime\t char *', 'asctime(${1:const struct tm *__tp})'), + ('asctime_r\t char *', + 'asctime_r(${1:const struct tm *__restrict __tp}, ${2:char *__restrict __buf})'), + ('asprintf\t int', 'asprintf(${1:char **__restrict __ptr}, ${2:const char *__restrict __fmt, ...})'), + ('at_quick_exit\t int', 'at_quick_exit(${1:void (*__func)()})'), + ('atexit\t int', 'atexit(${1:void (*__func)()})'), + ('atof\t double', 'atof(${1:const char *__nptr})'), + ('atoi\t int', 'atoi(${1:const char *__nptr})'), + ('atol\t long', 'atol(${1:const char *__nptr})') ] ) @@ -269,29 +272,29 @@ def test_missing_text_edit_but_we_do_have_insert_text_for_pyls(self): result, [ ('abc\t os', 'abc'), - ('abort\t os', 'abort'), - ('access(${1:path}, ${2:mode}, ${3:dir_fd}, ${4:effective_ids}, ${5:follow_symlinks})$0\t os', - 'access(${1:path}, ${2:mode}, ${3:dir_fd}, ${4:effective_ids}, ${5:follow_symlinks})$0'), + ('abort()\t os', 'abort'), + ('access(path, mode, dir_fd, effective_ids, follow_symlinks)\t os', + 'access(${1:path}, ${2:mode}, ${3:dir_fd}, ${4:effective_ids}, ${5:follow_symlinks})$0'), ('altsep\t os', 'altsep'), - ('chdir(${1:path})$0\t os', 'chdir(${1:path})$0'), - ('chmod(${1:path}, ${2:mode}, ${3:dir_fd}, ${4:follow_symlinks})$0\t os', - 'chmod(${1:path}, ${2:mode}, ${3:dir_fd}, ${4:follow_symlinks})$0'), - ('chown(${1:path}, ${2:uid}, ${3:gid}, ${4:dir_fd}, ${5:follow_symlinks})$0\t os', - 'chown(${1:path}, ${2:uid}, ${3:gid}, ${4:dir_fd}, ${5:follow_symlinks})$0'), - ('chroot(${1:path})$0\t os', 'chroot(${1:path})$0'), + ('chdir(path)\t os', 'chdir(${1:path})$0'), + ('chmod(path, mode, dir_fd, follow_symlinks)\t os', + 'chmod(${1:path}, ${2:mode}, ${3:dir_fd}, ${4:follow_symlinks})$0'), + ('chown(path, uid, gid, dir_fd, follow_symlinks)\t os', + 'chown(${1:path}, ${2:uid}, ${3:gid}, ${4:dir_fd}, ${5:follow_symlinks})$0'), + ('chroot(path)\t os', 'chroot(${1:path})$0'), ('CLD_CONTINUED\t os', 'CLD_CONTINUED'), ('CLD_DUMPED\t os', 'CLD_DUMPED'), ('CLD_EXITED\t os', 'CLD_EXITED'), ('CLD_TRAPPED\t os', 'CLD_TRAPPED'), - ('close(${1:fd})$0\t os', 'close(${1:fd})$0'), - ('closerange(${1:fd_low}, ${2:fd_high})$0\t os', 'closerange(${1:fd_low}, ${2:fd_high})$0'), - ('confstr(${1:name})$0\t os', 'confstr(${1:name})$0'), + ('close(fd)\t os', 'close(${1:fd})$0'), + ('closerange(fd_low, fd_high)\t os', 'closerange(${1:fd_low}, ${2:fd_high})$0'), + ('confstr(name)\t os', 'confstr(${1:name})$0'), ('confstr_names\t os', 'confstr_names'), - ('cpu_count\t os', 'cpu_count'), - ('ctermid\t os', 'ctermid'), + ('cpu_count()\t os', 'cpu_count'), + ('ctermid()\t os', 'ctermid'), ('curdir\t os', 'curdir'), ('defpath\t os', 'defpath'), - ('device_encoding(${1:fd})$0\t os', 'device_encoding(${1:fd})$0') + ('device_encoding(fd)\t os', 'device_encoding(${1:fd})$0') ] )