-
Notifications
You must be signed in to change notification settings - Fork 184
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
Enable full completions #47
Conversation
main.py
Outdated
return | ||
@classmethod | ||
def is_applicable(cls, settings): | ||
syntax = settings.get('syntax') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In rare cases syntax
may be None
(annoying ST bug), check for this just in case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case the behaviour would be as expected, but I'll add this fix anyways.
Is this documented somewhere?
main.py
Outdated
def is_after_trigger_character(self, location): | ||
if location > 0: | ||
prev_char = self.view.substr( | ||
sublime.Region(location - 1, location)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use sublime.Region(location - 1)
I believe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can even call self.view.substr(location - 1)
as substr
understands both a Region
and a Point
main.py
Outdated
dict) else response | ||
names = list(item.get('label') for item in items) | ||
debug('got completions', names) | ||
self.completions = list(self.format_completion(item) for item in items) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will you be doing anything with the sortText
property of the CompletionItem or will you let ST sort the results? The clangd
server uses sortText
to sort completions by priority, so you'll see properties and methods first, base classes somewhere middle, keywords last.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, is there a way to control what ST does with the completions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No there is not. ST merges and sorts all items it gets internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not immediately, it just presents that list as-is if the user hasn't typed more characters yet, so there might be some benefit to using sortText
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it could slow down LSP a lot if there are tons of completion items, you'll have to experiment with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rwols good to know!
main.py
Outdated
def __init__(self): | ||
self.completions = [] # type: List[Tuple[str, str]] | ||
self.refreshing = False | ||
class CompletionState(object): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had some trouble seeing "old" completions when I typed fast, I hope this class fixes that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From some quick tests using the changes in this PR I don't see this problem anymore, nice job!
main.py
Outdated
@@ -1605,7 +1605,7 @@ def format_completion(self, item) -> 'Tuple[str, str]': | |||
insertText = item.get("insertText") | |||
if insertText[0] == '$': # sublime needs leading '$' escaped. | |||
insertText = '\$' + insertText[1:] | |||
return ["{}\t{}".format(label, detail), insertText] | |||
return "{}\t{}".format(label, detail), insertText |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure, whether it's to be handled by LSP or the pyls, but detail
returns def function
for a function completion, which makes completions look a bit too verbose:
function def function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be something for pyls to look at.
16decfb
to
e0e7a38
Compare
Is anyone interested in giving this branch a test? I'm not 100% convinced it's working correctly. Example: sublime still shows |
Yes, I'm gonna give this a spin. EDIT: I don't see any problems at first glance (tested with clangd) |
main.py
Outdated
sublime.Region(locations[0] - 1, locations[0])) | ||
if prev_char not in autocomplete_triggers: | ||
return None | ||
elif self.state == CompletionState.REQUESTING or self.state == CompletionState.CANCELLING: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about: elif self.state in (CompletionState.REQUESTING, CompletionState.CANCELLING)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Seems quite easy to get the state machine stalled in CANCELLING state, once a language-server does not respond. From this point I'll never get any completion any more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice code suggestion. Why does the language server not respond?
Furthermore there seems to be a general issue with requesting completions. Sometimes a race condition between the onChange change notification and the completion request occures. If onChange is sent later then the completion response, python-language-server returns ArgumentError as it does not yet know about the content of the edited line. On the other hand the onChange triggers diagnostics scan and causes python-language-server to delay completions as it is busy with linting. Especially mypy plugin causes heavy delays. |
@deathaxe thanks for testing! In a discussion about the javascript-typescript server's completion performance the idea of processing diagnostics from a queue after a small delay was raised. This is worth proposing for pyls as well. |
I didn't investigate too much, but I think the race condition is related to
I hate delays. They are caused by processing anyway. But creating a multithread or multiprocess architecture with a pool of worker threads/processes might be a good idea for pyls to process multiple requests/notifications in parallel. This might help much as pyls may already be busy with multiple linters run after each other while a completion request is received at the moment. This requires to categorize the received requests/notifications some how. A new didChange request would need to be queued, but not processed until all pending operations on the last version of the document have finished. A new request such as a completion or signatureHelp might be added to the queue and processed immediately to respond as fast as possible, while diagnostics may still be in process. This requires at least one dedicated thread/process to handle the synchronization events such as didOpen/didChange/... and one dedicated thread/process to handle requests. A new didChange might would then be queued and being processed as soon as the running requests queue is empty. |
here's a little implementation to get a reference how quick jedi really is without LSP overhead. # -*- encoding: utf-8 -*-
import os
import sys
import sublime
import sublime_plugin
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib'))
from jedi.api import Script
sys.path.pop(0)
def format_completion(complete):
""" Returns a tuple of the string that would be visible in
the completion dialogue and the completion word
:type complete: jedi.api_classes.Completion
:rtype: (str, str)
"""
display, insert = complete.name + '\t' + complete.type, complete.name
return display, insert
class EventListener(sublime_plugin.EventListener):
def on_load_async(self, view):
# just load to trigger interpreter and create cache
self._script(view, 0)
def on_query_completions(self, view, prefix, locations):
point = locations[0]
if not view.match_selector(point, "source.python"):
return None
script = self._script(view, point)
completions = script.completions()
return (
[format_completion(complete) for complete in completions],
sublime.INHIBIT_WORD_COMPLETIONS
)
def _script(self, view, point):
path = view.file_name() or ''
region = sublime.Region(0, view.size())
source = view.substr(region)
line, column = view.rowcol(point)
return Script(source, line + 1, column, path=path) |
For issue #25, where all completion requests (even manual) were ignored unless the cursor followed a trigger character.
The following improvements were made:
To do: