SUBLIME_SETTINGS_FILENAME = 'sublime-ycmd.sublime-settings' SUBLIME_SETTINGS_RECOGNIZED_KEYS = [
    'ycmd_root_directory',
    'ycmd_default_settings_path',
    'ycmd_python_binary_path',
    'ycmd_language_whitelist',
    'ycmd_language_blacklist',
    'ycmd_language_filetype',
    'ycmd_idle_suicide_seconds',
    'ycmd_check_interval_seconds',
    'ycmd_log_level',
    'ycmd_log_file',
    'ycmd_keep_logs',
    'ycmd_force_semantic_completion',
    'sublime_ycmd_log_level',
    'sublime_ycmd_log_file',
    'sublime_ycmd_background_threads',
] SUBLIME_SETTINGS_WATCH_KEY = 'syplugin' SUBLIME_SETTINGS_YCMD_SERVER_KEYS = [
    'ycmd_root_directory',
    'ycmd_default_settings_path',
    'ycmd_python_binary_path',
    'ycmd_language_filetype',
    'ycmd_idle_suicide_seconds',
    'ycmd_check_interval_seconds',
    'ycmd_log_level',
    'ycmd_log_file',
    'ycmd_keep_logs',
] SUBLIME_SETTINGS_TASK_POOL_KEYS = [
    'sublime_ycmd_background_threads',
] class SettingsError(PluginError):
    '''
    Settings-related error class.
    Represents an issue with the plugin settings/configuration.
    '''
    MISSING = 'missing configuration'
    TYPE = 'type mismatch'

    def __init__(self, msg, type=None, key=None, value=None):
        super(SettingsError, self).__init__(msg)
        self._type = type
        self._msg = msg
        self._key = key
        self._value = value

    def desc(self):
        if self._type == SettingsError.MISSING:
            desc_prefix = 'Missing value'
        elif self._type == SettingsError.TYPE:
            desc_prefix = 'Type mismatch'
        else:
            desc_prefix = 'Settings error'

        desc = '%s for key "%s"' % (desc_prefix, self._key)
        return desc

    def __repr__(self):
        return '%s(%r)' % ('SettingsError', {
            'type': self._type,
            'msg': self._msg,
            'key': self._key,
            'value': self._value,
        }) def resolve_abspath(path, start=None):
    '''
    Resolves `path` to an absolute path. If the path is already an absolute
    path, it is returned as-is. Otherwise, it is joined to the `start` path,
    which is assumed to be an absolute path.
    If `start` is not provided, the current working directory is used.
    '''
    if not isinstance(path, str):
        raise TypeError('path must be a str: %r' % (path))

    # attempt to expand things like '~/' before proceeding
    path = resolve_env(path)

    if os.path.isabs(path):
        logger.debug('path is already absolute: %s', path)
        return path

    logger.debug('path is not absolute, need to resolve it')
    if start is None:
        start = os.getcwd()
        logger.debug('using working directory for relative paths: %s', start)
    assert isinstance(start, str), 'start must be a str: %r' % (start)

    logger.debug('joining path, start: %s, %s', path, start)
    return os.path.join(path, start) def resolve_binary_path(binpath, workingdir=None, *pathdirs):
    '''
    Resolves the binary path `binpath` to an absolute path based on the
    supplied parameters. The following rules are applied until a path is found:
    1.  If it is already an absolute path, it is returned as-is.
    2.  If a file exists at the location relative to the working directory,
        then the absolute path to that file is returned. When workingdir is
        `None`, the current working directory is used (probably '.').
    3.  For each path directory in pathdirs, apply same relative location step
        as above. Only the first match will be returned, even if there would be
        more matches. When no pathdirs are provided, directories in the PATH
        environment variable are used.
    If no path is found, this will return `None`.
    '''
    if not isinstance(binpath, str):
        raise TypeError('binpath must be a str: %r' % binpath)

    if os.path.isabs(binpath):
        logger.debug(
            'binpath already absolute, returning as-is: %r', binpath
        )
        return binpath

    if workingdir is None:
        curdir = os.curdir
        workingdir = curdir
        logger.debug('filling in current working directory: %s', curdir)

    if not pathdirs:
        rawpath = os.getenv('PATH', default=None)
        if rawpath is None:
            logger.warning('cannot read PATH environment variable, might not '
                           'be able to resolve binary paths correctly')
            # just in case, assign it a dummy iterable value too
            pathdirs = tuple()
        else:
            assert isinstance(rawpath, str), \
                '[internal] rawpath from os.getenv is not a str: %r' % rawpath
            pathdirs = rawpath.split(os.path.pathsep)

    def generate_binpaths():
        '''
        Provides an iterator for all possible absolute locations for binpath.
        Platform dependent suffixes are automatically added, if applicable.
        '''
        should_add_win_exts = os.name == 'nt'
        win_exts = ['.exe', '.cmd', '.bat']
        for pathdir in itertools.chain([workingdir], pathdirs):
            assert isinstance(pathdir, str), \
                '[internal] pathdir is not a str: %r' % pathdir
            yield os.path.join(pathdir, binpath)
            if should_add_win_exts:
                for win_ext in win_exts:
                    filebasename = '%s%s' % (binpath, win_ext)
                    yield os.path.join(pathdir, filebasename)

    found_files = filter(os.path.isfile, generate_binpaths())

    result_binpath = None
    try:
        result_binpath = next(found_files)
    except StopIteration:
        logger.debug('%s not found in %s, %s', binpath, workingdir, pathdirs)
    else:
        logger.debug('%s found at %s', binpath, result_binpath)

    return result_binpath def default_python_binary_path():
    '''
    Generates and returns a path to the python executable, as resolved by
    `resolve_binary_path`. This will automatically prefer pythonw in Windows.
    '''
    if os.name == 'nt':
        pythonw_binpath = resolve_binary_path('pythonw')
        if pythonw_binpath:
            return pythonw_binpath

    python_binpath = resolve_binary_path('python')
    if python_binpath:
        return python_binpath

    # best effort:
    return 'python' def get_cpu_count():
    ''' Returns the core count of the current system. '''
    try:
        return multiprocessing.cpu_count()
    except (AttributeError, NotImplementedError):
        return 1 def get_default_settings_path(ycmd_root_directory):
    '''
    Generates the path to the default settings json file from the ycmd module.
    The `ycmd_root_directory` should refer to the path to the repository.
    '''
    if not is_directory(ycmd_root_directory):
        logger.warning('invalid ycmd root directory: %s', ycmd_root_directory)
        # but whatever, fall through and provide the expected path anyway

    return os.path.join(ycmd_root_directory, 'ycmd', 'default_settings.json')
import itertools
import logging
    import sublime
    from ..subl.dummy import sublime
from ..subl.constants import (
    SUBLIME_SETTINGS_FILENAME,
    SUBLIME_SETTINGS_RECOGNIZED_KEYS,
    SUBLIME_SETTINGS_WATCH_KEY,
    SUBLIME_SETTINGS_YCMD_SERVER_KEYS,
    SUBLIME_SETTINGS_TASK_POOL_KEYS,
)
from ..subl.errors import (
    SettingsError,
)
from ..util.fs import (
    resolve_abspath,
    resolve_binary_path,
    default_python_binary_path,
)
from ..util.sys import get_cpu_count
from ..ycmd.settings import get_default_settings_path
            return False

        comparison_keys = itertools.chain(
            SUBLIME_SETTINGS_YCMD_SERVER_KEYS,
            SUBLIME_SETTINGS_TASK_POOL_KEYS,
        )
        for settings_key in comparison_keys:
            self_setting = getattr(self, settings_key)
            other_setting = getattr(other, settings_key)

            if self_setting != other_setting:
                return False

        return True

    def __bool__(self):
        return bool(self._ycmd_root_directory)

    def __hash__(self):
        return hash((
            self.ycmd_root_directory,
            self.ycmd_default_settings_path,
            self.ycmd_python_binary_path,
        ))

    def __iter__(self):
        ''' Dictionary-compatible iterator. '''
        return iter([
            (setting_key, getattr(self, setting_key))
            for setting_key in SUBLIME_SETTINGS_RECOGNIZED_KEYS
        ])

    def __str__(self):
        return str(dict(self))

    def __repr__(self):
        return '%s(%r)' % ('Settings', dict(self))


