# -*- coding: utf-8 -*-
from functools import partial
import os
import re
import sys
import time
import threading

import sublime
import sublime_plugin

from sublimelinter.loader import Loader
from sublimelinter.modules.base_linter import INPUT_METHOD_FILE

LINTERS = {}     # mapping of language name to linter module
QUEUE = {}       # views waiting to be processed by linter
ERRORS = {}      # error messages on given line obtained from linter; they are
                 # displayed in the status bar when cursor is on line with error
VIOLATIONS = {}  # violation messages, they are displayed in the status bar
WARNINGS = {}    # warning messages, they are displayed in the status bar
UNDERLINES = {}  # underline regions related to each lint message
TIMES = {}       # collects how long it took the linting to complete
MOD_LOAD = Loader(os.getcwdu(), LINTERS)  # utility to load (and reload
                 # if necessary) linter modules [useful when working on plugin]


# For snappier linting, different delays are used for different linting times:
# (linting time, delays)
DELAYS = (
    (50, (50, 100)),
    (100, (100, 300)),
    (200, (200, 500)),
    (400, (400, 1000)),
    (800, (800, 2000)),
    (1600, (1600, 3000)),
)

MARKS = {
    'violation': ('', 'dot'),
    'warning': ('', 'dot'),
    'illegal': ('', 'circle'),
}

# All available settings for SublimeLinter;
# only these are inherited from SublimeLinter.sublime-settings
ALL_SETTINGS = [
    'annotations',
    'csslint_options',
    'gjslint_ignore',
    'gjslint_options',
    'javascript_linter',
    'jshint_options',
    'jslint_options',
    'pep8',
    'pep8_ignore',
    'perl_linter',
    'pyflakes_ignore',
    'pyflakes_ignore_import_*',
    'coffeelint_options',
    'sublimelinter',
    'sublimelinter_delay',
    'sublimelinter_disable',
    'sublimelinter_executable_map',
    'sublimelinter_fill_outlines',
    'sublimelinter_gutter_marks',
    'sublimelinter_mark_style',
    'sublimelinter_notes',
    'sublimelinter_objj_check_ascii',
    'sublimelinter_popup_errors_on_save',
    'sublimelinter_syntax_map',
    'sublimelinter_wrap_find',
]

WHITESPACE_RE = re.compile(r'\s+')


def get_delay(t, view):
    delay = 0

    for _t, d in DELAYS:
        if _t <= t:
            delay = d

    delay = delay or DELAYS[0][1]

    # If the user specifies a delay greater than the built in delay,
    # figure they only want to see marks when idle.
    minDelay = int(view.settings().get('sublimelinter_delay', 0) * 1000)

    if minDelay > delay[1]:
        erase_lint_marks(view)

    return (minDelay, minDelay) if minDelay > delay[1] else delay


def last_selected_lineno(view):
    viewSel = view.sel()
    if not viewSel:
        return None
    return view.rowcol(viewSel[0].end())[0]


def update_statusbar(view):
    vid = view.id()
    lineno = last_selected_lineno(view)
    errors = []

    if lineno is not None:
        if vid in ERRORS and lineno in ERRORS[vid]:
            errors.extend(ERRORS[vid][lineno])

        if vid in VIOLATIONS and lineno in VIOLATIONS[vid]:
            errors.extend(VIOLATIONS[vid][lineno])

        if vid in WARNINGS and lineno in WARNINGS[vid]:
            errors.extend(WARNINGS[vid][lineno])

    if errors:
        view.set_status('Linter', '; '.join(errors))
    else:
        view.erase_status('Linter')


