Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new completion API #866

Merged
merged 71 commits into from
Mar 29, 2020
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
2a13eb5
Don't trigger AC on word_separators
predragnikolic Jan 4, 2020
0812061
Merge branch 'master' into add_auto_complete_ignored_triggers_setting
predragnikolic Jan 4, 2020
7cff503
Add back complete_all_chars
predragnikolic Jan 4, 2020
5dd6977
fix pycodestyle error
predragnikolic Jan 4, 2020
0a15bf6
Update plugin/completion.py
predragnikolic Jan 8, 2020
3ea2e42
Remove ignored_trigger_chars
predragnikolic Jan 8, 2020
0b58895
Integrate new completion api
predrag-codetribe Jan 13, 2020
12e196e
fix typo
predrag-codetribe Jan 13, 2020
8320d05
Support snippets
predragnikolic Jan 13, 2020
a9e7672
Merge branch 'master' into add_auto_complete_ignored_triggers_setting
predragnikolic Jan 13, 2020
c43c278
Insert textEdit completion,
predragnikolic Jan 13, 2020
e21b1a9
update text icon and remove unused function
predragnikolic Jan 13, 2020
a86c249
FIX AttributeError: module 'asyncio' has no attribute 'get_running_loop'
predragnikolic Jan 14, 2020
2f5aeae
Use text edit Range
predragnikolic Jan 25, 2020
59c6c0b
Merge branch 'fix-running-tests' into add_auto_complete_ignored_trigg…
predragnikolic Jan 25, 2020
534ae8a
use text edit range
predragnikolic Jan 25, 2020
c3d8807
make flake and mypy happy
predragnikolic Jan 27, 2020
40ef4be
Merge branch 'master' into add_auto_complete_ignored_triggers_setting
predragnikolic Jan 28, 2020
ea29e0b
Add typings and remove unused class property
predragnikolic Jan 28, 2020
7d78abb
When choosing a completion item the typed text (prefix) will be delet…
predragnikolic Jan 28, 2020
97ae903
Add cache
predragnikolic Jan 28, 2020
ea4dd30
Ignore mypy error instead of casting a type
predragnikolic Jan 30, 2020
8a11721
revert back timeout
predragnikolic Jan 30, 2020
f9d8bb9
Remove committing flag
predragnikolic Jan 30, 2020
793ec40
better comment
predragnikolic Jan 30, 2020
10ad58a
revert accidentally committed code
predragnikolic Jan 30, 2020
d2505b0
remove last_text_command as it is not necessary
predragnikolic Jan 30, 2020
542b489
remove completion_hint_type setting
predragnikolic Jan 30, 2020
d5a5d62
remove quotes from types
predragnikolic Jan 30, 2020
d7fd65d
Merge branch 'master' into add_auto_complete_ignored_triggers_setting
predragnikolic Feb 2, 2020
f46548a
Use text_edit['range'].end and remove duplicated functions
predragnikolic Feb 3, 2020
ff9e0ae
Skip calculating the offset by inserting the removed prefix first. An…
predragnikolic Feb 4, 2020
2f504f7
Add types and remove trigger_chars from instance properties
predragnikolic Feb 4, 2020
896dcef
Fix flake
predragnikolic Feb 4, 2020
87ab9e3
remove auto_complete_selector
predragnikolic Feb 4, 2020
1744b16
Handle multiple cursor completions
predragnikolic Feb 8, 2020
5782b46
Can be(will be probably) reverted. This is my try to fix the completi…
predragnikolic Feb 8, 2020
316d29d
Change keyword icon "κ" to something that looks like a key => "☌"
predragnikolic Feb 9, 2020
64627ee
Merge branch 'st4000-exploration' into add_auto_complete_ignored_trig…
predragnikolic Feb 9, 2020
8dd14b3
Revert "Change keyword icon "κ" to something that looks like a key =>…
predragnikolic Feb 9, 2020
f47083e
Remove if check for document_position
predragnikolic Feb 9, 2020
542245e
fix typo
predragnikolic Feb 9, 2020
44a3b2a
erase regoion for snippet, replace region for plain text
predragnikolic Feb 15, 2020
33496ac
don't allocate a dict
predragnikolic Feb 15, 2020
7b154e6
remove indent
predragnikolic Feb 15, 2020
c0805be
fix other failing tests
predragnikolic Feb 15, 2020
b528475
Use set_response and await_message in favour of test_compmpletion pro…
predragnikolic Feb 15, 2020
2497a45
Send a resolve request only if we don't have apply_additional_edits o…
predragnikolic Feb 17, 2020
9fe640a
Merge branch 'master' into add_auto_complete_ignored_triggers_setting
predragnikolic Feb 17, 2020
a99cff7
Add E2E tests for prefering insert text over label and text edit over…
predragnikolic Feb 17, 2020
49ccda6
add test to apply additional edits only one time
predragnikolic Feb 17, 2020
cc475e9
set "auto_complete_preserve_order" to "none" in tests
predragnikolic Feb 17, 2020
61099bc
make mypy and pyflake happy
predragnikolic Feb 17, 2020
9e54c83
Remove "complete_all_chars" setting in favor of sublime default "auto…
predragnikolic Feb 20, 2020
6fed5d3
Add test for intelephense
predragnikolic Feb 20, 2020
dae5eba
remove old completion samples
predragnikolic Feb 20, 2020
1ccb7ab
Fix sublime removing $ from the prefix
predragnikolic Feb 22, 2020
036e432
Revert "erase regoion for snippet, replace region for plain text"
predragnikolic Feb 22, 2020
d974c22
Workaround for TextEdit range not being valid when selection a comple…
predragnikolic Mar 23, 2020
e3f580c
Better handle multicursor text edits
predragnikolic Mar 23, 2020
f207bf2
Move save lines to on_query_completions, and clean up the a little
predragnikolic Mar 23, 2020
4468f8a
make flake8 and mypy happy
predragnikolic Mar 23, 2020
f38aaa0
Merge branch 'master' into add_auto_complete_ignored_triggers_setting
predragnikolic Mar 23, 2020
bc687e5
fix typo
predragnikolic Mar 23, 2020
07f7824
Dont use static class variables for saving the Line contents, because…
predragnikolic Mar 26, 2020
51bbd4d
fix for commit_completion re-triggers the completion panel "forever"
predragnikolic Mar 26, 2020
b5c0de6
Better handle multicursor text edits
predragnikolic Mar 27, 2020
34ae9e1
use transform_region to restore file contents
predragnikolic Mar 27, 2020
18c47e3
Resolve the completion promise in the handle_error
predragnikolic Mar 28, 2020
6e596e4
remove hiding auto_complete gloably after commit_completions
predragnikolic Mar 28, 2020
9d0d7ea
Merge branch 'st4000-exploration' into add_auto_complete_ignored_trig…
predragnikolic Mar 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions LSP.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,6 @@
// or just after trigger characters only otherwise.
"complete_all_chars": true,