def load_settings(filename=SUBLIME_SETTINGS_FILENAME):
    '''
    Fetches the resolved settings file `filename` from sublime, and parses
    it into a `Settings` instance. The file name should be the base name of
    the file (i.e. not the absolute/relative path to it).
    '''
    logger.debug('loading settings from: %s', filename)
    settings = sublime.load_settings(filename)

    logger.debug('parsing/extracting settings')
    return Settings(settings=settings)


def bind_on_change_settings(callback,
                            filename=SUBLIME_SETTINGS_FILENAME):
    '''
    Binds `callback` to the on-change-settings event. The settings are loaded
    from `filename`, which should be the base name of the file (i.e. not the
    path to it). When loading, the settings are parsed into a `Settings`
    instance, and this instance is supplied as an argument to the callback.
    When called, this will automatically load the settings for the first time,
    and immediately invoke `callback` with the initial settings.
    '''
    logger.debug('loading settings from: %s', filename)
    settings = sublime.load_settings(filename)

    def on_change_settings():
        logger.debug('settings have changed, reloading them')
        settings = sublime.load_settings(filename)
        extracted_settings = Settings(settings=settings)
        callback(extracted_settings)

    logger.debug(
        'binding on-change handler using key: %r', SUBLIME_SETTINGS_WATCH_KEY,
    )
    settings.clear_on_change(SUBLIME_SETTINGS_WATCH_KEY)
    settings.add_on_change(SUBLIME_SETTINGS_WATCH_KEY, on_change_settings)

    logger.debug('loading initial settings')
    initial_settings = Settings(settings=settings)

    logger.debug(
        'triggering callback with initial settings: %s', initial_settings
    )
    callback(initial_settings)

    return initial_settings


def validate_settings(settings):
    '''
    Checks `settings` for invalid/missing values. May raise `SettingsError`.
    This only checks for type-mismatches and missing settings. It does not
    check anything on the file system, as that could block.
    If there are no issues, this returns without doing anything. Otherwise,
    this will raise an exception of type `SettingsError`.
    '''
    if not isinstance(settings, Settings):
        raise TypeError('settings must be Settings: %r' % (settings))

    ycmd_root_directory = settings.ycmd_root_directory

    ycmd_default_settings_path = settings.ycmd_default_settings_path
    ycmd_python_binary_path = settings.ycmd_python_binary_path
    ycmd_language_whitelist = settings.ycmd_language_whitelist
    ycmd_language_blacklist = settings.ycmd_language_blacklist
    ycmd_language_filetype = settings.ycmd_language_filetype
    ycmd_idle_suicide_seconds = settings.ycmd_idle_suicide_seconds
    ycmd_check_interval_seconds = settings.ycmd_check_interval_seconds
    ycmd_log_level = settings.ycmd_log_level
    ycmd_log_file = settings.ycmd_log_file
    ycmd_keep_logs = settings.ycmd_keep_logs
    ycmd_force_semantic_completion = \
        settings.ycmd_force_semantic_completion
    sublime_ycmd_log_level = settings.sublime_ycmd_log_level
    sublime_ycmd_log_file = settings.sublime_ycmd_log_file
    sublime_ycmd_background_threads = \
        settings.sublime_ycmd_background_threads

    # required settings
    if not ycmd_root_directory:

        raise SettingsError(