def run(self): # Compile the CabalInspector: # TODO: Where to compile it? sublime.set_timeout(lambda: sublime.status_message('Compiling Haskell CabalInspector...'), 0) exit_code, out, err = call_and_wait(['ghc', '--make', CABAL_INSPECTOR_SOURCE_PATH, '-o', CABAL_INSPECTOR_EXE_PATH, '-outputdir', CABAL_INSPECTOR_OBJ_DIR]) if exit_code != 0: error_msg = u"SublimeHaskell: Failed to compile CabalInspector\n{0}".format(err) wait_for_window(lambda w: self.show_errors(w, error_msg)) else: sublime.set_timeout(lambda: sublime.status_message('Compiling Haskell CabalInspector' + u" \u2714"), 0) # Continue anyway # Compile the ModuleInspector: sublime.set_timeout(lambda: sublime.status_message('Compiling Haskell ModuleInspector...'), 0) exit_code, out, err = call_and_wait(['ghc', '--make', MODULE_INSPECTOR_SOURCE_PATH, '-o', MODULE_INSPECTOR_EXE_PATH, '-outputdir', MODULE_INSPECTOR_OBJ_DIR]) if exit_code != 0: error_msg = u"SublimeHaskell: Failed to compile ModuleInspector\n{0}".format(err) wait_for_window(lambda w: self.show_errors(w, error_msg)) return sublime.set_timeout(lambda: sublime.status_message('Compiling Haskell ModuleInspector' + u" \u2714"), 0) # For first time, inspect all open folders and files wait_for_window(lambda w: self.mark_all_files(w)) # TODO: If compilation failed, we can't proceed; handle this. # Periodically wake up and see if there is anything to inspect. while True: files_to_reinspect = [] with self.dirty_files_lock: files_to_reinspect = self.dirty_files self.dirty_files = [] # Find the cabal project corresponding to each "dirty" file: cabal_dirs = [] standalone_files = [] for filename in files_to_reinspect: d = get_cabal_project_dir_of_file(filename) if d is not None: cabal_dirs.append(d) else: standalone_files.append(filename) # Eliminate duplicate project directories: cabal_dirs = list(set(cabal_dirs)) standalone_files = list(set(standalone_files)) for d in cabal_dirs: self._refresh_all_module_info(d) for f in standalone_files: self._refresh_module_info(f) self.reinspect_event.wait(AGENT_SLEEP_TIMEOUT) self.reinspect_event.clear()
def _refresh_module_info(self, filename): "Rebuild module information for the specified file." # TODO: Only do this within Haskell files in Cabal projects. # TODO: Skip this file if it hasn't changed since it was last inspected. # TODO: Currently the ModuleInspector only delivers top-level functions # with hand-written type signatures. This code should make that clear. # If the file hasn't changed since it was last inspected, do nothing: modification_time = os.stat(filename).st_mtime if CHECK_MTIME: inspection_time = self._get_inspection_time_of_file(filename) if modification_time <= inspection_time: return exit_code, stdout, stderr = call_and_wait( [MODULE_INSPECTOR_EXE_PATH, filename]) if exit_code == 0: new_info = json.loads(stdout) else: # There was a problem parsing the file; create an error entry. new_info = {'error': 'ModuleInspector failed'} # Remember when this info was collected. new_info['inspectedAt'] = modification_time # Dump the currently-known module info to disk: formatted_json = json.dumps(self.info, indent=2) with open(OUTPUT_PATH, 'w') as f: f.write(formatted_json) with self.info_lock: self.info[filename] = new_info
def run(self): # Compile the ModuleInspector: sublime.status_message('Compiling Haskell ModuleInspector...') exit_code, out, err = call_and_wait(['ghc', '--make', MODULE_INSPECTOR_SOURCE_PATH, '-o', MODULE_INSPECTOR_EXE_PATH, '-outputdir', MODULE_INSPECTOR_OBJ_DIR]) # TODO: If compilation failed, we can't proceed; handle this. # Periodically wake up and see if there is anything to inspect. while True: files_to_reinspect = [] with self.dirty_files_lock: files_to_reinspect = self.dirty_files self.dirty_files = [] # Find the cabal project corresponding to each "dirty" file: cabal_dirs = [] for filename in files_to_reinspect: d = get_cabal_project_dir_of_file(filename) if d is not None: cabal_dirs.append(d) # Eliminate duplicate project directories: cabal_dirs = list(set(cabal_dirs)) for d in cabal_dirs: self._refresh_all_module_info(d) time.sleep(AGENT_SLEEP_DURATION)
def wait_for_build_to_complete(view, cabal_project_dir): """Start 'cabal build', wait for it to complete, then parse and diplay the resulting errors.""" # First hide error panel to show that something is going on sublime.set_timeout(lambda: hide_output(view), 0) exit_code, stdout, stderr = call_and_wait( ['cabal', 'build'], cwd=cabal_project_dir) # stderr/stdout can contain unicode characters stdout = stderr.decode('utf-8') stderr = stderr.decode('utf-8') success = exit_code == 0 # The process has terminated; parse and display the output: parsed_messages = parse_error_messages(cabal_project_dir, stderr) if parsed_messages: error_messages = u'\n'.join(unicode(x) for x in parsed_messages) else: error_messages = stderr success_message = 'SUCCEEDED' if success else 'FAILED' output = u'{0}\n\nBuild {1}'.format(error_messages, success_message) # Use set_timeout() so that the call occurs on the main Sublime thread: mark_errors_cb = lambda: mark_errors_in_views(parsed_messages) sublime.set_timeout(mark_errors_cb, 0) # TODO make this an option if success: sublime.status_message(u"Rebuilding Haskell \u2714") else: write_output_cb = lambda: write_output(view, output, cabal_project_dir) sublime.set_timeout(write_output_cb, 0)
def do_build(view, cabal_project_dir): exit_code, stdout, stderr = call_and_wait(['cabal', 'build'],cwd=cabal_project_dir) # stderr/stdout can contain unicode characters stdout = stderr.decode('utf-8') stderr = stderr.decode('utf-8') success = exit_code == 0 # The process has terminated; parse and display the output: parsed_messages = parse_error_messages(cabal_project_dir, stderr) if parsed_messages: error_messages = u'\n'.join(unicode(x) for x in parsed_messages) else: error_messages = stderr success_message = 'SUCCEEDED' if success else 'FAILED' output = u'{0}\n\nBuild {1}'.format(error_messages, success_message) # Use set_timeout() so that the call occurs on the main Sublime thread: callback = functools.partial(mark_errors_in_views, parsed_messages) sublime.set_timeout(callback, 0) # TODO make this an option if success: sublime.status_message("Rebuilding Haskell successful") else: callback = functools.partial(write_output, view, output, cabal_project_dir) sublime.set_timeout(callback, 0)
def wait_for_build_to_complete(add_to_path, view, cabal_project_dir): """Start 'cabal build', wait for it to complete, then parse and diplay the resulting errors.""" # First hide error panel to show that something is going on sublime.set_timeout(lambda: hide_output(view), 0) sublime.set_timeout(lambda: sublime.status_message("Rebuilding Haskell with Cabal"), 0) exit_code, stdout, stderr = call_and_wait(add_to_path, ["cabal", "build"], cwd=cabal_project_dir) # stderr/stdout can contain unicode characters stdout = stderr.decode("utf-8") stderr = stderr.decode("utf-8") success = exit_code == 0 # The process has terminated; parse and display the output: parsed_messages = parse_error_messages(cabal_project_dir, stderr) if parsed_messages: error_messages = u"\n".join(unicode(x) for x in parsed_messages) else: error_messages = stderr success_message = "SUCCEEDED" if success else "FAILED" output = u"{0}\n\nBuild {1}".format(error_messages, success_message) # Use set_timeout() so that the call occurs on the main Sublime thread: callback = functools.partial(mark_errors_in_views, parsed_messages) sublime.set_timeout(callback, 0) # TODO make this an option if success: sublime.set_timeout(lambda: sublime.status_message("Rebuilding Haskell successful"), 0) else: callback = functools.partial(write_output, view, output, cabal_project_dir) sublime.set_timeout(callback, 0)
def run_binary(name, bin_file, base_dir): exit_code, out, err = call_and_wait(bin_file, cwd=base_dir) window = sublime.active_window() if not window: return if exit_code == 0: sublime.set_timeout(lambda: sublime.status_message('SublimeHaskell: Running ' + name + u" \u2714"), 0) sublime.set_timeout(lambda: write_output(window, out, base_dir), 0) else: sublime.set_timeout(lambda: sublime.status_message('SublimeHaskell: Running ' + name + u" \u2717"), 0) sublime.set_timeout(lambda: write_output(window, err, base_dir), 0)
def _refresh_project_info(self, cabal_dir, project_name, cabal_file): exit_code, out, err = call_and_wait( [CABAL_INSPECTOR_EXE_PATH, cabal_file]) if exit_code == 0: new_info = json.loads(out) if 'error' not in new_info: if 'executables' in new_info: with autocompletion.projects_lock: autocompletion.projects[project_name] = { 'dir': cabal_dir, 'cabal': os.path.basename(cabal_file), 'executables': new_info['executables'] }
def wait_for_chain_to_complete(view, cabal_project_dir, msg, cmds): """Chains several commands, wait for them to complete, then parse and display the resulting errors.""" # First hide error panel to show that something is going on sublime.set_timeout(lambda: hide_output(view), 0) # run and wait commands, fail on first fail for cmd in cmds: exit_code, stdout, stderr = call_and_wait( cmd, cwd=cabal_project_dir) if exit_code != 0: break parse_output_messages_and_show(view, msg, cabal_project_dir, exit_code, stderr)
def _refresh_module_info(self, filename): "Rebuild module information for the specified file." # TODO: Only do this within Haskell files in Cabal projects. # TODO: Skip this file if it hasn't changed since it was last inspected. # TODO: Currently the ModuleInspector only delivers top-level functions # with hand-written type signatures. This code should make that clear. # If the file hasn't changed since it was last inspected, do nothing: if not filename.endswith('.hs'): return modification_time = os.stat(filename).st_mtime if CHECK_MTIME: inspection_time = self._get_inspection_time_of_file(filename) if modification_time <= inspection_time: return exit_code, stdout, stderr = call_and_wait( [MODULE_INSPECTOR_EXE_PATH, filename]) # Update only when module is ok if exit_code == 0: new_info = json.loads(stdout) if 'error' not in new_info: # Load standard modules if 'imports' in new_info: # Get all module names (filter away imports without module name) module_names = filter(id, [i.get('importName') for i in new_info['imports']]) # PRELUDE: Add Prelude to the imports if it is not imported manually if u'Prelude' not in module_names: module_names.insert(0, u'Prelude') for import_name in module_names: self._load_standard_module(import_name) # Remember when this info was collected. new_info['inspectedAt'] = modification_time # Dump the currently-known module info to disk: formatted_json = json.dumps(autocompletion.info, indent=2) with open(OUTPUT_PATH, 'w') as f: f.write(formatted_json) with autocompletion.info_lock: autocompletion.info[filename] = new_info autocompletion.module_completions.add(new_info['moduleName'])
def wait_for_chain_to_complete(view, cabal_project_dir, msg, cmds, on_done): """Chains several commands, wait for them to complete, then parse and display the resulting errors.""" # First hide error panel to show that something is going on sublime.set_timeout(lambda: hide_output(view), 0) # run and wait commands, fail on first fail for cmd in cmds: exit_code, stdout, stderr = call_and_wait(cmd, cwd=cabal_project_dir) if exit_code != 0: break errmsg = stderr if stderr else stdout # Notify UI thread that commands are done sublime.set_timeout(on_done, 0) parse_output_messages_and_show(view, msg, cabal_project_dir, exit_code, errmsg)