def run_once(linter, view, **kwargs):
    '''run a linter on a given view regardless of user setting'''
    if not linter:
        return

    vid = view.id()
    ERRORS[vid] = {}
    VIOLATIONS[vid] = {}
    WARNINGS[vid] = {}
    start = time.time()
    text = view.substr(sublime.Region(0, view.size())).encode('utf-8')
    lines, error_underlines, violation_underlines, warning_underlines, ERRORS[vid], VIOLATIONS[vid], WARNINGS[vid] = linter.run(view, text, view.file_name().encode('utf-8') or '')

    UNDERLINES[vid] = error_underlines[:]
    UNDERLINES[vid].extend(violation_underlines)
    UNDERLINES[vid].extend(warning_underlines)

    add_lint_marks(view, lines, error_underlines, violation_underlines, warning_underlines)

    if view.settings().get('sublimelinter_notes'):
        highlight_notes(view)

    update_statusbar(view)
    end = time.time()
    TIMES[vid] = (end - start) * 1000  # Keep how long it took to lint

    if kwargs.get('event', None) == 'on_post_save' and view.settings().get('sublimelinter_popup_errors_on_save'):
        popup_error_list(view)


def popup_error_list(view):
    vid = view.id()
    errors = ERRORS[vid].copy()

    for message_map in [VIOLATIONS[vid], WARNINGS[vid]]:
        for line, messages in message_map.items():
            if line in errors:
                errors[line].extend(messages)
            else:
                errors[line] = messages

    # Flatten the errors into a list
    error_list = []

    for line in sorted(errors.keys()):
        for index, message in enumerate(errors[line]):
            error_list.append({'line': line, 'message': message})

    panel_items = []

    for error in error_list:
        line_text = view.substr(view.full_line(view.text_point(error['line'], 0)))
        item = [error['message'], u'{0}: {1}'.format(error['line'] + 1, line_text.strip())]
        panel_items.append(item)

    def on_done(selected_item):
        if selected_item == -1:
            return

        selected = view.sel()
        selected.clear()

        error = error_list[selected_item]
        region_begin = view.text_point(error['line'], 0)

        # Go to the first non-whitespace character of the line
        line_text = view.substr(view.full_line(region_begin))
        match = WHITESPACE_RE.match(line_text)

        if (match):
            region_begin += len(match.group(0))

        selected.add(sublime.Region(region_begin, region_begin))
        # We have to force a move to update the cursor position
        view.run_command('move', {'by': 'characters', 'forward': True})
        view.run_command('move', {'by': 'characters', 'forward': False})
        view.show_at_center(region_begin)

    view.window().show_quick_panel(panel_items, on_done)


def add_lint_marks(view, lines, error_underlines, violation_underlines, warning_underlines):
    '''Adds lint marks to view.'''
    vid = view.id()
    erase_lint_marks(view)
    types = {'warning': warning_underlines, 'violation': violation_underlines, 'illegal': error_underlines}

    for type_name, underlines in types.items():
        if underlines:
            view.add_regions('lint-underline-' + type_name, underlines, 'sublimelinter.underline.' + type_name, sublime.DRAW_EMPTY_AS_OVERWRITE)

    if lines:
        outline_style = view.settings().get('sublimelinter_mark_style', 'outline')

        # This test is for the legacy "fill" setting; it will be removed
        # in a future version (likely v1.7).
        if view.settings().get('sublimelinter_fill_outlines', False):
            outline_style = 'fill'

        gutter_mark_enabled = True if view.settings().get('sublimelinter_gutter_marks', False) else False

        outlines = {'warning': [], 'violation': [], 'illegal': []}

        for line in ERRORS[vid]:
            outlines['illegal'].append(view.full_line(view.text_point(line, 0)))

        for line in WARNINGS[vid]:
            outlines['warning'].append(view.full_line(view.text_point(line, 0)))

        for line in VIOLATIONS[vid]:
            outlines['violation'].append(view.full_line(view.text_point(line, 0)))

        for lint_type in outlines:
            if outlines[lint_type]:
                args = [
                    'lint-outlines-{0}'.format(lint_type),
                    outlines[lint_type],
                    'sublimelinter.outline.{0}'.format(lint_type),
                    MARKS[lint_type][gutter_mark_enabled]
                ]

                if outline_style == 'none':
                    args.append(sublime.HIDDEN)
                elif outline_style == 'fill':
                    pass  # outlines are filled by default
                else:
                    args.append(sublime.DRAW_OUTLINED)
                view.add_regions(*args)


def erase_lint_marks(view):
    '''erase all "lint" error marks from view'''
    view.erase_regions('lint-underline-illegal')
    view.erase_regions('lint-underline-violation')
    view.erase_regions('lint-underline-warning')
    view.erase_regions('lint-outlines-illegal')
    view.erase_regions('lint-outlines-violation')
    view.erase_regions('lint-outlines-warning')
    view.erase_regions('lint-annotations')


def get_lint_regions(view, reverse=False, coalesce=False):
    vid = view.id()
    underlines = UNDERLINES.get(vid, [])[:]

    if (coalesce):
        # Each of these regions is one character, so transform it into the character points
        points = sorted([region.begin() for region in underlines])

        # Now coalesce adjacent characters into a single region
        underlines = []
        last_point = -999

        for point in points:
            if point != last_point + 1:
                underlines.append(sublime.Region(point, point))
            else:
                region = underlines[-1]
                underlines[-1] = sublime.Region(region.begin(), point)

            last_point = point

    # Now get all outlines, which includes the entire line where underlines are
    outlines = view.get_regions('lint-outlines-illegal')
    outlines.extend(view.get_regions('lint-outlines-violation'))
    outlines.extend(view.get_regions('lint-outlines-warning'))
    outlines.extend(view.get_regions('lint-annotations'))

    # If an outline region contains an underline region, use only the underline
    regions = underlines

    for outline in outlines:
        contains_underlines = False

        for underline in underlines:
            if outline.contains(underline):
                contains_underlines = True
                break

        if not contains_underlines:
            regions.append(outline)

    return sorted(regions, key=lambda x: x.begin(), reverse=reverse)


def select_lint_region(view, region):
    selected = view.sel()
    selected.clear()

    # Find the first underline region within the region to select.
    # If there are none, put the cursor at the beginning of the line.
    underlineRegion = find_underline_within(view, region)

    if underlineRegion is None:
        underlineRegion = sublime.Region(region.begin(), region.begin())

    selected.add(underlineRegion)
    view.show(underlineRegion, True)


def find_underline_within(view, region):
    underlines = view.get_regions('lint-underline-illegal')
    underlines.extend(view.get_regions('lint-underline-violation'))
    underlines.extend(view.get_regions('lint-underline-warning'))
    underlines.sort(key=lambda x: x.begin())

    for underline in underlines:
        if region.contains(underline):
            return underline

    return None


def syntax_name(view):
    syntax = os.path.basename(view.settings().get('syntax'))
    syntax = os.path.splitext(syntax)[0]
    return syntax


def select_linter(view, ignore_disabled=False):
    '''selects the appropriate linter to use based on language in current view'''
    syntax = syntax_name(view)
    lc_syntax = syntax.lower()
    language = None
    linter = None
    syntaxMap = view.settings().get('sublimelinter_syntax_map', {})

    if syntax in syntaxMap:
        language = syntaxMap[syntax].lower()
    elif lc_syntax in syntaxMap:
        language = syntaxMap[lc_syntax].lower()
    elif lc_syntax in LINTERS:
        language = lc_syntax

    if language:
        if ignore_disabled:
            disabled = []
        else:
            disabled = view.settings().get('sublimelinter_disable', [])

        if language not in disabled:
            linter = LINTERS[language]

            # If the enabled state is False, it must be checked.
            # Enabled checking has to be deferred to first view use because
            # user settings cannot be loaded during plugin startup.
            if not linter.enabled:
                enabled, message = linter.check_enabled(view)
                print 'SublimeLinter: {0} {1} ({2})'.format(language, 'enabled' if enabled else 'disabled', message)

                if not enabled:
                    del LINTERS[language]
                    linter = None

    return linter


