Skip to content

Commit

Permalink
Fix completions being shown at wrong location after moving cursor (#133
Browse files Browse the repository at this point in the history
…) (#134)

Introduced CompletionRequest which tracks some request information that we can
compare when completion is done and decide whether we can show the completions.
  • Loading branch information
rchl authored and niosus committed Nov 2, 2016
1 parent d844b45 commit 2e29406
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 31 deletions.
32 changes: 18 additions & 14 deletions EasyClangComplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,21 @@ def completion_finished(self, future):
Args:
future (concurrent.Future): future holding completion result
"""
if future.done():
(job_id, completions) = future.result()
if job_id == self.current_job_id:
self.current_completions = completions
if self.current_completions:
# we only want to trigger the autocompletion popup if there
# are new completions to show there. Otherwise let it be.
SublBridge.show_auto_complete(
sublime.active_window().active_view())
if not future.done():
return
(completion_request, completions) = future.result()
if completion_request.get_identifier() != self.current_job_id:
return
active_view = sublime.active_window().active_view()
if completion_request.is_suitable_for_view(active_view):
self.current_completions = completions
else:
log.debug(" ignoring completions")
self.current_completions = []
if self.current_completions:
# we only want to trigger the autocompletion popup if there
# are new completions to show there. Otherwise let it be.
SublBridge.show_auto_complete(active_view)

def on_query_completions(self, view, prefix, locations):
""" Function that is called when user queries completions in the code
Expand All @@ -222,7 +228,8 @@ def on_query_completions(self, view, prefix, locations):
log.debug(" on_query_completions view id %s", view.buffer_id())
log.debug(" prefix: %s, locations: %s" % (prefix, locations))
trigger_pos = locations[0] - len(prefix)
current_pos_id = Tools.get_position_identifier(view, trigger_pos)
completion_request = tools.CompletionRequest(view, trigger_pos)
current_pos_id = completion_request.get_identifier()
log.debug(" this position has identifier: '%s'", current_pos_id)

if not self.completer:
Expand Down Expand Up @@ -255,10 +262,7 @@ def on_query_completions(self, view, prefix, locations):
log.debug(" starting async auto_complete with id: %s",
self.current_job_id)
future = EasyClangComplete.pool_read.submit(
self.completer.complete,
view,
trigger_pos,
self.current_job_id)
self.completer.complete, completion_request)
future.add_done_callback(self.completion_finished)

# show default completions for now if allowed
Expand Down
6 changes: 2 additions & 4 deletions plugin/completion/base_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,11 @@ def init_for_view(self, view, settings):
search_scope=search_scope)
log.debug(" flags_manager loaded")

def complete(self, view, cursor_pos, current_job_id):
def complete(self, completion_request):
"""Function to generate completions. See children for implementation.
Args:
view (sublime.View): current view
cursor_pos (int): sublime provided poistion of the cursor
current_job_id (str): identifier for this completion job
completion_request (CompletionRequest): request object
Raises:
NotImplementedError: Guarantees we do not call this abstract method
Expand Down
8 changes: 5 additions & 3 deletions plugin/completion/bin_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,22 +136,24 @@ def init_for_view(self, view, settings):

log.debug(" clang flags are: %s", self.flags_dict[view.buffer_id()])

def complete(self, view, cursor_pos, current_job_id):
def complete(self, completion_request):
""" This function is called asynchronously to create a list of
autocompletions. It builds up a clang command that is then executed
as a subprocess. The output is parsed for completions """
view = completion_request.get_view()
if not view.buffer_id() in self.flags_dict:
log.error(" cannot complete view: %s", view.buffer_id())
return (None, None)
start = time.time()
output_text = self.run_clang_command(view, "complete", cursor_pos)
output_text = self.run_clang_command(
view, "complete", completion_request.get_trigger_position())
raw_complete = output_text.splitlines()
end = time.time()
log.debug(" code complete done in %s seconds", end - start)

completions = Completer._parse_completions(raw_complete)
log.debug(' completions: %s' % completions)
return (current_job_id, completions)
return (completion_request, completions)

def update(self, view, show_errors):
"""update build for current view
Expand Down
8 changes: 5 additions & 3 deletions plugin/completion/lib_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,16 @@ def init_for_view(self, view, settings):
self.timer = Timer(Completer.timer_period, self.__remove_old_TUs)
self.timer.start()

