Skip to content

Commit

Permalink
Merge pull request #25 from tomv564/clean_tests
Browse files Browse the repository at this point in the history
Clean up test logic, simplify auto complete
  • Loading branch information
tomv564 committed Oct 23, 2015
2 parents 4a20e45 + 125b7e9 commit 537d3a9
Show file tree
Hide file tree
Showing 15 changed files with 404 additions and 293 deletions.
64 changes: 20 additions & 44 deletions event_listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
from req import Req
from win import Win
from stack_ide_manager import StackIDEManager, send_request
import response as res

from response import parse_autocompletions

class StackIDESaveListener(sublime_plugin.EventListener):
"""
Expand Down Expand Up @@ -58,6 +57,8 @@ class StackIDEAutocompleteHandler(sublime_plugin.EventListener):
def __init__(self):
super(StackIDEAutocompleteHandler, self).__init__()
self.returned_completions = []
self.view = None
self.refreshing = False

def on_query_completions(self, view, prefix, locations):

Expand All @@ -67,16 +68,16 @@ def on_query_completions(self, view, prefix, locations):
window = view.window()
if not StackIDEManager.is_running(window):
return

# Check if this completion query is due to our refreshing the completions list
# after receiving a response from stack-ide, and if so, don't send
# another request for completions.
if not view.settings().get("refreshing_auto_complete"):
if not self.refreshing:
self.view = view
request = Req.get_autocompletion(filepath=relative_view_file_name(view),prefix=prefix)
send_request(window, request, Win(window).update_completions)
send_request(window, request, self._handle_response)

# Clear the flag to uninhibit future completion queries
view.settings().set("refreshing_auto_complete", False)
# Clear the flag to allow future completion queries
self.refreshing = False
return list(self.format_completion(*completion) for completion in self.returned_completions)


Expand All @@ -86,42 +87,17 @@ def format_completion(self, prop, scope):
scope.importedFrom.module if scope else ''),
prop.name]

def _handle_response(self, response):
self.returned_completions = list(parse_autocompletions(response))
self.view.run_command('hide_auto_complete')
sublime.set_timeout(self.run_auto_complete, 0)

def on_window_command(self, window, command_name, args):
"""
Implements a hacky way of returning data to the StackIDEAutocompleteHandler instance,
wherein SendStackIDERequestCommand calls a update_completions command on the window,
which is really just a dummy command that we intercept here in order to assign the resulting
completions to returned_completions to then, finally, return the next time on_query_completions
is called.
"""
if not StackIDEManager.is_running(window):
return
if args == None:
return None
completions = args.get("completions")
if command_name == "update_completions" and completions:
# Log.debug("INTERCEPTED:\n " + str(completions) + "\n")
self.returned_completions = list(res.parse_autocompletions(completions))

# Hide the auto_complete popup so we can reopen it,
# triggering a new on_query_completions
# call to pickup our new self.returned_completions.
window.active_view().run_command('hide_auto_complete')

def reactivate():
# We read this in on_query_completions to prevent sending a duplicate
# request for completions when we're only trying to re-trigger the completions
# popup; otherwise we get an infinite loop of
# autocomplete > request completions > receive response > close/reopen to refresh
# > autocomplete > request completions > etc.
window.active_view().settings().set("refreshing_auto_complete", True)
window.active_view().run_command('auto_complete', {
'disable_auto_insert': True,
# 'api_completions_only': True,
'next_competion_if_showing': False
})
# Wait one runloop before reactivating, to give the hide command a chance to finish
sublime.set_timeout(reactivate, 0)
return None

def run_auto_complete(self):
self.refreshing = True
self.view.run_command("auto_complete", {
'disable_auto_insert': True,
# 'api_completions_only': True,
'next_completion_if_showing': False,
# 'auto_complete_commit_on_tab': True,
})
126 changes: 82 additions & 44 deletions stack_ide.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,23 @@ def __init__(self, window, settings, backend=None):
self.is_alive = True
self.is_active = False
self.process = None
folder = first_folder(window)
(project_in, project_name) = os.path.split(folder)
self.project_path = first_folder(window)
(project_in, project_name) = os.path.split(self.project_path)
self.project_name = project_name

self.alt_env = os.environ.copy()
add_to_PATH = settings.add_to_PATH
if len(add_to_PATH) > 0:
self.alt_env["PATH"] = os.pathsep.join(add_to_PATH + [self.alt_env.get("PATH","")])
reset_env(settings.add_to_PATH)

if backend is None:
self._backend = boot_ide_backend(folder, self.alt_env, self.handle_response)
else:
self._backend = stack_ide_start(self.project_path, self.project_name, self.handle_response)
else: # for testing
self._backend = backend
self._backend.handler = self.handle_response

self.is_active = True
self.include_targets = set()

# TODO: could check packages here to fix the 'project_dir must equal packagename issue'

sublime.set_timeout_async(self.load_initial_targets, 0)


Expand All @@ -72,14 +74,7 @@ def load_initial_targets(self):
"""
Get the initial list of files to check
"""

proc = subprocess.Popen(["stack", "ide", "load-targets", self.project_name],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=first_folder(self.window), env=self.alt_env,
universal_newlines=True,
creationflags=CREATE_NO_WINDOW)
outs, errs = proc.communicate()
initial_targets = outs.splitlines()
initial_targets = stack_ide_loadtargets(self.project_path, self.project_name)
sublime.set_timeout(lambda: self.update_files(initial_targets), 0)


Expand Down Expand Up @@ -118,37 +113,51 @@ def handle_response(self, data):
seq_id = data.get("seq")

if seq_id is not None:
handler = self.conts.get(seq_id)
del self.conts[seq_id]
if handler is not None:
if contents is not None:
sublime.set_timeout(lambda:handler(contents), 0)
else:
Log.warning("Handler not found for seq", seq_id)

# Check that stack-ide talks a version of the protocal we understand
self._send_to_handler(contents, seq_id)

elif tag == "ResponseWelcome":
expected_version = (0,1,1)
version_got = tuple(contents) if type(contents) is list else contents
if expected_version > version_got:
Log.error("Old stack-ide protocol:", version_got, '\n', 'Want version:', expected_version)
complain("wrong-stack-ide-version",
"Please upgrade stack-ide to a newer version.")
elif expected_version < version_got:
Log.warning("stack-ide protocol may have changed:", version_got)
else:
Log.debug("stack-ide protocol version:", version_got)

# Pass progress messages to the status bar
self._handle_welcome(contents)

elif tag == "ResponseUpdateSession":
self._handle_update_session(contents)

elif tag == "ResponseShutdownSession":
Log.debug("Stack-ide process has shut down")

elif tag == "ResponseLog":
Log.debug(contents.rstrip())

else:
Log.normal("Unhandled response: ", data)

def _send_to_handler(self, contents, seq_id):
"""
Looks up a previously registered handler for the incoming response
"""
handler = self.conts.get(seq_id)
del self.conts[seq_id]
if handler is not None:
if contents is not None:
sublime.set_timeout(lambda:handler(contents), 0)
else:
Log.warning("Handler not found for seq", seq_id)


def _handle_welcome(self, welcome):
"""
Identifies if we support the current version of the stack ide api
"""
expected_version = (0,1,1)
version_got = tuple(welcome) if type(welcome) is list else welcome
if expected_version > version_got:
Log.error("Old stack-ide protocol:", version_got, '\n', 'Want version:', expected_version)
complain("wrong-stack-ide-version",
"Please upgrade stack-ide to a newer version.")
elif expected_version < version_got:
Log.warning("stack-ide protocol may have changed:", version_got)
else:
Log.debug("stack-ide protocol version:", version_got)


def _handle_update_session(self, update_session):
"""
Expand All @@ -169,19 +178,48 @@ def __del__(self):
finally:
self.process = None