def highlight_notes(view):
    '''highlight user-specified annotations in a file'''
    view.erase_regions('lint-annotations')
    text = view.substr(sublime.Region(0, view.size()))
    regions = LINTERS['annotations'].built_in_check(view, text, '')

    if regions:
        view.add_regions('lint-annotations', regions, 'sublimelinter.annotations', sublime.DRAW_EMPTY_AS_OVERWRITE)


def _update_view(view, filename, **kwargs):
    # It is possible that by the time the queue is run,
    # the original file is no longer being displayed in the view,
    # or the view may be gone. This happens especially when
    # viewing files temporarily by single-clicking on a filename
    # in the sidebar or when selecting a file through the choose file palette.
    valid_view = False
    view_id = view.id()

    for window in sublime.windows():
        for v in window.views():
            if v.id() == view_id:
                valid_view = True
                break

    if not valid_view or view.is_loading() or view.file_name().encode('utf-8') != filename:
        return

    try:
        run_once(select_linter(view), view, **kwargs)
    except RuntimeError, ex:
        print ex


def queue_linter(linter, view, timeout=-1, preemptive=False, event=None):
    '''Put the current view in a queue to be examined by a linter'''
    if linter is None:
        erase_lint_marks(view)  # may have changed file type and left marks behind

        # No point in queuing anything if no linters will run
        if not view.settings().get('sublimelinter_notes'):
            return

    if preemptive:
        timeout = busy_timeout = 0
    elif timeout == -1:
        timeout, busy_timeout = get_delay(TIMES.get(view.id(), 100), view)
    else:
        busy_timeout = timeout

    kwargs = {'timeout': timeout, 'busy_timeout': busy_timeout, 'preemptive': preemptive, 'event': event}
    queue(view, partial(_update_view, view, view.file_name().encode('utf-8'), **kwargs), kwargs)


def _callback(view, filename, kwargs):
    kwargs['callback'](view, filename, **kwargs)


def background_linter():
    __lock_.acquire()

    try:
        callbacks = QUEUE.values()
        QUEUE.clear()
    finally:
        __lock_.release()

    for callback in callbacks:
        sublime.set_timeout(callback, 0)

################################################################################
# Queue dispatcher system:

queue_dispatcher = background_linter
queue_thread_name = 'background linter'
MAX_DELAY = 10


def queue_loop():
    '''An infinite loop running the linter in a background thread meant to
       update the view after user modifies it and then does no further
       modifications for some time as to not slow down the UI with linting.'''
    global __signaled_, __signaled_first_

    while __loop_:
        #print 'acquire...'
        __semaphore_.acquire()
        __signaled_first_ = 0
        __signaled_ = 0
        #print 'DISPATCHING!', len(QUEUE)
        queue_dispatcher()


def queue(view, callback, kwargs):
    global __signaled_, __signaled_first_
    now = time.time()
    __lock_.acquire()

    try:
        QUEUE[view.id()] = callback
        timeout = kwargs['timeout']
        busy_timeout = kwargs['busy_timeout']

        if now < __signaled_ + timeout * 4:
            timeout = busy_timeout or timeout

        __signaled_ = now
        _delay_queue(timeout, kwargs['preemptive'])

        if not __signaled_first_:
            __signaled_first_ = __signaled_
            #print 'first',
        #print 'queued in', (__signaled_ - now)
    finally:
        __lock_.release()


def _delay_queue(timeout, preemptive):
    global __signaled_, __queued_
    now = time.time()

    if not preemptive and now <= __queued_ + 0.01:
        return  # never delay queues too fast (except preemptively)

    __queued_ = now
    _timeout = float(timeout) / 1000

    if __signaled_first_:
        if MAX_DELAY > 0 and now - __signaled_first_ + _timeout > MAX_DELAY:
            _timeout -= now - __signaled_first_
            if _timeout < 0:
                _timeout = 0
            timeout = int(round(_timeout * 1000, 0))

    new__signaled_ = now + _timeout - 0.01

    if __signaled_ >= now - 0.01 and (preemptive or new__signaled_ >= __signaled_ - 0.01):
        __signaled_ = new__signaled_
        #print 'delayed to', (preemptive, __signaled_ - now)

        def _signal():
            if time.time() < __signaled_:
                return
            __semaphore_.release()

        sublime.set_timeout(_signal, timeout)