// Controls which hints the completion panel displays
// "auto": completion details if available or kind otherwise
// "detail": completion details if available
// "kind": completion kind if available
// "none": completion item label only
"completion_hint_type": "auto",

// Disable Sublime Text's explicit and word completion.
"only_show_lsp_completions": false,

Expand Down
1 change: 0 additions & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ Add these settings to your Sublime settings, Syntax-specific settings and/or in

* `complete_all_chars` `true` *request completions for all characters, not just trigger characters*
* `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"*
* `show_references_in_quick_panel` `false` *show symbol references in Sublime's quick panel instead of the bottom panel*
* `show_view_status` `true` *show permanent language server status in the status bar*
* `auto_show_diagnostics_panel` `always` (`never`, `saved`) *open the diagnostics panel automatically if there are diagnostics*
Expand Down
348 changes: 96 additions & 252 deletions plugin/completion.py

Large diffs are not rendered by default.

125 changes: 55 additions & 70 deletions plugin/core/completion.py
Original file line number Diff line number Diff line change
@@ -1,76 +1,61 @@
from .protocol import CompletionItemKind, Range
from .types import Settings
from .logging import debug
import sublime
from .typing import Tuple, Optional, Dict, List, Union


completion_item_kind_names = {v: k for k, v in CompletionItemKind.__dict__.items()}


def get_completion_hint(item: dict, settings: Settings) -> Optional[str]:
# choose hint based on availability and user preference
hint = None
if settings.completion_hint_type == "auto":
hint = item.get("detail")
if not hint:
kind = item.get("kind")
if kind:
hint = completion_item_kind_names[kind]
elif settings.completion_hint_type == "detail":
hint = item.get("detail")
elif settings.completion_hint_type == "kind":
kind = item.get("kind")
if kind:
hint = completion_item_kind_names.get(kind)
return hint


def format_completion(item: dict, word_col: int, settings: Settings) -> Tuple[str, str]:
# Sublime handles snippets automatically, so we don't have to care about insertTextFormat.
trigger = item["label"]

hint = get_completion_hint(item, settings)

# label is an alternative for insertText if neither textEdit nor insertText is provided
replacement = text_edit_text(item, word_col) or item.get("insertText") or trigger

if replacement[0] != trigger[0]:
# fix some common cases when server sends different start on label and replacement.
if replacement[0] == '$':
trigger = '$' + trigger # add missing $
elif replacement[0] == '-':
trigger = '-' + trigger # add missing -
elif trigger[0] == ':':
replacement = ':' + replacement # add missing :
elif trigger[0] == '$':
trigger = trigger[1:] # remove leading $
elif trigger[0] == ' ' or trigger[0] == '•':
trigger = trigger[1:] # remove clangd insertion indicator
else:
debug("WARNING: Replacement prefix does not match trigger '{}'".format(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, replacement


def text_edit_text(item: dict, word_col: int) -> Optional[str]:
text_edit = item.get('textEdit')
if text_edit:
edit_range, edit_text = text_edit.get("range"), text_edit.get("newText")
if edit_range and edit_text:
edit_range = Range.from_lsp(edit_range)

# debug('textEdit from col {}, {} applied at col {}'.format(
# edit_range.start.col, edit_range.end.col, word_col))

if edit_range.start.col <= word_col:
# if edit starts at current word, we can use it.
# if edit starts before current word, use the whole thing and we'll fix it up later.
return edit_text

return None
compleiton_kinds = {
rwols marked this conversation as resolved.
Show resolved Hide resolved
1: (sublime.KIND_ID_MARKUP, "Ξ", "Text"),
2: (sublime.KIND_ID_FUNCTION, "λ", "Method"),
3: (sublime.KIND_ID_FUNCTION, "λ", "Function"),
4: (sublime.KIND_ID_FUNCTION, "c", "Constructor"),
5: (sublime.KIND_ID_VARIABLE, "f", "Field"),
6: (sublime.KIND_ID_VARIABLE, "v", "Variable"),
7: (sublime.KIND_ID_TYPE, "c", "Class"),
8: (sublime.KIND_ID_TYPE, "i", "Interface"),
9: (sublime.KIND_ID_NAMESPACE, "◪", "Module"),
rwols marked this conversation as resolved.
Show resolved Hide resolved
10: (sublime.KIND_ID_VARIABLE, "ρ", "Property"),
11: (sublime.KIND_ID_VARIABLE, "u", "Unit"),
12: (sublime.KIND_ID_VARIABLE, "ν", "Value"),
13: (sublime.KIND_ID_TYPE, "ε", "Enum"),
14: (sublime.KIND_ID_KEYWORD, "κ", "Keyword"),
15: (sublime.KIND_ID_SNIPPET, "s", "Snippet"),
16: (sublime.KIND_ID_AMBIGUOUS, "c", "Color"),
17: (sublime.KIND_ID_AMBIGUOUS, "#", "File"),
18: (sublime.KIND_ID_AMBIGUOUS, "⇢", "Reference"),
19: (sublime.KIND_ID_AMBIGUOUS, "ƒ", "Folder"),
20: (sublime.KIND_ID_TYPE, "ε", "EnumMember"),
21: (sublime.KIND_ID_VARIABLE, "π", "Constant"),
22: (sublime.KIND_ID_TYPE, "s", "Struct"),
23: (sublime.KIND_ID_FUNCTION, "e", "Event"),
24: (sublime.KIND_ID_KEYWORD, "ο", "Operator"),
25: (sublime.KIND_ID_TYPE, "τ", "Type Parameter")
}


def format_completion(item: dict) -> sublime.CompletionItem:
trigger = item.get('label') or ""
annotation = item.get('detail') or ""
kind = sublime.KIND_AMBIGUOUS

item_kind = item.get("kind")
if item_kind:
kind = compleiton_kinds.get(item_kind, sublime.KIND_AMBIGUOUS)

is_deprecated = item.get("deprecated", False)
if is_deprecated:
list_kind = list(kind)
list_kind[1] = '⚠'
list_kind[2] = "⚠ {} - Deprecated".format(list_kind[2])
kind = tuple(list_kind) # type: ignore
rwols marked this conversation as resolved.
Show resolved Hide resolved

return sublime.CompletionItem.command_completion(
trigger,
command="lsp_select_completion_item",
args={
"item": item
},
annotation=annotation,
kind=kind
)


def parse_completion_response(response: Optional[Union[Dict, List]]) -> Tuple[List[Dict], bool]:
Expand Down
5 changes: 5 additions & 0 deletions plugin/core/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ class CompletionItemKind(object):
completion_item_kinds = list(range(CompletionItemKind.Text, CompletionItemKind.TypeParameter + 1))


class InsertTextFormat:
PlainText = 1
Snippet = 2


class DocumentHighlightKind(object):
Unknown = 0
Text = 1
Expand Down
3 changes: 2 additions & 1 deletion plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ def get_initialize_params(workspace_folders: List[WorkspaceFolder], designated_f
},
"completion": {
"completionItem": {
"snippetSupport": True
"snippetSupport": True,
"deprecatedSupport": True
},
"completionItemKind": {
"valueSet": completion_item_kinds
Expand Down
1 change: 0 additions & 1 deletion plugin/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ def update_settings(settings: Settings, settings_obj: sublime.Settings) -> None:
settings.show_symbol_action_links = read_bool_setting(settings_obj, "show_symbol_action_links", False)
settings.only_show_lsp_completions = read_bool_setting(settings_obj, "only_show_lsp_completions", False)
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.show_references_in_quick_panel = read_bool_setting(settings_obj, "show_references_in_quick_panel", False)
settings.disabled_capabilities = read_array_setting(settings_obj, "disabled_capabilities", [])
settings.log_debug = read_bool_setting(settings_obj, "log_debug", False)
Expand Down
1 change: 0 additions & 1 deletion plugin/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ def __init__(self) -> None:
self.show_code_actions_bulb = False
self.show_symbol_action_links = False
self.complete_all_chars = False
self.completion_hint_type = "auto"
self.show_references_in_quick_panel = False
self.disabled_capabilities = [] # type: List[str]
self.log_debug = True
Expand Down
37 changes: 36 additions & 1 deletion stubs/sublime.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ CLASS_LINE_END = ... # type: int
CLASS_EMPTY_LINE = ... # type: int
INHIBIT_WORD_COMPLETIONS = ... # type: int
INHIBIT_EXPLICIT_COMPLETIONS = ... # type: int
DYNAMIC_COMPLETIONS = ... # type: int
DIALOG_CANCEL = ... # type: int
DIALOG_YES = ... # type: int
DIALOG_NO = ... # type: int
Expand All @@ -67,6 +68,24 @@ UI_ELEMENT_OPEN_FILES = ... # type: int
LAYOUT_INLINE = ... # type: int
LAYOUT_BELOW = ... # type: int
LAYOUT_BLOCK = ... # type: int
KIND_ID_AMBIGUOUS = ... # type: int
KIND_ID_KEYWORD = ... # type: int
KIND_ID_TYPE = ... # type: int
KIND_ID_FUNCTION = ... # type: int
KIND_ID_NAMESPACE = ... # type: int
KIND_ID_NAVIGATION = ... # type: int
KIND_ID_MARKUP = ... # type: int
KIND_ID_VARIABLE = ... # type: int
KIND_ID_SNIPPET = ... # type: int
KIND_AMBIGUOUS = ... # type: Tuple[int, str, str]
KIND_KEYWORD = ... # type: Tuple[int, str, str]
KIND_TYPE = ... # type: Tuple[int, str, str]
KIND_FUNCTION = ... # type: Tuple[int, str, str]
KIND_NAMESPACE = ... # type: Tuple[int, str, str]
KIND_NAVIGATION = ... # type: Tuple[int, str, str]
KIND_MARKUP = ... # type: Tuple[int, str, str]
KIND_VARIABLE = ... # type: Tuple[int, str, str]
KIND_SNIPPET = ... # type: Tuple[int, str, str]


class Settings:
Expand Down Expand Up @@ -238,6 +257,22 @@ def get_macro() -> Sequence[dict]:
...


class CompletionItem:
@classmethod
def command_completion(cls,
trigger: str,
command: str,
args: dict = {},
annotation: str = "",
kind: Tuple[int, str, str] = KIND_AMBIGUOUS
) -> 'CompletionItem':
...

class CompletionList:
def set_completions(self, completions: List[CompletionItem], flags: int = 0) -> None:
...


class Window:
window_id = ... # type: int
settings_object = ... # type: Settings
Expand Down Expand Up @@ -491,7 +526,7 @@ class Selection(Sized):
def clear(self) -> None:
...

def add(self, x: Region) -> None:
def add(self, x: Union[Region, int]) -> None:
...

def add_all(self, regions: Sequence[Region]) -> None:
Expand Down
2 changes: 0 additions & 2 deletions tests/test_completion.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from LSP.plugin.completion import CompletionHandler
from LSP.plugin.completion import CompletionState
from LSP.plugin.core.registry import is_supported_view
from setup import CI, SUPPORTED_SYNTAX, TextDocumentTestCase, add_config, remove_config, text_config
from unittesting import DeferrableTestCase
Expand Down Expand Up @@ -181,7 +180,6 @@ def test_simple_label(self) -> 'Generator':

# now wait for server response
yield from self.await_message('textDocument/completion')
self.assertEquals(handler.state, CompletionState.IDLE)
self.assertEquals(len(handler.completions), 2)

# verify insertion works
Expand Down
20 changes: 7 additions & 13 deletions tests/test_completion_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from os import path
import json
from LSP.plugin.core.completion import format_completion, parse_completion_response
from LSP.plugin.core.types import Settings
try:
from typing import Optional, Dict
assert Optional and Dict
Expand All @@ -19,9 +18,6 @@ def load_completion_sample(name: str) -> 'Dict':
intelephense_completion_sample = load_completion_sample("intelephense_completion_sample")


settings = Settings()


class CompletionResponseParsingTests(unittest.TestCase):

def test_no_response(self):
Expand All @@ -40,15 +36,13 @@ def test_incomplete_dict_response(self):
class CompletionFormattingTests(unittest.TestCase):

def test_only_label_item(self):
rwols marked this conversation as resolved.
Show resolved Hide resolved
result = format_completion({"label": "asdf"}, 0, settings)
result = format_completion({"label": "asdf"})
self.assertEqual(len(result), 2)
self.assertEqual("asdf", result[0])
self.assertEqual("asdf", result[1])

def test_prefers_insert_text(self):
rwols marked this conversation as resolved.
Show resolved Hide resolved
result = format_completion(
{"label": "asdf", "insertText": "Asdf"},
0, settings)
result = format_completion({"label": "asdf", "insertText": "Asdf"})
self.assertEqual(len(result), 2)
self.assertEqual("asdf", result[0])
self.assertEqual("Asdf", result[1])
Expand All @@ -68,7 +62,7 @@ def test_ignores_text_edit(self):
}
}

result = format_completion(item, 0, settings)
result = format_completion(item)
self.assertEqual(len(result), 2)
self.assertEqual("$true", result[0])
self.assertEqual("\\$true", result[1])
Expand All @@ -93,12 +87,12 @@ def test_use_label_as_is(self):
}
}
last_col = 1
result = format_completion(item, last_col, settings)
result = format_completion(item)
self.assertEqual(result, ('const\t Keyword', 'const'))

def test_text_edit_intelephense(self):
rwols marked this conversation as resolved.
Show resolved Hide resolved
last_col = 1
result = [format_completion(item, last_col, settings) for item in intelephense_completion_sample]
result = [format_completion(item) for item in intelephense_completion_sample]
self.assertEqual(
result,
[
Expand All @@ -125,7 +119,7 @@ def test_text_edit_clangd(self):
# handler.last_location = 1
# handler.last_prefix = ""
last_col = 1
result = [format_completion(item, last_col, settings) for item in clangd_completion_sample]
result = [format_completion(item) for item in clangd_completion_sample]
# We should prefer textEdit over insertText. This test covers that.
self.assertEqual(
result, [('argc\t int', 'argc'), ('argv\t const char **', 'argv'),
Expand All @@ -150,7 +144,7 @@ def test_text_edit_clangd(self):

def test_missing_text_edit_but_we_do_have_insert_text_for_pyls(self):
last_col = 1
result = [format_completion(item, last_col, settings) for item in pyls_completion_sample]
result = [format_completion(item) for item in pyls_completion_sample]
self.assertEqual(
result,
[
Expand Down