class SettingsManager(six.with_metaclass(Singleton, object)):
    def init(self):
        self._settings = sublime.load_settings(settings_filename)
        self._defaults = {
            'rfdocs_update_url': rfdocs_update_url,
            'show_version_in_autocomplete_box': True,
            'log_level': 'error',
            'associated_file_extensions': ['.txt', '.robot'],
            'separator': {
                'between_kw_and_args': '  ',
                'between_args': '...  ',
                'kw_and_args_one_line': False
            },
            # should be path to python interpreter with robot installed.
            'python_interpreter': 'python'
        }
        for prop, value in self._defaults.items():
            if not self._settings.has(prop):
                self._settings.set(prop, value)
        self.save()
        # kind of hidden settings, that are needed permanently
        self.scanner_config = os.path.join(
            sublime.packages_path(),
            'User', '{0}.scanners'.format(plugin_name)
        )
        if not os.path.exists(self.scanner_config):
            shutil.copy2(scanner_config_path, self.scanner_config)
        self.rfdocs_dir = rfdocs_dir_path
        self.rfdocs_manifest = rfdocs_manifest_path
        self.python_libs_dir = python_libs_dir_path
        self.resources_dir = resources_dir_path

    def load(self):
        self._settings = sublime.load_settings(settings_filename)

    def set(self, name, value):
        self._settings.set(name, value)

    def get(self, name):
        return self._settings.get(name) or self._defaults[name]

    def save(self):
        sublime.save_settings(settings_filename)

    def __getattr__(self, name):
        return self.get(name)

    def to_dict(self):
        return dict((name, self.get(name)) for name in self._defaults.keys())
class DynamicAutoCompleteListsAndDefinitionsAggregator(
        six.with_metaclass(Singleton, object)):
    def __init__(self):
        self.autocomplete_keywords = []
        self.autocomplete_variables = []
        self.autocomplete_method = 'get_source'

    def clear_autocomplete(self):
        self.autocomplete_keywords = []

    def has_autocomplete(self):
        return any([self.autocomplete_keywords, self.autocomplete_variables])

    # add data
    def add_kw_for_autocomplete(self, keyword):
        self.autocomplete_keywords.append(keyword)

    def add_var_for_autocomplete(self, var):
        self.autocomplete_variables.append(var)

    # get data
    def _get_kw_info_for_autocomplete(self, kw):
        return kw.name + '\t' + getattr(
            kw, self.autocomplete_method)(), kw.get_name_and_signature()

    # get autocomplete data for keywords
    def get_keywords_autocomplete_list(self, word):
        return [
            self._get_kw_info_for_autocomplete(kw)
            for kw in self.autocomplete_keywords
            if word.lower() in kw.name.lower()
        ]

        # get autocomplete data for vars
    def get_vars_autocomplete_list(self, word):
        return [(var.name + '\t' + str(var.source or var.type_),
                 '{0}'.format(insert_robot_var(var.name)))
                for var in self.autocomplete_variables
                if word.lower() in var.name.lower()]

    # get autocomplete data for all types
    def get_autocomplete_list(self, word):
        if word.startswith('$') or word.startswith(
                '@'):  # scalar($) and list(@) variables
            return self.get_vars_autocomplete_list(word)
        return self.get_keywords_autocomplete_list(word)
