def inspect(self): tmo_event = threading.Event() tmo_event.clear() delta_t = None while not self.inspect_loop.is_set(): # Wait for the on_modified method to set the flag to let us know that there's something # for which we need to take action. if Settings.COMPONENT_DEBUG.fly_mode: print( 'fly: waiting for check flag, timeout {0}'.format(delta_t)) self.inspect_flag.wait(delta_t) if not self.inspect_loop.is_set(): with self.fly_lock: delta_t = self.next_inspect - time.time() if Settings.COMPONENT_DEBUG.fly_mode: print('fly: delta_t = {0}'.format(delta_t)) if delta_t <= 0: done_inspect = threading.Event() done_inspect.clear() Utils.run_async('fly-inspect', self.do_inspect, done_inspect) # Timeout shouldn't be needed... but paranoia is a good thing. done_inspect.wait(WAIT_TIMEOUT) delta_t = None self.inspect_flag.clear()
def do_load(self, view): filename = view.file_name() if not Common.view_is_haskell_source(view) or not filename: return if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_load {1}.'.format(type(self).__name__, filename)) view_settings = view.settings() or {} if Settings.PLUGIN.use_improved_syntax and (filename.endswith(".hs") or filename.endswith(".hsc") or \ view_settings.get('syntax', '').endswith('.tmLanguage')): view_settings.set( 'syntax', 'Packages/SublimeHaskell/Syntaxes/Haskell-SublimeHaskell.sublime-syntax' ) EventCommon.assoc_to_project(view, self.backend_mgr, filename) _project_dir, project_name = Common.locate_cabal_project_from_view( view) if Settings.PLUGIN.enable_infer_types: BackendManager.active_backend().infer(files=[filename]) Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename, {'drop_all': False})
def on_post_save(self, view): if not Common.view_is_inspected_source(view): return if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_post_save invoked.'.format(type(self).__name__)) filename = view.file_name() if not filename: if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_post_save: no file name.'.format( type(self).__name__)) return _project_dir, project_name = Common.locate_cabal_project_from_view( view) if Common.view_is_haskell_source(view): self.type_cache.remove(filename) if Settings.PLUGIN.enable_auto_build: view.window().run_command('sublime_haskell_build_auto') else: EventCommon.do_check_lint( view, continue_success=self.post_successful_check) Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename, False)
def on_activated(self): filename = self.view.file_name() if filename: if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_activated invoked.'.format(type(self).__name__)) Utils.run_async('on_activated', self.activated_worker, self.view, filename)
def on_hover(self, view, point, hover_zone): # Note: view.file_name() is not set in certain views, such as the "Haskell Show Types Panel". Avoid # generating lookup errors, which are logged in the console window (for better or worse.) if Common.is_haskell_source(view) and view.file_name(): # Ensure that we never block the Python main thread. info_pop = InfoPop.SublimeHaskellHoverPopup(view, view.file_name(), point, hover_zone) Utils.run_async('SublimeHaskellPopup.on_hover', info_pop.do_hover)
def fly_check(self): tmo_event = threading.Event() tmo_event.clear() delta_t = None while not self.fly_check_loop.is_set(): # Wait for the on_modified method to set the flag to let us know that there's something # for which we need to take action. if Settings.COMPONENT_DEBUG.fly_mode: print('fly: waiting for check flag, timeout {0}'.format(delta_t)) self.fly_check_flag.wait(delta_t) if not self.fly_check_loop.is_set(): with self.fly_lock: delta_t = self.next_flycheck - time.time() if Settings.COMPONENT_DEBUG.fly_mode: print('fly: delta_t = {0}'.format(delta_t)) if delta_t <= 0: done_check = threading.Event() done_check.clear() Utils.run_async('fly-check', self.do_fly, done_check) done_check.wait() delta_t = None self.fly_check_flag.clear()
def do_activated(self, view, filename): if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_activated file: {1}.'.format( type(self).__name__, filename)) if view and filename: Utils.run_async('on_activated', self.activated_worker, view, filename)
def do_hover(self, view, point, hover_zone): # Note: view.file_name() is not set in certain views, such as the "Haskell Show Types Panel". Avoid # generating lookup errors, which are logged in the console window (for better or worse.) filename = view.file_name() if filename and Common.view_is_haskell_source(view): # Ensure that we never block the Python main thread. info_pop = InfoPop.SublimeHaskellHoverPopup(view, view.file_name(), point, hover_zone) Utils.run_async('SublimeHaskellPopup.on_hover', info_pop.do_hover)
def on_modified(self, view): filename = view.file_name() if filename is None or not Common.is_haskell_source(view): return if Settings.COMPONENT_DEBUG.event_viewer: print('{0} invoked.'.format(type(self).__name__ + ".on_modified")) if Settings.PLUGIN.lint_check_fly: Utils.run_async('fly check', self.fly, view) self.type_cache.remove(filename)
def do_inspection(self): self.busy = True try: scan_paths = [] files_to_reinspect = [] projects = [] files = [] with self.dirty_paths as dirty_paths: if Settings.COMPONENT_DEBUG.inspection: print( 'do_inspection: dirty_paths: {0}'.format(dirty_paths)) scan_paths = dirty_paths[:] del dirty_paths[:] with self.dirty_files as dirty_files: if Settings.COMPONENT_DEBUG.inspection: print( 'do_inspection: dirty_files: {0}'.format(dirty_files)) projects = [] files = [] for finspect in dirty_files.keys() or []: projdir = Common.get_cabal_project_dir_of_file(finspect) if projdir is not None: projects.append(projdir) files.append(finspect) projects = list(set(projects)) files = list(set(files)) file_contents = dict([(file, content) for file, content in dirty_files.items() if content]) dirty_files.clear() self.inspect(scan_paths, projects, files, file_contents) cand_cabals = [] with self.cabal_to_load as cabals_to_load: cand_cabals = cabals_to_load del cabals_to_load[:] for cabal in cand_cabals: Utils.run_async('inspect cabal {0}'.format(cabal), self.inspect_cabal, cabal) if files_to_reinspect and Settings.PLUGIN.enable_hdocs: self.backend.docs(files=files_to_reinspect) finally: self.busy = False
def run_build(self, view, project_name, project_dir, config): # Don't build if a build is already running for this project # We compare the project_name for simplicity (projects with same # names are of course possible, but unlikely, so we let them wait) if project_name in self.PROJECTS_BEING_BUILT: Logging.log("Waiting for build action on '%s' to complete." % project_name, Logging.LOG_WARNING) Common.show_status_message('Already building %s' % project_name, is_ok=False, priority=5) return # Set project as building self.PROJECTS_BEING_BUILT.add(project_name) Logging.log('project build tool: {0}'.format(Settings.get_project_setting(view, 'haskell_build_tool')), Logging.LOG_DEBUG) Logging.log('settings build tool: {0}'.format(Settings.PLUGIN.haskell_build_tool), Logging.LOG_DEBUG) build_tool_name = Settings.get_project_setting(view, 'haskell_build_tool', Settings.PLUGIN.haskell_build_tool) if build_tool_name == 'stack' and not self.is_stack_project(project_dir): # rollback to cabal build_tool_name = 'cabal' tool = self.BUILD_TOOL[build_tool_name] # Title of tool: Cabal, Stack tool_title = tool['name'] # Title of action: Cleaning, Building, etc. action_title = config['message'] # Tool name: cabal tool_name = tool['command'] # Tool arguments (commands): build, clean, etc. tool_steps = config['steps'][build_tool_name] # Config override override_config = Settings.get_project_setting(view, 'active_stack_config') override_args = [] if override_config: override_args = ['--stack-yaml', override_config] # Assemble command lines to run (possibly multiple steps) commands = [[tool_name] + override_args + step if isinstance(step, list) else step for step in tool_steps] Logging.log('running build commands: {0}'.format(commands), Logging.LOG_TRACE) def done_callback(): # Set project as done being built so that it can be built again self.PROJECTS_BEING_BUILT.remove(project_name) # Run them msg = '{0} {1} with {2}\ncommands:\n{3}'.format(action_title, project_name, tool_title, commands) Logging.log(msg, Logging.LOG_DEBUG) Common.show_status_message_process(msg, priority=3) Utils.run_async('wait_for_chain_to_complete', self.wait_for_chain_to_complete, view, project_dir, msg, commands, on_done=done_callback)
def on_done(self, idx): if idx == -1: return selected = self.executables[idx] name = selected['name'] base_dir = selected['dir'] bin_file = os.path.join(selected['dist'], 'build', name, name) hide_output(self.window) # Run in thread Utils.run_async( type(self).__name__, self.run_binary, name, bin_file, base_dir)
def do_new(self, view): filename = view.file_name() if not Common.view_is_haskell_source(view) or not filename: return if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_new invoked.'.format(type(self).__name__)) EventCommon.assoc_to_project(view, self.backend_mgr, filename) _project_dir, project_name = Common.locate_cabal_project_from_view(view) Utils.run_async('rescan {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename, {'drop_all': True}) view.settings().set('translate_tabs_to_spaces', True)
def on_load(self, view): filename = view.file_name() if not Common.view_is_haskell_source(view) or not filename: return if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_load {1}.'.format(type(self).__name__, filename)) EventCommon.assoc_to_project(view, self.backend_mgr, filename) _project_dir, project_name = Common.locate_cabal_project_from_view( view) Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename, {'drop_all': False})
def do_inspection(self): self.busy = True try: scan_paths = [] files_to_reinspect = [] projects = [] files = [] with self.dirty_paths as dirty_paths: if Settings.COMPONENT_DEBUG.inspection: print('do_inspection: dirty_paths: {0}'.format(dirty_paths)) scan_paths = dirty_paths[:] del dirty_paths[:] with self.dirty_files as dirty_files: if Settings.COMPONENT_DEBUG.inspection: print('do_inspection: dirty_files: {0}'.format(dirty_files)) projects = [] files = [] for finspect in dirty_files.keys() or []: projdir = Common.get_cabal_project_dir_of_file(finspect) if projdir is not None: projects.append(projdir) files.append(finspect) projects = list(set(projects)) files = list(set(files)) file_contents = dict([(file, content) for file, content in dirty_files.items() if content]) dirty_files.clear() self.inspect(scan_paths, projects, files, file_contents) cand_cabals = [] with self.cabal_to_load as cabals_to_load: cand_cabals = cabals_to_load del cabals_to_load[:] for cabal in cand_cabals: Utils.run_async('inspect cabal {0}'.format(cabal), self.inspect_cabal, cabal) if files_to_reinspect and Settings.PLUGIN.enable_hdocs: self.backend.docs(files=files_to_reinspect) finally: self.busy = False
def on_activated(self, view): filename = view.file_name() if filename is None or not Common.is_inspected_source(view): return if Settings.COMPONENT_DEBUG.event_viewer: print('{0} invoked.'.format(type(self).__name__ + ".on_activated")) def activated_worker(): with self.backend_mgr: self.assoc_to_project(view, filename) project_name = Common.locate_cabal_project_from_view(view)[1] if Common.is_haskell_source(view): self.autocompleter.get_completions_async(project_name, filename) Utils.run_async('on_activated', activated_worker)
def run_build(self, project_name, project_dir, config): # Don't build if a build is already running for this project # We compare the project_name for simplicity (projects with same # names are of course possible, but unlikely, so we let them wait) if project_name in self.PROJECTS_BEING_BUILT: Logging.log("Waiting for build action on '%s' to complete." % project_name, Logging.LOG_WARNING) Common.sublime_status_message('Already building {0}'.format(project_name)) return # Set project as building self.PROJECTS_BEING_BUILT.add(project_name) Logging.log('project build tool: {0}'.format(Settings.get_project_setting(self.view, 'haskell_build_tool')), Logging.LOG_DEBUG) Logging.log('settings build tool: {0}'.format(Settings.PLUGIN.haskell_build_tool), Logging.LOG_DEBUG) build_tool_name = Settings.get_project_setting(self.view, 'haskell_build_tool', Settings.PLUGIN.haskell_build_tool) if build_tool_name == 'stack' and not self.is_stack_project(project_dir): # rollback to cabal build_tool_name = 'cabal' tool = self.BUILD_TOOL[build_tool_name] # Title of tool: Cabal, Stack tool_title = tool['name'] # Title of action: Cleaning, Building, etc. action_title = config['message'] # Tool name: cabal tool_name = tool['command'] # Tool arguments (commands): build, clean, etc. tool_steps = config['steps'][build_tool_name] # Config override override_args = [] override_config = Settings.get_project_setting(self.view, 'active_stack_config') if tool_name == 'stack' else '' if override_config: override_args = ['--stack-yaml', override_config] # Assemble command lines to run (possibly multiple steps) commands = [[tool_name] + override_args + step if isinstance(step, list) else step for step in tool_steps] Logging.log('running build commands: {0}'.format(commands), Logging.LOG_TRACE) # Run them ## banner = '{0} {1} with {2}\ncommands:\n{3}'.format(action_title, project_name, tool_title, commands) banner = '{0} {1} with {2}'.format(action_title, project_name, tool_title) Logging.log(banner, Logging.LOG_DEBUG) Utils.run_async('wait_for_chain_to_complete', self.wait_for_chain_to_complete, self.view, project_name, project_dir, banner, commands)
def do_load(self, view): filename = view.file_name() if not Common.view_is_haskell_source(view) or not filename: return if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_load {1}.'.format(type(self).__name__, filename)) view_settings = view.settings() or {} if Settings.PLUGIN.use_improved_syntax and (filename.endswith(".hs") or filename.endswith(".hsc") or \ view_settings.get('syntax', '').endswith('.tmLanguage')): view_settings.set('syntax', 'Packages/SublimeHaskell/Syntaxes/Haskell-SublimeHaskell.sublime-syntax') EventCommon.assoc_to_project(view, self.backend_mgr, filename) _project_dir, project_name = Common.locate_cabal_project_from_view(view) Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename, {'drop_all': False})
def do_inspection(self): self.busy = True try: scan_paths = [] with self.dirty_paths as dirty_paths: scan_paths = dirty_paths[:] dirty_paths[:] = [] files_to_reinspect = [] with self.dirty_files as dirty_files: files_to_reinspect = dirty_files[:] dirty_files[:] = [] projects = [] files = [] if len(files_to_reinspect) > 0: projects = [] files = [] for finspect in files_to_reinspect: projdir = Common.get_cabal_project_dir_of_file(finspect) if projdir is not None: projects.append(projdir) else: files.append(finspect) projects = list(set(projects)) files = list(set(files)) self.inspect(paths=scan_paths, projects=projects, files=files) load_cabal = [] with self.cabal_to_load as cabal_to_load: load_cabal = cabal_to_load[:] cabal_to_load[:] = [] for cabal in load_cabal: Utils.run_async('inspect cabal {0}'.format(cabal), self.inspect_cabal, cabal) if files_to_reinspect and Settings.PLUGIN.enable_hdocs: self.backend.docs(files=files_to_reinspect) finally: self.busy = False
def dispatch_response(self, resp): resp_id = resp.get('id') if not resp_id and 'request' in resp: # Error without an id, id is in the 'request' key's content. orig_req = json.loads(resp['request']) resp_id = orig_req.get('id') if resp_id: with self.request_map as requests: reqdata = requests.get(resp_id) if reqdata: callbacks, wait_event, _ = reqdata # Unconditionally call the notify callback: if 'notify' in resp: if Settings.COMPONENT_DEBUG.callbacks: print('id {0}: notify callback'.format(resp_id)) callbacks.call_notify(resp['notify']) if 'result' in resp or 'error' in resp: if wait_event: requests[resp_id] = (callbacks, wait_event, resp) # The wait-er calls callbacks, cleans up after the request. wait_event.set() else: # Enqueue for async receiver: Put the (resp_id, resp, reqdata) tuple onto the queue del requests[resp_id] if 'result' in resp: Utils.run_async('result {0}'.format(resp_id), callbacks.call_response, resp['result']) elif 'error' in resp: err = resp.pop("error") Utils.run_async('error {0}'.format(resp_id), callbacks.call_error, err, resp) else: msg = 'HsDevClient.receiver: request data expected for {0}, none found.'.format( resp_id) Logging.log(msg, Logging.LOG_WARNING) elif Logging.is_log_level(Logging.LOG_ERROR): print('HsDevClient.receiver: request w/o id\n{0}'.format( pprint.pformat(resp)))
def on_post_save(self, view): filename = view.file_name() if filename is not None and Common.is_inspected_source(view): if Settings.COMPONENT_DEBUG.event_viewer: print('{0} invoked.'.format(type(self).__name__ + ".on_post_save")) if Common.is_haskell_source(view): self.type_cache.remove(filename) self.trigger_build(view) if Settings.PLUGIN.prettify_on_save: if Settings.PLUGIN.prettify_executable == 'stylish-haskell': view.run_command('sublime_haskell_stylish') elif Settings.PLUGIN.prettify_executable == 'hindent': view.run_command('sublime_haskell_hindent') # Ensure that the source scan happens after trigger_build -- the inspector is active, so the SublimeHaskell # commands that we try to execute end up being disabled. project_name = Common.locate_cabal_project_from_view(view)[1] Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename)
def do_post_save(self, view): if not Common.view_is_inspected_source(view): return current_time = time.clock() last_update = self.update_cache.get(view.file_name()) if last_update is not None and last_update[0] == view.change_count( ) and (current_time - last_update[1]) < 0.2: # view contents equals # and last update was in less then 0.2s before, skipping print('SublimeHaskellEventListener: duplicate save detected.') return self.update_cache[view.file_name()] = (view.change_count(), current_time) if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_post_save invoked.'.format(type(self).__name__)) filename = view.file_name() if not filename: if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_post_save: no file name.'.format( type(self).__name__)) return _project_dir, project_name = Common.locate_cabal_project_from_view( view) if Common.view_is_haskell_source(view): self.type_cache.remove(filename) if Settings.PLUGIN.enable_auto_build: Builder.Builder( view, continue_success=self.post_successful_check).auto_build() else: EventCommon.do_check_lint( view, continue_success=self.post_successful_check) Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename, False)
def update_completions_async(self, project_name, files=None, drop_all=False): if drop_all: Utils.run_async('drop all completions', self.autocompleter.drop_completions_async) else: for file in files or []: Utils.run_async('{0}: drop completions'.format(file), self.autocompleter.drop_completions_async, file) for file in files or []: Utils.run_async('{0}: init completions'.format(file), self.autocompleter.get_completions_async, project_name, file)
def dispatch_response(self, resp): resp_id = resp.get('id') if not resp_id and 'request' in resp: # Error without an id, id is in the 'request' key's content. orig_req = json.loads(resp['request']) resp_id = orig_req.get('id') if resp_id: with self.request_map as requests: reqdata = requests.get(resp_id) if reqdata: callbacks, wait_event, _ = reqdata # Unconditionally call the notify callback: if 'notify' in resp: if Settings.COMPONENT_DEBUG.callbacks: print('id {0}: notify callback'.format(resp_id)) callbacks.call_notify(resp['notify']) if 'result' in resp or 'error' in resp: if wait_event: requests[resp_id] = (callbacks, wait_event, resp) # The wait-er calls callbacks, cleans up after the request. wait_event.set() else: # Enqueue for async receiver: Put the (resp_id, resp, reqdata) tuple onto the queue del requests[resp_id] if 'result' in resp: Utils.run_async('result {0}'.format(resp_id), callbacks.call_response, resp['result']) elif 'error' in resp: err = resp.pop("error") Utils.run_async('error {0}'.format(resp_id), callbacks.call_error, err, resp) else: msg = 'HsDevClient.receiver: request data expected for {0}, none found.'.format(resp_id) Logging.log(msg, Logging.LOG_WARNING) elif Logging.is_log_level(Logging.LOG_ERROR): print('HsDevClient.receiver: request w/o id\n{0}'.format(pprint.pformat(resp)))
def do_post_save(self, view): if not Common.view_is_inspected_source(view): return if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_post_save invoked.'.format(type(self).__name__)) filename = view.file_name() if not filename: if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_post_save: no file name.'.format(type(self).__name__)) return _project_dir, project_name = Common.locate_cabal_project_from_view(view) if Common.view_is_haskell_source(view): self.type_cache.remove(filename) if Settings.PLUGIN.enable_auto_build: Builder.Builder(view, continue_success=self.post_successful_check).auto_build() else: EventCommon.do_check_lint(view, continue_success=self.post_successful_check) Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename, False)
def update_completions_async(autocompleter, project_name, files=None, drop_all=False): if drop_all: Utils.run_async('drop all completions', autocompleter.drop_completions_async) else: for file in files or []: Utils.run_async('{0}: drop completions'.format(file), autocompleter.drop_completions_async, file) for file in files or []: Utils.run_async('{0}: init completions'.format(file), autocompleter.generate_completions_cache, project_name, file)
def run_chain_build_thread(view, cabal_project_dir, msg, cmds, on_done): Common.show_status_message_process(msg, priority=3) Utils.run_async('run_chain_build_thread', wait_for_chain_to_complete, view, cabal_project_dir, msg, cmds, on_done)
def run(self, **_kwargs): Utils.run_async('reinspect all', self.reinspect_all)
def run(self, **_args): Utils.run_async('restarting backend', self.do_restart)
def run(self): # Prevents the Python main thread from blocking. Utils.run_async(type(self).__name__ + '.do_startup', self.do_startup)
def run(self, _edit, **kwargs): Utils.run_async('SublimeHaskellCheckAndLint', self.run_chain, [hsdev_check(), hsdev_lint()], 'Checking and Linting', fly_mode=kwargs.get('fly', False))
def change_backend(self, idx): if idx >= 0: Utils.run_async('change backend: startup', self.start_new_backend, self.backend_names[idx])
def do_activated(self, view, filename): if Settings.COMPONENT_DEBUG.event_viewer: print('{0}.on_activated file: {1}.'.format(type(self).__name__, filename)) if view and filename: Utils.run_async('on_activated', self.activated_worker, view, filename)
def run(self, **_args): # Prevents the Python main thread from blocking. Utils.run_async(type(self).__name__ + '.do_shutdown', self.do_shutdown)