def complete(self, view, cursor_pos, current_job_id):
def complete(self, completion_request):
""" This function is called asynchronously to create a list of
autocompletions. Using the current translation unit it queries libclang
for the possible completions.
"""
view = completion_request.get_view()
file_body = view.substr(sublime.Region(0, view.size()))
(row, col) = SublBridge.cursor_pos(view, cursor_pos)
(row, col) = SublBridge.cursor_pos(
view, completion_request.get_trigger_position())

# unsaved files
files = [(view.file_name(), file_body)]
Expand All @@ -224,7 +226,7 @@ def complete(self, view, cursor_pos, current_job_id):
else:
completions = Completer._parse_completions(complete_obj)
log.debug(' completions: %s' % completions)
return (current_job_id, completions)
return (completion_request, completions)

def update(self, view, show_errors):
"""Reparse the translation unit. This speeds up completions
Expand Down
56 changes: 51 additions & 5 deletions plugin/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,57 @@ def contains(file_path, query):
return False


class CompletionRequest(object):

""" An wrapper for completer request.
Provides a way to identify a completer request and provide some information
used when creating the request.
"""

def __init__(self, view, trigger_position):
"""
Initializes the object.
Args:
view (sublime.View): The view for which request is created.
trigger_position(int): The position for which request was created.
"""
self._view = view
self._trigger_position = trigger_position

def get_view(self):
""" Returns the view for which completion was requested. """
return self._view

def get_trigger_position(self):
""" Returns position of the trigger for which completion was requested.
"""
return self._trigger_position

def get_identifier(self):
""" Generates unique tuple for file and trigger position """
return (self._view.buffer_id(), self._trigger_position)

def is_suitable_for_view(self, view):
""" Returns True if specified view and its current position is deemed
suitable for completions generated by this completion request. """
if view != self._view:
log.debug(" active view doesn't match completion view")
return False
# We accept both current position and position to the left of the
# current word as valid as we don't know how much user already typed
# after the trigger.
current_position = view.sel()[0].a
valid_positions = [current_position, view.word(current_position).a]
if self._trigger_position not in valid_positions:
log.debug(
" view's trigger positions %s doesn't match completed trigger "
"position %s" % (valid_positions, self._trigger_position))
return False
return True


class Tools:

"""just a bunch of helpful tools to unclutter main file
Expand Down Expand Up @@ -497,11 +548,6 @@ def get_unique_str(init_string):
import hashlib
return hashlib.md5(init_string.encode('utf-8')).hexdigest()

@staticmethod
def get_position_identifier(view, position_in_file):
""" Generate unique tuple for file and position in the file """
return (view.buffer_id(), position_in_file)

@staticmethod
def find_flag_idx(flags, prefix):
""" Find index of flag with given prefix in list.
Expand Down
7 changes: 5 additions & 2 deletions tests/test_complete.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from plugin.plugin_settings import Settings
from plugin.completion.bin_complete import Completer as CompleterBin
from plugin.completion.lib_complete import Completer as CompleterLib
from plugin.tools import CompletionRequest
from plugin.tools import PKG_NAME


Expand Down Expand Up @@ -143,7 +144,8 @@ def test_complete(self):

# Load the completions.
settings = Settings()
(_, completions) = completer.complete(self.view, pos, "¯\_(ツ)_/¯")
request = CompletionRequest(self.view, pos)
(_, completions) = completer.complete(request)

# Verify that we got the expected completions back.
self.assertIsNotNone(completions)
Expand All @@ -165,7 +167,8 @@ def test_complete_vector(self):

# Load the completions.
settings = Settings()
(_, completions) = completer.complete(self.view, pos, "¯\_(ツ)_/¯")
request = CompletionRequest(self.view, pos)
(_, completions) = completer.complete(request)

# Verify that we got the expected completions back.
self.assertIsNotNone(completions)
Expand Down

0 comments on commit 2e29406

Please sign in to comment.