def __init__(self, host, port=HSDEV_DEFAULT_PORT): super().__init__() self.daemon = True self.cabal_to_load = LockedObject.LockedObject([]) self.dirty_files = LockedObject.LockedObject([]) self.dirty_paths = LockedObject.LockedObject([]) self.client = HsDevClient.HsDev(host, port) self.client_back = HsDevClient.HsDev(host, port) self.reinspect_event = threading.Event()
def __init__(self, backend): super().__init__() # The backend, whose support functions we invoke: self.backend = backend # (Re-)Inspection state: self.dirty_lock = threading.Lock() self.cabal_to_load = LockedObject.LockedObject([]) self.dirty_files = LockedObject.LockedObject([]) self.dirty_paths = LockedObject.LockedObject([]) self.busy = False
def __init__(self): self.language_pragmas = [] self.flags_pragmas = [] # cabal name => set of modules, where cabal name is 'cabal' for cabal or sandbox path # for cabal-devs self.module_completions = LockedObject.LockedObject({}) # keywords self.keywords = ['do', 'case', 'of', 'let', 'in', 'data', 'instance', 'type', 'newtype', 'where', 'deriving', 'import', 'module'] self.current_filename = None # filename ⇒ preloaded completions + None ⇒ all completions self.cache = LockedObject.LockedObject(CompletionCache())
def load(self): settings = get_settings() for (key, (attr, default)) in SetttingsContainer.attr_dict.items(): value = settings.get(key, default) ## Uncomment to debug. Do NOT use logging because it causes a circular dependency. ## print('Settings.load: {0} = {1}'.format(attr, value)) setattr(self, attr, value) install_updater(settings, self, key) self.changes = LockedObject.LockedObject({})
def __init__(self): super().__init__() self.backend_mgr = BackendManager.BackendManager() self.type_cache = Types.SourceHaskellTypeCache() self.autocompleter = Autocomplete.AutoCompleter() # Fly mode state: self.fly_view = LockedObject.LockedObject({'view': None, 'mtime': None}) self.fly_event = threading.Event() self.fly_agent = threading.Thread(target='fly_check')
def __init__(self, backend_mgr): # Primitive socket pool: self.backend_mgr = backend_mgr self.socket_pool = [] # Request receiver thread: self.rcvr_thread = None self.rcvr_event = threading.Event() self.request_q = queue.Queue() self.request_map = LockedObject.LockedObject({}) self.serial_lock = threading.RLock() self.request_serial = 1
def load(self): settings = get_settings() for (key, (attr, default)) in SettingsContainer.attr_dict.items(): value = settings.get(key, default) ## Uncomment to debug. Do NOT use logging because it causes a circular dependency. ## print('Settings.load: {0} = {1}'.format(attr, value)) setattr(self, attr, value) install_updater(settings, self, key) self.changes = LockedObject.LockedObject({}) ## New backend upgrade warning: old_stuff = [] for old_setting in [ 'enable_hsdev', 'enable_ghc_mod', 'enable_hdevtools', 'hdevtools_socket', 'hsdev_host', 'hsdev_local_process', 'hsdev_port' ]: if settings.get(old_setting) is not None: old_stuff.append(old_setting) if old_stuff: msg = ['Old SublimeHaskell backend settings found:', ''] msg = msg + old_stuff msg = msg + [ '', 'You are now using the default SublimeHaskell settings' 'for the \'backend\' preference.', '', 'Please look at the default settings and customize/migrate', 'them as needed in your user settings.', '', '(Preferences > Package Settings > SublimeHaskell)' ] sublime.message_dialog('\n'.join(msg)) if settings.get('add_to_path'): msg = [ '\'add_to_path\' setting detected. You probably meant \'add_to_PATH\'.' ] sublime.message_dialog('\n'.join(msg)) if settings.get('prettify_executable') not in [ 'stylish-haskell', 'hindent' ]: msg = [ '\'{0}\' is not a recognized Haskell indenter/prettifier. Recognized prettifiers are:', '', 'stylish-haskell', 'hindent', '', 'Please check your \'prettify_executable\' setting.' ] sublime.message_dialog('\n'.join(msg).format( settings.get('prettify_executable'))) if settings.get('inhibit_completions'): msg = [ 'The \'inhibit_completions\' setting has been replaced by ' '\'add_word_completions\' and \'add_default_completions\'', '', 'Please customize your settings with these two flags, ' 'delete the \'inhibit_completions\' setting.' ] sublime.message_dialog('\n'.join(msg))
def __init__(self, host, port): self.host = host self.port = port self.connecting = threading.Event() self.connected = threading.Event() self.socket = None self.listener = None self.autoconnect = True self.request_map = LockedObject.LockedObject({}) self.request_serial = 1 self.connect_fun = None self.part = '' self.on_connected = None self.on_disconnected = None self.on_reconnect = None
def __init__(self): # Instantiate the attributes (rationale: style and pylint error checking) self.add_standard_dirs = None self.add_to_path = [] self.auto_build_mode = None self.auto_complete_imports = None self.auto_complete_language_pragmas = None self.auto_completion_popup = None self.auto_run_tests = None self.enable_auto_build = None self.enable_auto_check = None self.enable_auto_lint = None self.enable_ghc_mod = None self.enable_hdevtools = None self.enable_hdocs = None self.enable_hsdev = None self.ghc_opts = None self.ghci_opts = None self.haskell_build_tool = None self.hdevtools_socket = None self.hsdev_host = None self.hsdev_local_process = None self.hsdev_log_config = None self.hsdev_log_level = None self.hsdev_port = None self.inhibit_completions = None self.inspect_modules = None self.lint_check_fly = None self.lint_check_fly_idle = None self.log = None self.show_error_window = None self.show_output_window = None self.unicode_symbol_info = None self.use_improved_syntax = None # Set attributes to their respective default values: for (attr, default) in SetttingsContainer.attr_dict.values(): setattr(self, attr, default) # Additional change callbacks to propagate: self.changes = LockedObject.LockedObject({}) # Write-access lock self.wlock = threading.RLock()
class StatusMessagesManager(threading.Thread): # msg ⇒ StatusMessage messages = LockedObject.LockedObject({}) # [StatusMessage × time] priorities = LockedObject.LockedObject([]) def __init__(self): super().__init__() self.daemon = True self.interval = 0.1 self.event = threading.Event() self.ticks = 0 self.timer = None def run(self): while True: self.event.wait(60.0) self.event.clear() self.ticks = 0 # Ok, there are some messages, start showing them while self.show(): self.timer = threading.Timer(self.interval, self.tick) self.timer.start() self.timer.join() def show(self): # Show current message, clear event if no events with StatusMessagesManager.priorities as prios: if not prios: return False else: cur_msg, _ = prios[0] sublime_status_message(cur_msg.message(self.ticks)) return True def tick(self): self.ticks = self.ticks + 1 # Tick all messages, remove outdated, resort priority list with StatusMessagesManager.priorities as prios: for prio in prios: prio[0].tick(self.interval) self.update() def add(self, new_message): with StatusMessagesManager.priorities as prios: prios.append((new_message, time.clock())) with StatusMessagesManager.messages as msgs: msgs[new_message.msg] = new_message self.update() self.event.set() def get(self, key): with StatusMessagesManager.messages as msgs: return msgs.get(key) def update(self): # Update priority list with StatusMessagesManager.priorities as prios: prios[:] = list(filter(lambda p: p[0].is_active(), prios)) # Ended processes goes first, then by priority, and then by time of message addition prios.sort(key=lambda x: (x[0].is_process, -x[0].priority, x[1])) with StatusMessagesManager.messages as msgs: ums = dict(filter(lambda m: m[1].is_active(), msgs.items())) msgs.clear() msgs.update(ums)
def load(self): settings = get_settings() for (key, (attr, default)) in SetttingsContainer.attr_dict.items(): setattr(self, attr, settings.get(key, default)) install_updater(settings, self, key) self.changes = LockedObject.LockedObject({})
def reset_cache(): global WHICH_CACHE WHICH_CACHE = LockedObject.LockedObject({})
import os import os.path import SublimeHaskell.internals.locked_object as LockedObject import SublimeHaskell.internals.utils as Utils def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) # Tool name -> executable path cache. Avoids probing the file system multiple times. WHICH_CACHE = LockedObject.LockedObject({}) def which(cmd, env_path): cmd_is_list = isinstance(cmd, list) the_cmd = cmd[0] if cmd_is_list else cmd cmd_args = cmd[1:] if cmd_is_list else [] if os.path.isabs(the_cmd): return cmd with WHICH_CACHE as cache: cval = cache.get(the_cmd) if cval is not None: return [cval] + cmd_args if cmd_is_list else cval else: exe_exts = [''] if not Utils.is_windows() else ['.exe', '.cmd', '.bat']
class ProcHelper(object): """Command and tool process execution helper.""" # Tool name -> executable path cache. Avoids probing the file system multiple times. which_cache = LockedObject.LockedObject({}) # Augmented environment for the subprocesses. Specifically, we really want # to augment the user's PATH used to search for executables and tools: augmented_env = None def __init__(self, command, **popen_kwargs): """Open a pipe to a command or tool.""" if ProcHelper.augmented_env is None: ProcHelper.augmented_env = ProcHelper.get_extended_env() self.process = None self.process_err = None if is_windows(): startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW popen_kwargs['startupinfo'] = startupinfo # Allow caller to specify something different for stdout or stderr -- provide # the default here if unspecified. if popen_kwargs.get('stdout') is None: popen_kwargs['stdout'] = subprocess.PIPE if popen_kwargs.get('stderr') is None: popen_kwargs['stderr'] = subprocess.PIPE try: normcmd = ProcHelper.which(command, ProcHelper.augmented_env['PATH']) if normcmd is not None: self.process = subprocess.Popen(normcmd , stdin=subprocess.PIPE , env=ProcHelper.augmented_env , **popen_kwargs) else: self.process = None self.process_err = "SublimeHaskell.ProcHelper: {0} was not found on PATH!".format(command[0]) except OSError as os_exc: self.process_err = \ '\n'.join(["SublimeHaskell: Problem executing '{0}'".format(' '.join(command)) , 'Operating system error: {0}'.format(os_exc) ]) if os_exc.errno == errno.EPIPE: # Most likely reason: subprocess output a usage message stdout, stderr = self.process.communicate() exit_code = self.process.wait() self.process_err = self.process_err + \ '\n'.join(['' , 'Process exit code: {0}'.format(exit_code) , '' , "output:" , stdout if stdout and len(stdout) > 0 else "--no output--" , '' , 'error:' , stderr if stderr and len(stderr) > 0 else "--no error output--"]) self.process = None else: self.process = None raise os_exc # 'with' statement support: def __enter__(self): return self def __exit__(self, _type, _value, _traceback): self.cleanup() return False def cleanup(self): if self.process is not None: self.process.stdin.close() self.process.stdout.close() if self.process.stderr is not None: # stderr can be None if it is tied to stdout (i.e., 'stderr=subprocess.STDOUT') self.process.stderr.close() def wait(self, input_str=None): """Wait for subprocess to complete and exit, collect and decode ``stdout`` and ``stderr``, returning the tuple ``(exit_code, stdout, stderr)```""" if self.process is not None: stdout, stderr = self.process.communicate(Utils.encode_bytes(input_str) if input_str is not None else '') exit_code = self.process.wait() # Ensure that we reap the file descriptors. self.cleanup() return (exit_code, Utils.decode_bytes(stdout), Utils.decode_bytes(stderr)) else: return (-1, '', self.process_err or "?? unknown error -- no process.") # Update the augmented environment when `add_to_PATH` or `add_standard_dirs` change. @staticmethod def update_environment(_key, _val): # Reinitialize the tool -> path cache: ProcHelper.which_cache = LockedObject.LockedObject({}) ProcHelper.augmented_env = ProcHelper.get_extended_env() # Generate the augmented environment for subprocesses. This copies the # current process environment and updates PATH with `add_to_PATH` extras. @staticmethod def get_extended_env(): def normalize_path(dpath): return os.path.normpath(os.path.expandvars(os.path.expanduser(dpath))) def cabal_config(): cconfig = os.environ.get('CABAL_CONFIG') or \ ('~/.cabal' if not is_windows() else '%APPDATA%/cabal') + \ "/config" # Various patterns to match... re_user_dirs = re.compile(r'^install-dirs\s+user') re_global_dirs = re.compile(r'^install-dirs\s+global') re_section = re.compile(r'^\w+') re_prefix = re.compile(r'prefix:\s+(.*)$') re_bindir = re.compile(r'bindir:\s+(.*)$') # Things to collect user_prefix = "$HOME/.cabal" if not is_windows() else "%APPDATA%/cabal" # FIXME: Need to interrogate Shel32 for the Windows PROGRAMFILES known # folder path: global_prefix = "/usr/local" if not is_windows() else "%PROGRAMFILES%/Haskell" user_bindir = "bin" global_bindir = "bin" p_state = 0 try: with open(normalize_path(cconfig), 'rU') as f_cconfig: # You would think that the Cabal maintainers would use a # well known file format... But now, they didn't. And they # had to go with an indentation-specific format. # # This is a "cheap and dirty" scanner to pick up for line in f_cconfig: line = line.rstrip() # One of the sections? if re_user_dirs.match(line): p_state = 1 elif re_global_dirs.match(line): p_state = 2 elif re.match(r'^\s+\w', line): # prefix attribute? m_prefix = re_prefix.search(line) if m_prefix: if p_state == 1: user_prefix = m_prefix.group(1) elif p_state == 2: global_prefix = m_prefix.group(1) # bindir attribute? m_bindir = re_bindir.search(line) if m_bindir: if p_state == 1: user_bindir = m_bindir.group(1) elif p_state == 2: global_bindir = m_bindir.group(1) elif re_section.match(line): p_state = 0 except IOError: # Silently fail. pass return [os.path.join(user_prefix, user_bindir) , os.path.join(global_prefix, global_bindir) ] ext_env = dict(os.environ) env_path = os.getenv('PATH') or "" std_places = [] if Settings.PLUGIN.add_standard_dirs: std_places = ["$HOME/.local/bin" if not is_windows() else "%APPDATA%/local/bin"] + cabal_config() std_places = list(filter(os.path.isdir, map(normalize_path, std_places))) add_to_path = list(filter(os.path.isdir, map(normalize_path, Settings.PLUGIN.add_to_path, []))) Logging.log("std_places = {0}".format(std_places), Logging.LOG_INFO) Logging.log("add_to_PATH = {0}".format(add_to_path), Logging.LOG_INFO) ext_env['PATH'] = os.pathsep.join(add_to_path + std_places + [env_path]) return ext_env @staticmethod def which(args, env_path): def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) with ProcHelper.which_cache as cache: cval = cache.get(args[0]) if cval is not None: return [cval] + args[1:] else: exe_exts = [''] if not is_windows() else ['.exe', '.cmd', '.bat'] program = args[0] fpath, _ = os.path.split(program) if fpath: if is_exe(program): return args else: for path in env_path.split(os.pathsep): path = path.strip('"') for ext in exe_exts: exe_file = os.path.join(path, program) if is_exe(exe_file + ext): with ProcHelper.which_cache as cache: cache[program] = exe_file return [exe_file] + args[1:] return None @staticmethod def run_process(command, input_string='', **popen_kwargs): """Execute a subprocess, wait for it to complete, returning a ``(exit_code, stdout, stderr)``` tuple.""" with ProcHelper(command, **popen_kwargs) as proc: return proc.wait(input_string) @staticmethod def invoke_tool(command, tool_name, inp='', on_result=None, filename=None, on_line=None, check_enabled=True, **popen_kwargs): if check_enabled and not Settings.PLUGIN.__getattribute__(Utils.tool_enabled(tool_name)): return None source_dir = get_source_dir(filename) def mk_result(result): return on_result(result) if on_result else result try: with ProcHelper(command, cwd=source_dir, **popen_kwargs) as proc: exit_code, stdout, stderr = proc.wait(inp) if exit_code != 0: raise Exception('{0} exited with exit code {1} and stderr: {2}'.format(tool_name, exit_code, stderr)) if on_line: for line in io.StringIO(stdout): on_line(mk_result(line)) else: return mk_result(stdout) except OSError as os_exc: if os_exc.errno == errno.ENOENT: errmsg = "SublimeHaskell: {0} was not found!\n'{1}' is set to False".format(tool_name, Utils.tool_enabled(tool_name)) Common.output_error_async(sublime.active_window(), errmsg) Settings.PLUGIN.__setattr__(Utils.tool_enabled(tool_name), False) else: Logging.log('{0} fails with {1}, command: {2}'.format(tool_name, os_exc, command), Logging.LOG_ERROR) return None return None
def update_environment(_key, _val): # Reinitialize the tool -> path cache: ProcHelper.which_cache = LockedObject.LockedObject({}) ProcHelper.augmented_env = ProcHelper.get_extended_env()
def __init__(self): super().__init__() self.daemon = True self.view = LockedObject.LockedObject({'view': None, 'mtime': None}) self.event = threading.Event()