Skip to content

Commit

Permalink
Completion filtering fixes (#822)
Browse files Browse the repository at this point in the history
* Don't run on_completion_inserted on insert_best_completion

Pressing <tab> in documents results in huge "LSP: No match for inserted "<entire document>.." entries in the console.

* Don't cancel requested completions for the exact same word while typing ahead.

If typing view.settings, completion at dot is cancelled by "s", "e" etc. even though original request is still relevant

* Don't present completion results from previous word

If typing view.settings() quickly, a popup would show at the cursor offering "settings()".
In the response handler, we detect if cursor has passed a word boundary and ignore the result if so.

* Revert fix for #745, causing extra requests after trigger character

If there are no completion results, typing view.s would trigger requests at both '.' and 's'.
  • Loading branch information
tomv564 authored Dec 10, 2019
1 parent 9f44f55 commit ed15d93
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 41 deletions.
40 changes: 25 additions & 15 deletions plugin/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .core.completion import parse_completion_response, format_completion
from .core.registry import session_for_view, client_from_session, LSPViewEventListener
from .core.configurations import is_supported_syntax
from .core.documents import get_document_position, is_at_word
from .core.documents import get_document_position, position_is_word
from .core.sessions import Session
from .core.edit import parse_text_edit

Expand Down Expand Up @@ -121,11 +121,6 @@ def is_same_completion(self, prefix: str, locations: 'List[int]') -> bool:
if self.last_location < 0:
return False

# issue 745, some servers return nothing until some chars into a word are returned
# Don't cache these empty responses.
if prefix and self.last_prefix == "" and not self.completions:
return False

# completion requests from the same location with the same prefix are cached.
current_start = locations[0] - len(prefix)
last_start = self.last_location - len(self.last_prefix)
Expand Down Expand Up @@ -176,8 +171,11 @@ def on_modified(self) -> None:

def on_completion_inserted(self) -> None:
# get text inserted from last completion
word = self.view.word(self.last_location)
begin = word.begin()
begin = self.last_location
if position_is_word(self.view, begin):
word = self.view.word(self.last_location)
begin = word.begin()

region = sublime.Region(begin, self.view.sel()[0].end())
inserted = self.view.substr(region)

Expand Down Expand Up @@ -235,8 +233,9 @@ def on_query_completions(self, prefix: str, locations: 'List[int]') -> 'Optional
self.completions = []

elif self.state in (CompletionState.REQUESTING, CompletionState.CANCELLING):
self.next_request = (prefix, locations)
self.state = CompletionState.CANCELLING
if not reuse_completion:
self.next_request = (prefix, locations)
self.state = CompletionState.CANCELLING

elif self.state == CompletionState.APPLYING:
self.state = CompletionState.IDLE
Expand All @@ -246,7 +245,7 @@ def on_query_completions(self, prefix: str, locations: 'List[int]') -> 'Optional
return None

def on_text_command(self, command_name: str, args: 'Optional[Any]') -> None:
self.committing = command_name in ('commit_completion', 'insert_best_completion', 'auto_complete')
self.committing = command_name in ('commit_completion', 'auto_complete')

def do_request(self, prefix: str, locations: 'List[int]') -> None:
self.next_request = None
Expand Down Expand Up @@ -291,12 +290,23 @@ def apply_additional_edits(self, additional_edits: 'List[Dict]') -> None:
def handle_response(self, response: 'Optional[Union[Dict,List]]') -> None:
if self.state == CompletionState.REQUESTING:

last_col = self.last_location
if is_at_word(self.view, None):
completion_start = self.last_location
if position_is_word(self.view, self.last_location):
# if completion is requested in the middle of a word, where does it start?
word = self.view.word(self.last_location)
word_start = word.begin()
_last_row, last_col = self.view.rowcol(word_start)
completion_start = word.begin()

current_word_start = self.view.sel()[0].begin()
if position_is_word(self.view, current_word_start):
current_word_region = self.view.word(current_word_start)
current_word_start = current_word_region.begin()

if current_word_start != completion_start:
debug('completion results for', completion_start, 'now at', current_word_start, 'discarding')
self.state = CompletionState.IDLE
return

_last_row, last_col = self.view.rowcol(completion_start)

response_items, response_incomplete = parse_completion_response(response)
self.response_items = response_items
Expand Down
6 changes: 5 additions & 1 deletion plugin/core/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ def get_position(view: sublime.View, event: 'Optional[dict]' = None) -> int:

def is_at_word(view: sublime.View, event: 'Optional[dict]') -> bool:
pos = get_position(view, event)
point_classification = view.classify(pos)
return position_is_word(view, pos)


def position_is_word(view: sublime.View, position: int) -> bool:
point_classification = view.classify(position)
if point_classification & SUBLIME_WORD_MASK:
return True
else:
Expand Down
50 changes: 25 additions & 25 deletions tests/test_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@
'range': {
'start': {
'line': 0,
'character': 6
'character': 5
},
'end': {
'line': 0,
'character': 6
'character': 5
}
}
})
Expand Down Expand Up @@ -164,7 +164,7 @@ def test_simple_label(self):
if handler:
# todo: want to test trigger chars instead?
# self.view.run_command('insert', {"characters": '.'})
result = handler.on_query_completions("", [1])
result = handler.on_query_completions("", [0])

