From 7ff0ccc0d2dd99bdf081b3428bcd07be6c4f8633 Mon Sep 17 00:00:00 2001 From: ddcien Date: Wed, 6 Nov 2019 18:32:19 +0800 Subject: [PATCH 1/7] Update CompletionItemKind --- pyls/lsp.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyls/lsp.py b/pyls/lsp.py index 36a8d842..a3f88d38 100644 --- a/pyls/lsp.py +++ b/pyls/lsp.py @@ -24,6 +24,13 @@ class CompletionItemKind(object): Color = 16 File = 17 Reference = 18 + Folder = 19 + EnumMember = 20 + Constant = 21 + Struct = 22 + Event = 23 + Operator = 24 + TypeParameter = 25 class DocumentHighlightKind(object): From 400950b4e6bf8a183b8f65462faead23f6fbca8d Mon Sep 17 00:00:00 2001 From: ddcien Date: Wed, 6 Nov 2019 18:33:49 +0800 Subject: [PATCH 2/7] Add 'textDocument/info' C->S request for debug the client --- pyls/python_ls.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyls/python_ls.py b/pyls/python_ls.py index 9084bc77..c0a21315 100644 --- a/pyls/python_ls.py +++ b/pyls/python_ls.py @@ -296,6 +296,14 @@ def m_text_document__did_open(self, textDocument=None, **_kwargs): self._hook('pyls_document_did_open', textDocument['uri']) self.lint(textDocument['uri'], is_saved=True) + def m_text_document__info(self, textDocument=None, **_kwargs): + workspace = self._match_uri_to_workspace(textDocument['uri']) + doc = workspace.documents[textDocument['uri']] + return { + 'version': doc.version, + 'text': doc.source + } + def m_text_document__did_change(self, contentChanges=None, textDocument=None, **_kwargs): workspace = self._match_uri_to_workspace(textDocument['uri']) for change in contentChanges: From 10456aef6f83bf685fc15ba0c48057a4abcec46e Mon Sep 17 00:00:00 2001 From: ddcien Date: Wed, 6 Nov 2019 18:42:07 +0800 Subject: [PATCH 3/7] Add textEdit field to CompletionItem in jedi_completion TODO: rope_completion --- pyls/_utils.py | 22 ++++++++++++++++++++++ pyls/plugins/jedi_completion.py | 25 ++++++++++++++++++++----- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/pyls/_utils.py b/pyls/_utils.py index 919bf1c5..647f3323 100644 --- a/pyls/_utils.py +++ b/pyls/_utils.py @@ -111,6 +111,28 @@ def list_to_string(value): return ",".join(value) if isinstance(value, list) else value +def merge_string(a: str, b: str) -> str: + if len(a) < len(b): + if b.startswith(a): + return b + + for i in range(len(a)): + if b.startswith(a[i:]): + break + + return a[:i] + b + + else: + if a.endswith(b): + return a + + for i in range(len(b) -1, -1, -1): + if a.endswith(b[:i]): + break + + return (a[:-i] if i else a) + b + + def merge_dicts(dict_a, dict_b): """Recursively merge dictionary b into dictionary a. diff --git a/pyls/plugins/jedi_completion.py b/pyls/plugins/jedi_completion.py index c552e396..e2529e03 100644 --- a/pyls/plugins/jedi_completion.py +++ b/pyls/plugins/jedi_completion.py @@ -31,10 +31,10 @@ 'property': lsp.CompletionItemKind.Property, 'import': lsp.CompletionItemKind.Module, 'keyword': lsp.CompletionItemKind.Keyword, - 'constant': lsp.CompletionItemKind.Variable, + 'constant': lsp.CompletionItemKind.Constant, 'variable': lsp.CompletionItemKind.Variable, 'value': lsp.CompletionItemKind.Value, - 'param': lsp.CompletionItemKind.Variable, + 'param': lsp.CompletionItemKind.TypeParameter, 'statement': lsp.CompletionItemKind.Keyword, } @@ -51,17 +51,27 @@ def pyls_completions(config, document, position): settings = config.plugin_settings('jedi_completion', document_path=document.path) should_include_params = settings.get('include_params') - return [_format_completion(d, snippet_support and should_include_params) for d in definitions] or None + return [_format_completion(d, position, snippet_support and should_include_params) for d in definitions] or None -def _format_completion(d, include_params=True): +def _format_completion(d, position, include_params=True): + insert_text = _utils.merge_string(d.name, d.complete) + start_character = position['character'] + len(d.complete) - len(insert_text) + completion = { 'label': _label(d), 'kind': _TYPE_MAP.get(d.type), 'detail': _detail(d), 'documentation': _utils.format_docstring(d.docstring()), 'sortText': _sort_text(d), - 'insertText': d.name + 'insertText': insert_text, + 'textEdit': { + 'range': { + 'start': {'line': position['line'], 'character': start_character}, + 'end': position, + }, + 'newText': insert_text, + } } if include_params and hasattr(d, 'params') and d.params: @@ -75,7 +85,9 @@ def _format_completion(d, include_params=True): if i < len(positional_args) - 1: snippet += ', ' snippet += ')$0' + completion['insertText'] = snippet + completion['textEdit']['newText'] = snippet return completion @@ -85,6 +97,9 @@ def _label(definition): params = ', '.join([param.name for param in definition.params]) return '{}({})'.format(definition.name, params) + if definition.type == 'param': + return definition.name + '=' + return definition.name From 1a6c51cee23a90e354f31c5df97bd9000462cbc1 Mon Sep 17 00:00:00 2001 From: ddcien Date: Wed, 6 Nov 2019 19:01:14 +0800 Subject: [PATCH 4/7] Add textEdit field to CompletionItem in rope_completion --- pyls/plugins/rope_completion.py | 16 ++++++++++++---- pyls/workspace.py | 8 ++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pyls/plugins/rope_completion.py b/pyls/plugins/rope_completion.py index e556e464..519b349f 100644 --- a/pyls/plugins/rope_completion.py +++ b/pyls/plugins/rope_completion.py @@ -1,6 +1,6 @@ # Copyright 2017 Palantir Technologies, Inc. import logging -from rope.contrib.codeassist import code_assist, sorted_proposals +from rope.contrib.codeassist import code_assist, sorted_proposals, starting_offset from pyls import hookimpl, lsp @@ -37,6 +37,7 @@ def pyls_completions(config, workspace, document, position): return [] definitions = sorted_proposals(definitions) + start = document.position_at_offset(starting_offset(document.source, offset)) new_definitions = [] for d in definitions: try: @@ -48,7 +49,14 @@ def pyls_completions(config, workspace, document, position): 'kind': _kind(d), 'detail': '{0} {1}'.format(d.scope or "", d.name), 'documentation': doc or "", - 'sortText': _sort_text(d) + 'sortText': _sort_text(d), + 'textEdit': { + 'range': { + 'start': start, + 'end': position, + }, + 'newText': d.name, + } }) definitions = new_definitions @@ -97,10 +105,10 @@ def _kind(d): 'property': lsp.CompletionItemKind.Property, 'import': lsp.CompletionItemKind.Module, 'keyword': lsp.CompletionItemKind.Keyword, - 'constant': lsp.CompletionItemKind.Variable, + 'constant': lsp.CompletionItemKind.Constant, 'variable': lsp.CompletionItemKind.Variable, 'value': lsp.CompletionItemKind.Value, - 'param': lsp.CompletionItemKind.Variable, + 'param': lsp.CompletionItemKind.TypeParameter, 'statement': lsp.CompletionItemKind.Keyword, } diff --git a/pyls/workspace.py b/pyls/workspace.py index 0ffab25a..a74e2893 100644 --- a/pyls/workspace.py +++ b/pyls/workspace.py @@ -174,6 +174,14 @@ def apply_change(self, change): self._source = new.getvalue() + def position_at_offset(self, offset): + lines = self.source[:offset].splitlines(True) + last_line = lines[-1] + if last_line.rstrip() == last_line: + return {'line': len(lines) -1, 'character': len(last_line)} + + return {'line': len(lines), 'character': 0} + def offset_at_position(self, position): """Return the byte-offset pointed at by the given position.""" return position['character'] + len(''.join(self.lines[:position['line']])) From 3a72254ee159f70859639e0970d2930b8289a4fc Mon Sep 17 00:00:00 2001 From: ddcien Date: Thu, 7 Nov 2019 09:48:01 +0800 Subject: [PATCH 5/7] make python2 test pass --- pyls/_utils.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyls/_utils.py b/pyls/_utils.py index 647f3323..54ae7da0 100644 --- a/pyls/_utils.py +++ b/pyls/_utils.py @@ -111,16 +111,14 @@ def list_to_string(value): return ",".join(value) if isinstance(value, list) else value -def merge_string(a: str, b: str) -> str: +def merge_string(a, b): if len(a) < len(b): if b.startswith(a): return b for i in range(len(a)): if b.startswith(a[i:]): - break - - return a[:i] + b + return a[:i] + b else: if a.endswith(b): @@ -128,9 +126,9 @@ def merge_string(a: str, b: str) -> str: for i in range(len(b) -1, -1, -1): if a.endswith(b[:i]): - break + return a[:-i] + b - return (a[:-i] if i else a) + b + return a + b def merge_dicts(dict_a, dict_b): From 218a0aa3a404aae3053ebb4ed5e5d20f7300a7a3 Mon Sep 17 00:00:00 2001 From: ddcien Date: Thu, 7 Nov 2019 10:21:27 +0800 Subject: [PATCH 6/7] Fix position_at_offset bug --- pyls/workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyls/workspace.py b/pyls/workspace.py index a74e2893..5b85eed5 100644 --- a/pyls/workspace.py +++ b/pyls/workspace.py @@ -177,7 +177,7 @@ def apply_change(self, change): def position_at_offset(self, offset): lines = self.source[:offset].splitlines(True) last_line = lines[-1] - if last_line.rstrip() == last_line: + if last_line.rstrip('\r\n') == last_line: return {'line': len(lines) -1, 'character': len(last_line)} return {'line': len(lines), 'character': 0} From c6b458477db9738e13e264dd57fbf313dfd26a30 Mon Sep 17 00:00:00 2001 From: ddcien Date: Thu, 7 Nov 2019 10:24:58 +0800 Subject: [PATCH 7/7] Make python2 test pass --- pyls/_utils.py | 2 +- pyls/workspace.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyls/_utils.py b/pyls/_utils.py index 54ae7da0..7c6261df 100644 --- a/pyls/_utils.py +++ b/pyls/_utils.py @@ -124,7 +124,7 @@ def merge_string(a, b): if a.endswith(b): return a - for i in range(len(b) -1, -1, -1): + for i in range(len(b) - 1, -1, -1): if a.endswith(b[:i]): return a[:-i] + b diff --git a/pyls/workspace.py b/pyls/workspace.py index 5b85eed5..0095fbd3 100644 --- a/pyls/workspace.py +++ b/pyls/workspace.py @@ -178,7 +178,7 @@ def position_at_offset(self, offset): lines = self.source[:offset].splitlines(True) last_line = lines[-1] if last_line.rstrip('\r\n') == last_line: - return {'line': len(lines) -1, 'character': len(last_line)} + return {'line': len(lines) - 1, 'character': len(last_line)} return {'line': len(lines), 'character': 0}