Skip to content

Commit

Permalink
Merge branch 'main' into feat/diag-annotations
Browse files Browse the repository at this point in the history
* main:
  Completions overhaul (#2010)
  Tweaks for signature help popup (#2006)
  Add GDScript syntax highlighting instructions
  • Loading branch information
rchl committed Aug 2, 2022
2 parents a0439b0 + ac91446 commit 1a45555
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 121 deletions.
1 change: 1 addition & 0 deletions docs/src/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ The color scheme rule only works if the "background" color is different from the
| ----- | ----------- |
| `entity.name.function.sighelp.lsp` | Function name in the signature help popup |
| `variable.parameter.sighelp.lsp` | Function argument in the signature help popup |
| `variable.parameter.sighelp.active.lsp` | Function argument which is currently highlighted in the signature help popup |

### Annotations

Expand Down
7 changes: 4 additions & 3 deletions docs/src/language_servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,11 @@ Follow installation instructions on [LSP-gopls](https://github.com/sublimelsp/LS
!!! info "Visit [gopls repo](https://github.com/golang/tools/tree/master/gopls) for more info."
Enable multi-module workspace support by setting the `experimentalWorkspaceModule` to `true`. Most features will work across modules, but some, such as `goimports`, will not work as expected. Please note that this setting is still very experimental.

## Godot (GDScript)
## GDScript (Godot Engine)

1. Launch the Godot Editor on the project you are working on and leave it running.
2. Open `Preferences > Package Settings > LSP > Settings` and add the `"godot-lsp"` client configuration to the `"clients"`:
1. Install the [GDScript (Godot Engine)](https://packagecontrol.io/packages/GDScript%20(Godot%20Engine)) package from Package Control for syntax highlighting.
2. Launch the Godot Editor on the project you are working on and leave it running.
3. Open `Preferences > Package Settings > LSP > Settings` and add the `"godot-lsp"` client configuration to the `"clients"`:

```json
{
Expand Down
3 changes: 2 additions & 1 deletion plugin/core/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ class SemanticTokenModifiers:
SignatureInformation = TypedDict('SignatureInformation', {
'label': str,
'documentation': Union[str, Dict[str, str]],
'parameters': List[ParameterInformation]
'parameters': List[ParameterInformation],
'activeParameter': int
}, total=False)


Expand Down
2 changes: 2 additions & 0 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@ def get_initialize_params(variables: Dict[str, str], workspace_folders: List[Wor
},
"signatureHelp": {
"dynamicRegistration": True,
"contextSupport": True,
"signatureInformation": {
"activeParameterSupport": True,
"documentationFormat": ["markdown", "plaintext"],
"parameterInformation": {
"labelOffsetSupport": True
Expand Down
44 changes: 16 additions & 28 deletions plugin/core/signature_help.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from .logging import debug
from .protocol import SignatureHelp
from .protocol import SignatureHelpContext
from .protocol import SignatureInformation
from .registry import LspTextCommand
from .typing import Optional, List
Expand Down Expand Up @@ -66,26 +65,18 @@ def render(self, view: sublime.View) -> str:
except IndexError:
return ""
formatted = [] # type: List[str]
intro = self._render_intro()
if intro:
formatted.append(intro)
if self.has_multiple_signatures():
formatted.append(self._render_intro())
formatted.extend(self._render_label(view, signature))
formatted.extend(self._render_docs(view, signature))
return "".join(formatted)

def context(self, trigger_kind: int, trigger_character: str, is_retrigger: bool) -> SignatureHelpContext:
def active_signature_help(self) -> SignatureHelp:
"""
Extract the state out of this state machine to send back to the language server.
XXX: Currently unused. Revisit this some time in the future.
"""
self._state["activeSignature"] = self._active_signature_index
return {
"triggerKind": trigger_kind,
"triggerCharacter": trigger_character,
"isRetrigger": is_retrigger,
"activeSignatureHelp": self._state
}
return self._state

def has_multiple_signatures(self) -> bool:
"""Does the current signature help state contain more than one overload?"""
Expand All @@ -96,18 +87,13 @@ def select_signature(self, forward: bool) -> None:
new_index = self._active_signature_index + (1 if forward else -1)
self._active_signature_index = max(0, min(new_index, len(self._signatures) - 1))

def active_signature(self) -> SignatureInformation:
return self._signatures[self._active_signature_index]

def _render_intro(self) -> Optional[str]:
if len(self._signatures) > 1:
fmt = '<p><div style="font-size: 0.9rem"><b>{}</b> of <b>{}</b> overloads ' + \
"(use ↑ ↓ to navigate, press Esc to hide):</div></p>"
return fmt.format(
self._active_signature_index + 1,
len(self._signatures),
)
return None
def _render_intro(self) -> str:
fmt = '<p><div style="font-size: 0.9rem"><b>{}</b> of <b>{}</b> overloads ' + \
"(use ↑ ↓ to navigate, press Esc to hide):</div></p>"
return fmt.format(
self._active_signature_index + 1,
len(self._signatures),
)

def _render_label(self, view: sublime.View, signature: SignatureInformation) -> List[str]:
formatted = [] # type: List[str]
Expand All @@ -118,6 +104,7 @@ def _render_label(self, view: sublime.View, signature: SignatureInformation) ->
parameters = signature.get("parameters")
if parameters:
prev, start, end = 0, 0, 0
active_parameter_index = signature.get("activeParameter", self._active_parameter_index)
for i, param in enumerate(parameters):
rawlabel = param["label"]
if isinstance(rawlabel, list):
Expand All @@ -136,7 +123,7 @@ def _render_label(self, view: sublime.View, signature: SignatureInformation) ->
end = start + len(rawlabel)
if prev < start:
formatted.append(_function(view, label[prev:start]))
formatted.append(_parameter(view, label[start:end], i == self._active_parameter_index))
formatted.append(_parameter(view, label[start:end], i == active_parameter_index))
prev = end
if end < len(label):
formatted.append(_function(view, label[end:]))
Expand Down Expand Up @@ -164,7 +151,7 @@ def _parameter_documentation(self, view: sublime.View, signature: SignatureInfor
if not parameters:
return None
try:
parameter = parameters[self._active_parameter_index]
parameter = parameters[signature.get("activeParameter", self._active_parameter_index)]
except IndexError:
return None
documentation = parameter.get("documentation")
Expand All @@ -186,7 +173,8 @@ def _function(view: sublime.View, content: str) -> str:


def _parameter(view: sublime.View, content: str, emphasize: bool) -> str:
return _wrap_with_scope_style(view, content, "variable.parameter.sighelp.lsp", emphasize)
scope = "variable.parameter.sighelp.active.lsp" if emphasize else "variable.parameter.sighelp.lsp"
return _wrap_with_scope_style(view, content, scope, emphasize)


def _wrap_with_scope_style(view: sublime.View, content: str, scope: str, emphasize: bool) -> str:
Expand Down
91 changes: 38 additions & 53 deletions plugin/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,66 +959,51 @@ 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}</{0}>".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 += "<p>"
# `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 += "</p>"
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') 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', -1), 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_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_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_detail))
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
24 changes: 22 additions & 2 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from .core.protocol import Range
from .core.protocol import Request
from .core.protocol import SignatureHelp
from .core.protocol import SignatureHelpContext
from .core.protocol import SignatureHelpTriggerKind
from .core.registry import best_session
from .core.registry import windows
from .core.sessions import AbstractViewListener
Expand Down Expand Up @@ -492,10 +494,28 @@ def do_signature_help_async(self, manual: bool) -> None:
last_char = previous_non_whitespace_char(self.view, pos)
if manual or last_char in triggers:
self.purge_changes_async()
params = text_document_position_params(self.view, pos)
position_params = text_document_position_params(self.view, pos)
context_params = {} # type: SignatureHelpContext
if manual:
context_params["triggerKind"] = SignatureHelpTriggerKind.Invoked
else:
context_params["triggerKind"] = SignatureHelpTriggerKind.TriggerCharacter
context_params["triggerCharacter"] = last_char
context_params["isRetrigger"] = self._sighelp is not None
if self._sighelp:
context_params["activeSignatureHelp"] = self._sighelp.active_signature_help()
params = {
"textDocument": position_params["textDocument"],
"position": position_params["position"],
"context": context_params
}
language_map = session.markdown_language_id_to_st_syntax_map()
request = Request.signatureHelp(params, self.view)
session.send_request_async(request, lambda resp: self._on_signature_help(resp, pos, language_map))
elif self.view.match_selector(pos, "meta.function-call.arguments"):
# Don't force close the signature help popup while the user is typing the parameters.
# See also: https://github.com/sublimehq/sublime_text/issues/5518
pass
else:
# TODO: Refactor popup usage to a common class. We now have sigHelp, completionDocs, hover, and diags
# all using a popup. Most of these systems assume they have exclusive access to a popup, while in
Expand Down Expand Up @@ -536,7 +556,7 @@ def _show_sighelp_popup(self, content: str, point: int) -> None:
show_lsp_popup(
self.view,
content,
flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY | sublime.COOPERATE_WITH_AUTO_COMPLETE,
flags=sublime.COOPERATE_WITH_AUTO_COMPLETE,
location=point,
on_hide=self._on_sighelp_hide,
on_navigate=self._on_sighelp_navigate)
Expand Down
Loading

0 comments on commit 1a45555

Please sign in to comment.