def boot_ide_backend(folder, env, response_handler):
env = {}

def reset_env(add_to_PATH):
global env
env = os.environ.copy()
if len(add_to_PATH) > 0:
env["PATH"] = os.pathsep.join(add_to_PATH + [env.get("PATH","")])


def stack_ide_packages(project_path):
proc = subprocess.Popen(["stack", "ide", "packages"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=project_path, env=env,
universal_newlines=True,
creationflags=CREATE_NO_WINDOW)
outs, errs = proc.communicate()
return outs.splitlines()


def stack_ide_loadtargets(project_path, package):

Log.debug("Requesting load targets for ", package)
proc = subprocess.Popen(["stack", "ide", "load-targets", package],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=project_path, env=env,
universal_newlines=True,
creationflags=CREATE_NO_WINDOW)
outs, errs = proc.communicate()
# TODO: check response!
return outs.splitlines()


def stack_ide_start(project_path, package, response_handler):
"""
Start up a stack-ide subprocess for the window, and a thread to consume its stdout.
"""

# Assumes the library target name is the same as the project dir
(project_in, project_name) = os.path.split(folder)

Log.debug("Calling stack with PATH:", env['PATH'] if env else os.environ['PATH'])
Log.debug("Calling stack ide start with PATH:", env['PATH'] if env else os.environ['PATH'])

process = subprocess.Popen(["stack", "ide", "start", project_name],
process = subprocess.Popen(["stack", "ide", "start", package],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=folder, env=env,
cwd=project_path, env=env,
creationflags=CREATE_NO_WINDOW
)

Expand Down
Loading

0 comments on commit 537d3a9

Please sign in to comment.