def delay_queue(timeout):
    __lock_.acquire()
    try:
        _delay_queue(timeout, False)
    finally:
        __lock_.release()


# only start the thread once - otherwise the plugin will get laggy
# when saving it often.
__semaphore_ = threading.Semaphore(0)
__lock_ = threading.Lock()
__queued_ = 0
__signaled_ = 0
__signaled_first_ = 0

# First finalize old standing threads:
__loop_ = False
__pre_initialized_ = False


def queue_finalize(timeout=None):
    global __pre_initialized_

    for thread in threading.enumerate():
        if thread.isAlive() and thread.name == queue_thread_name:
            __pre_initialized_ = True
            thread.__semaphore_.release()
            thread.join(timeout)
queue_finalize()

# Initialize background thread:
__loop_ = True
__active_linter_thread = threading.Thread(target=queue_loop, name=queue_thread_name)
__active_linter_thread.__semaphore_ = __semaphore_
__active_linter_thread.start()

################################################################################

UNRECOGNIZED = '''
* Unrecognized option * :  %s
==============================================

'''


def view_in_tab(view, title, text, file_type):
    '''Helper function to display information in a tab.
    '''
    tab = view.window().new_file()
    tab.set_name(title)
    _id = tab.buffer_id()
    tab.set_scratch(_id)
    tab.settings().set('gutter', True)
    tab.settings().set('line_numbers', False)
    tab.set_syntax_file(file_type)
    ed = tab.begin_edit()
    tab.insert(ed, 0, text)
    tab.end_edit(ed)
    return tab, _id


def lint_views(linter):
    if not linter:
        return

    viewsToLint = []

    for window in sublime.windows():
        for view in window.views():
            viewLinter = select_linter(view)

            if viewLinter == linter:
                viewsToLint.append(view)

    for view in viewsToLint:
        queue_linter(linter, view, preemptive=True)


def reload_view_module(view):
    for name, linter in LINTERS.items():
        module = sys.modules[linter.__module__]

        if module.__file__.encode('utf-8') == view.file_name().encode('utf-8'):
            print 'SublimeLinter: reloading language:', linter.language
            MOD_LOAD.reload_module(module)
            lint_views(linter)
            break


def settings_changed():
    for window in sublime.windows():
        for view in window.views():
            linter = select_linter(view)

            if (linter):
                reload_settings(view)


def reload_settings(view):
    '''Restores user settings.'''
    settings = sublime.load_settings(__name__ + '.sublime-settings')
    settings.clear_on_change(__name__)
    settings.add_on_change(__name__, settings_changed)

    for setting in ALL_SETTINGS:
        if settings.get(setting) != None:
            view.settings().set(setting, settings.get(setting))

    if view.settings().get('sublimelinter') == None:
        view.settings().set('sublimelinter', True)


class LintCommand(sublime_plugin.TextCommand):
    '''command to interact with linters'''

    def __init__(self, view):
        self.view = view
        self.help_called = False

    def run_(self, action):
        '''method called by default via view.run_command;
           used to dispatch to appropriate method'''
        if not action:
            return

        try:
            lc_action = action.lower()
        except AttributeError:
            return

        if lc_action == 'reset':
            self.reset()
        elif lc_action == 'on':
            self.on()
        elif lc_action == 'load-save':
            self.enable_load_save()
        elif lc_action == 'save-only':
            self.enable_save_only()
        elif lc_action == 'off':
            self.off()
        elif action.lower() in LINTERS:
            self._run(lc_action)

    def reset(self):
        '''Removes existing lint marks and restores user settings.'''
        erase_lint_marks(self.view)
        reload_settings(self.view)

    def on(self):
        '''Turns background linting on.'''
        self.view.settings().set('sublimelinter', True)
        queue_linter(select_linter(self.view), self.view, preemptive=True)

    def enable_load_save(self):
        '''Turns load-save linting on.'''
        self.view.settings().set('sublimelinter', 'load-save')
        erase_lint_marks(self.view)

    def enable_save_only(self):
        '''Turns save-only linting on.'''
        self.view.settings().set('sublimelinter', 'save-only')
        erase_lint_marks(self.view)

    def off(self):
        '''Turns background linting off.'''
        self.view.settings().set('sublimelinter', False)
        erase_lint_marks(self.view)

    def _run(self, name):
        '''runs an existing linter'''
        run_once(LINTERS[name.lower()], self.view)