class StaticAutoCompleteListsAndDefinitionsAggregator(
        six.with_metaclass(Singleton, object)):
    def __init__(self):
        self.autocomplete_keywords = []
        self.autocomplete_sources = []
        self.autocomplete_variables = []
        self.definitions_keywords = {}
        self.definitions_sources = {}
        self.definitions_variables = {}
        self._autocomplete_method = 'get_source_name'

    @property
    def autocomplete_method(self):
        return self._autocomplete_method

    @autocomplete_method.setter
    def autocomplete_method(self, method):
        allowed_methods = ('get_source', 'get_source_name')
        if method not in allowed_methods:
            raise ValueError(
                'Invalid method for autocomplete: {0}. Must be one from: {1}'.
                format(method, ', '.join(allowed_methods)))
        self._autocomplete_method = method

    def clear_autocomplete(self):
        self.autocomplete_keywords = []
        self.autocomplete_sources = []
        self.autocomplete_variables = []

    def clear_definitions(self):
        self.definitions_keywords = {}
        self.definitions_sources = {}
        self.definitions_variables = {}

    def clear_data(self):
        self.clear_autocomplete()
        self.clear_definitions()

    def has_autocomplete(self):
        return any([
            self.autocomplete_keywords, self.autocomplete_sources,
            self.autocomplete_variables
        ])

    def has_definitions(self):
        return any([
            self.definitions_keywords, self.definitions_sources,
            self.definitions_variables
        ])

    def has_data(self):
        return all([self.has_autocomplete(), self.has_definitions()])

    # add data
    def add_kw_for_autocomplete(self, keyword):
        self.autocomplete_keywords.append(keyword)

    def add_kw_for_definitions(self, keyword):
        self.definitions_keywords.setdefault(keyword.name, set()).add(keyword)

    def add_source_for_definitions(self, source):
        self.definitions_sources.setdefault(source.name, set()).add(source)

    def add_source_for_autocomplete(self, source):
        self.autocomplete_sources.append(source)

    def add_var_for_autocomplete(self, var):
        self.autocomplete_variables.append(var)

    def add_var_for_definitions(self, var):
        self.definitions_variables.setdefault(var.name, set()).add(var)

    def add_builtin_vars_for_autocomplete_and_definitions_lists(self):
        for var_name, var_value in RFGlobalVars.variables.items():
            var = RFVariable(source=None, name=var_name, value=var_value)
            self.add_var_for_autocomplete(var)
            self.add_var_for_definitions(var)

    # get data
    def _get_kw_info_for_autocomplete(self, kw):
        return kw.name + '\t' + getattr(
            kw, self.autocomplete_method)(), kw.get_name_and_signature()

    # get autocomplete data for keywords
    def get_keywords_autocomplete_list(self, word, name_of_source=None):
        if name_of_source:
            name_of_source = name_of_source.lower()
            # only name of library is given and autocomplete is triggered after dot character.
            # BuiltIn.
            if word == '':
                return [
                    self._get_kw_info_for_autocomplete(kw)
                    for kw in self.autocomplete_keywords
                    if kw.source.name.lower() == name_of_source
                ]
                # name of library and part of keyword name are given
            # BuiltIn.Call
            return [
                self._get_kw_info_for_autocomplete(kw)
                for kw in self.autocomplete_keywords
                if word.lower() in kw.name.lower()
                and kw.source.name.lower() == name_of_source
            ]
            # only part of keyword name is given
        return [
            self._get_kw_info_for_autocomplete(kw)
            for kw in self.autocomplete_keywords
            if word.lower() in kw.name.lower()
        ]

    # get autocomplete data for keywords parents (sources)
    def get_sources_autocomplete_list(self, word):
        return [(source.name + '\t' + str(source), source.name)
                for source in self.autocomplete_sources
                if word.lower() in source.name.lower()]

    # get autocomplete data for vars
    def get_vars_autocomplete_list(self, word):
        return [(var.name + '\t' + str(var.source or var.type_),
                 '{0}'.format(insert_robot_var(var.name)))
                for var in self.autocomplete_variables
                if word.lower() in var.name.lower()]

    # get autocomplete data for all types
    def get_autocomplete_list(self, word, name_of_source=None):
        autocomplete_list = []
        keywords = self.get_keywords_autocomplete_list(
            word, name_of_source=name_of_source)
        if name_of_source:
            return keywords
        variables = self.get_vars_autocomplete_list(word)
        if word.startswith('$') or word.startswith(
                '@'):  # scalar($) and list(@) variables
            return variables
        sources = self.get_sources_autocomplete_list(word)
        autocomplete_list.extend(keywords)
        autocomplete_list.extend(sources)
        return autocomplete_list

    # get definitions
    def get_keywords_definitions_list(self, word, name_of_source=None):
        definitions_list = []
        if name_of_source:
            if word in self.definitions_keywords:
                candidates = [
                    candidate for candidate in self.definitions_keywords[word]
                    if candidate.source.name.lower() == name_of_source.lower()
                ]
                definitions_list.extend(candidates)
        elif word in self.definitions_keywords:
            definitions_list.extend(self.definitions_keywords[word])
        return definitions_list

    def get_filtered_keywords_definitions_list(self,
                                               word,
                                               name_of_source=None,
                                               filter_by_attrs=()):
        keywords = self.get_keywords_definitions_list(
            word, name_of_source=name_of_source)
        if not filter_by_attrs:
            return keywords
        return [
            kw for kw in keywords
            if any([getattr(kw, attr, None) for attr in filter_by_attrs])
        ]

    def get_sources_definitions_list(self, word):
        definitions_list = []
        if word in self.definitions_sources:
            definitions_list.extend(self.definitions_sources[word])
        return definitions_list

    def get_vars_definitions_list(self, word):
        definitions_list = []
        if word in self.definitions_variables:
            definitions_list.extend(self.definitions_variables[word])
        return definitions_list