From e8f6928b25ea13fb41eecba2dee035beffa36237 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Thu, 28 Jul 2022 17:49:38 +0200 Subject: [PATCH 1/5] Completion overhaul --- plugin/core/views.py | 90 ++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/plugin/core/views.py b/plugin/core/views.py index 6200d55db..b0e31d532 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -939,66 +939,50 @@ def format_diagnostic_for_html( return "".join(formatted) -def _is_completion_item_deprecated(item: CompletionItem) -> bool: - if item.get("deprecated", False): - return True - tags = item.get("tags") - if isinstance(tags, list): - return CompletionItemTag.Deprecated in tags - return False - - -def _wrap_in_tags(tag: str, item: str) -> str: - return "<{0}>{1}".format(tag, html.escape(item)) - - def format_completion( item: CompletionItem, index: int, can_resolve_completion_items: bool, session_name: str ) -> sublime.CompletionItem: # This is a hot function. Don't do heavy computations or IO in this function. - item_kind = item.get("kind") - kind = COMPLETION_KINDS.get(item_kind, KIND_UNSPECIFIED) if item_kind else KIND_UNSPECIFIED - - if _is_completion_item_deprecated(item): - kind = (kind[0], '!', "⚠ {} - Deprecated".format(kind[2])) - - lsp_label = item["label"] - lsp_label_details = item.get("labelDetails") - lsp_filter_text = item.get("filterText") - st_annotation = (item.get("detail") or "").replace('\n', ' ') - - st_details = "" - if can_resolve_completion_items or item.get("documentation"): - st_details += make_command_link("lsp_resolve_docs", "More", {"index": index, "session_name": session_name}) - if lsp_label_details: - if st_details: - st_details += " | " - lsp_label_detail = lsp_label_details.get("detail") - lsp_label_description = lsp_label_details.get("description") - st_details += "

" - # `label` should be rendered most prominent. - st_details += _wrap_in_tags("b", lsp_label) - if isinstance(lsp_label_detail, str): - # `detail` should be rendered less prominent than `label`. - # The string is appended directly after `label`, with no additional white space applied. - st_details += html.escape(lsp_label_detail) - if isinstance(lsp_label_description, str): - # `description` should be rendered less prominent than `detail`. - # Additional separation is added. - st_details += " - {}".format(_wrap_in_tags("i", lsp_label_description)) - st_details += "

" - elif lsp_filter_text and lsp_filter_text != lsp_label: - if st_details: - st_details += " | " - st_details += _wrap_in_tags("p", lsp_label) + + lsp_label = item['label'] + lsp_label_details = item.get('labelDetails', {}).get('detail', "") + lsp_label_description = item.get('labelDetails', {}).get('description', "") + lsp_filter_text = item.get('filterText', "") + lsp_detail = item.get('detail', "").replace("\n", " ") + + kind = COMPLETION_KINDS.get(item.get('kind', 0), KIND_UNSPECIFIED) + + details = [] # type: List[str] + if can_resolve_completion_items or item.get('documentation'): + details.append(make_command_link('lsp_resolve_docs', "More", {'index': index, 'session_name': session_name})) + + if lsp_label_details and (lsp_label + lsp_label_details).startswith(lsp_filter_text): + trigger = lsp_label + lsp_label_details + annotation = lsp_label_description or lsp_detail + elif lsp_label.startswith(lsp_filter_text): + trigger = lsp_label + annotation = lsp_detail + if lsp_label_details: + details.append(html.escape(lsp_label + lsp_label_details)) + if lsp_label_description: + details.append(html.escape(lsp_label_description)) + else: + trigger = lsp_filter_text + annotation = lsp_detail + details.append(html.escape(lsp_label + lsp_label_details)) + if lsp_label_description: + details.append(html.escape(lsp_label_description)) + + if item.get('deprecated') or CompletionItemTag.Deprecated in item.get('tags', []): + annotation = "DEPRECATED - " + annotation if annotation else "DEPRECATED" completion = sublime.CompletionItem.command_completion( - trigger=lsp_filter_text or lsp_label, - command="lsp_select_completion_item", + trigger=trigger, + command='lsp_select_completion_item', args={"item": item, "session_name": session_name}, - annotation=st_annotation, + annotation=annotation, kind=kind, - details=st_details) - if item.get("textEdit"): + details=" | ".join(details)) + if item.get('textEdit'): completion.flags = sublime.COMPLETION_FLAG_KEEP_PREFIX return completion From a1db0166781ba9aeed6a9a9859d735f8abba0796 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Fri, 29 Jul 2022 16:32:02 +0200 Subject: [PATCH 2/5] Rename variable --- plugin/core/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/core/views.py b/plugin/core/views.py index b0e31d532..a9f857012 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -945,7 +945,7 @@ def format_completion( # This is a hot function. Don't do heavy computations or IO in this function. lsp_label = item['label'] - lsp_label_details = item.get('labelDetails', {}).get('detail', "") + lsp_label_detail = item.get('labelDetails', {}).get('detail', "") lsp_label_description = item.get('labelDetails', {}).get('description', "") lsp_filter_text = item.get('filterText', "") lsp_detail = item.get('detail', "").replace("\n", " ") @@ -956,20 +956,20 @@ def format_completion( if can_resolve_completion_items or item.get('documentation'): details.append(make_command_link('lsp_resolve_docs', "More", {'index': index, 'session_name': session_name})) - if lsp_label_details and (lsp_label + lsp_label_details).startswith(lsp_filter_text): - trigger = lsp_label + lsp_label_details + if lsp_label_detail and (lsp_label + lsp_label_detail).startswith(lsp_filter_text): + trigger = lsp_label + lsp_label_detail annotation = lsp_label_description or lsp_detail elif lsp_label.startswith(lsp_filter_text): trigger = lsp_label annotation = lsp_detail - if lsp_label_details: - details.append(html.escape(lsp_label + lsp_label_details)) + if lsp_label_detail: + details.append(html.escape(lsp_label + lsp_label_detail)) if lsp_label_description: details.append(html.escape(lsp_label_description)) else: trigger = lsp_filter_text annotation = lsp_detail - details.append(html.escape(lsp_label + lsp_label_details)) + details.append(html.escape(lsp_label + lsp_label_detail)) if lsp_label_description: details.append(html.escape(lsp_label_description)) From 4655e56bb81da72b937269f32888d9009501e818 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Sun, 31 Jul 2022 21:44:50 +0200 Subject: [PATCH 3/5] Try fix tests --- tests/test_completion.py | 46 +++++++++++----------------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/tests/test_completion.py b/tests/test_completion.py index 1ca0a79c7..0afa86947 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -142,7 +142,6 @@ def test_var_prefix_added_in_insertText(self) -> 'Generator': 'sortText': '0006USERPROFILE', 'label': 'USERPROFILE', 'additionalTextEdits': None, - 'detail': None, 'data': None, 'kind': 6, 'command': None, @@ -178,7 +177,6 @@ def test_pure_insertion_text_edit(self) -> 'Generator': } }, 'label': '$someParam', - 'filterText': None, 'data': None, 'command': None, 'detail': 'null', @@ -612,8 +610,7 @@ def test_show_deprecated_flag(self) -> None: "deprecated": True } # type: CompletionItem formatted_completion_item = format_completion(item_with_deprecated_flag, 0, False, "") - self.assertEqual('!', formatted_completion_item.kind[1]) - self.assertEqual('⚠ Method - Deprecated', formatted_completion_item.kind[2]) + self.assertIn("DEPRECATED", formatted_completion_item.annotation) def test_show_deprecated_tag(self) -> None: item_with_deprecated_tags = { @@ -622,8 +619,7 @@ def test_show_deprecated_tag(self) -> None: "tags": [CompletionItemTag.Deprecated] } # type: CompletionItem formatted_completion_item = format_completion(item_with_deprecated_tags, 0, False, "") - self.assertEqual('!', formatted_completion_item.kind[1]) - self.assertEqual('⚠ Method - Deprecated', formatted_completion_item.kind[2]) + self.assertIn("DEPRECATED", formatted_completion_item.annotation) def test_strips_carriage_return_in_insert_text(self) -> 'Generator': yield from self.verify( @@ -662,37 +658,37 @@ def check( check( resolve_support=False, - expected_regex=r"^

f

$", + expected_regex=r"^f$", label="f", label_details=None ) check( resolve_support=False, - expected_regex=r"^

f\(X& x\)

$", + expected_regex=r"^f\(X& x\)$", label="f", label_details={"detail": "(X& x)"} ) check( resolve_support=False, - expected_regex=r"^

f\(X& x\) - does things

$", + expected_regex=r"^f\(X& x\) \| does things$", label="f", label_details={"detail": "(X& x)", "description": "does things"} ) check( resolve_support=True, - expected_regex=r"^More \|

f

$", + expected_regex=r"^More \| f$", label="f", label_details=None ) check( resolve_support=True, - expected_regex=r"^More \|

f\(X& x\)

$", + expected_regex=r"^More \| f\(X& x\)$", label="f", label_details={"detail": "(X& x)"} ) check( resolve_support=True, - expected_regex=r"^More \|

f\(X& x\) - does things

$", # noqa: E501 + expected_regex=r"^More \| f\(X& x\) \| does things$", # noqa: E501 label="f", label_details={"detail": "(X& x)", "description": "does things"} ) @@ -709,41 +705,23 @@ def check( if label_details is not None: lsp["labelDetails"] = label_details native = format_completion(lsp, 0, resolve_support, "") - self.assertRegex(native.details, expected_regex) + self.assertRegex(native.trigger, expected_regex) check( resolve_support=False, - expected_regex=r"^$", + expected_regex=r"^f$", label="f", label_details=None ) check( resolve_support=False, - expected_regex=r"^

f\(X& x\)

$", + expected_regex=r"^f\(X& x\)$", label="f", label_details={"detail": "(X& x)"} ) check( resolve_support=False, - expected_regex=r"^

f\(X& x\) - does things

$", - label="f", - label_details={"detail": "(X& x)", "description": "does things"} - ) - check( - resolve_support=True, - expected_regex=r"^More$", - label="f", - label_details=None - ) - check( - resolve_support=True, - expected_regex=r"^More \|

f\(X& x\)

$", - label="f", - label_details={"detail": "(X& x)"} - ) - check( - resolve_support=True, - expected_regex=r"^More \|

f\(X& x\) - does things

$", # noqa: E501 + expected_regex=r"^f\(X& x\)$", label="f", label_details={"detail": "(X& x)", "description": "does things"} ) From 147ecded51189e2d20f3f2ce1c05f72d571a7d2d Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Sun, 31 Jul 2022 22:06:48 +0200 Subject: [PATCH 4/5] Protect against out of spec usage of null instead of string values --- plugin/core/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugin/core/views.py b/plugin/core/views.py index a9f857012..ca489eb71 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -945,10 +945,11 @@ def format_completion( # This is a hot function. Don't do heavy computations or IO in this function. lsp_label = item['label'] - lsp_label_detail = item.get('labelDetails', {}).get('detail', "") - lsp_label_description = item.get('labelDetails', {}).get('description', "") - lsp_filter_text = item.get('filterText', "") - lsp_detail = item.get('detail', "").replace("\n", " ") + lsp_label_details = item.get('labelDetails') or {} + lsp_label_detail = lsp_label_details.get('detail') or "" + lsp_label_description = lsp_label_details.get('description') or "" + lsp_filter_text = item.get('filterText') or "" + lsp_detail = (item.get('detail') or "").replace("\n", " ") kind = COMPLETION_KINDS.get(item.get('kind', 0), KIND_UNSPECIFIED) From b704fe4b8e1ea05ddac19d6dc65eb0dd9739434f Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 1 Aug 2022 17:09:05 +0200 Subject: [PATCH 5/5] -1 --- plugin/core/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/core/views.py b/plugin/core/views.py index ca489eb71..18e9cfe5a 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -951,7 +951,7 @@ def format_completion( lsp_filter_text = item.get('filterText') or "" lsp_detail = (item.get('detail') or "").replace("\n", " ") - kind = COMPLETION_KINDS.get(item.get('kind', 0), KIND_UNSPECIFIED) + kind = COMPLETION_KINDS.get(item.get('kind', -1), KIND_UNSPECIFIED) details = [] # type: List[str] if can_resolve_completion_items or item.get('documentation'):