class BackgroundLinter(sublime_plugin.EventListener):
    '''This plugin controls a linter meant to work in the background
    to provide interactive feedback as a file is edited. It can be
    turned off via a setting.
    '''

    def __init__(self):
        super(BackgroundLinter, self).__init__()
        self.lastSelectedLineNo = -1

    def on_modified(self, view):
        if view.is_scratch():
            return

        if view.settings().get('sublimelinter') != True:
            erase_lint_marks(view)
            return

        linter = select_linter(view)

        # File-based linters are not invoked during a modify
        if linter and linter.input_method == INPUT_METHOD_FILE:
            erase_lint_marks(view)
            return

        # Reset the last selected line number so that the current line will show error messages
        # when update_statusbar is called.
        self.lastSelectedLineNo = -1
        queue_linter(linter, view)

    def on_load(self, view):
        reload_settings(view)

        if view.is_scratch() or view.settings().get('sublimelinter') == False or view.settings().get('sublimelinter') == 'save-only':
            return

        queue_linter(select_linter(view), view, event='on_load')

    def on_post_save(self, view):
        if view.is_scratch() or view.settings().get('sublimelinter') == False:
            return

        reload_view_module(view)
        queue_linter(select_linter(view), view, preemptive=True, event='on_post_save')

    def on_selection_modified(self, view):
        if view.is_scratch():
            return
        delay_queue(1000)  # on movement, delay queue (to make movement responsive)

        # We only display errors in the status bar for the last line in the current selection.
        # If that line number has not changed, there is no point in updating the status bar.
        lastSelectedLineNo = last_selected_lineno(view)

        if lastSelectedLineNo != self.lastSelectedLineNo:
            self.lastSelectedLineNo = lastSelectedLineNo
            update_statusbar(view)


class FindLintErrorCommand(sublime_plugin.TextCommand):
    '''This command is just a superclass for other commands, it is never enabled.'''
    def is_enabled(self):
        return select_linter(self.view) is not None

    def find_lint_error(self, forward):
        linter = select_linter(self.view, ignore_disabled=True)

        if not linter:
            return

        self.view.run_command('lint', linter.language)
        regions = get_lint_regions(self.view, reverse=not forward, coalesce=True)

        if len(regions) == 0:
            sublime.error_message('No lint errors.')
            return

        selected = self.view.sel()
        point = selected[0].begin() if forward else selected[-1].end()
        regionToSelect = None

        # If going forward, find the first region beginning after the point.
        # If going backward, find the first region ending before the point.
        # If nothing is found in the given direction, wrap to the first/last region.
        if forward:
            for index, region in enumerate(regions):
                if point < region.begin():
                    regionToSelect = region
                    break
        else:
            for index, region in enumerate(regions):
                if point > region.end():
                    regionToSelect = region
                    break

        # If there is only one error line and the cursor is in that line, we cannot move.
        # Otherwise wrap to the first/last error line unless settings disallow that.
        if regionToSelect is None and (len(regions) > 1 or not regions[0].contains(point)):
            if self.view.settings().get('sublimelinter_wrap_find', True):
                regionToSelect = regions[0]

        if regionToSelect is not None:
            select_lint_region(self.view, regionToSelect)
        else:
            sublime.error_message('No {0} lint errors.'.format('next' if forward else 'previous'))

        return regionToSelect


