diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7cd6f32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ \ No newline at end of file diff --git a/CodeFormatter.sublime-settings b/CodeFormatter.sublime-settings new file mode 100644 index 0000000..e69de29 diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap new file mode 100644 index 0000000..ca94095 --- /dev/null +++ b/Default (Windows).sublime-keymap @@ -0,0 +1,9 @@ +[ + { "keys": ["ctrl+shift+;"], "command": "show_overlay", "args": {"overlay": "goto", "text": "#"} }, + {"keys": ["ctrl+;"],"command": "run_macro_file","args": {"file": "Packages/User/endsemicolon.sublime-macro"} + }, + {"keys": ["ctrl+shift+'"],"command": "run_macro_file","args": {"file": "Packages/User/quotes.sublime-macro"} + }, + {"keys": ["ctrl+'"],"command": "run_macro_file","args": {"file": "Packages/User/simplequotes.sublime-macro"} + }, +] diff --git a/Package Control.sublime-settings b/Package Control.sublime-settings new file mode 100644 index 0000000..c98e05a --- /dev/null +++ b/Package Control.sublime-settings @@ -0,0 +1,25 @@ +{ + "installed_packages": + [ + "CodeFormatter", + "CTags", + "DocBlockr", + "Emmet", + "Git", + "Gitignore", + "Goto Documentation", + "LiveStyle", + "PHP Companion", + "Phpcs", + "Sass", + "SCSS", + "SCSS Snippets", + "SideBarEnhancements", + "SideBarGit", + "SublimeCodeIntel", + "sublimelint", + "SyncedSideBar", + "Theme - Phoenix", + "Theme - Spacegray" + ] +} diff --git a/Preferences.sublime-settings b/Preferences.sublime-settings new file mode 100644 index 0000000..8f27f76 --- /dev/null +++ b/Preferences.sublime-settings @@ -0,0 +1,14 @@ +{ + "color_scheme": "Packages/Color Scheme - Default/Monokai.tmTheme", + "font_size": 14, + "ignored_packages": + [ + "Vintage" + ], + "phoenix_color_blue": true, + "phoenix_dirty_bottom_bar": true, + "phoenix_dirty_bottom_bar_red": true, + "phoenix_solid_current_tab": true, + "theme": "Phoenix Dark.sublime-theme", + "word_wrap": true +} diff --git a/Side Bar.sublime-settings b/Side Bar.sublime-settings new file mode 100644 index 0000000..d20627f --- /dev/null +++ b/Side Bar.sublime-settings @@ -0,0 +1,3 @@ +{ + "version": "setting no longer updated" +} diff --git a/endsemicolon.sublime-macro b/endsemicolon.sublime-macro new file mode 100644 index 0000000..ecf7a44 --- /dev/null +++ b/endsemicolon.sublime-macro @@ -0,0 +1,17 @@ +[ + { + "args": + { + "extend": false, + "to": "eol" + }, + "command": "move_to" + }, + { + "args": + { + "characters": ";" + }, + "command": "insert" + } +] diff --git a/linters/.version b/linters/.version new file mode 100644 index 0000000..22d04d6 --- /dev/null +++ b/linters/.version @@ -0,0 +1 @@ +a4c0b528832a037ccc0c398c81157ba996cdfcc6 \ No newline at end of file diff --git a/linters/applescript.py b/linters/applescript.py new file mode 100644 index 0000000..1d432e9 --- /dev/null +++ b/linters/applescript.py @@ -0,0 +1,113 @@ +from lint import Linter +import json +import platform +import subprocess + + +def clean_output(args): + return '\n'.join([a.decode('utf8') for a in args if a]) + + +def popen(*cmd): + p = subprocess.Popen(cmd, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + return clean_output(p.communicate()) + + +lint_script = ''' +import sys +from Foundation import NSAppleScript, NSConcreteValue, NSRange +import objc +import json + +class CustomCodec(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, NSConcreteValue): + if obj.objCType() == NSRange.__typestr__: + r = obj.rangeValue() + return (r.location, r.length) + return json.JSONEncoder.default(self, obj) + +def lint(code): + code = code.decode('utf8') + linter = NSAppleScript.alloc().initWithSource_(code) + errors = dict(linter.compileAndReturnError_(None)[1] or {}) + objc.recycleAutoreleasePool() + return CustomCodec().encode(errors) + +if __name__ == '__main__': + code = sys.stdin.read() + print lint(code) +''' + + +find_app_script = ''' +import LaunchServices +import sys +code, ref, url = LaunchServices.LSFindApplicationForInfo( + LaunchServices.kLSUnknownCreator, None, sys.argv[1], None, None) +if url: + sys.stdout.write(url.path().encode('utf8')) +''' + + +app_name_cache = {} +def find_app(name): + if not name.endswith('.app'): + name += '.app' + if not name in app_name_cache: + app = popen('/usr/bin/python', '-c', find_app_script, name) + app_name_cache[name] = app + return app_name_cache[name] + +APP_NAME_SEL = 'string.quoted.double.application-name.applescript' + + +class AppleScript(Linter): + @classmethod + def can_lint(cls, language): + if platform.system() != 'Darwin': + return + return 'AppleScript' in language + + def lint(self): + tell_apps = [ + (region, self.view.substr(region).strip('"')) + for region in self.view.find_by_selector(APP_NAME_SEL) + ] + any_invalid = False + for region, name in tell_apps: + if not find_app(name): + any_invalid = True + start = region.a + 1 + end = region.b - start - 1 + line = self.code[:start].count('\n') + line_len = len(self.code.split('\n')[line]) + offset = 0 + if line: + start -= self.code[:start].rindex('\n') + 1 + + end = min(line_len - start, end) + self.highlight.range(line, start, end) + self.error(line, 'Could not find app named {}'.format(name)) + + if any_invalid: + return + + out = self.communicate(('/usr/bin/python', '-c', lint_script), self.code) + out = out.replace('\u2019', '\'') + error = json.loads(out) + if error: + brief = error['NSAppleScriptErrorBriefMessage'] + # message = error['NSAppleScriptErrorMessage'] + start, end = error['NSAppleScriptErrorRange'] + + line = self.code[:start].count('\n') + offset = 0 + if line: + offset = start - self.code[:start].rindex('\n') + + self.highlight.range(line, offset, end - offset) + self.error(line, brief) diff --git a/linters/c.py b/linters/c.py new file mode 100644 index 0000000..abb9f0d --- /dev/null +++ b/linters/c.py @@ -0,0 +1,51 @@ +import os + +from lint import Linter +from sublimelint.lint.util import find + +def find_includes(filename): + includes = [] + if filename: + parent = os.path.dirname(filename) + if not parent: + return includes + + includes.append('-I' + parent) + inc = find(parent, 'include') + if inc: + includes.append('-I' + inc) + + return includes + +class C(Linter): + language = 'c' + cmd = ('clang',) + args = ('-xc', '-fsyntax-only', '-std=c99', '-Werror', '-pedantic') + regex = ( + r'^:(?P\d+):(?P\d+):' + r'(?:(?P[{}0-9:\-]+):)?\s+' + r'(?P.+)' + ) + defaults = { + 'cmd': cmd, + 'args': args, + 'include': [], + } + + def run(self, cmd, code): + cmd = tuple(self.settings.get('cmd'),) or self.cmd + cmd += tuple(self.settings.get('args', [])) + for include in self.settings.get('include', []): + cmd += ('-I{}'.format(include),) + cmd += ('-',) + tuple(find_includes(self.filename)) + return super().communicate(cmd, code) + +class CPP(C): + language = 'c++' + cmd = ('clang++',) + args = ('-xc++', '-fsyntax-only', '-std=c++11', '-Werror', '-pedantic') + defaults = { + 'cmd': cmd, + 'args': args, + 'include': [], + } diff --git a/linters/coffee.py b/linters/coffee.py new file mode 100644 index 0000000..a085718 --- /dev/null +++ b/linters/coffee.py @@ -0,0 +1,6 @@ +from lint import Linter + +class Coffee(Linter): + language = 'coffeescript' + cmd = ('coffee', '--compile', '--stdio') + regex = r'^[A-Za-z]+: (?P.+) on line (?P\d+)' diff --git a/linters/css.py b/linters/css.py new file mode 100644 index 0000000..b933e3d --- /dev/null +++ b/linters/css.py @@ -0,0 +1,13 @@ +from lint import Linter + +class CSS(Linter): + language = 'css' + cmd = ('csslint',) + regex = ( + r'^\d+: (?P(error|warning)) at line (?P\d+), col (?P\d+)$\W' + r'^(?P.*)$' + ) + multiline = True + + def run(self, cmd, code): + return self.tmpfile(cmd, code, suffix='.css') diff --git a/linters/eclim.py b/linters/eclim.py new file mode 100644 index 0000000..b9a960a --- /dev/null +++ b/linters/eclim.py @@ -0,0 +1,69 @@ +import json +import os +import tempfile + +from lint import Linter +from sublimelint.lint.util import find + +class Eclim(Linter): + language = 'java' + cmd = ('eclim', '-command', 'java_src_update') + regex = r'.' + + defaults = { + 'disable': True, + } + + def run(self, cmd, code): + project = find(os.path.dirname(self.filename), '.project', True) + if not project: + return + + filename = self.filename.replace(project, '', 1).lstrip(os.sep) + project = os.path.basename(project) + + # can't stdin or temp use file - hack time? + # this *could* become a tmp directory + # but I'd need to know all files to copy + # from the source project + tmp = tempfile.mktemp() + os.rename(self.filename, tmp) + # at least we get some inode protection on posix + inode = None + + with open(self.filename, 'wb') as f: + f.write(code) + if os.name == 'posix': + inode = os.stat(self.filename).st_ino + + try: + cmd = cmd + ('-p', project, '-f', filename, '-v') + output = self.communicate(cmd, '') + finally: + if inode is not None: + new_inode = os.stat(self.filename).st_ino + if new_inode != inode: + # they saved over our tmp file, bail + return output + + os.unlink(self.filename) + os.rename(tmp, self.filename) + + return output + + def find_errors(self, output): + try: + obj = json.loads(output) + for item in obj: + # TODO: highlight warnings in a different color? + # warning = item['warning'] + line, col = item['line']-1, item['column']-1 + message = item['message'] + yield True, line, col, message, None + except Exception: + error = 'eclim error' + if 'Connection refused' in output: + error += ' Connection Refused' + yield True, 0, None, error, None + # maybe do this on line one? + # yield {"eclim_exception": str(e)} diff --git a/linters/go.py b/linters/go.py new file mode 100644 index 0000000..a0a98fb --- /dev/null +++ b/linters/go.py @@ -0,0 +1,31 @@ +import os +from lint import Linter + +def find_files(root, ext): + root = root.rstrip(os.sep) + os.sep + ret = [] + for base, dirs, names in os.walk(root): + for name in names: + if name.endswith(ext): + base = base.replace(root, '', 1) + ret.append(os.path.join(base, name)) + return ret + +class Golang(Linter): + language = 'go' + cmd = ('go', 'build', '-gcflags', '-e -N') + regex = r'.+?:(?P\d+): (?P.+)' + + def run(self, cmd, code): + code = code.encode('utf8') + path = os.path.split(self.filename)[0] + if not self.filename or not path: + tools = self.popen(('go', 'tool')).communicate()[0].decode('utf8').split('\n') + for compiler in ('6g', '8g'): + if compiler in tools: + return self.tmpfile(('go', 'tool', compiler, '-e', '-o', os.devnull), code, suffix='.go') + else: + os.chdir(path) + files = find_files(path, '.go') + answer = self.tmpdir(cmd, files, code) + return answer diff --git a/linters/haml.py b/linters/haml.py new file mode 100644 index 0000000..cbc2f75 --- /dev/null +++ b/linters/haml.py @@ -0,0 +1,6 @@ +from lint import Linter + +class HAML(Linter): + language = 'ruby haml' + cmd = ('haml', '-c') + regex = r'^.*line (?P\d+):\s*(?P.+)$' diff --git a/linters/html.py b/linters/html.py new file mode 100644 index 0000000..0d5ba9d --- /dev/null +++ b/linters/html.py @@ -0,0 +1,6 @@ +from lint import Linter + +class HTML(Linter): + language = 'html' + cmd = ('tidy', '-q', '-e', '-utf8') + regex = r'^line (?P\d+) column (?P\d+) - (Warning|Error)?\s*:?\s*(?P.+)$' diff --git a/linters/java.py b/linters/java.py new file mode 100644 index 0000000..89bff50 --- /dev/null +++ b/linters/java.py @@ -0,0 +1,14 @@ +from lint import Linter + +class Java(Linter): + language = 'java' + cmd = ('javac', '-Xlint') + regex = r'^[^:]+:(?P\d+): (?P.*)$' + + # this linter doesn't work very well with projects/imports + defaults = { + 'disable': True, + } + + def run(self, *args): + return self.tmpfile(*args, suffix='.java') diff --git a/linters/javascript.py b/linters/javascript.py new file mode 100644 index 0000000..f0abc67 --- /dev/null +++ b/linters/javascript.py @@ -0,0 +1,10 @@ +from lint import Linter + +class JavaScript(Linter): + language = 'javascript' + cmd = ('jsl', '-stdin') + regex = r'^\((?P\d+)\):\s+(?P.+)' + +class EmbeddedJS(JavaScript): + language = 'html' + selector = 'source.js.embedded.html' diff --git a/linters/lua.py b/linters/lua.py new file mode 100644 index 0000000..6677248 --- /dev/null +++ b/linters/lua.py @@ -0,0 +1,9 @@ +from lint import Linter + +class Lua(Linter): + language = ('coronasdklua', 'lua') + cmd = ('luac', '-p') + regex = '^luac: [^:]+:(?P\d+): (?P.+)' + + def run(self, cmd, code): + return self.tmpfile(cmd, code, suffix='.lua') diff --git a/linters/nasm.py b/linters/nasm.py new file mode 100644 index 0000000..a5b078a --- /dev/null +++ b/linters/nasm.py @@ -0,0 +1,10 @@ +from lint import Linter +import os + +class Nasm(Linter): + language = 'x86 assembly' + cmd = ('nasm', '-X', 'gnu', '-I.', '-o', os.devnull) + regex = r'^[^:]+:(?P\d+): (?P.*)$' + + def run(self, cmd, code): + return self.tmpfile(cmd, code, suffix='.asm') diff --git a/linters/perl.py b/linters/perl.py new file mode 100644 index 0000000..73e33f5 --- /dev/null +++ b/linters/perl.py @@ -0,0 +1,6 @@ +from lint import Linter + +class Perl(Linter): + language = 'perl' + cmd = ('perl', '-c') + regex = r'(?P.+?) at .+? line (?P\d+)(, near "(?P.+?)")?' diff --git a/linters/php.py b/linters/php.py new file mode 100644 index 0000000..e427f3b --- /dev/null +++ b/linters/php.py @@ -0,0 +1,14 @@ +from lint import Linter + +class PHP(Linter): + language = ('php', 'html') + cmd = ('php', '-l', '-n', '-d display_errors=On') + regex = r'^Parse error:\s*(?Pparse|syntax) error,?\s*(?P.+?)?\s+in\s+.+?\s*line\s+(?P\d+)' + + def match_error(self, r, line): + match, row, col, error, near = super().match_error(r, line) + + if match and match.group('type') == 'parse' and not error: + error = 'parse error' + + return match, row, col, error, near diff --git a/linters/puppet.py b/linters/puppet.py new file mode 100644 index 0000000..3445f63 --- /dev/null +++ b/linters/puppet.py @@ -0,0 +1,9 @@ +from lint import Linter + +class Puppet(Linter): + language = 'puppet' + cmd = ('puppet', 'parser', 'validate', '--color=false') + regex = r'^([^:]+:){2}\s*(?P(Syntax error at|Could not match) \'?(?P[^ ]*?)\'?.*) at [^:]*:(?P\d+)$' + + def run(self, cmd, code): + return self.tmpfile(cmd, code, suffix='.puppet') diff --git a/linters/python.py b/linters/python.py new file mode 100644 index 0000000..57a6193 --- /dev/null +++ b/linters/python.py @@ -0,0 +1,28 @@ +import sublime +import sys + +from lint import Linter +from sublimelint.lint.util import which + +class Python(Linter): + language = 'python' + cmd = 'pyflakes' + regex = r'^.+:(?P\d+):\s*(?P.+)' + + def run(self, cmd, code): + python3 = False + if (self.filename or '').startswith(sublime.packages_path()): + if sys.version_info >= (3, 0): + python3 = True + + first_line = code.split('\n', 1)[0] + if first_line.startswith('#!') and 'python3' in first_line: + python3 = True + + if python3 and which('python3'): + # python 3 + pyflakes = which('pyflakes') + cmd = ('python3', pyflakes) + return self.communicate(cmd, code) + else: + return self.communicate(cmd, code) diff --git a/linters/ruby.py b/linters/ruby.py new file mode 100644 index 0000000..1acbfc2 --- /dev/null +++ b/linters/ruby.py @@ -0,0 +1,6 @@ +from lint import Linter + +class Ruby(Linter): + language = 'ruby' + cmd = ('ruby', '-wc') + regex = r'^.+:(?P\d+):\s+(?P.+)' diff --git a/linters/todo.py b/linters/todo.py new file mode 100644 index 0000000..4266f18 --- /dev/null +++ b/linters/todo.py @@ -0,0 +1,18 @@ +from lint import Linter + +class TODO(Linter): + scope = 'string' + selector = 'comment' + outline = False + + @classmethod + def can_lint(cls, language): + return True + + def lint(self): + lines = self.code.split('\n') + for i in range(len(lines)): + if 'TODO' in lines[i]: + todo = lines[i].index('TODO') + self.highlight.range(i, todo, 4) + self.error(i, 'TODO') diff --git a/linters/xml.py b/linters/xml.py new file mode 100644 index 0000000..f89fe94 --- /dev/null +++ b/linters/xml.py @@ -0,0 +1,6 @@ +from lint import Linter + +class XML(Linter): + language = 'xml' + cmd = ('xmllint', '-noout', '-') + regex = r'^.+:(?P\d+):\s+(parser error : )?(?P.+)' diff --git a/quotes.sublime-macro b/quotes.sublime-macro new file mode 100644 index 0000000..88a885d --- /dev/null +++ b/quotes.sublime-macro @@ -0,0 +1,9 @@ +[ + { + "args": + { + "contents": "\"$0\"" + }, + "command": "insert_snippet" + } +] diff --git a/simplequotes.sublime-macro b/simplequotes.sublime-macro new file mode 100644 index 0000000..411dcad --- /dev/null +++ b/simplequotes.sublime-macro @@ -0,0 +1,9 @@ +[ + { + "args": + { + "contents": "'$0'" + }, + "command": "insert_snippet" + } +]