Пример #1
0
 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()
Пример #2
0
 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
Пример #3
0
    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())
Пример #4
0
 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({})
Пример #5
0
 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')
Пример #6
0
 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
Пример #7
0
    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))
Пример #8
0
    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
Пример #9
0
    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()
Пример #10
0
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)
Пример #11
0
 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({})
Пример #12
0
def reset_cache():
    global WHICH_CACHE
    WHICH_CACHE = LockedObject.LockedObject({})
Пример #13
0
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']
Пример #14
0
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
Пример #15
0
 def update_environment(_key, _val):
     # Reinitialize the tool -> path cache:
     ProcHelper.which_cache = LockedObject.LockedObject({})
     ProcHelper.augmented_env = ProcHelper.get_extended_env()
Пример #16
0
 def __init__(self):
     super().__init__()
     self.daemon = True
     self.view = LockedObject.LockedObject({'view': None, 'mtime': None})
     self.event = threading.Event()