class FindNextLintErrorCommand(FindLintErrorCommand):
    def run(self, edit):
        '''
        Move the cursor to the next lint error in the current view.
        The search will wrap to the top unless the sublimelinter_wrap_find
        setting is set to false.
        '''
        self.find_lint_error(forward=True)


class FindPreviousLintErrorCommand(FindLintErrorCommand):
    def run(self, edit):
        '''
        Move the cursor to the previous lint error in the current view.
        The search will wrap to the bottom unless the sublimelinter_wrap_find
        setting is set to false.
        '''
        self.find_lint_error(forward=False)


class SublimelinterWindowCommand(sublime_plugin.WindowCommand):
    def is_enabled(self):
        view = self.window.active_view()

        if view:
            if view.is_scratch():
                return False
            else:
                return True
        else:
            return False

    def run_(self, args):
        pass


class SublimelinterAnnotationsCommand(SublimelinterWindowCommand):
    '''Commands to extract annotations and display them in
       a file
    '''
    def run_(self, args):
        linter = LINTERS.get('annotations', None)

        if linter is None:
            return

        view = self.window.active_view()

        if not view:
            return

        text = view.substr(sublime.Region(0, view.size())).encode('utf-8')
        filename = view.file_name().encode('utf-8')
        notes = linter.extract_annotations(text, view, filename)
        _, filename = os.path.split(filename)
        annotations_view, _id = view_in_tab(view, 'Annotations from {0}'.format(filename), notes, '')


class SublimelinterCommand(SublimelinterWindowCommand):
    def is_enabled(self):
        enabled = super(SublimelinterCommand, self).is_enabled()

        if not enabled:
            return False

        linter = select_linter(self.window.active_view(), ignore_disabled=True)
        return linter is not None

    def run_(self, args={}):
        view = self.window.active_view()
        action = args.get('action', '')

        if view and action:
            if action == 'lint':
                self.lint_view(view, show_popup_list=args.get('show_popup', False))
            else:
                view.run_command('lint', action)

    def lint_view(self, view, show_popup_list):
        linter = select_linter(view, ignore_disabled=True)

        if linter:
            view.run_command('lint', linter.language)
            regions = get_lint_regions(view, coalesce=True)

            if regions:
                if show_popup_list:
                    popup_error_list(view)
                else:
                    sublime.error_message('{0} lint error{1}.'.format(len(regions), 's' if len(regions) != 1 else ''))
            else:
                sublime.error_message('No lint errors.')
        else:
            syntax = syntax_name(view)
            sublime.error_message('No linter for the syntax "{0}"'.format(syntax))


class SublimelinterLintCommand(SublimelinterCommand):
    def is_enabled(self):
        enabled = super(SublimelinterLintCommand, self).is_enabled()

        if enabled:
            view = self.window.active_view()

            if view and view.settings().get('sublimelinter') == True:
                return False

        return enabled


class SublimelinterShowErrorsCommand(SublimelinterCommand):
    def is_enabled(self):
        return super(SublimelinterShowErrorsCommand, self).is_enabled()


class SublimelinterEnableLoadSaveCommand(SublimelinterCommand):
    def is_enabled(self):
        enabled = super(SublimelinterEnableLoadSaveCommand, self).is_enabled()

        if enabled:
            view = self.window.active_view()

            if view and view.settings().get('sublimelinter') == 'load-save':
                return False

        return enabled


class SublimelinterEnableSaveOnlyCommand(SublimelinterCommand):
    def is_enabled(self):
        enabled = super(SublimelinterEnableSaveOnlyCommand, self).is_enabled()

        if enabled:
            view = self.window.active_view()

            if view and view.settings().get('sublimelinter') == 'save-only':
                return False

        return enabled


class SublimelinterDisableCommand(SublimelinterCommand):
    def is_enabled(self):
        enabled = super(SublimelinterDisableCommand, self).is_enabled()

        if enabled:
            view = self.window.active_view()

            if view and view.settings().get('sublimelinter') == False:
                return False

        return enabled