# synchronous response
self.assertTrue(handler.initialized)
Expand All @@ -180,7 +180,7 @@ def test_simple_label(self):
self.assertEquals(len(handler.completions), 2)

# verify insertion works
self.view.run_command("insert_best_completion")
self.view.run_command("commit_completion")
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())), 'asdf')

Expand All @@ -191,9 +191,9 @@ def test_simple_inserttext(self):
handler = self.get_view_event_listener("on_query_completions")
self.assertIsNotNone(handler)
if handler:
handler.on_query_completions("", [1])
handler.on_query_completions("", [0])
yield 100
self.view.run_command("insert_best_completion")
self.view.run_command("commit_completion")
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())),
insert_text_completions[0]["insertText"])
Expand All @@ -209,7 +209,7 @@ def test_var_prefix_using_label(self):
if handler:
handler.on_query_completions("", [1])
yield 100
self.view.run_command("insert_best_completion")
self.view.run_command("commit_completion")
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())), '$what')

Expand All @@ -229,7 +229,7 @@ def test_var_prefix_added_in_insertText(self):
if handler:
handler.on_query_completions("", [1])
yield 100
self.view.run_command("insert_best_completion")
self.view.run_command("commit_completion")
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())), '$what')

Expand All @@ -249,7 +249,7 @@ def test_var_prefix_added_in_label(self):
if handler:
handler.on_query_completions("", [1])
yield 100
self.view.run_command("insert_best_completion")
self.view.run_command("commit_completion")
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())), '$what')

Expand All @@ -264,9 +264,9 @@ def test_space_added_in_label(self):
handler = self.get_view_event_listener("on_query_completions")
self.assertIsNotNone(handler)
if handler:
handler.on_query_completions("", [1])
handler.on_query_completions("", [0])
yield 100
self.view.run_command("insert_best_completion")
self.view.run_command("commit_completion")
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())), 'const')

Expand All @@ -287,7 +287,7 @@ def test_dash_missing_from_label(self):
if handler:
handler.on_query_completions("", [1])
yield 100
self.view.run_command("insert_best_completion")
self.view.run_command("commit_completion")
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())),
'-UniqueId')
Expand All @@ -309,8 +309,8 @@ def test_edit_before_cursor(self):
handler.on_query_completions("myF", [7])
yield 100
# note: invoking on_text_command manually as sublime doesn't call it.
handler.on_text_command('insert_best_completion', {})
self.view.run_command("insert_best_completion", {})
handler.on_text_command('commit_completion', {})
self.view.run_command("commit_completion", {})
yield AFTER_INSERT_COMPLETION_DELAY
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())),
Expand All @@ -331,11 +331,11 @@ def test_edit_after_nonword(self):
handler = self.get_view_event_listener("on_query_completions")
self.assertIsNotNone(handler)
if handler:
handler.on_query_completions("", [6])
handler.on_query_completions("", [5])
yield 100
# note: invoking on_text_command manually as sublime doesn't call it.
handler.on_text_command('insert_best_completion', {})
self.view.run_command("insert_best_completion", {})
handler.on_text_command('commit_completion', {})
self.view.run_command("commit_completion", {})
yield AFTER_INSERT_COMPLETION_DELAY
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())),
Expand All @@ -355,8 +355,8 @@ def test_implement_all_members_quirk(self):
if handler:
handler.on_query_completions("", [1])
yield 100
handler.on_text_command('insert_best_completion', {})
self.view.run_command('insert_best_completion', {})
handler.on_text_command('commit_completion', {})
self.view.run_command('commit_completion', {})
yield AFTER_INSERT_COMPLETION_DELAY
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())),
Expand All @@ -369,11 +369,11 @@ def test_additional_edits(self):
handler = self.get_view_event_listener("on_query_completions")
self.assertIsNotNone(handler)
if handler:
handler.on_query_completions("", [1])
handler.on_query_completions("", [0])
yield 100
# note: invoking on_text_command manually as sublime doesn't call it.
handler.on_text_command('insert_best_completion', {})
self.view.run_command("insert_best_completion", {})
handler.on_text_command('commit_completion', {})
self.view.run_command("commit_completion", {})
yield AFTER_INSERT_COMPLETION_DELAY
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())),
Expand All @@ -388,15 +388,15 @@ def test_resolve_for_additional_edits(self):
handler = self.get_view_event_listener("on_query_completions")
self.assertIsNotNone(handler)
if handler:
handler.on_query_completions("", [1])
handler.on_query_completions("", [0])

# note: ideally the handler is initialized with resolveProvider capability
handler.resolve = True

yield 100
# note: invoking on_text_command manually as sublime doesn't call it.
handler.on_text_command('insert_best_completion', {})
self.view.run_command("insert_best_completion", {})
handler.on_text_command('commit_completion', {})
self.view.run_command("commit_completion", {})
yield AFTER_INSERT_COMPLETION_DELAY
self.assertEquals(
self.view.substr(sublime.Region(0, self.view.size())),
Expand Down

0 comments on commit ed15d93

Please sign in to comment.