Skip to content

Commit

Permalink
centralize printing, add settings, improve PATH, fix misc bugs
Browse files Browse the repository at this point in the history
debug printing is now handled through `persist.debug`
additionally, it can be disabled through the setting 'debug' (issue #25)

settings are loaded from SublimeLint.sublime-settings
added commands to load user and default settings through the command launcher

PATH is now detected using /bin/bash --login if it's your system shell

some extraneous linting bugs are fixed
Linter.popen now cleanly prints a "command not found" message if necesssary
better support for case-insensitive filesystems when reloading modules
  • Loading branch information
lunixbochs committed Mar 29, 2012
1 parent b182229 commit e2cbad8
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 63 deletions.
16 changes: 16 additions & 0 deletions Default.sublime-commands
@@ -0,0 +1,16 @@
[
{
"caption": "Preferences: SublimeLint – Default",
"command": "open_file", "args":
{
"file": "${packages}/sublimelint/SublimeLint.sublime-settings"
}
},
{
"caption": "Preferences: SublimeLint – User",
"command": "open_file", "args":
{
"file": "${packages}/User/SublimeLint.sublime-settings"
}
}
]
3 changes: 3 additions & 0 deletions SublimeLint.sublime-settings
@@ -0,0 +1,3 @@
{
"debug": false
}
128 changes: 81 additions & 47 deletions lint/linter.py
Expand Up @@ -4,6 +4,7 @@


import sublime import sublime
import re import re
import persist


from highlight import Highlight from highlight import Highlight


Expand All @@ -27,6 +28,7 @@ def __init__(self, view, syntax, filename='untitled'):
self.view = view self.view = view
self.syntax = syntax self.syntax = syntax
self.filename = filename self.filename = filename

if self.regex: if self.regex:
self.regex = re.compile(self.regex) self.regex = re.compile(self.regex)


Expand All @@ -46,8 +48,12 @@ def assign(cls, view):


settings = view.settings() settings = view.settings()
syn = settings.get('syntax') syn = settings.get('syntax')
if not syn: return if not syn:

if id in cls.linters:
del cls.linters[id]

return

match = syntax_re.search(syn) match = syntax_re.search(syn)


if match: if match:
Expand All @@ -61,7 +67,7 @@ def assign(cls, view):
return return


linters = set() linters = set()
for entry in cls.languages.values(): for name, entry in cls.languages.items():
if entry.can_lint(syntax): if entry.can_lint(syntax):
linter = entry(view, syntax) linter = entry(view, syntax)
linters.add(linter) linters.add(linter)
Expand All @@ -71,9 +77,11 @@ def assign(cls, view):
else: else:
if id in cls.linters: if id in cls.linters:
del cls.linters[id] del cls.linters[id]

return linters return linters


del cls.linters[id]

@classmethod @classmethod
def reload(cls, mod): def reload(cls, mod):
''' '''
Expand Down Expand Up @@ -121,25 +129,26 @@ def lint(self, code=None):
if not code: return if not code: return


output = self.communicate(self.cmd, code) output = self.communicate(self.cmd, code)
print repr(output) if output:

persist.debug('Output:', repr(output))
for line in output.splitlines():
line = line.strip() for line in output.splitlines():

line = line.strip()
match, row, col, message, near = self.match_error(self.regex, line)
if match: match, row, col, message, near = self.match_error(self.regex, line)
if row or row is 0: if match:
if col or col is 0: if row or row is 0:
self.highlight.range(row, col) if col or col is 0:
elif near: self.highlight.range(row, col)
self.highlight.near(row, near) elif near:
self.highlight.near(row, near)
else:
self.highlight.line(row)

if row in errors:
errors[row].append(message)
else: else:
self.highlight.line(row) errors[row] = [message]

if row in errors:
errors[row].append(message)
else:
errors[row] = [message]


def draw(self, prefix='lint'): def draw(self, prefix='lint'):
self.highlight.draw(self.view, prefix) self.highlight.draw(self.view, prefix)
Expand All @@ -152,16 +161,17 @@ def clear(self, prefix='lint'):
@classmethod @classmethod
def can_lint(cls, language): def can_lint(cls, language):
language = language.lower() language = language.lower()
if isinstance(cls.language, basestring) and language == cls.language: if cls.language:
return True if language == cls.language:
elif isinstance(cls.language, (list, tuple, set)) and language in cls.language: return True
return True elif language in cls.language:
else: return True
return False else:
return False


def error(self, line, error): def error(self, line, error):
self.highlight.line(line) self.highlight.line(line)

error = str(error) error = str(error)
if line in self.errors: if line in self.errors:
self.errors[line].append(error) self.errors[line].append(error)
Expand All @@ -182,21 +192,47 @@ def match_error(self, r, line):
return match, None, None, '', None return match, None, None, '', None


# popen methods # popen methods

def communicate(self, cmd, code): def communicate(self, cmd, code):
out = self.popen(cmd).communicate(code) out = self.popen(cmd)
return (out[0] or '') + (out[1] or '') if out is not None:
out = out.communicate(code)
return (out[0] or '') + (out[1] or '')
else:
return ''

def create_environment(self):
env = os.environ
if os.name == 'posix':
# find PATH using shell --login
if 'SHELL' in env and env['SHELL'] in ('/bin/bash', ):
shell = (env['SHELL'], '--login', '-c', 'echo _SUBL_ $PATH')
path = self.popen(shell, env).communicate()[0]
env['PATH'] = path.split('_SUBL_ ', 1)[1].split('\n', 1)[0]
# guess PATH
else:
for path in (
'/usr/bin', '/usr/local/bin',
'/usr/local/php/bin', '/usr/local/php5/bin'
):
if not path in env['PATH']:
env['PATH'] += (':' + path)

return env


def tmpfile(self, cmd, code, suffix=''): def tmpfile(self, cmd, code, suffix=''):
f = tempfile.NamedTemporaryFile(suffix=suffix) f = tempfile.NamedTemporaryFile(suffix=suffix)
f.write(code) f.write(code)
f.flush() f.flush()


cmd = tuple(cmd) + (f.name,) cmd = tuple(cmd) + (f.name,)
out = self.popen(cmd).communicate('') out = self.popen(cmd)
return (out[0] or '') + (out[1] or '') if out:
out = out.communicate('')
return (out[0] or '') + (out[1] or '')
else:
return ''


def popen(self, cmd): def popen(self, cmd, env=None):
if isinstance(cmd, basestring): if isinstance(cmd, basestring):
cmd = cmd, cmd = cmd,


Expand All @@ -206,15 +242,13 @@ def popen(self, cmd):
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
info.wShowWindow = subprocess.SW_HIDE info.wShowWindow = subprocess.SW_HIDE


env = os.environ if env is None:
if os.name == 'posix': env = self.create_environment()
for path in (
'/usr/bin', '/usr/local/bin', try:
'/usr/local/php/bin', '/usr/local/php5/bin' return subprocess.Popen(cmd, stdin=subprocess.PIPE,
): stdout=subprocess.PIPE, stderr=subprocess.PIPE,
if not path in env['PATH']: startupinfo=info, env=env)
env['PATH'] += (':' + path) except OSError, err:

persist.debug('SublimeLint: Error launching', repr(cmd))
return subprocess.Popen(cmd, stdin=subprocess.PIPE, persist.debug('Error was:', err.strerror)
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
startupinfo=info, env=env)
12 changes: 6 additions & 6 deletions lint/modules.py
Expand Up @@ -5,6 +5,7 @@
import glob import glob


import traceback import traceback
import persist


class Modules: class Modules:
def __init__(self, cwd, path): def __init__(self, cwd, path):
Expand All @@ -14,7 +15,7 @@ def __init__(self, cwd, path):
self.modules = {} self.modules = {}


def load(self, name): def load(self, name):
print 'SublimeLint: loading `%s`' % name persist.debug('SublimeLint: loading `%s`' % name)
pushd = os.getcwd() pushd = os.getcwd()
os.chdir(self.base) os.chdir(self.base)
path = list(sys.path) path = list(sys.path)
Expand All @@ -28,10 +29,10 @@ def load(self, name):
# second, we get an updated version of the module with reload() so development is easier # second, we get an updated version of the module with reload() so development is easier
mod = sys.modules[name] = reload(sys.modules[name]) mod = sys.modules[name] = reload(sys.modules[name])
except: except:
print 'SublimeLint: Error importing `%s`' % name persist.debug('SublimeLint: error importing `%s`' % name)
print '-'*20 persist.debug('-'*20)
print traceback.format_exc() persist.debug(traceback.format_exc())
print '-'*20 persist.debug('-'*20)


self.modules[name] = mod self.modules[name] = mod


Expand All @@ -45,7 +46,6 @@ def load(self, name):


def reload(self, mod): def reload(self, mod):
name = mod.__name__ name = mod.__name__
print 'SublimeLint: reloading `%s`' % name
if name in self.modules: if name in self.modules:
return self.load(name) return self.load(name)


Expand Down
29 changes: 23 additions & 6 deletions lint/persist.py
Expand Up @@ -12,6 +12,14 @@ class Daemon:
views = {} views = {}
last_run = {} last_run = {}


def __init__(self):
self.settings = sublime.load_settings('SublimeLint.sublime-settings')
self.settings.add_on_change('lint-persist-settings', self.update_settings)
self.update_settings()

def update_settings(self):
self.debug = self.settings.get('debug', False)

def start(self, callback): def start(self, callback):
self.callback = callback self.callback = callback


Expand Down Expand Up @@ -51,22 +59,31 @@ def loop(self):


elif isinstance(item, basestring): elif isinstance(item, basestring):
if item == 'reload': if item == 'reload':
print 'SublimeLint daemon detected a reload' self.printf('SublimeLint daemon detected a reload')
else: else:
print 'SublimeLint: Unknown message sent to daemon:', item self.printf('SublimeLint: Unknown message sent to daemon:', item)
except: except:
print 'Error in SublimeLint daemon:' self.printf('Error in SublimeLint daemon:')
print '-'*20 self.printf('-'*20)
print traceback.format_exc() self.printf(traceback.format_exc())
print '-'*20 self.printf('-'*20)


def hit(self, view): def hit(self, view):
self.q.put((view.id(), time.time())) self.q.put((view.id(), time.time()))


def delay(self): def delay(self):
self.q.put(0.01) self.q.put(0.01)


def printf(self, *args):
if not self.debug: return

for arg in args:
print arg,
print

if not 'already' in globals(): if not 'already' in globals():
queue = Daemon() queue = Daemon()
debug = queue.printf

errors = {} errors = {}
already = True already = True
30 changes: 26 additions & 4 deletions sublimelint.py
Expand Up @@ -22,9 +22,14 @@ class SublimeLint(sublime_plugin.EventListener):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
sublime_plugin.EventListener.__init__(self, *args, **kwargs) sublime_plugin.EventListener.__init__(self, *args, **kwargs)


self.settings = sublime.load_settings('SublimeLint.sublime-settings')
self.settings.add_on_change('lint-settings', self.update_settings)
self.update_settings()

self.loaded = set() self.loaded = set()
self.linted = set() self.linted = set()
self.modules = Modules(cwd, 'languages').load_all() self.modules = Modules(cwd, 'languages').load_all()
self.pending_on_change = set()
persist.queue.start(self.lint) persist.queue.start(self.lint)


# this gives us a chance to lint the active view on fresh install # this gives us a chance to lint the active view on fresh install
Expand All @@ -34,11 +39,14 @@ def __init__(self, *args, **kwargs):


self.start = time.time() self.start = time.time()


def update_settings(self):
pass

def lint(self, view_id): def lint(self, view_id):
view = Linter.get_view(view_id) view = Linter.get_view(view_id)


if view is not None: if view is not None:
print 'SublimeLint: running on `%s`' % os.path.split(view.file_name() or 'untitled')[1] persist.debug('SublimeLint: running on `%s`' % os.path.split(view.file_name() or 'untitled')[1])
code = Linter.text(view) code = Linter.text(view)
thread.start_new_thread(Linter.lint_view, (view_id, code, self.finish)) thread.start_new_thread(Linter.lint_view, (view_id, code, self.finish))


Expand All @@ -58,6 +66,8 @@ def finish(self, view, linters):


def hit(self, view): def hit(self, view):
self.linted.add(view.id()) self.linted.add(view.id())
if view.size() == 0: return

persist.queue.hit(view) persist.queue.hit(view)


# callins # callins
Expand All @@ -84,15 +94,27 @@ def on_new(self, view):
settings = view.settings() settings = view.settings()
syntax = settings.get('syntax') syntax = settings.get('syntax')
def on_change(): def on_change():
if settings.get('syntax') != syntax: # weird, the recursion bug seems to only be happening on one untitled view?
Linter.assign(view) if view.id() in self.pending_on_change:
return

try:
self.pending_on_change.add(view.id())
if settings.get('syntax') != syntax:
Linter.assign(view)

finally:
self.pending_on_change.remove(view.id())


settings.clear_on_change('lint-syntax')
settings.add_on_change('lint-syntax', on_change) settings.add_on_change('lint-syntax', on_change)


def on_post_save(self, view): def on_post_save(self, view):
# this will reload submodules if they are saved with sublime text # this will reload submodules if they are saved with sublime text
for name, module in self.modules.modules.items(): for name, module in self.modules.modules.items():
if module.__file__ == view.file_name(): if os.name == 'posix' and (
os.stat(module.__file__).st_ino == os.stat(view.file_name()).st_ino
) or module.__file__ == view.file_name():
self.modules.reload(module) self.modules.reload(module)
Linter.reload(name) Linter.reload(name)
break break
Expand Down

0 comments on commit e2cbad8

Please sign in to comment.