def read_and_decode_json_file(filename):
    """ returns None or json-decoded file contents as object,list,... """
    f = read_file(filename)
    return json.loads(f) if f is not None else None def file_exists(filename):
    """ returns weather the file exists """
    return os.path.isfile(os.path.normcase(filename)) def is_ts(view):
    if view is None:
        return False
    fn = view.file_name()
    fn2 = view.file_name()
    fn3 = view.file_name()
    if fn is None or fn2 is None:
        return False
    if fn is None or fn2 is None or fn3 is None:
        pass
        #import spdb ; spdb.start()
    return fn.endswith('.ts') and not fn.endswith('.d.ts') def is_dts(view):
    return view.file_name() and view.file_name().endswith('.d.ts') def read_file(filename):
    """ returns None or file contents if available """
    filename = os.path.normcase(filename) # back to \\ in nt
    if os.path.isfile(filename):
        try:
            if os.name == 'nt':
                return open(filename, 'r', encoding='utf8').read()
            else:
                return codecs.open(filename, 'r', 'utf-8').read()
        except IOError:
            pass
    return None def find_tsconfigdir(rootdir):
    """ Returns the normalized dir, in which the tsconfig.json file is located"""
    rootdir = os.path.abspath(rootdir)
    try:
        if file_exists(os.path.join(rootdir, "tsconfig.json")):
            return os.path.normcase(rootdir)
    except FileNotFoundError:
        pass

    parentdir = os.path.abspath(os.path.join(rootdir, os.pardir))
    if parentdir == rootdir:
        return None
    else:
        return find_tsconfigdir(parentdir) def is_plugin_temporarily_disabled(folder=None):
    """ Returns True if the plugin is disabled globally or for folder.
        Folder can be a view """
    if is_plugin_globally_disabled() or folder is None:
        return is_plugin_globally_disabled()
    if folder is not None and isinstance(folder, sublime.View):
        if folder.file_name() is None:
            return is_plugin_globally_disabled()
        folder = os.path.dirname(folder.file_name())
    folder = os.path.normcase(folder)
    return folder in plugin_disabled_for_folders class CancelCommand(Exception):
    """ Throw this exception in a command. The decorator
        catch_CancelCommand will catch it and cancel silently """
    pass def Debug(classification, text):
    if 'all' in print_classifications or classification in print_classifications:
        print("ArcticTypescript: %s: %s" % (classification.ljust(8), text))
    if classification not in possible_classifications:
        print("ArcticTypescript: debug: got unknown debug message classification: %s. " \
            "Consider adding this to possible_classifications" % classification)
    sys.stdout.flush() def get_first(somelist, function):
    """ Returns the first item of somelist for which function(item) is True """
    for item in somelist:
        if function(item):
            return item
    return None def get_deep(obj, selector):
    """ Returns the object given by the selector, eg a:b:c:1 for ['a']['b']['c'][1] """
    try:
        if type(selector) is str:
            selector = selector.split(':')

        if len(selector) == 0:
            return obj

        top_selector = selector.pop(0)

        if type(obj) is list:
            top_selector = int(top_selector)
            return get_deep(obj[top_selector], selector)

        if type(obj) is dict:
            return get_deep(obj[top_selector], selector)

        if obj is None:
            raise KeyError()
    except:
        raise KeyError(str(selector)) def random_str():
    """ Returns a random string, made out of the current time """
    return make_hash(str(time.time())) class ProjectWizzard(object):


    def __init__(self, view, finished_callback):
        self.finished_callback = finished_callback
        self.window = view.window()
        if not self.window: # workaround
            self.window = view.window()
        self.view = view
        self.tsconfigfolder = None
        self.module = None
        self.outfile = None
        self.outdir = None
        self.target = "es5"
        self.sourcemap = True
        self.files = []
        Debug('project+', 'Disable ArcticTypescript while in WizzardMode')
        set_plugin_temporarily_disabled() # Disable global until wizzard is cancled or finished
        assert is_plugin_temporarily_disabled()


    def _prepare(self, message, action_default=None, action_cancel=None):
        """ Prepare quick_dialog """
        self.messages = []
        self.actions = []
        self.action_cancel = action_cancel
        self.action_default = action_default
        if message:
            if isinstance(message, str):
                message = [message]
            self.messages.append(message)
            self.actions.append(None)
        #Debug('project+', 'Hide Overlay')
        #self.window.run_command("hide_overlay")


    def _show_and_action(self):
        """ Show quick dialog and execute action """
        # every message must consist of the same number of lines
        max_lines = 0
        for m in self.messages:
            max_lines = max(max_lines, len(m))
        for m in self.messages:
            while len(m) < max_lines:
                m.append('')
        def on_select(i):
            #self.window.run_command("hide_overlay")
            if i == -1:
                Debug('project+', '-1: Quick panel canceled')
                sublime.set_timeout(self._cleanup, 50)
                if self.action_cancel is not None:
                    sublime.set_timeout(self.action_cancel, 50)
            elif i >= 0:
                if self.actions[i] is not None:
                    sublime.set_timeout(self.actions[i], 50)
                elif self.action_default is not None:
                    sublime.set_timeout(self.action_default, 50)
                Debug('project+', 'Nr %i selected' % i)
        self.window.show_quick_panel(self.messages, on_select)


    def handle_tsconfig_error(self, tsconfigfile, message=""):
        """ User will be asked to create a tsconfig.json file """
        self._prepare(message)

        self.messages.append(['> Open your tsconfig.json and Edit it'])
        self.actions.append(lambda: [self.window.open_file(tsconfigfile), self._cleanup()])

        self.messages.append(['> Show tsconfig.json example'])
        self.actions.append(lambda: [self.window.open_file(package_path + '/examples/basic_browser_project/tsconfig.json'), self._cleanup()])

        self.messages.append(['> Documentation: README ', 'see Chapter Settings'])
        self.actions.append(lambda: [self.window.open_file(package_path + '/README.md'), self._cleanup()])

        self._show_and_action()


    def new_tsconfig_wizzard(self, message=""):
        """ User will be asked to create a new tsconfig.json file """
        self._prepare(message, action_default=lambda: self.new_tsconfig_wizzard(message))

        self.messages.append(['Choose an option from below:'])
        self.actions.append(None)

        self.messages.append(['> Create a tsconfig.json file',
                              '(You will be asked questions at the bottom of the window.)'])
        self.actions.append(lambda: self._create_tsconfigjson())

        self.messages.append(['> I don\'t understand please show me the README file'])
        self.actions.append(lambda: [self.window.open_file(package_path + '/README.md'), self._cleanup()])

        self.messages.append(['> Disable ArcticTypescript for this folder'])
        self.actions.append(lambda: [set_plugin_temporarily_disabled(folder=self.view), self._cleanup()])

        Debug('project+', 'Show first quickpanel')
        self._show_and_action()


    def _create_tsconfigjson(self):
        """ Location for the tsconfig.json file """
        Debug('project+', 'Ask for tsconfig.json location')
        self.window.show_input_panel("Location for tsconfig.json (use your project folder)",
                                     os.path.dirname(self.view.file_name()),
                                     lambda folder: self._set_folder_and_go_on(folder),
                                     None, # on change
                                     lambda: [self._cleanup(),
                                               set_plugin_temporarily_disabled(folder=self.view),
                                              sublime.message_dialog(
                                        "ArcticTypescript disabled for this file's folder")]
                                    )


    def _set_folder_and_go_on(self, tsconfigfolder):
        tsconfigfolder = os.path.abspath(tsconfigfolder)
        is_direct_parent =  os.path.relpath(tsconfigfolder,
                                            os.path.dirname(self.view.file_name())) \
                            .replace('../', '').replace('..', '').replace('.', '') \
                            == ""
        if is_direct_parent:
            self.tsconfigfolder = tsconfigfolder
            self.tspath = os.path.abspath(os.path.join(self.tsconfigfolder, 'tsconfig.json'))
            self._ask_output_type()
        else:
            sublime.status_message("#### You have to choose a parent folder! ####")
            self._create_tsconfigjson()


    def _ask_output_type(self, message=""):
        """ Singlefile output or multiple files (AMD or commonjs style) """
        self._prepare(message, action_default=lambda: self._ask_output_type(message))

        self.messages.append(['Choose compiler behaviour:'])
        self.actions.append(None)

        self.messages.append(['> Compile to single file',
                              'use /// <reference path="ab.ts" /> with',
                              '  module { export } to spread code'])
        self.actions.append(lambda: [self._set_module(None), self._ask_output_file(), Debug('project', 'Single file selected')])

        self.messages.append(['> Commonjs: Compile to multiple files for NODEJS',
                              'not for browser use',
                              'use import a = require(\'utils\'); and export ... to spread code' ])
        self.actions.append(lambda: [self._set_module('commonjs'), self._ask_output_directory(), Debug('project', 'Commonjs selected')])

        self.messages.append(['> AMD: Compile to multiple files for requirejs',
                              'for BROWSER use, but also for Nodejs and Rhino',
                              'use import a = require(\'utils\'); and export ... to spread code' ])
        self.actions.append(lambda: [self._set_module('amd'), self._ask_output_directory('built/js/'), Debug('project', 'AMD selected')])

        Debug('project+', 'Show compiler behaviour quickpanel')
        self._show_and_action()


    def _set_module(self, module):
        self.module = module


    def _ask_output_file(self):
        """ For single file output, ask for the output filename """
        Debug('project+', 'Ask for output file')
        self.window.show_input_panel("Relative output location for the compiled .js FILE: %s/" % self.tsconfigfolder,
                                     "built/out.js",
                                     lambda outfile: [self._set_out(outfile), self._ask_for_files()],
                                     None, # on change
                                     lambda: [self._cleanup(),
                                               set_plugin_temporarily_disabled(folder=self.view),
                                              sublime.message_dialog(
                                                "ArcticTypescript disabled for this file's folder")]
                                    )


    def _set_out(self, outfile):
        self.outfile = outfile


    def _ask_output_directory(self, default="built/"):
        """ For multiple file output, ask for the output directory """
        Debug('project+', 'Ask for output directory')
        self.window.show_input_panel("Relative output FOLDER for all compiled js files: %s/" % self.tsconfigfolder,
                                     default,
                                     lambda outdir: [self._set_outdir(outdir), self._ask_for_files()],
                                     None, # on change
                                     lambda: [self._cleanup(),
                                               set_plugin_temporarily_disabled(folder=self.view),
                                               sublime.message_dialog(
                                                "ArcticTypescript disabled for this file's folder")])


    def _set_outdir(self, outdir):
        self.outdir = outdir


    def _ask_for_files(self, dialog=True):
        """ Ask to define all files which are the roots of the reference tree """
        if dialog:
            sublime.message_dialog("Please name all files which should be compiled, relative to %s/ \n \n You don't need to specify files which are refrenced using /// <reference> or import a = require('a');\n\n So only name the files which are on top of your reference tree.\n\n Press <Esc> or enter nothing to finish." % self.tsconfigfolder)

        firstfile = os.path.relpath(self.view.file_name(), self.tsconfigfolder)
        Debug('project+', 'Ask for files')
        self.window.show_input_panel("(%i) Please name all files which should be compiled, relative to %s/"
                                        % (len(self.files), self.tsconfigfolder),
                                     firstfile if dialog else "",
                                     lambda file: self._file_entered(file),
                                     None, # on change
                                     lambda: self._file_entered(""))


    def _file_entered(self, filename):
        if filename == "":
            self._finish()
        else:
            self.files.append(filename)
            self._ask_for_files(False)


    def _finish(self):
        """ make and write tsconfig.json. Call finished_callback """
        Debug('project+', 'Wizzard finished: Create and write tsconfig.json')
        content = { "compilerOptions" : {}, "files" : {}}
        if self.module is None:
            content['compilerOptions']['out'] = self.outfile
            content['compilerOptions']['target'] = self.target
            content['compilerOptions']['sourceMap'] = self.sourcemap
        elif self.module == "commonjs" or self.module == "amd":
            content['compilerOptions']['outDir'] = self.outdir
            content['compilerOptions']['target'] = self.target
            content['compilerOptions']['sourceMap'] = self.sourcemap
            content['compilerOptions']['module'] = self.module

        content['files'] = self.files

        self.write_json_to_tsconfigfile(content)

        if self.finished_callback:
            self.finished_callback()

        sublime.active_window().open_file(self.tspath)
        sublime.message_dialog("For more compilerOptions, see tsc --help.\n\n To configure ArcticTypescript, see README.md")

        self._cleanup()


    def _cleanup(self):
        Debug('project+', 'Enable ArcticTypescript again')
        set_plugin_temporarily_enabled()


    def write_json_to_tsconfigfile(self, content_obj):
        """ Write the python object content_obj into tsconfig.json """
        content_str = json.dumps(content_obj, sort_keys=True, indent=4)
        file_ref = open(self.tspath, "w")
        file_ref.write(content_str);
        file_ref.close() class ErrorsHighlighter(object):

    # Constants
    underline = sublime.DRAW_SQUIGGLY_UNDERLINE | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.DRAW_EMPTY_AS_OVERWRITE


    def __init__(self, project):
        self.project = project
        self._icon_paths()


    def _icon_paths(self):
        """ defines paths for icons """
        if os.name == 'nt':
            self.error_icon = ".."+os.path.join(package_path.split('Packages')[1], 'icons', 'bright-illegal')
            self.warning_icon = ".."+os.path.join(package_path.split('Packages')[1], 'icons', 'bright-warning')
        else:
            self.error_icon = "Packages"+os.path.join(package_path.split('Packages')[1], 'icons', 'bright-illegal.png')
            self.warning_icon = "Packages"+os.path.join(package_path.split('Packages')[1], 'icons', 'bright-warning.png')


    @max_calls(name='Errors.highlight')
    def highlight_all_open_files(self):
        """ update hightlights (red underline) in all files, using the errors in project """

        self.errors = {}

        # iterate through all open views, to remove all remaining outdated underlinings
        for window in sublime.windows():
            for view in window.views():
                if is_ts(view):
                    error_regions, warning_regions, error_texts = \
                        self.project.errors.tssjs_to_highlighter(view)

                    self.errors[fn2k(view.file_name())] = error_texts

                    # apply regions, even if empty (that will remove every highlight in that file)
                    view.add_regions('typescript-error' , error_regions , 'invalid' , self.error_icon, self.underline)
                    view.add_regions('typescript-warnings' , warning_regions , 'invalid' , self.warning_icon, self.underline)


    previously_error_under_cursor = False

    @max_calls(name='ErrorHighlighter.display_error_in_status_if_cursor')
    def display_error_in_status_if_cursor(self, view):
        """
            Displays the error message in the sublime status
            line if the cursor is above an error (in source code).
            For the click on the error list, see T3SVIEWS.ERROR.on_click()
        """
        try:
            error = self._get_error_at(view.sel()[0].begin(), view.file_name())
        except:
            # no selection in view
            return
        if error is not None:
            sublime.status_message(error)
            self.previously_error_under_cursor = True
        elif self.previously_error_under_cursor: # only clear once
            sublime.status_message('')
            self.previously_error_under_cursor = False


    def _get_error_at(self, pos, filename):
        """ Returns the error at pos in filename """
        if fn2k(filename) in self.errors:
            for (start, end), error_msg in self.errors[fn2k(filename)].items():
                if pos >= start and pos <= end:
                    return error_msg

        return None class Errors(object):
    def __init__(self, project):
        self.project = project
        self.lasterrors = []
        self.message = "" # contains exception message if an error occured
        pass

    @max_calls()
    def start_recalculation(self):
        self.project.tsserver.errors(self.on_results)

    @max_calls()
    def on_results(self, errors):
        """ this is the callback from the async process if new errors have been calculated """
        try:
            self.failure = ""
            self.lasterrors = json.loads(errors)
            if isinstance(self.lasterrors, str):
                self.lasterrors = [ self._provide_better_explanations_for_tss_errors(self.lasterrors)]
                # self.lasterrors is a list or None
            if not isinstance(self.lasterrors, list):
                raise Warning("tss.js internal error: %s" % errors)
            self._provide_better_explanations_for_some_errors()
            self._tssjs_to_errorview()

        except BaseException as e: # Also catches JSON exceptions
            self.lasterrors = []
            self.failure = "%s" % e
            Debug('error', 'Internal ArcticTypescript error during show_errors: %s (Exception Message: %s)' % (errors, e))

        self.project.highlighter.highlight_all_open_files()
        sublime.active_window().run_command('typescript_error_panel_set_text',
                                            { "project_id": self.project.id } )


    def _provide_better_explanations_for_tss_errors(self, errorstr):
        """ Provides better explatations for hard tss errors,
            which cause typescript-tools to return a string
            instead of an error list []
            Returns an error dict like typescript-tools would have
                done for usual errors."""

        # File <match1> is not registered in tsconfig.json
        match1 = re.match("(.*)(Could not find file: ')(.*)'.*", errorstr)
        if match1:
            missing_file = match1.groups()[2]
            better_error = "\"Maybe this file is missing in your tsconfig.json['files'] list:\n    %s \"" % missing_file
            Debug('notify', better_error)
            return {
                "file": missing_file,
                "start": {"line": 1, "character": 1},
                "end": {"line": 1, "character": 1},
                "text": better_error,
                "code": 0000,
                "phase":"Semantics",
                "category":"TSSError"
            }

        return None


    def _provide_better_explanations_for_some_errors(self):

        # do not use : inside of explanation
        additions = {1148 : '// What to do? Either use /// <reference path="x.ts" /> instead of import x = require("x");, or switch to external modules (Add the compilerOptions module="amd"|"commonjs" and out="some/builddir" to tsconfig.json). Restart ArcticTypescript or sublime.'}

        for e in self.lasterrors:
            for code, add in additions.items():
                if e['code'] == code:
                    e['text'] = e['text'] + ' ' + add


    def _tssjs_to_errorview(self):
        """
            Takes the de-jsoned output of the tss.js error command and creates the content for the error view.
            It also creates a relation between each line in the error view and the file and position of the error.

            Sets    self.text
                    self.line_to_file[line] = filename
                    self.line_to_pos[line] = ((l,c),(l,c))

        """
        line_to_file = {}
        line_to_pos = {}
        text = []

        previous_file = ''
        line = 0

        for e in self.lasterrors:
            filename = e['file'].split('/')[-1]
            if previous_file != filename:
                text.append("\n\nOn File : %s \n" % filename)
                line += 3
                previous_file = filename

            text.append("\n%i >" % e['start']['line'])
            #text.append(re.sub(r'^.*?:\s*', '', e['text'].replace('\r','')))
            text.extend(self._flatten_errortext(e['text']))
            line += 1

            a = (e['start']['line']-1, e['start']['character']-1)
            b = (e['end']['line']-1, e['end']['character']-1)
            line_to_pos[line] = (a,b)
            line_to_file[line] = e['file']


        if len(self.lasterrors) == 0:
            text.append("\n\nno errors")

        text.append('\n')

        self.line_to_file = line_to_file
        self.line_to_pos = line_to_pos
        self.text = ''.join(text)


    def _flatten_errortext(self, text_or_dict):
        text = []
        if isinstance(text_or_dict, str):
            text.append(text_or_dict.replace('\r',''))
        elif isinstance(text_or_dict, dict) and 'messageText' in text_or_dict:
            text.append(text_or_dict['messageText'])
            if 'next' in text_or_dict and 'messageText' in text_or_dict['next']:
                text.append('\n   >')
                text.append(text_or_dict['next']['messageText'])
        else:
            Debug('error', 'typescript-tools error["text"] has unexpected type %s.' % type(text_or_dict))
        return text


    def tssjs_to_highlighter(self, view):
        """
            Creates a list of error and warning regions for all errors.

            Returns error_regions, warning_regions, error_texts
        """
        error_texts = {} # key: textpoint start and end, value: description
        error_regions = []
        warning_regions = []

        for e in self.lasterrors:
            if fn2k(e['file']) == fn2k(view.file_name()):

                a = view.text_point(e['start']['line']-1, e['start']['character']-1)
                b = view.text_point(e['end']['line']-1, e['end']['character']-1)

                error_texts[(a,b)] = ''.join(self._flatten_errortext(e['text']))

                if e['category'] == 'Error':
                    error_regions.append(sublime.Region(a,b))
                else:
                    warning_regions.append(sublime.Region(a,b))

        return error_regions, warning_regions, error_texts


    @max_calls()
    def on_close_typescript_project(self, root):
        """ Will be called when a typescript project is closed (eg all files closed).
            But there may be other open typescript projects.
            -> for future use :) """
        pass class Completion(object):

    completion_chars = ['.']#['.',':']
    completion_list = []
    interface = False
    enabled_for_col_reference = ''     # 'dot' or 'cursor'
    enabled_for = {'line': 0, 'col': 0, 'viewid': -1}

    def __init__(self, project):
        self.project = project

    # PREPARE LISTE
    def prepare_list(self, tss_result_json):
        del self.completion_list[:]

        try:
            entries = json.loads(tss_result_json)
            entries = entries['entries']
        except:
            if tss_result_json.strip() == 'null':
                sublime.status_message('ArcticTypescript: no completions available')
            else:
                Debug('error', 'Completion request failed: %s' % tss_result_json)
            return 0

        for entry in entries:
            if self.interface and entry['kind'] != 'primitive type' and entry['kind'] != 'interface' : continue
            key = self._get_list_key(entry)
            value = self._get_list_value(entry)
            self.completion_list.append((key,value))

        self.completion_list.sort()
        return len(self.completion_list)


    # GET LISTE
    def get_list(self):
        return self.completion_list


    # TYPESCRIPT COMPLETION ?
    def trigger(self, view, force_enable=False):

        cursor_pos = view.sel()[0].begin() # cursor pos as int
        (cursor_line, cursor_col) = view.rowcol(cursor_pos)

        char = view.substr(cursor_pos-1)


        enabled = force_enable or (char in self.completion_chars)
        self.interface = char is ':'

        if enabled:
            Debug('autocomplete', "Autocompletion for line %i , %i, forced=%s" % (cursor_line+1, cursor_col+1, force_enable) )

            is_member = is_member_completion( get_content_of_line_at(view, cursor_pos) )
            is_member_str = str( is_member ).lower() # 'true' or 'false'

            # do execute tss.js complete for cursor positon after last dot, so we use
            # sublimes fuzzy mechanism to reduce the list, not the mechanism
            # (1:1 matching of the typed chars) tss.js would use

            autocomplete_col = 0

            if is_member:
                autocomplete_col = get_col_after_last_dot( get_content_of_line_at(view, cursor_pos) )
                self.enabled_for_col_reference = 'dot'
                Debug('autocomplete', " -> use dot as referene")
                if autocomplete_col != cursor_col:
                    Debug('autocomplete', " -> dot is on col %i, use this col instead of cursor position %i" % (autocomplete_col+1, cursor_col+1))
            else:
                Debug('autocomplete', " -> use cursor position %i for autocomplete" % (cursor_col+1))
                self.enabled_for_col_reference = 'cursor'
                autocomplete_col = cursor_col

            self.enabled_for['viewid'] = view.id()
            self.enabled_for['line'] = cursor_line
            self.enabled_for['col'] = autocomplete_col

            Debug('autocomplete', " -> push current file contents as update to tss.js")
            self.project.tsserver.update(view)

            def async_react_completions_available(tss_result_json, filename, line, col, is_member_str):
                Debug('autocomplete', "Autocompletion results available for line %i , %i" % (line+1, col+1) )

                i = self.prepare_list(tss_result_json)
                Debug('autocomplete', " -> prepare List (%i items)" % i )

                # view or line changed
                current_view = sublime.active_window().active_view()
                current_cursor_pos = current_view.sel()[0].begin()
                (current_cursor_line, current_cursor_col) = current_view.rowcol(current_cursor_pos)

                Debug('autocomplete', " => CL: {0}, L: {1}, efcr: {2}, ccc: {3}, col: {4}, ismstr: {5}"
                        .format(current_cursor_line,
                                line,
                                self.enabled_for_col_reference,
                                current_cursor_col,
                                col,
                                is_member_str))

                if current_view.id() != self.enabled_for['viewid'] or filename != current_view.file_name():
                    Debug('autocomplete', " -> file changed since activation of autocomplete or out-dated request -> cancel")
                    return
                if current_cursor_line != self.enabled_for['line'] or current_cursor_line != line:
                    Debug('autocomplete', " -> line changed since start of autocomplete (%i to %i) or out-dated request -> cancel" % (current_cursor_line, line) )
                    return
                if self.enabled_for_col_reference == 'cursor' \
                    and (current_cursor_col != self.enabled_for['col'] or current_cursor_col != col):
                    Debug('autocomplete', " -> cursor changed position (current col: %i ; at command issue: %i) or out-dated request -> cancel" % (current_cursor_col, col) )
                    return

                if is_member_str == 'true':
                    current_dot_col = get_col_after_last_dot( get_content_of_line_at(view, current_cursor_pos) )
                    if self.enabled_for_col_reference == 'dot' \
                        and (current_dot_col != self.enabled_for['col'] or current_dot_col != col):
                        Debug('autocomplete', " -> it's not the same dot reference anymore (current dot pos: %i ; at command issue: %i) or out-dated request -> cancel" % (current_dot_col, col) )
                        return


                Debug('autocomplete', " -> command to sublime to now show autocomplete box with prepared list" )

                # this will trigger Listener.on_query_completions
                # but on_query_completions needs to have the completion list
                # already available
                current_view.run_command('auto_complete',{
                    'disable_auto_insert': True,
                    'api_completions_only': True,
                    'next_completion_if_showing': True
                })
                Debug('autocomplete', " -> (sublime cmd finished)" )

            self.project.tsserver.complete(view.file_name(), cursor_line, autocomplete_col, is_member_str, async_react_completions_available)




    # ENTRY KEY
    def _get_list_key(self,entry):

        #{'name': 'SVGLineElement',
        # 'kind': 'var',
        # 'kindModifiers': 'declare',
        # 'type': 'interface SVGLineElement\nvar SVGLineElement: {\n    new (): SVGLineElement;\n    prototype: SVGLineElement;\n}',
        # 'docComment': ''}


        kindModifiers = get_prefix(entry['kindModifiers'])
        kind = get_prefix(entry['kind'])
        type_ = entry['type'] if 'type' in entry else entry['name']
        type_ = type_.split('\n')[0]

        if kindModifiers == "" and kind == "":
            kind = get_prefix(type_.split(' ')[0])

        return kindModifiers+' '+kind+' '+str(entry['name'])+' '+str(type_)


    # ENTRY VALUE
    def _get_list_value(self,entry):

        # {'kind': 'method', 'docComment': '', 'kindModifiers': 'declare', 'type': '(method) MSNodeExtensions.swapNode(otherNode: Node): Node', 'name': 'swapNode'}
        # {'kind': 'property', 'docComment': '', 'kindModifiers': 'declare', 'type': '(property) GlobalEventHandlers.onpointerup: (ev: PointerEvent) => any', 'name': 'onpointerup'}
        # {'kind': 'property', 'docComment': '', 'kindModifiers': 'declare', 'type': '(property) Node.DOCUMENT_TYPE_NODE: number', 'name': 'DOCUMENT_TYPE_NODE'}
        # {'kind': 'method', 'docComment': 'Allows updating the print settings for the page.', 'kindModifiers': 'declare', 'type': '(method) Document.updateSettings(): void', 'name': 'updateSettings'}
        # {'kindModifiers': 'declare', 'docComment': '', 'kind': 'function', 'name': 'setTimeout', 'type': '(function) setTimeout(handler: any, timeout?: any, ...args: any[]): number'}

        type_ = entry['type'] if 'type' in entry else entry['name']

        # remove (<kind>)
        kind_part = "(%s)" % entry['kind']
        if type_.startswith(kind_part):
            type_ = type_[len(kind_part):]

        # catches the inner argumetns of a function call
        match = re.match('.*\((.*)\):', str(type_))
        result = []

        if match:
            variables = self._parse_args(match.group(1))
            count = 1
            for variable in variables:
                splits = variable.split(':')
                if len(splits) > 1:
                    data = '"'+variable+'"'
                    data = '${'+str(count)+':'+data+'}'
                    result.append(data)
                    count = count+1
                else:
                    result.append('')

            return re.escape(entry['name'])+'('+','.join(result)+')'
        else:
            return re.escape(entry['name'])

    # PARSE FUNCTION ARGUMENTS
    def _parse_args(self, group):
        # group = "otherNode: Node, param2: string"
        args = []
        arg = ""
        callback = False

        for char in group:
            if char == '(' or char == '<':
                arg += char
                callback = True
            elif char == ')' or char == '>':
                arg += char
                callback = False
            elif char == ',':
                if callback == False:
                    args.append(arg)
                    arg = ""
                else:
                    arg+=char
            else:
                arg+=char

        args.append(arg)
        return args class Processes(object):
    """
        Keeps two tss.js Processes and adapters for each project root.
        Process SLOW is for slow commands like tss>errors which can last more than 5s easily.
        Process FAST is for fast reacting commands eg. for autocompletion or type.
    """

    def __init__(self, project):
        """ starts the two processes """
        self.project = project
        self.slow = None
        self.fast = None
        self.start_tss_processes()


    def is_initialized(self):
        """ Returns True if both processes (SLOW and FAST) have been started. """
        return self.slow.started and self.fast.started

    def get_initialisation_error_message(self):
        """ Returns Errormessage if initializing has failed, otherwise false"""
        if self.slow.error:
            return self.slow.error
        if self.fast.error:
            return self.fast.error
        return False

    def start_tss_processes(self):
        """
            Start tss.js (2 times).
            Displays message to user while starting and calls project.on_services_started() afterwards
        """
        Debug('notify', 'starting tsserver ' + self.project.tsconfigfile)
        self.slow = TssJsStarterThread(self.project)
        self.slow.start()

        self.fast = TssJsStarterThread(self.project)
        self.fast.start()

        self._wait_for_finish_and_notify_user()

    def kill(self):
        """ Trigger killing of adapter, tss.js and queue. """

        Debug('tss+', "Killing tss.js process and adapter thread (for slow and fast lane) (Closing project %s)"
                 % self.project.tsconfigfile)
        self.slow.kill_tssjs_queue_and_adapter()
        self.fast.kill_tssjs_queue_and_adapter()



    def _wait_for_finish_and_notify_user(self, i=1, dir=-1):
        """ Displays animated Message as long as TSS is initing. Is recoursive function. """

        if self.get_initialisation_error_message():
            sublime.error_message('Typescript initializion error for : %s\n >>> %s\n ArcticTypescript is disabled until you restart sublime.'
                         % (self.project.tsconfigfile, self.get_initialisation_error_message()))
            set_plugin_temporarily_disabled()
            return

        if not self.is_initialized():
            (i, dir) = self._display_animated_init_message(i, dir)
            # recursive:
            sublime.set_timeout(lambda: self._wait_for_finish_and_notify_user(i, dir), 100)
        else:
            # starting finished ->
            MESSAGE.show('Typescript project intialized for file : %s' % self.project.tsconfigfile, True, with_panel=False)
            self.project.on_services_started()

    def _display_animated_init_message(self, i, dir):
        if i in [1, 8]:
            dir *= -1
        i += dir
        anim_message = ' Typescript project is initializing [%s]' % '='.rjust(i).ljust(8)

        MESSAGE.repeat(anim_message, with_panel=False)

        return (i, dir) class TypescriptToolsWrapper(object):
    """ This Class translates the available commands to the corresponding string
        command for the typescript-tools from clausreinke   """


    def __init__(self, project):
        self.project = project
        self.added_files = {} # added_files[filename] = hash
        self.executed_with_most_recent_file_contents = []
        self.is_killing = False


    # RELOAD PROCESS
    @max_calls()
    def reload(self, callback=None):
        AsyncCommand('reload', self.project) \
            .set_id('reload') \
            .set_result_callback(lambda r: callback is None or callback()) \
            .append_to_both_queues()
        self.project.errors.start_recalculation()


    # GET INDEXED FILES
    @max_calls()
    def get_tss_indexed_files(self, callback):
        AsyncCommand('files', self.project) \
            .do_json_decode_tss_answer() \
            .set_result_callback(callback) \
            .append_to_fast_queue()

    # DUMP FILE (untested)
    @max_calls()
    def dump(self, filename, output, callback):
        dump_command = 'dump {0} {1}'.format( output, fn2l(realfn(filename)) )
        AsyncCommand(dump_command, self.project) \
            .set_result_callback(callback) \
            .append_to_fast_queue()

    # Evaluate Javascript (refer to utils/debug.py)
    @max_calls()
    def eva(self, js_cmd):
        def cb(strres):
            import json
            js = json.loads(strres)
            # Sometimes this is to much output. Activate and adapt this if you are evaluating '_this'
            #del js['fileNameToScript']['c:/Users/danie_000/AppData/Roaming/Sublime Text 3/Packages/ArcticTypescript/bin/node_modules/typescript/bin/lib.d.ts']
            #del js['snapshots']['c:/Users/danie_000/AppData/Roaming/Sublime Text 3/Packages/ArcticTypescript/bin/node_modules/typescript/bin/lib.d.ts']
            print(json.dumps(js, indent=3))
            self.last_eva = js
        eva = 'eva {0}'.format(js_cmd)
        AsyncCommand(eva, self.project) \
            .set_result_callback(cb) \
            .append_to_slow_queue()

    # TYPE
    @max_calls()
    def type(self, filename, line, col, callback):
        """ callback({ tss type answer }, filename=, line=, col=) """

        type_command = 'type {0} {1} {2}'.format( str(line+1), str(col+1), fn2l(realfn(filename)) )

        AsyncCommand(type_command, self.project) \
            .set_id("type_command") \
            .set_callback_kwargs(filename=filename, line=line, col=col) \
            .do_json_decode_tss_answer() \
            .set_result_callback(callback) \
            .append_to_fast_queue()


    # DEFINITION
    @max_calls()
    def definition(self, filename, line, col, callback):
        """ callback({ tss type answer }, filename=, line=, col=) """

        definition_command = 'definition {0} {1} {2}'.format( str(line+1), str(col+1), fn2l(realfn(filename)) )

        AsyncCommand(definition_command, self.project) \
            .set_id("definition_command") \
            .set_callback_kwargs(filename=filename, line=line, col=col) \
            .do_json_decode_tss_answer() \
            .set_result_callback(callback) \
            .append_to_fast_queue()


    # REFERENCES
    @max_calls()
    def references(self, filename, line, col, callback):
        """ callback({ tss type answer }, filename=, line=, col=) """

        references_command = 'references {0} {1} {2}'.format( str(line+1), str(col+1), fn2l(realfn(filename)) )

        AsyncCommand(references_command, self.project) \
            .set_id("references_command") \
            .set_callback_kwargs(filename=filename, line=line, col=col) \
            .do_json_decode_tss_answer() \
            .set_result_callback(callback) \
            .append_to_fast_queue()

    # STRUCTURE
    @max_calls()
    def structure(self, filename, sender_view_id, callback):
        """ callback({ tss type answer }, filename=, sender_view_id=) """
        return # structure command has been dropped

        structure_command = 'structure {0}'.format(fn2l(realfn(filename)))

        AsyncCommand(structure_command, self.project) \
            .set_id("structure_command for view %i" % sender_view_id) \
            .set_callback_kwargs(filename=filename, sender_view_id=sender_view_id) \
            .do_json_decode_tss_answer() \
            .set_result_callback(callback) \
            .append_to_fast_queue()


    # ASK FOR COMPLETIONS
    @max_calls()
    def complete(self, filename, line, col, is_member_str, callback):
        """ callback("tss type answer as string") """

        completions_command = 'completions {0} {1} {2} {3}'.format(is_member_str, str(line+1), str(col+1), fn2l(realfn(filename)))

        Debug('autocomplete', "Send async completion command for line %i , %i" % (line+1, col+1))

        AsyncCommand(completions_command, self.project) \
            .set_id("completions_command") \
            .procrastinate() \
            .set_result_callback(callback) \
            .set_callback_kwargs(filename=filename, line=line, col=col, is_member_str=is_member_str) \
            .append_to_fast_queue()


    # UPDATE FILE
    @max_calls()
    def update(self, view):
        """ updates the view.buffer's content to the buffer in tss.js """

        # only update if the file contents have changed since last update call on this file
        filename, lines, content = get_file_infos(view)
        if self.need_update(filename, content):
            update_command = 'update nocheck {0} {1}\n{2}'.format(str(lines+1), fn2l(realfn(filename)), content)

            AsyncCommand(update_command, self.project) \
                .set_id('update %s' % filename) \
                .append_to_both_queues()

            self.on_file_contents_have_changed()


    # ADD FILE
    @max_calls()
    def add(self, filename, lines, content):

        update_command = 'update nocheck {0} {1}\n{2}'.format(str(lines+1), fn2l(realfn(filename)), content)

        AsyncCommand(update_command, self.project) \
            .set_id('add %s' % filename) \
            .append_to_both_queues()

        self.need_update(filename, content) # save current state
        self.on_file_contents_have_changed()


    @max_calls()
    def need_update(self, filename, unsaved_content):
        """ Returns True if <unsaved_content> has changed since last call to need_update(). """
        newhash = make_hash(unsaved_content)
        oldhash = self.added_files[filename] if filename in self.added_files else "nohash"
        if newhash == oldhash:
            Debug('tss+', "NO UPDATE needed for file : %s" % filename)
            return False
        else:
            Debug('tss+', "UPDATE needed for file %s : %s" % (newhash, filename) )
            self.added_files[filename] = newhash
            return True


    def on_file_contents_have_changed(self):
        """
            Every command that wants to only be executed when file changes have been made
            can use self.executed_with_most_recent_file_contents to remember a previous execution.
            After any change, this array will be cleared
        """
        self.executed_with_most_recent_file_contents = []

    def files_changed_after_last_call(self, cmd_hint):
        """
            Returns True if there have been any file changes after last call
            to this function.
        """
        if cmd_hint in self.executed_with_most_recent_file_contents:
            return False
        self.executed_with_most_recent_file_contents.append(cmd_hint)
        return True


    # ERRORS
    @max_calls()
    def errors(self, callback=None):
        """ callback format: callback(result) """

        # TODO: this may prevent initial error display or if user triggers manually
        if not self.files_changed_after_last_call('errors'):
            return

        T3SVIEWS.ERROR.on_calculation_initiated()

        AsyncCommand('showErrors', self.project) \
            .set_id('showErrors') \
            .procrastinate() \
            .activate_debounce() \
            .set_result_callback(lambda errors: [callback(errors), T3SVIEWS.ERROR.on_calculation_finished()] ) \
            .set_executing_callback(lambda: T3SVIEWS.ERROR.on_calculation_executing()) \
            .set_replaced_callback(lambda by: T3SVIEWS.ERROR.on_calculation_replaced()) \
            .append_to_slow_queue()


    # KILL PROCESS (if no more files in editor)
    @max_calls()
    def kill(self, finished_callback):

        if not self.project.is_initialized() or self.is_killing:
            return

        self.is_killing = True


        def on_quit(msg=""):
            if hasattr(self, 'finished_callback_called'):
                return
            self.finished_callback_called = True
            finished_callback()

        # send quit and kill process afterwards
        AsyncCommand('quit', self.project) \
            .set_id('quit') \
            .set_result_callback(on_quit) \
            .append_to_both_queues()

        sublime.set_timeout(on_quit, 5000)

        #TODO: this function is old and not updated
        # - project class should kill processes, but before executing this,
        #   so a Quit command can be send to tss.js
        # - maybe: get_tss_indexed_files can be used to prevent project from
        #          closing, if there are opened views with belonging .ts files,
        #          but if these views are not in projcet.views.
        #          But in this case it would be better to add those views to
        #          projcet.views using tsserver.get_tss_indexed_files


        # removed class Compiler(Thread):

    def __init__(self, project, window_for_panel, triggered_for_file):
        self.triggered_for_file = triggered_for_file
        self.window_for_panel = window_for_panel
        self.project = project
        self.p = None
        self.cancel_build = False
        Thread.__init__(self)

    def run(self):
        if self.p is not None:
            Debug('error', "Only use Compiler Object once!")

        self.cwd = os.path.abspath(self.project.tsconfigdir)
        node_path, compile_cmd = self._make_commandline()
        self.kwargs = get_kwargs()

        self._prepare_pre_and_post_commands()

        PANEL.clear(self.window_for_panel)

        if self.post_pre_authorized:
            for pre_cmd in self.pre_processing_commands:
                pre_cmd = expand_variables(str(pre_cmd), self.project, use_cache=True)
                self._run_command(pre_cmd, shell=True)

        self._run_command(compile_cmd)

        self.project.show_compiled_file()

        if self.post_pre_authorized:
            for post_cmd in self.post_processing_commands:
                post_cmd = expand_variables(str(post_cmd), self.project, use_cache=True)
                self._run_command(post_cmd, shell=True)

        self._show_output("<<< Finished")

    def _prepare_pre_and_post_commands(self):
        # PRE-Processing-commands
        self.pre_processing_commands = self.project.get_setting("pre_processing_commands", use_cache=True)
        self.post_processing_commands = self.project.get_setting("post_processing_commands", use_cache=True)

        if isinstance(self.pre_processing_commands, str):
            self.pre_processing_commands = [self.pre_processing_commands]
        if isinstance(self.post_processing_commands, str):
            self.post_processing_commands = [self.post_processing_commands]

        self._authorize_shell_execution()


    def _authorize_shell_execution(self):
        total_commands = self.pre_processing_commands + self.post_processing_commands

        if self.cancel_build:
            self.post_pre_authorized = False
            return

        if self.project.authorized_commands == total_commands:
            self.post_pre_authorized = True
            Debug('build', 'Post/Pre allowed before')
            return

        if self.project.forbidden_commands == total_commands:
            self.post_pre_authorized = False
            Debug('build', 'Post/Pre forbidden before')
            return

        # commands have changed, so ask again

        messages = [
            ['Security alert: Allow execution of the following commands IN A SHELL?'] + total_commands,
            ['> YES'],
            ['> NO, only build']
        ]

        max_lines = 0
        for m in messages:
            max_lines = max(max_lines, len(m))
        for m in messages:
            while len(m) < max_lines:
                m.append('')

        self.answer_pending = Queue()

        def on_done(i):
            Debug('build', 'User answered: %i' % i)
            if i == 1: # YES
                self.project.authorized_commands = total_commands
                self.project.forbidden_commands = []
            elif i == 2: # NO
                self.project.authorized_commands = []
                self.project.forbidden_commands = total_commands
            else:
                Debug('build', '> Cancel Build')
                self.cancel_build = True
            self.answer_pending.put("")

        Debug('build', 'Ask for authoritation of post/pre commands')
        sublime.active_window().show_quick_panel(messages, on_done)
        # Wait until user has choosen
        self.answer_pending.get() # blocking until answered
        Debug('build', 'Continue with Compiler Thread')

        self._authorize_shell_execution() # check again, this time it should not ask


    def _run_command(self, cmd, shell=False):
        if self.cancel_build:
            return
        Debug('build', 'BUILD EXECUTE: %s' % str(cmd))
        if isinstance(cmd, str):
            self._show_output(">>> %s\n" % cmd.replace("\n", "\\n"))
        else:
            self._show_output(">>> %s\n" % cmd)

        self.p = Popen(cmd, stdin=PIPE, stdout=PIPE, cwd=self.cwd, shell=shell, **self.kwargs)
        self._run_forward_compiler_output()


    def _make_commandline(self):
        """ generates the commandline to start either tss.js or tsserver.js,
            depending on the project settings """
        node_path = default_node_path(self.project.get_setting('node_path'))
        tsc_path = default_tsc_path(self.project.get_setting('tsc_path'),
                                    self.project)

        cmdline = [node_path, tsc_path, "--project", "."]

        return node_path, cmdline


    def _run_forward_compiler_output(self):
        for line in iter(self.p.stdout.readline, b''):
                Debug('build+', 'BUILD RESULTS: %s' % line)
                try:
                    line = line.decode('UTF-8')
                    line = line.replace('\r', '')
                except ValueError as v:
                    break
                self._show_output(line)
        Debug('build+', 'BUILD RESULTS: all lined read')
        self.p.stdout.close()
        self._show_output("\n")


    def kill(self):
        self.cancel_build = True
        if self.p:
            try:
                Debug('build+', 'BUILD: Kill process!')
                self.p.terminate()
                self.p.kill()
                self._show_output("<<< Process has been terminated. Waiting for Compiler Thread to finish\n")
                self.p.communicate() # release readline() block
                Debug('build+', 'BUILD: process killed')
            except Exception as e:
                Debug('error', "Failure while killing compiler thread: %s" % e)


    def _show_output(self, line):
        PANEL.show(self.window_for_panel)
        PANEL.update(line)
        #window.run_command('typescript_build_view',
        #                   {"filename":line['output'].replace('[end]','\n')}) class Refactor():

    def __init__(self, edit, project, refs, old_name, new_name):
        Thread.__init__(self)
        self.project = project
        self.refs = {r['ref'] for r in refs}
        self.old_name = old_name
        self.new_name = new_name
        self.edit_token = edit_token


    def start(self):
        self.run()

    def run(self):
        self.sort_by_file()
        Debug('refactor', self.refs_by_file)

        for (file_, refs_in) in self.refs_by_file.values():
            self.refactor_file(file_, refs_in)


    def sort_by_file(self):
        """ sort references by file """

        self.refs_by_file = {} # key is the fn2k'ed filename

        for ref in self.refs:
            file_ = fn2k(ref['file'])
            if file_ not in self.refs_by_file:
                self.refs_by_file[file_] = []

            self.refs_by_file[file_].append(ref)


    def refactor_file(self, file_name_keyed, refs_in):



        Debug('refactor', 'got here! :D')
        return T3SVIEWS = T3SViews() MESSAGE = Message() class TsconfigLinter(object):
    """ Executes a linting on view.
        View has to be a tsconfig.json file.
        Marks errors and saves an error map in views settings.
        FatalErrors: IO Errors -> raise CancelCommands, the unexpected
        HardErrors: Syntax Errors, Wrong Types
        SoftErrors: Spellings, non existent files, unknown keys.
        Inserts a default structure if the file is empty. """


    def __init__(self, view=None, file_name=None):
        """ Starts linting.
            Raises CancelCommand if a fatal error occures. """

        # Initializing
        self.linted = False
        self.view = view
        self.file_name = file_name


        self.error_regions = []
        self.tsconfig = None
        self.harderrors = [] # would prevent from expanding filesglob
        self.softerrors = [] # would allow expandglob.js to work
        self.numerrors = 0;
        self.msg, self.line, self.col, self.char = None, None, None, None

        # reading
        self._read_file_and_set_paths()
        self._check_empty_and_insert_default()
        # main syntax checks
        if not self._check_jsonsyntax():
            if self._check_root_dicts():
                # syntax is fine -> linting
                self._check_key_spellings()
                self._check_unknown_keys()
                self._validate_values()
                self._check_files_are_strings()
                self._check_files_are_ts_files()
                self._check_files_exist()

        # display errors
        self._add_regions()
        self._store_error_locations_in_views_settings()
        self.linted = True


    # ######################################################################
    # ###############                   HELPERS                   ##########
    # ######################################################################


    def _hard_error(self, msg, pos):
        """ Display and cound hard error """
        self.harderrors.append((pos, msg))
        self.numerrors += 1
        Debug('tsconfig.json', msg)


    def _soft_error(self, msg, pos):
        """ Display and count soft error """
        self.softerrors.append((pos, msg))
        self.numerrors += 1
        # Debug('tsconfig.json', msg)

    def _store_error_locations_in_views_settings(self):
        """ Stores the errors in view.settings()
            Format: [<((a,b), msg)>] """
        if self.view:
            error_locations = []
            error_locations.extend(self.harderrors)
            error_locations.extend(self.softerrors)
            self.view.settings().set('tsconfig-lints', error_locations)


    # ######################################################################
    # ###############            DISPLAY                          ##########
    # ######################################################################


    def _add_regions(self):
        """ Display all found Errors """
        if self.view:
            self.view.add_regions('tsconfig-error', self.error_regions,
                                  'invalid', 'dot', sublime.DRAW_NO_FILL)


    # ######################################################################
    # ###############                   READ CONTENT              ##########
    # ######################################################################


    def _read_file_and_set_paths(self):
        """ Reads content from view into self.content and self.len """
        if self.view:
            self.file_name = self.view.file_name()
            self.content = get_content(self.view)
            self.len = len(self.content)
        elif self.file_name:
            self.content = read_file(self.file_name)
            self.len = len(self.content)
        else:
            Debug('error', "Tsconfiglinter: either view or filename is required");

        self.tsconfigdir = os.path.abspath(os.path.dirname(self.file_name))

        if self.content is None:
            self.numerrors += 1
            Debug('tsconfig', 'Could not read content from view.')
            raise CancelCommand()


    def _check_empty_and_insert_default(self):
        """ Checks if content is empty and inserts default tsonfig.json if so.
            Raises CancelCommand() if empty before. """

        if self.content == "":
            if not is_tsglobexpansion_disabled() and self.view:
                Debug('tsconfig', 'put default base structure to tsconfig.json')
                self.view.run_command('append', {'characters':
                        json.dumps(empty_tsconfig, indent=4)})
            raise CancelCommand()


    # ######################################################################
    # ###############                   JSON                      ##########
    # ######################################################################


    def _check_jsonsyntax(self):
        """ Parse self.content into self.tsconfig.
            Raise CancelCommand on Fatal Error.
            Make HardErrors on syntax errors.
            Return True if parsing has succeeded, even if there were errors. """

        try:
            # DECODE
            self.tsconfig = json.loads(self.content)
        except ValueError as e:
            Debug('tsconfig', 'json error %s %s' % (type(e), e))

            # Parse -> self.msg,line,col,char
            self._parse_jsonerror(e.args[0])

            # Parsing of exception failed
            if self.msg is None:
                Debug('tsconfig.json', 'json error %s' % e)
                self.numerrors += 1
                raise CancelCommand()

            # Display
            self._hard_error("%s (Line %i Column %i)"
                             % (self.msg, self.line, self.col),
                             self._lint_range(self.char))

            return True # Allow detailed linting

        except Exception as e:
            # The Unexpected
            Debug('tsconfig', 'unexpected json.loads() error %s %s'
                               % (type(e), e))
            self.numerrors += 1
            raise CancelCommand()

        return False


    def _parse_jsonerror(self, error):
        """ Parse the exception message:
            extract line, char, col, msg into self.* """

        try:
            self.msg, line, col, char = re.match(
                        '(.*): line ([\d]+) column ([\d]+) \(char ([\d]+)\)',
                        error).groups()
        except Exception:
            self.msg, self.line, self.col, self.char = None, None, None, None
            return

        # str -> int -> str should be the same

        self.line = int(line)
        self.col = int(col)
        self.char = int(char)

        if str(self.line) != line \
                or str(self.col) != col \
                or str(self.char) != char:
            self.msg, self.line, self.col, self.char = None, None, None, None


        # Some Possible JSON Errors:

        # <class 'ValueError'> Expecting property name enclosed in
        #                      double quotes: line 2 column 25 (char 26)
        # <class 'ValueError'> Expecting ':' delimiter: line 2
        #                      column 27 (char 28)
        # <class 'ValueError'> Expecting object: line 2 column 28 (char 29)


    # ######################################################################
    # ###############            LINT BASE STRUCTURE, KEYS        ##########
    # ######################################################################


    def _check_key_spellings(self):
        """ Check Spelling of main dicts: currently only caSe """

        self._expect_key_of_obj_to_be_y(self.tsconfig, "compilerOptions")
        self._expect_key_of_obj_to_be_y(self.tsconfig, "files")
        self._expect_key_of_obj_to_be_y(self.tsconfig, "filesGlob")
        self._expect_key_of_obj_to_be_y(self.tsconfig, "ArcticTypescript")


    def _expect_key_of_obj_to_be_y(self, obj, y):
        """ Checks for wrong spellings (caSe) of y in the keys of obj """

        key_found = False
        for k in obj.keys():
            if k.lower() == y.lower():
                key_found = True

                if k == y:
                    return

                self._soft_error("key '%s' is spelled wrong" % k,
                                self._lint_key(k))
        return key_found


    def _check_root_dicts(self):
        """ Check Type of root dicts == dict.
            Returns True if everything is ok. """

        # root
        if type(self.tsconfig) != dict:
            self._hard_error("root structure must be an object: { }",
                            self._lint_range(0, self.len - 1))
            return False

        # first layer
        valid = self._execute_validator(dict, self.tsconfig, 'compilerOptions'), \
                self._execute_validator(list, self.tsconfig, 'files'), \
                self._execute_validator(list, self.tsconfig, 'filesGlob'), \
                self._execute_validator(dict, self.tsconfig, 'ArcticTypescript')

        return all(valid)

    def _check_unknown_keys(self):
        """ Check for unknown keys in compilerOptions and ArcticTypescript """

        if "compilerOptions" in self.tsconfig:
            for option in self.tsconfig["compilerOptions"].keys():
                if option not in allowed_compileroptions:
                    self._soft_error("unknown key '%s' in compilerOptions"
                                     % option,
                                     self._lint_key(option))

        if "ArcticTypescript" in self.tsconfig:
            for option in self.tsconfig["ArcticTypescript"].keys():
                if option not in allowed_settings:
                    self._soft_error("unknown key '%s' in ArcticTypescript"
                                     % option,
                                     self._lint_key(option))


    # ######################################################################
    # ###############            LINT VALUES                      ##########
    # ######################################################################


    def _validate_values(self):
        """ Validate compilerOptions and ArcticTypescript using the validators
            from utils.options """

        if "compilerOptions" in self.tsconfig:
            for key, validator in compileroptions_validations.items():
                self._execute_validator(validator,
                                        self.tsconfig["compilerOptions"], key)

        if "ArcticTypescript" in self.tsconfig:
            for key, validator in settings_validations.items():
                self._execute_validator(validator,
                                        self.tsconfig["ArcticTypescript"], key)



    def _execute_validator(self, validator, dict_with_uservalue, key):
        """ Execute validation for dict_with_uservalue[key]
            and display error on validation failure.
            HardErrors on type mismatch,
            SoftErrors on regex or [<str>] mismatch.
            Returns True if key does not exists or if it is valid """

        # exists?
        if key not in dict_with_uservalue:
            return True
        uservalue = dict_with_uservalue[key]

        # type
        if validator == str:
            if type(uservalue) != str:
                self._hard_error("value of '%s' has to be a string" % key,
                                 self._lint_value_of_key(key))
                return False

        if validator == list:
            if type(uservalue) != list:
                self._hard_error("value of '%s' has to be a list" % key,
                                self._lint_value_of_key(key))
                return False

        if validator == bool:
            if type(uservalue) != bool:
                self._hard_error("value of '%s' has to be true or false" % key,
                                self._lint_value_of_key(key))
                return False

        if validator == dict:
            if type(uservalue) != dict:
                self._hard_error("value of '%s' has to be an object: { }" % key,
                                self._lint_value_of_key(key))
                return False

        if validator == int:
            if type(uservalue) != int:
                self._hard_error("value of '%s' has to be a integer" % key,
                                self._lint_value_of_key(key))
                return False

        if validator == float:
            if not (type(uservalue) == int or type(uservalue) == float):
                self._hard_error("value of '%s' has to be a number" % key,
                                self._lint_value_of_key(key))
                return False

        # regex
        if type(validator) == str:
            if type(uservalue) != str:
                self._hard_error("value of '%s' has to be a string" % key,
                                self._lint_value_of_key(key))
                return False
            else:
                m = re.match(validator, uservalue)
                if m is None:
                    self._soft_error("value of '%s' should match regex %s" %
                                                (key, validator),
                                    self._lint_value_of_key(key))
                    return False

        # list of string values
        if type(validator) == list:
            if type(uservalue) != str:
                self._hard_error("value of '%s' has to be a string" % key,
                                self._lint_value_of_key(key))
                return False
            else:
                if uservalue not in validator:
                    self._soft_error("value of '%s' should be one of %s" %
                                                (key, validator),
                                    self._lint_value_of_key(key))
                    return False
        return True


    def _check_files_are_strings(self):
        """
            Checks if all files are Strings. > SoftError
            Checks if all filesGlobs are Strings. > HardError (because then
            expandGlob will not work)
        """
        if "files" in self.tsconfig:
            for file_ in self.tsconfig["files"]:
                if type(file_) is not str:
                    self._soft_error("all files have to be strings",
                                    self._lint_value_of_key("files"))
                    break

        if "filesGlob" in self.tsconfig:
            for glob_ in self.tsconfig["filesGlob"]:
                if type(glob_) is not str:
                    self._hard_error("all filesGlobs have to be strings",
                                    self._lint_value_of_key("filesGlob"))
                    break


    # ######################################################################
    # ###############            LINT FILES (exist, extension)    ##########
    # ######################################################################


    def _check_files_exist(self):
        """ Checks if the files exist > SoftError.
            Does nothing if to much files are listed. """

        if "files" in self.tsconfig:
            if len(self.tsconfig["files"]) > 1000:
                return

            for file_ in self.tsconfig["files"]:
                file_path = os.path.join(self.tsconfigdir, file_)
                if not os.path.isfile(file_path):
                    self._soft_error("file %s does not exist or is not a file"
                                     % file_,
                                     self._lint_string_value(file_))

    def _check_files_are_ts_files(self):
        """ Checks for file extension .ts > SoftError """

        if "files" in self.tsconfig:
            for file_ in self.tsconfig["files"]:
                if not file_.endswith('.ts'):
                    self._soft_error("file %s is no .ts file" % file_,
                                    self._lint_string_value(file_))


    # ######################################################################
    # ###############              LINT HELPERS                   ##########
    # ######################################################################


    def _lint_key(self, keyname):
        """ Simple string search for keyname in parenteses, lint that key.
            Returns the position (sublime.Region) of that lint """

        quote_style_2 = self.content.find('"%s"' % keyname)
        if quote_style_2 != -1:
            return self._lint_range(quote_style_2 + 1, len(keyname))

        quote_style_1 = self.content.find("'%s'" % keyname)
        if quote_style_1 != -1:
            return self._lint_range(quote_style_1 + 1, len(keyname))

    def _lint_string_value(self, value):
        """ Simple string search for value in parenteses, lint that value.
            Returns the position (sublime.Region) of that lint """

        quote_style_2 = self.content.find('"%s"' % value)
        if quote_style_2 != -1:
            return self._lint_range(quote_style_2 + 1, len(value))

        quote_style_1 = self.content.find("'%s'" % keyname)
        if quote_style_1 != -1:
            return self._lint_range(quote_style_1 + 1, len(value))


    def _lint_value_of_key(self, keyname):
        """
            Simple string search for keyname in parenteses, lint the
            value of that key.
            Returns the position (sublime.Region) of that lint
        """

        keypos = self.content.find("'%s'" % keyname)
        if keypos == -1:
            keypos = self.content.find('"%s"' % keyname)
        if keypos == -1:
            return

        # set pointer to end of key
        p = keypos + len(keyname) + 2

        # find :
        while self.content[p] != ":" and p < self.len - 1:
            p += 1

        return self._lint_range(p + 1, 3)


    def _lint_range(self, char, length=0):
        """ Lints from position char.
            Returns the position (sublime.Region) of that lint """

        if length == 0:
            region = sublime.Region(
                        char - 2 if char >= 2            else 0,
                        char + 2 if char <= self.len - 2 else self.len)
        else:
            region = sublime.Region(char, char + length)
        self.error_regions.append(region)
        return (region.a, region.b) OPENED_PROJECTS = {}
import sublime
import os
import sys
from ..utils.fileutils import read_and_decode_json_file, file_exists, \
                              is_ts, is_dts, read_file
from ..utils.pathutils import find_tsconfigdir
from ..utils.disabling import is_plugin_temporarily_disabled
from ..utils.CancelCommand import CancelCommand
from ..utils.options import allowed_compileroptions, allowed_settings
from ..utils import get_deep, get_first, random_str, Debug
from .ProjectWizzard import ProjectWizzard
from .ErrorsHighlighter import ErrorsHighlighter
from .Errors import Errors
from .Completion import Completion
from ..server.Processes import Processes
from ..server.TypescriptToolsWrapper import TypescriptToolsWrapper
from ..commands.Compiler import Compiler
from ..commands.Refactor import Refactor
from ..display.T3SViews import T3SVIEWS
from ..display.Message import MESSAGE
from ..tsconfiglint.TsconfigLinter import TsconfigLinter
from .globals import OPENED_PROJECTS


    def get_common_path_prefix_of_files(self, use_cache=False):
        """ Returns the common dir which all files of tsconfig.json["files"]
            have in commmon: "src" for ["src/main.ts", "src/test.ts"] """
        try:
            files = get_deep(self._get_tsconfigsettings(use_cache), 'files')

            if len(files) > 0:
                common_prefix = os.path.dirname(files[0])
                for f in files:
                    dir_ = os.path.dirname(f)
                    # shorten prefix until it is matched
                    while len(common_prefix) > 0:
                        if dir_.startswith(common_prefix):
                            break
                        else:
                            common_prefix = common_prefix[:-1]
                return common_prefix
        except KeyError:
            pass

        return ""


    def _get_tsconfigsettings(self, use_cache=False):
        """ No cacheing by default """
        if use_cache and hasattr(self, 'tsconfigcache'):
            return self.tsconfigcache
        if file_exists(self.tsconfigfile):
            try:
                self.tsconfigcache = read_and_decode_json_file(self.tsconfigfile)
            except Exception as e:
                Debug('notify', "Error reading tsconfig.json: %s. Close Project." % e)
                show_tsconfig_failed_wizzard(self.tsconfigfile)
                self.close_project()
                raise CancelCommand
        else:
            self.tsconfigcache = {}
        return self.tsconfigcache


    def get_setting(self, settingskey, use_cache=False):
        """
            Allowed settings are defined in allowed_settings.
            Settings can be located in multiple files with these priorities:
            A setting in 1. overrides a setting in 3.
            1.  *   tsconfig.json['ArcticTypescript'][KEY]
            2.      Sublime-Settings: http://www.sublimetext.com/docs/3/settings.html
            2.0       Distraction Free Settings
            2.1       Packages/User/<syntax=TypeScript>.sublime-settings['ArcticTypescript'][KEY]
            2.2       Packages/<syntax=TypeScript>/<syntax=TypeScript>.sublime-settings['ArcticTypescript'][KEY]
            2.3 *     <ProjectSettings>.sublime-settings['settings']['ArcticTypescript'][KEY]
            2.4 *     Packages/User/Preferences.sublime-settings['ArcticTypescript'][KEY]
                      You can open this file via Menu -> Preferences -> "Settings - User"
            2.5       Packages/Default/Preferences (<platform>).sublime-settings['ArcticTypescript'][KEY]
            2.6       Packages/Default/Preferences.sublime-settings['ArcticTypescript'][KEY]
            3.      Sublime config dir/Packages/User/ArcticTypescript.sublime-settings[KEY]
                    You can open this file via Menu
                    -> Preferences -> Package Settings -> ArcticTypescript -> "Settings - User"


            Where should i put the settings? (recommendation):
                * Use 2.4. or 3. for personal settings across all typescript projects
                * Use 2.3 for personal, project specific settings
                * Use 1. if you are not part of a team
                         or for settings for everyone
                         or for project specific settings if you don't have created a sublime project

        """
        if settingskey not in allowed_settings:
            Debug('notify', "Requested unknown setting: %s. Will always be None."
                  % settingskey)
            return None

        # 1. tsconfig.json['ArcticTypescript'][KEY]
        try:
            return get_deep(self._get_tsconfigsettings(use_cache),
                        'ArcticTypescript:' + settingskey)
        except KeyError:
            pass

        # 2.  Sublime-Settings: http://www.sublimetext.com/docs/3/settings.html
        #     <ProjectSettings>.sublime-settings['settings']['ArcticTypescript'][KEY]
        try:
            return get_deep(self.views[0].settings().get('ArcticTypescript'), settingskey)
        except KeyError:
            pass

        # 3.  Sublime config dir/Packages/User/ArcticTypescript.sublime-settings[KEY]
        # Sublime will merge the defaults from the package file
        try:
            settingskeys = settingskey.split(':')
            firstkey = settingskeys.pop(0)
            if not self.ArcticTypescript_sublime_settings.has(firstkey):
                raise KeyError()
            setting = self.ArcticTypescript_sublime_settings.get(firstkey)
            return get_deep(setting, settingskeys)
        except KeyError:
            pass


        Debug('project', "No default setting for %s could not be found for project %s." % (settingskey, self.tsconfigfile, ))
        raise Exception("Arctic Typescript Bug: Valid setting requested, but default value can not be found.")


    # ###############################################    COMPILE   #############


    def compile_once(self, window_for_panel, triggered_for_file):

        if self.is_compiling and self.compiler is not None:
            if self.compiler.is_alive():
                sublime.status_message('Still compiling. Use Goto Anything > "ArcticTypescript: Terminate All Builds" to cancel.')
                self.compiler._show_output("")
            else:
                self.is_compiling = False

        if not self.is_compiling:
            self.is_compiling = True

            self.compiler = Compiler(self, window_for_panel, triggered_for_file)