├── .gitignore
├── SBTError.hidden-tmLanguage
├── LICENSE
├── errorreporter.py
├── SublimeSBT.sublime-settings
├── util.py
├── SBTOutput.hidden-tmLanguage
├── Default (Linux).sublime-keymap
├── Default (Windows).sublime-keymap
├── Default.sublime-commands
├── sbterror.py
├── SBTOutput.hidden-tmTheme
├── errorview.py
├── sbtsettings.py
├── errormarker.py
├── Default (OSX).sublime-keymap
├── project.py
├── errorreport.py
├── Main.sublime-menu
├── highlighter.py
├── sbtview.py
├── outputmon.py
├── README.md
├── sbtrunner.py
└── sublimesbt.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | package-metadata.json
3 | *tmLanguage.json
4 | *tmTheme.json
5 | *.cache
6 |
--------------------------------------------------------------------------------
/SBTError.hidden-tmLanguage:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | name
6 | SBT Error
7 | patterns
8 |
9 |
10 | match
11 | -- Error --
12 | name
13 | entity.label.error.sbt-error
14 |
15 |
16 | match
17 | -- Test Failure --
18 | name
19 | entity.label.error.sbt-error
20 |
21 |
22 | match
23 | -- Warning --
24 | name
25 | entity.label.warning.sbt-error
26 |
27 |
28 | scopeName
29 | error.sbt
30 | uuid
31 | dc4ce12f-5399-4d33-947e-fc339c5d567f
32 |
33 |
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Jason Arhart
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/errorreporter.py:
--------------------------------------------------------------------------------
1 | try:
2 | from .errormarker import ErrorMarker
3 | from .util import delayed
4 | except(ValueError):
5 | from errormarker import ErrorMarker
6 | from util import delayed
7 |
8 |
9 | class ErrorReporter(object):
10 |
11 | def __init__(self, window, error_report, settings):
12 | self._marker = ErrorMarker(window, error_report, settings)
13 | self._error_report = error_report
14 |
15 | def error(self, error):
16 | self._error_report.add_error(error)
17 | self._marker.mark_error(error)
18 | self._marker.update_status()
19 |
20 | def finish(self):
21 | self._error_report.cycle()
22 | self._marker.mark_errors()
23 |
24 | def clear(self):
25 | self._error_report.clear()
26 | self._marker.clear()
27 |
28 | def show_errors(self):
29 | self._marker.mark_errors()
30 |
31 | def show_errors_in(self, filename):
32 | self._marker.mark_errors_in(filename)
33 |
34 | def hide_errors_in(self, filename):
35 | self._error_report.clear_file(filename)
36 | self._marker.hide_errors_in(filename)
37 |
38 | def update_status(self):
39 | self._marker.update_status()
40 |
41 | def update_status_now(self):
42 | self._marker.update_status_now()
43 |
--------------------------------------------------------------------------------
/SublimeSBT.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | // An array representing the command line to use to start sbt. Depending on
3 | // your setup you may need to put the full path to the file here.
4 | "sbt_command": ["sbt"],
5 |
6 | // An array representing the command line to use to start sbt for a Play
7 | // Framework project. Depending on your setup you may need to put the full
8 | // path to the file here.
9 | "play_command": ["play"],
10 |
11 | // A string representing the sbt command to use to run tests.
12 | "test_command": "test",
13 |
14 | // A string representing the sbt command to use to run the project.
15 | "run_command": "run",
16 |
17 | // The output encoding to use when running sbt
18 | "encoding": "UTF-8",
19 |
20 | // How to mark errors in the source code.
21 | "error_marking": {
22 |
23 | // The mark style to us. Valid values are "dot", "outline", or "both".
24 | "style": "both",
25 |
26 | // The scope used to color the outline.
27 | "scope": "invalid.illegal"
28 | },
29 |
30 | "failure_marking": {
31 | "style": "both",
32 | "scope": "invalid.illegal"
33 | },
34 |
35 | "warning_marking": {
36 | "style": "both",
37 | "scope": "source.scala"
38 | },
39 |
40 | // The color scheme to use for the output panel.
41 | "color_scheme": "Packages/SublimeSBT/SBTOutput.hidden-tmTheme",
42 |
43 | // The maximum number of unique entries to keep in the command history
44 | "history_length": 20
45 | }
46 |
--------------------------------------------------------------------------------
/util.py:
--------------------------------------------------------------------------------
1 | import sublime
2 |
3 | import functools
4 | import itertools
5 | import threading
6 |
7 |
8 | def maybe(value):
9 | if value is not None:
10 | yield value
11 |
12 |
13 | def group_by(xs, kf):
14 | grouped = {}
15 | for k, i in itertools.groupby(xs, kf):
16 | grouped.setdefault(k, []).extend(i)
17 | return grouped
18 |
19 |
20 | class delayed(object):
21 |
22 | def __init__(self, timeout):
23 | self.timeout = timeout
24 |
25 | def __call__(self, f):
26 |
27 | def call_with_timeout(*args, **kwargs):
28 | sublime.set_timeout(functools.partial(f, *args, **kwargs),
29 | self.timeout)
30 |
31 | return call_with_timeout
32 |
33 |
34 | class SynchronizedCache(object):
35 |
36 | def __init__(self):
37 | self.__items = {}
38 | self.__lock = threading.RLock()
39 |
40 | def __call__(self, key, f):
41 | with self.__lock:
42 | if key not in self.__items:
43 | self.__items[key] = f()
44 | return self.__items[key]
45 |
46 |
47 | class MetaOnePerWindow(type):
48 |
49 | def __init__(cls, name, bases, dct):
50 | super(MetaOnePerWindow, cls).__init__(name, bases, dct)
51 | cls.instance_cache = SynchronizedCache()
52 |
53 | def __call__(cls, window):
54 | return cls.instance_cache(window.id(), lambda: type.__call__(cls, window))
55 |
56 |
57 | OnePerWindow = MetaOnePerWindow('OnePerWindow', (object,), {})
58 |
--------------------------------------------------------------------------------
/SBTOutput.hidden-tmLanguage:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | name
6 | SBT Output
7 | patterns
8 |
9 |
10 | captures
11 |
12 | 1
13 |
14 | name
15 | entity.label.success.sbt-output
16 |
17 |
18 | match
19 | ^\[(success)\]
20 | name
21 | meta.label.sbt-output
22 |
23 |
24 | captures
25 |
26 | 1
27 |
28 | name
29 | entity.label.error.sbt-output
30 |
31 |
32 | match
33 | ^\[(error)\]
34 | name
35 | meta.label.sbt-output
36 |
37 |
38 | captures
39 |
40 | 1
41 |
42 | name
43 | entity.label.warning.sbt-output
44 |
45 |
46 | match
47 | ^\[(warn)\]
48 | name
49 | meta.label.sbt-output
50 |
51 |
52 | captures
53 |
54 | 1
55 |
56 | name
57 | entity.label.other.sbt-output
58 |
59 |
60 | match
61 | ^\[([a-z]+)\]
62 | name
63 | meta.label.sbt-output
64 |
65 |
66 | scopeName
67 | output.sbt
68 | uuid
69 | aeb00e40-9cfb-4b4d-96b3-767d9381eaff
70 |
71 |
72 |
--------------------------------------------------------------------------------
/Default (Linux).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | { "keys": ["ctrl+shift+o"], "command": "show_sbt" },
3 |
4 | { "keys": ["ctrl+alt+l"], "command": "list_sbt_errors" },
5 | { "keys": ["ctrl+alt+n"], "command": "next_sbt_error" },
6 | { "keys": ["ctrl+alt+o"], "command": "show_sbt_error_output" },
7 |
8 | { "keys": ["ctrl+alt+h"], "command": "sbt_show_history" },
9 | { "keys": ["ctrl+alt+shift+h"], "command": "sbt_show_history", "args": {"editable": true} },
10 |
11 | { "keys": ["enter"], "command": "sbt_submit", "context": [{ "key": "in_sbt_view" }] },
12 | { "keys": ["ctrl+d"], "command": "sbt_eot", "context": [{ "key": "in_sbt_view" }] },
13 |
14 | { "keys": ["backspace"], "command": "sbt_delete_left", "context": [{ "key": "in_sbt_view" }] },
15 | { "keys": ["shift+backspace"], "command": "sbt_delete_left", "context": [{ "key": "in_sbt_view" }] },
16 |
17 | { "keys": ["ctrl+backspace"], "command": "sbt_delete_word_left", "context": [{ "key": "in_sbt_view" }] },
18 | { "keys": ["ctrl+delete"], "command": "sbt_delete_word_right", "context": [{ "key": "in_sbt_view" }] },
19 |
20 | // Disable keys that can modify the output
21 | { "keys": ["ctrl+shift+backspace"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
22 | { "keys": ["ctrl+x"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
23 | { "keys": ["ctrl+shift+k"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
24 | { "keys": ["ctrl+shift+up"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
25 | { "keys": ["ctrl+shift+down"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
26 | { "keys": ["ctrl+forward_slash"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
27 | { "keys": ["ctrl+shift+forward_slash"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
28 | { "keys": ["ctrl+j"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
29 | { "keys": ["ctrl+shift+d"], "command": "noop", "context": [{ "key": "in_sbt_view" }] }
30 | ]
31 |
--------------------------------------------------------------------------------
/Default (Windows).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | { "keys": ["ctrl+shift+o"], "command": "show_sbt" },
3 |
4 | { "keys": ["ctrl+alt+l"], "command": "list_sbt_errors" },
5 | { "keys": ["ctrl+alt+n"], "command": "next_sbt_error" },
6 | { "keys": ["ctrl+alt+o"], "command": "show_sbt_error_output" },
7 |
8 | { "keys": ["ctrl+alt+h"], "command": "sbt_show_history" },
9 | { "keys": ["ctrl+alt+shift+h"], "command": "sbt_show_history", "args": {"editable": true} },
10 |
11 | { "keys": ["enter"], "command": "sbt_submit", "context": [{ "key": "in_sbt_view" }] },
12 | { "keys": ["ctrl+d"], "command": "sbt_eot", "context": [{ "key": "in_sbt_view" }] },
13 |
14 | { "keys": ["backspace"], "command": "sbt_delete_left", "context": [{ "key": "in_sbt_view" }] },
15 | { "keys": ["shift+backspace"], "command": "sbt_delete_left", "context": [{ "key": "in_sbt_view" }] },
16 |
17 | { "keys": ["ctrl+backspace"], "command": "sbt_delete_word_left", "context": [{ "key": "in_sbt_view" }] },
18 | { "keys": ["ctrl+delete"], "command": "sbt_delete_word_right", "context": [{ "key": "in_sbt_view" }] },
19 |
20 | // Disable keys that can modify the output
21 | { "keys": ["ctrl+shift+backspace"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
22 | { "keys": ["ctrl+x"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
23 | { "keys": ["ctrl+shift+k"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
24 | { "keys": ["ctrl+shift+up"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
25 | { "keys": ["ctrl+shift+down"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
26 | { "keys": ["ctrl+forward_slash"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
27 | { "keys": ["ctrl+shift+forward_slash"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
28 | { "keys": ["ctrl+j"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
29 | { "keys": ["ctrl+shift+d"], "command": "noop", "context": [{ "key": "in_sbt_view" }] }
30 | ]
31 |
--------------------------------------------------------------------------------
/Default.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | { "caption": "SBT: Start SBT", "command": "start_sbt" },
3 | { "caption": "SBT: Stop SBT", "command": "stop_sbt" },
4 | { "caption": "SBT: Kill SBT", "command": "kill_sbt" },
5 | { "caption": "SBT: Show SBT Output", "command": "show_sbt" },
6 | { "caption": "SBT: Compile", "command": "sbt", "args": {"command": "compile"} },
7 | { "caption": "SBT: Test:Compile", "command": "sbt", "args": {"command": "test:compile"} },
8 | { "caption": "SBT: Test", "command": "sbt_test" },
9 | { "caption": "SBT: Test-Only", "command": "sbt_test_only" },
10 | { "caption": "SBT: Test-Quick", "command": "sbt_test_quick" },
11 | { "caption": "SBT: Run", "command": "sbt_run" },
12 | { "caption": "SBT: Package", "command": "sbt", "args": {"command": "package"} },
13 | { "caption": "SBT: Start Console", "command": "sbt", "args": {"command": "console"} },
14 | { "caption": "SBT: Start Continuous Compiling", "command": "sbt", "args": {"command": "~ compile"} },
15 | { "caption": "SBT: Start Continuous Test:Compiling", "command": "sbt", "args": {"command": "~ test:compile"} },
16 | { "caption": "SBT: Start Continuous Testing", "command": "sbt_continuous_test" },
17 | { "caption": "SBT: Start Continuous Test-Only", "command": "sbt_continuous_test_only" },
18 | { "caption": "SBT: Start Continuous Test-Quick", "command": "sbt_continuous_test_quick" },
19 | { "caption": "SBT: Reload", "command": "sbt_reload" },
20 | { "caption": "SBT: Show Error List", "command": "list_sbt_errors" },
21 | { "caption": "SBT: Show Next Error", "command": "next_sbt_error" },
22 | { "caption": "SBT: Clear Errors", "command": "clear_sbt_errors" },
23 | { "caption": "SBT: Show Error Output", "command": "show_sbt_error_output" },
24 | { "caption": "SBT: Show History", "command": "sbt_show_history" },
25 | { "caption": "SBT: Show History and Edit", "command": "sbt_show_history", "args": {"editable": true} },
26 | { "caption": "SBT: Clear History", "command": "sbt_clear_history" }
27 | ]
28 |
--------------------------------------------------------------------------------
/sbterror.py:
--------------------------------------------------------------------------------
1 | try:
2 | from .util import delayed
3 | except(ValueError):
4 | from util import delayed
5 |
6 | from threading import Event
7 |
8 | import re
9 |
10 | class SbtError(object):
11 |
12 | def __init__(self, project, filename, line, message, error_type, extra_lines):
13 | self.line = int(line)
14 | if len(extra_lines) > 0 and re.match(r' *^', extra_lines[-1]):
15 | self.column_spec = ':%i' % len(extra_lines[-1])
16 | else:
17 | self.column_spec = ''
18 | self.message = message
19 | self.error_type = error_type
20 | self.__finished = Event()
21 | self.__finish(project, filename, extra_lines)
22 |
23 | @property
24 | def filename(self):
25 | self.__finished.wait()
26 | return self.__filename
27 |
28 | @property
29 | def relative_path(self):
30 | self.__finished.wait()
31 | return self.__relative_path
32 |
33 | @property
34 | def text(self):
35 | self.__finished.wait()
36 | return self.__text
37 |
38 | def list_item(self):
39 | return [self.message, '%s:%i%s' % (self.relative_path, self.line, self.column_spec)]
40 |
41 | def encoded_position(self):
42 | return '%s:%i%s' % (self.filename, self.line, self.column_spec)
43 |
44 | @delayed(0)
45 | def __finish(self, project, filename, extra_lines):
46 | try:
47 | self.__filename = project.expand_filename(filename)
48 | if self.__filename:
49 | self.__relative_path = project.relative_path(self.__filename)
50 | if self.error_type == 'failure':
51 | self.__text = '%s (%s:%i)' % (self.message, filename, self.line)
52 | else:
53 | extra_lines.insert(0, '%s:%i: %s' % (self.__relative_path, self.line, self.message))
54 | self.__text = '\n'.join(extra_lines)
55 | finally:
56 | self.__finished.set()
57 |
--------------------------------------------------------------------------------
/SBTOutput.hidden-tmTheme:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | name
6 | SBT Output
7 | settings
8 |
9 |
10 | settings
11 |
12 | background
13 | #000000
14 | caret
15 | #999999
16 | foreground
17 | #DDDDDD
18 | invisibles
19 | #333333
20 | lineHighlight
21 | #222222
22 | selection
23 | #444444
24 |
25 |
26 |
27 | name
28 | Success Label
29 | scope
30 | entity.label.success.sbt-output
31 | settings
32 |
33 | foreground
34 | #00EE00
35 |
36 |
37 |
38 | name
39 | Error Label
40 | scope
41 | entity.label.error.sbt-output, entity.label.error.sbt-error
42 | settings
43 |
44 | foreground
45 | #FF0000
46 |
47 |
48 |
49 | name
50 | Warning Label
51 | scope
52 | entity.label.warning.sbt-output, entity.label.warning.sbt-error
53 | settings
54 |
55 | foreground
56 | #DDDD00
57 |
58 |
59 |
60 | name
61 | Output Label
62 | scope
63 | meta.label.sbt-output
64 | settings
65 |
66 | foreground
67 | #AAAAAA
68 |
69 |
70 |
71 | uuid
72 | 7b2b9f2b-a2d6-4270-8639-8c898d0fe0ef
73 |
74 |
75 |
--------------------------------------------------------------------------------
/errorview.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import sublime_plugin
3 |
4 | try:
5 | from .sbtsettings import SBTSettings
6 | from .util import OnePerWindow
7 | except(ValueError):
8 | from sbtsettings import SBTSettings
9 | from util import OnePerWindow
10 |
11 |
12 | class ErrorView(OnePerWindow):
13 |
14 | error_type_display = {
15 | 'error': 'Error',
16 | 'warning': 'Warning',
17 | 'failure': 'Test Failure'
18 | }
19 |
20 | def __init__(self, window):
21 | self.window = window
22 | self.settings = SBTSettings(window)
23 | self.panel = self.window.get_output_panel('sbt_error')
24 | self.panel.set_read_only(True)
25 | self.panel.settings().set('line_numbers', False)
26 | self.panel.settings().set('gutter', False)
27 | self.panel.settings().set('scroll_past_end', False)
28 | self.panel.set_syntax_file("Packages/SublimeSBT/SBTError.hidden-tmLanguage")
29 | self._update_panel_colors()
30 | self.settings.add_on_change(self._update_panel_colors)
31 |
32 | def show(self):
33 | self._update_panel_colors()
34 | self.window.run_command('show_panel', {'panel': 'output.sbt_error'})
35 |
36 | def hide(self):
37 | self.window.run_command('hide_panel', {'panel': 'output.sbt_error'})
38 |
39 | def show_error(self, error):
40 | self.show()
41 | self.panel.run_command('sbt_show_error_text',
42 | {'text': self._error_text(error)})
43 | self.panel.sel().clear()
44 | self.panel.show(0)
45 |
46 | def clear(self):
47 | self.panel.run_command('sbt_show_error_text', {'text': ''})
48 | self.panel.sel().clear()
49 |
50 | def _error_text(self, error):
51 | banner = ' -- %s --' % type(self).error_type_display[error.error_type]
52 | return '%s\n%s' % (banner, error.text)
53 |
54 | def _update_panel_colors(self):
55 | self.panel.settings().set('color_scheme', self.settings.get('color_scheme'))
56 |
57 |
58 | class SbtShowErrorTextCommand(sublime_plugin.TextCommand):
59 |
60 | def run(self, edit, text):
61 | self.view.set_read_only(False)
62 | self.view.replace(edit, sublime.Region(0, self.view.size()), text)
63 | self.view.set_read_only(True)
64 |
--------------------------------------------------------------------------------
/sbtsettings.py:
--------------------------------------------------------------------------------
1 | import sublime
2 |
3 | try:
4 | from .util import maybe
5 | except(ValueError):
6 | from util import maybe
7 |
8 |
9 | class SBTSettings(object):
10 |
11 | def __init__(self, window):
12 | self.window = window
13 | self._plugin_settings = sublime.load_settings('SublimeSBT.sublime-settings')
14 | self._migrate_user_config()
15 |
16 | def sbt_command(self):
17 | return self.get('sbt_command')
18 |
19 | def play_command(self):
20 | return self._view_settings().get('sbt_command', self._plugin_settings.get('play_command'))
21 |
22 | def test_command(self):
23 | return self.get('test_command')
24 |
25 | def run_command(self):
26 | return self.get('run_command')
27 |
28 | def mark_style(self, error_type='error'):
29 | self.mark_settings(error_type).get('style')
30 |
31 | def error_scope(self, error_type='error'):
32 | self.mark_settings(error_type).get('scope')
33 |
34 | def color_scheme(self):
35 | return self.get('color_scheme')
36 |
37 | def mark_settings(self, error_type='error'):
38 | for settings in maybe(self.get('%s_marking' % error_type)):
39 | return settings
40 | return self.global_mark_settings()
41 |
42 | def global_mark_settings(self):
43 | return {
44 | 'style': self.get('mark_style'),
45 | 'scope': self.get('error_scope')
46 | }
47 |
48 | def get(self, name):
49 | return self._view_settings().get(name, self._plugin_settings.get(name))
50 |
51 | def add_on_change(self, on_change):
52 | self._plugin_settings.add_on_change('SublimeSBT', on_change)
53 |
54 | def _view_settings(self):
55 | for view in maybe(self.window.active_view()):
56 | return view.settings().get('SublimeSBT', {})
57 | return {}
58 |
59 | def _migrate_user_config(self):
60 | style = self._plugin_settings.get('mark_style', None)
61 | scope = self._plugin_settings.get('error_scope', None)
62 | if style is not None or scope is not None:
63 | for key in ('%s_marking' % t for t in ('error', 'failure', 'warning')):
64 | mark_settings = self._plugin_settings.get(key, {})
65 | if style is not None:
66 | mark_settings['style'] = style
67 | if scope is not None:
68 | mark_settings['scope'] = scope
69 | self._plugin_settings.set(key, mark_settings)
70 | self._plugin_settings.erase('mark_style')
71 | self._plugin_settings.erase('error_scope')
72 | sublime.save_settings('SublimeSBT.sublime-settings')
73 |
--------------------------------------------------------------------------------
/errormarker.py:
--------------------------------------------------------------------------------
1 | try:
2 | from .highlighter import CodeHighlighter
3 | from .util import delayed, maybe
4 | except(ValueError):
5 | from highlighter import CodeHighlighter
6 | from util import delayed, maybe
7 |
8 |
9 | class ErrorMarker(object):
10 |
11 | def __init__(self, window, error_report, settings):
12 | self._window = window
13 | self._error_report = error_report
14 | self.__settings = settings
15 | self.__highlighter = None
16 | settings.add_on_change(self.mark_errors)
17 |
18 | @delayed(0)
19 | def mark_errors(self):
20 | for view in self._window.views():
21 | errors = self._error_report.sorted_errors_in(view.file_name())
22 | self._mark_errors_in_view(view, errors)
23 |
24 | @delayed(0)
25 | def mark_errors_in(self, filename):
26 | errors = self._error_report.sorted_errors_in(filename)
27 | for view in self._file_views(filename):
28 | self._mark_errors_in_view(view, errors)
29 |
30 | @delayed(0)
31 | def hide_errors_in(self, filename):
32 | for view in self._file_views(filename):
33 | self._highlighter.clear(view)
34 |
35 | @delayed(0)
36 | def mark_error(self, error):
37 | for view in self._file_views(error.filename):
38 | self._highlighter.highlight(view, [error])
39 |
40 | @delayed(0)
41 | def clear(self):
42 | for view in self._window.views():
43 | self._highlighter.clear(view)
44 |
45 | @delayed(0)
46 | def update_status(self):
47 | self.update_status_now()
48 |
49 | def update_status_now(self):
50 | for view in maybe(self._window.active_view()):
51 | self._highlighter.set_status_message(view, self._status_message(view))
52 |
53 | def _mark_errors_in_view(self, view, errors):
54 | if errors and not view.is_dirty():
55 | self._highlighter.highlight(view, errors, replace=True)
56 | else:
57 | self._highlighter.clear(view)
58 |
59 | def _status_message(self, view):
60 | for errors in maybe(self._line_errors(view)):
61 | return '(%s)' % ')('.join([e.message for e in errors])
62 |
63 | def _file_views(self, filename):
64 | for view in self._window.views():
65 | if filename == view.file_name():
66 | yield view
67 |
68 | def _line_errors(self, view):
69 | row, _ = view.rowcol(view.sel()[0].begin())
70 | return self._error_report.errors_at(view.file_name(), row + 1)
71 |
72 | def _current_error_in_view(self, view):
73 | return self._error_report.current_error_in(view.file_name())
74 |
75 | @property
76 | def _highlighter(self):
77 | if self.__highlighter is None:
78 | self.__highlighter = CodeHighlighter(self.__settings, self._current_error_in_view)
79 | return self.__highlighter
80 |
--------------------------------------------------------------------------------
/Default (OSX).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | { "keys": ["ctrl+shift+o"], "command": "show_sbt" },
3 |
4 | { "keys": ["ctrl+alt+l"], "command": "list_sbt_errors" },
5 | { "keys": ["ctrl+alt+n"], "command": "next_sbt_error" },
6 | { "keys": ["ctrl+alt+o"], "command": "show_sbt_error_output" },
7 |
8 | { "keys": ["ctrl+alt+h"], "command": "sbt_show_history" },
9 | { "keys": ["ctrl+alt+shift+h"], "command": "sbt_show_history", "args": {"editable": true} },
10 |
11 | { "keys": ["enter"], "command": "sbt_submit", "context": [{ "key": "in_sbt_view" }] },
12 | { "keys": ["ctrl+d"], "command": "sbt_eot", "context": [{ "key": "in_sbt_view" }] },
13 |
14 | { "keys": ["backspace"], "command": "sbt_delete_left", "context": [{ "key": "in_sbt_view" }] },
15 | { "keys": ["shift+backspace"], "command": "sbt_delete_left", "context": [{ "key": "in_sbt_view" }] },
16 | { "keys": ["ctrl+shift+backspace"], "command": "sbt_delete_left", "context": [{ "key": "in_sbt_view" }] },
17 |
18 | { "keys": ["ctrl+backspace"], "command": "sbt_delete_word_left", "context": [{ "key": "in_sbt_view" }] },
19 | { "keys": ["alt+backspace"], "command": "sbt_delete_word_left", "context": [{ "key": "in_sbt_view" }] },
20 | { "keys": ["ctrl+alt+backspace"], "command": "sbt_delete_word_left", "context": [{ "key": "in_sbt_view" }] },
21 | { "keys": ["alt+shift+backspace"], "command": "sbt_delete_word_left", "context": [{ "key": "in_sbt_view" }] },
22 |
23 | { "keys": ["ctrl+delete"], "command": "sbt_delete_word_right", "context": [{ "key": "in_sbt_view" }] },
24 | { "keys": ["alt+delete"], "command": "sbt_delete_word_right", "context": [{ "key": "in_sbt_view" }] },
25 | { "keys": ["ctrl+alt+delete"], "command": "sbt_delete_word_right", "context": [{ "key": "in_sbt_view" }] },
26 | { "keys": ["alt+shift+delete"], "command": "sbt_delete_word_right", "context": [{ "key": "in_sbt_view" }] },
27 |
28 | { "keys": ["super+backspace"], "command": "sbt_delete_bol", "context": [{ "key": "in_sbt_view" }] },
29 | { "keys": ["super+shift+backspace"], "command": "sbt_delete_bol", "context": [{ "key": "in_sbt_view" }] },
30 |
31 | // Disable keys that can modify the output
32 | { "keys": ["ctrl+alt+shift+backspace"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
33 | { "keys": ["ctrl+x"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
34 | { "keys": ["ctrl+shift+k"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
35 | { "keys": ["super+k", "super+backspace"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
36 | { "keys": ["ctrl+super+up"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
37 | { "keys": ["ctrl+super+down"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
38 | { "keys": ["super+forward_slash"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
39 | { "keys": ["super+alt+forward_slash"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
40 | { "keys": ["super+j"], "command": "noop", "context": [{ "key": "in_sbt_view" }] },
41 | { "keys": ["super+shift+d"], "command": "noop", "context": [{ "key": "in_sbt_view" }] }
42 | ]
43 |
--------------------------------------------------------------------------------
/project.py:
--------------------------------------------------------------------------------
1 | import sublime
2 |
3 | try:
4 | from .sbtsettings import SBTSettings
5 | from .errorreport import ErrorReport
6 | from .errorreporter import ErrorReporter
7 | from .util import maybe, OnePerWindow
8 | except(ValueError):
9 | from sbtsettings import SBTSettings
10 | from errorreport import ErrorReport
11 | from errorreporter import ErrorReporter
12 | from util import maybe, OnePerWindow
13 |
14 | import os
15 | import re
16 | from glob import glob
17 |
18 |
19 | class Project(OnePerWindow):
20 |
21 | def __init__(self, window):
22 | self.window = window
23 | self.settings = SBTSettings(window)
24 | self.error_report = ErrorReport()
25 | self.error_reporter = ErrorReporter(window,
26 | self.error_report,
27 | self.settings)
28 |
29 | def project_root(self):
30 | for folder in self.window.folders():
31 | if self._is_sbt_folder(folder):
32 | return folder
33 |
34 | def is_sbt_project(self):
35 | return self.project_root() is not None
36 |
37 | def is_play_project(self):
38 | for root in maybe(self.project_root()):
39 | if self._play_build_files(root):
40 | return True
41 |
42 | def sbt_command(self):
43 | if self.is_play_project():
44 | return self.settings.play_command()
45 | else:
46 | return self.settings.sbt_command()
47 |
48 | def setting(self, name):
49 | return self.settings.get(name)
50 |
51 | def expand_filename(self, filename):
52 | if len(os.path.dirname(filename)) > 0:
53 | return filename
54 | else:
55 | return self._find_in_project(filename)
56 |
57 | def relative_path(self, filename):
58 | return os.path.relpath(filename, self.project_root())
59 |
60 | def open_project_file(self, filename, line):
61 | full_path = os.path.join(self.project_root(), filename)
62 | self.window.open_file('%s:%i' % (full_path, line),
63 | sublime.ENCODED_POSITION)
64 |
65 | def _is_sbt_folder(self, folder):
66 | if self._sbt_build_files(folder) or self._scala_build_files(folder):
67 | return True
68 |
69 | def _sbt_build_files(self, folder):
70 | return glob(os.path.join(folder, '*.sbt'))
71 |
72 | def _scala_build_files(self, folder):
73 | return glob(os.path.join(folder, 'project', '*.scala'))
74 |
75 | def _play_build_files(self, folder):
76 | return list(filter(self._is_play_build, self._scala_build_files(folder)))
77 |
78 | def _is_play_build(self, build_path):
79 | try:
80 | with open(build_path, 'r') as build_file:
81 | for line in build_file.readlines():
82 | if re.search(r'\b(?:play\.|Play)Project\b', line):
83 | return True
84 | except:
85 | return False
86 |
87 | def _find_in_project(self, filename):
88 | for path, _, files in os.walk(self.project_root()):
89 | if filename in files:
90 | return os.path.join(path, filename)
91 |
--------------------------------------------------------------------------------
/errorreport.py:
--------------------------------------------------------------------------------
1 | try:
2 | from .sbterror import SbtError
3 | from .util import maybe
4 | except(ValueError):
5 | from sbterror import SbtError
6 | from util import maybe
7 |
8 |
9 | class ErrorReport(object):
10 |
11 | def __init__(self):
12 | self._errors = {}
13 | self._old_errors = {}
14 | self._new_errors = {}
15 | self._set_current(None)
16 |
17 | def clear(self):
18 | self._errors.clear()
19 | self._old_errors.clear()
20 | self._new_errors.clear()
21 | self._set_current(None)
22 |
23 | def add_error(self, error):
24 | if error.filename:
25 | if error.filename not in self._new_errors:
26 | self._new_errors[error.filename] = {}
27 | file_errors = self._new_errors[error.filename]
28 | if error.line not in file_errors:
29 | file_errors[error.line] = []
30 | file_errors[error.line].append(error)
31 | self._merge_errors()
32 |
33 | def cycle(self):
34 | self._old_errors = self._new_errors
35 | self._new_errors = {}
36 | self._merge_errors()
37 |
38 | def all_errors(self):
39 | for filename in sorted(self._errors.keys()):
40 | for error in self.sorted_errors_in(filename):
41 | yield error
42 |
43 | def focus_error(self, error):
44 | for i, e in enumerate(self.all_errors()):
45 | if e == error:
46 | self._set_current(i)
47 |
48 | def next_error(self):
49 | sorted_errors = list(self.all_errors())
50 | if sorted_errors:
51 | if self._index is None:
52 | self._set_current(0)
53 | else:
54 | self._set_current((self._index + 1) % len(sorted_errors))
55 | else:
56 | self._set_current(None)
57 | return self.current_error
58 |
59 | def sorted_errors_in(self, filename):
60 |
61 | def sort_errors(errors):
62 | for line in sorted(errors.keys()):
63 | for error in sorted(errors[line], key=lambda e: e.error_type):
64 | yield error
65 |
66 | for errors in maybe(self.errors_in(filename)):
67 | return list(sort_errors(errors))
68 |
69 | def errors_at(self, filename, line):
70 | for errors in maybe(self.errors_in(filename)):
71 | return errors.get(line)
72 |
73 | def errors_in(self, filename):
74 | return self._errors.get(filename)
75 |
76 | def current_error_in(self, filename):
77 | for error in maybe(self.current_error):
78 | if error.filename == filename:
79 | return error
80 |
81 | def clear_file(self, filename):
82 | for errors in [self._old_errors, self._new_errors, self._errors]:
83 | if filename in errors:
84 | del errors[filename]
85 | if self.current_error_in(filename):
86 | self._set_current(None)
87 |
88 | def has_errors(self):
89 | return len(self._errors) > 0
90 |
91 | def _merge_errors(self):
92 | self._errors = dict(list(self._old_errors.items()) + list(self._new_errors.items()))
93 | self._set_current(None)
94 |
95 | def _set_current(self, i):
96 | sorted_errors = list(self.all_errors())
97 | if i is not None and i < len(sorted_errors):
98 | self._index = i
99 | self.current_error = sorted_errors[i]
100 | else:
101 | self._index = None
102 | self.current_error = None
103 |
--------------------------------------------------------------------------------
/Main.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "Preferences",
4 | "mnemonic": "n",
5 | "id": "preferences",
6 | "children":
7 | [
8 | {
9 | "caption": "Package Settings",
10 | "mnemonic": "P",
11 | "id": "package-settings",
12 | "children":
13 | [
14 | {
15 | "caption": "SublimeSBT",
16 | "children":
17 | [
18 | {
19 | "command": "open_file",
20 | "args": {"file": "${packages}/SublimeSBT/README.md"},
21 | "caption": "README"
22 | },
23 | { "caption": "-" },
24 | {
25 | "command": "open_file", "args":
26 | {
27 | "file": "${packages}/SublimeSBT/SublimeSBT.sublime-settings"
28 | },
29 | "caption": "Settings – Default"
30 | },
31 | {
32 | "command": "open_file", "args":
33 | {
34 | "file": "${packages}/User/SublimeSBT.sublime-settings"
35 | },
36 | "caption": "Settings – User"
37 | },
38 | { "caption": "-" },
39 | {
40 | "command": "open_file",
41 | "args": {
42 | "file": "${packages}/SublimeSBT/Default (OSX).sublime-keymap",
43 | "platform": "OSX"
44 | },
45 | "caption": "Key Bindings – Default"
46 | },
47 | {
48 | "command": "open_file",
49 | "args": {
50 | "file": "${packages}/SublimeSBT/Default (Linux).sublime-keymap",
51 | "platform": "Linux"
52 | },
53 | "caption": "Key Bindings – Default"
54 | },
55 | {
56 | "command": "open_file",
57 | "args": {
58 | "file": "${packages}/SublimeSBT/Default (Windows).sublime-keymap",
59 | "platform": "Windows"
60 | },
61 | "caption": "Key Bindings – Default"
62 | },
63 | {
64 | "command": "open_file",
65 | "args": {
66 | "file": "${packages}/User/Default (OSX).sublime-keymap",
67 | "platform": "OSX"
68 | },
69 | "caption": "Key Bindings – User"
70 | },
71 | {
72 | "command": "open_file",
73 | "args": {
74 | "file": "${packages}/User/Default (Linux).sublime-keymap",
75 | "platform": "Linux"
76 | },
77 | "caption": "Key Bindings – User"
78 | },
79 | {
80 | "command": "open_file",
81 | "args": {
82 | "file": "${packages}/User/Default (Windows).sublime-keymap",
83 | "platform": "Windows"
84 | },
85 | "caption": "Key Bindings – User"
86 | }
87 | ]
88 | }
89 | ]
90 | }
91 | ]
92 | }
93 | ]
94 |
--------------------------------------------------------------------------------
/highlighter.py:
--------------------------------------------------------------------------------
1 | import sublime
2 |
3 | try:
4 | from .util import group_by, maybe
5 | except(ValueError):
6 | from util import group_by, maybe
7 |
8 |
9 | class CodeHighlighter(object):
10 |
11 | error_types = ['error', 'failure', 'warning']
12 |
13 | def __init__(self, settings, current_error_in_view):
14 | self.settings = settings
15 | self._current_error_in_view = current_error_in_view
16 | self.bookmark_key = 'sublimesbt_bookmark'
17 | self.status_key = 'SBT'
18 | self._update_highlight_args()
19 | settings.add_on_change(self._update_highlight_args)
20 |
21 | def set_status_message(self, view, message):
22 | if message:
23 | view.set_status(self.status_key, message)
24 | else:
25 | view.erase_status(self.status_key)
26 |
27 | def clear(self, view):
28 | view.erase_regions(self.bookmark_key)
29 | for error_type in type(self).error_types:
30 | view.erase_regions(self.region_key(error_type))
31 |
32 | def highlight(self, view, errors, replace=False):
33 | bookmarked_line = self._bookmark_error(view)
34 | grouped = group_by(errors, lambda e: e.error_type)
35 | for error_type in type(self).error_types:
36 | lines = [e.line for e in grouped.get(error_type, list())]
37 | lines = [l for l in lines if l != bookmarked_line]
38 | self._highlight_lines(view, lines, error_type, replace)
39 |
40 | def region_key(self, error_type):
41 | return 'sublimesbt_%s_marking' % error_type
42 |
43 | def region_scope(self, error_type):
44 | return self._mark_settings(error_type)['scope']
45 |
46 | def _bookmark_error(self, view):
47 | for error in maybe(self._current_error_in_view(view)):
48 | region = self._create_region(view, error.line)
49 | self._clear_highlight(view, region)
50 | view.add_regions(self.bookmark_key,
51 | [region],
52 | self.region_scope(error.error_type),
53 | *self._bookmark_args(error.error_type))
54 | return error.line
55 |
56 | def _highlight_lines(self, view, lines, error_type, replace):
57 | regions = self._all_regions(view, self._create_regions(view, lines), error_type, replace)
58 | self._highlight_regions(view, regions, error_type)
59 |
60 | def _highlight_regions(self, view, regions, error_type):
61 | view.add_regions(self.region_key(error_type),
62 | regions,
63 | self.region_scope(error_type),
64 | *self._highlight_args[error_type])
65 |
66 | def _clear_highlight(self, view, region):
67 | for error_type in type(self).error_types:
68 | regions = view.get_regions(self.region_key(error_type))
69 | if region in regions:
70 | regions = [r for r in regions if r != region]
71 | self._highlight_regions(view, regions, error_type)
72 |
73 | def _all_regions(self, view, new_regions, error_type, replace):
74 | if replace:
75 | return new_regions
76 | else:
77 | return view.get_regions(self.region_key(error_type)) + new_regions
78 |
79 | def _create_regions(self, view, lines):
80 | return [self._create_region(view, l) for l in lines]
81 |
82 | def _create_region(self, view, lineno):
83 | line = view.line(view.text_point(lineno - 1, 0))
84 | r = view.find(r'\S', line.begin())
85 | if r is not None and line.contains(r):
86 | return sublime.Region(r.begin(), line.end())
87 | else:
88 | return line
89 |
90 | def _bookmark_args(self, error_type):
91 | return ['bookmark', self._highlight_args[error_type][-1]]
92 |
93 | def _update_highlight_args(self):
94 | self._highlight_args = {
95 | 'error': self._create_highlight_args('error'),
96 | 'failure': self._create_highlight_args('failure'),
97 | 'warning': self._create_highlight_args('warning')
98 | }
99 |
100 | def _create_highlight_args(self, error_type):
101 | style = self._mark_settings(error_type)['style']
102 | if style == 'dot':
103 | return ['dot', sublime.HIDDEN]
104 | elif style == 'outline':
105 | return [sublime.DRAW_OUTLINED]
106 | else:
107 | return ['dot', sublime.DRAW_OUTLINED]
108 |
109 | def _mark_settings(self, error_type):
110 | return self.settings.get('%s_marking' % error_type)
111 |
--------------------------------------------------------------------------------
/sbtview.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import sublime_plugin
3 |
4 | try:
5 | from .sbtsettings import SBTSettings
6 | from .util import maybe, OnePerWindow
7 | except(ValueError):
8 | from sbtsettings import SBTSettings
9 | from util import maybe, OnePerWindow
10 |
11 | import re
12 |
13 |
14 | class SbtView(OnePerWindow):
15 |
16 | settings = {
17 | "line_numbers": False,
18 | "gutter": False,
19 | "rulers": [],
20 | "word_wrap": False,
21 | "draw_centered": False,
22 | "highlight_line": False
23 | }
24 |
25 | @classmethod
26 | def is_sbt_view(cls, view):
27 | if view is not None:
28 | for window in maybe(view.window()):
29 | sbt_view = cls(window)
30 | return sbt_view.panel.id() == view.id()
31 |
32 | def __init__(self, window):
33 | self.window = window
34 | self.settings = SBTSettings(window)
35 | self.panel = self.window.get_output_panel('sbt')
36 | self.panel.set_syntax_file("Packages/SublimeSBT/SBTOutput.hidden-tmLanguage")
37 | for name, setting in SbtView.settings.items():
38 | self.panel.settings().set(name, setting)
39 | self._update_panel_colors()
40 | self.settings.add_on_change(self._update_panel_colors)
41 | self._output_size = 0
42 | self._set_running(False)
43 |
44 | def start(self):
45 | self.clear_output()
46 | self.show()
47 | self._set_running(True)
48 |
49 | def finish(self):
50 | self.show_output('\n -- Finished --\n')
51 | self._set_running(False)
52 |
53 | def show(self):
54 | self._update_panel_colors()
55 | self.window.run_command('show_panel', {'panel': 'output.sbt'})
56 | sublime.set_timeout(self._show_selection, 0)
57 |
58 | def hide(self):
59 | self.window.run_command('hide_panel', {'panel': 'output.sbt'})
60 |
61 | def focus(self):
62 | self.window.focus_view(self.panel)
63 | self.panel.show(self.panel.size())
64 |
65 | def show_output(self, output):
66 | output = self._clean_output(output)
67 | self.show()
68 | self._append_output(output)
69 | self._output_size = self.panel.size()
70 | self.panel.show(self._output_size)
71 | self.panel.sel().clear()
72 | self.panel.sel().add(sublime.Region(self._output_size, self._output_size))
73 |
74 | def clear_output(self):
75 | self._erase_output(sublime.Region(0, self.panel.size()))
76 |
77 | def take_input(self):
78 | input_region = sublime.Region(self._output_size, self.panel.size())
79 | input = self.panel.substr(input_region)
80 | if sublime.platform() == 'windows':
81 | self._append_output('\n')
82 | else:
83 | self._erase_output(input_region)
84 | return input
85 |
86 | def delete_left(self):
87 | if self.panel.sel()[0].begin() > self._output_size:
88 | self.panel.run_command('left_delete')
89 |
90 | def delete_bol(self):
91 | if self.panel.sel()[0].begin() >= self._output_size:
92 | p = self.panel.sel()[-1].end()
93 | self._erase_output(sublime.Region(self._output_size, p))
94 |
95 | def delete_word_left(self):
96 | if self.panel.sel()[0].begin() > self._output_size:
97 | for r in self.panel.sel():
98 | p = max(self.panel.word(r).begin(), self._output_size)
99 | self.panel.sel().add(sublime.Region(p, r.end()))
100 | self._erase_output(*self.panel.sel())
101 |
102 | def delete_word_right(self):
103 | if self.panel.sel()[0].begin() >= self._output_size:
104 | for r in self.panel.sel():
105 | p = self.panel.word(r).end()
106 | self.panel.sel().add(sublime.Region(r.begin(), p))
107 | self._erase_output(*self.panel.sel())
108 |
109 | def update_writability(self):
110 | self.panel.set_read_only(not self._running or
111 | self.panel.sel()[0].begin() < self._output_size)
112 |
113 | def _set_running(self, running):
114 | self._running = running
115 | self.update_writability()
116 |
117 | def _append_output(self, output):
118 | self._run_command('sbt_append_output', output=output)
119 |
120 | def _erase_output(self, *regions):
121 | self._run_command('sbt_erase_output',
122 | regions=[[r.begin(), r.end()] for r in regions])
123 |
124 | def _run_command(self, name, **kwargs):
125 | self.panel.set_read_only(False)
126 | self.panel.run_command(name, kwargs)
127 | self.update_writability()
128 |
129 | def _clean_output(self, output):
130 | return self._strip_codes(self._normalize_lines(output))
131 |
132 | def _normalize_lines(self, output):
133 | return output.replace('\r\n', '\n').replace('\033M', '\r')
134 |
135 | def _show_selection(self):
136 | self.panel.show(self.panel.sel()[0].begin(), True)
137 |
138 | def _strip_codes(self, output):
139 | return re.sub(r'\033\[[0-9;]+[mK]', '', output)
140 |
141 | def _update_panel_colors(self):
142 | self.panel.settings().set('color_scheme', self.settings.get('color_scheme'))
143 |
144 |
145 | class SbtAppendOutputCommand(sublime_plugin.TextCommand):
146 |
147 | def run(self, edit, output):
148 | for i, s in enumerate(output.split('\r')):
149 | if i > 0:
150 | self.view.replace(edit, self.view.line(self.view.size()), s)
151 | else:
152 | self.view.insert(edit, self.view.size(), s)
153 |
154 |
155 | class SbtEraseOutputCommand(sublime_plugin.TextCommand):
156 |
157 | def run(self, edit, regions):
158 | for a, b in reversed(regions):
159 | self.view.erase(edit, sublime.Region(int(a), int(b)))
160 |
--------------------------------------------------------------------------------
/outputmon.py:
--------------------------------------------------------------------------------
1 | try:
2 | from .sbterror import SbtError
3 | from .util import maybe
4 | except(ValueError):
5 | from sbterror import SbtError
6 | from util import maybe
7 |
8 | import re
9 |
10 |
11 | class BuildOutputMonitor(object):
12 |
13 | def __init__(self, project):
14 | self.project = project
15 | self._parsers = [ErrorParser, TestFailureParser, MultilineTestFailureParser,
16 | FinishedParser]
17 | self._parser = None
18 | self._buffer = ''
19 |
20 | def __call__(self, output):
21 | lines = re.split(r'(?:\r\n|\n|\r)', self._buffer + output)
22 | self._buffer = lines[-1]
23 | for line in lines[0:-1]:
24 | self._output_line(self._strip_terminal_codes(line))
25 |
26 | def _output_line(self, line):
27 | if self._parser:
28 | self._parser = self._parser.parse(line)
29 | else:
30 | self._parser = self._start_parsing(line)
31 |
32 | def _start_parsing(self, line):
33 | for parser_class in self._parsers:
34 | for parser in parser_class.start(self.project, line):
35 | return parser
36 |
37 | def _strip_terminal_codes(self, line):
38 | return re.sub(r'\033(?:M|\[[0-9;]+[mK])', '', line)
39 |
40 |
41 | class OutputParser(object):
42 |
43 | def parse(self, line):
44 | self.finish()
45 |
46 |
47 | class AbstractErrorParser(OutputParser):
48 |
49 | def __init__(self, project, line, filename, lineno, message):
50 | self.project = project
51 | self.reporter = project.error_reporter
52 | self.filename = filename
53 | self.lineno = lineno
54 | self.message = message
55 | self.extra_lines = []
56 |
57 | def finish(self):
58 | self.reporter.error(self._error())
59 |
60 | def _extra_line(self, line):
61 | self.extra_lines.append(line)
62 |
63 | def _error(self):
64 | return SbtError(project=self.project,
65 | filename=self.filename,
66 | line=self.lineno,
67 | message=self.message,
68 | error_type=self.error_type,
69 | extra_lines=self.extra_lines)
70 |
71 |
72 | class ErrorParser(AbstractErrorParser):
73 |
74 | @classmethod
75 | def start(cls, project, line):
76 | for m in maybe(re.match(r'^\[(error|warn)\]\s+(.+?):(\d+):(?:(\d+):)?\s+(.+)$', line)):
77 | yield cls(project,
78 | line=line,
79 | label=m.group(1),
80 | filename=m.group(2),
81 | lineno=int(m.group(3)),
82 | message=m.group(5))
83 |
84 | def __init__(self, project, line, label, filename, lineno, message):
85 | AbstractErrorParser.__init__(self, project, line, filename, lineno, message)
86 | if label == 'warn':
87 | self.error_type = 'warning'
88 | else:
89 | self.error_type = 'error'
90 |
91 | def parse(self, line):
92 | for t in maybe(self._match_last_line(line)):
93 | self._extra_line(t)
94 | return self.finish()
95 | for t in maybe(self._match_line(line)):
96 | self._extra_line(t)
97 | return self
98 | return self.finish()
99 |
100 | def _match_last_line(self, line):
101 | for m in maybe(re.match(r'\[(?:error|warn)\] (\s*\^\s*)$', line)):
102 | return m.group(1)
103 |
104 | def _match_line(self, line):
105 | for m in maybe(re.match(r'\[(?:error|warn)\] (.*)$', line)):
106 | return m.group(1)
107 |
108 |
109 | class TestFailureParser(AbstractErrorParser):
110 |
111 | # Single line failures of the form:
112 | # [error|info] ... (filename::nn)
113 |
114 | @classmethod
115 | def start(cls, project, line):
116 | for m in maybe(re.match(r'\[(?:error|info)\]\s+(.+)\s+\(([^:]+):(\d+)\)$', line)):
117 | yield cls(project,
118 | line=line,
119 | filename=m.group(2),
120 | lineno=int(m.group(3)),
121 | message=m.group(1))
122 |
123 | def __init__(self, project, line, filename, lineno, message):
124 | AbstractErrorParser.__init__(self, project, line, filename, lineno, message)
125 | self.error_type = 'failure'
126 |
127 |
128 | class MultilineTestFailureParser(AbstractErrorParser):
129 |
130 | # Multi-line failures of the form:
131 | # [info] - test description here *** FAILED ***
132 | # [info] ...
133 | # [info] ... (filename:nn)
134 |
135 | @classmethod
136 | def start(cls, project, line):
137 | for m in maybe(re.match(r'\[info\] - (.+) \*\*\* FAILED \*\*\*$', line)):
138 | yield cls(project,
139 | line=line,
140 | message=m.group(1))
141 |
142 | def __init__(self, project, line, message):
143 | AbstractErrorParser.__init__(self, project, line, "dummy", 0, message)
144 | self.error_type = 'error'
145 |
146 | def parse(self, line):
147 | for (t, filename, lineno) in maybe(self._match_last_line(line)):
148 | self._extra_line(t)
149 | self.filename = filename
150 | self.lineno = lineno
151 | return self.finish()
152 | for t in maybe(self._match_line(line)):
153 | self._extra_line(t)
154 | return self
155 | return self.finish()
156 |
157 | def _match_last_line(self, line):
158 | for m in maybe(re.match(r'\[info\] (.+) \(([^:]+):(\d+)\)$', line)):
159 | return (m.group(1), m.group(2), int(m.group(3)))
160 |
161 | def _match_line(self, line):
162 | for m in maybe(re.match(r'\[info\] (.*)$', line)):
163 | return m.group(1)
164 |
165 |
166 | class FinishedParser(OutputParser):
167 |
168 | @classmethod
169 | def start(cls, project, line):
170 | if re.match(r'\[(?:success|error)\] Total time:', line):
171 | yield cls(project)
172 |
173 | def __init__(self, project):
174 | self.reporter = project.error_reporter
175 |
176 | def finish(self):
177 | self.reporter.finish()
178 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SublimeSBT
2 | ==========
3 | Scala [SBT](http://www.scala-sbt.org/) build tool integration for
4 | [Sublime Text 2](http://sublimetext.com/2) and
5 | [Sublime Text 3](http://sublimetext.com/3).
6 |
7 | Features
8 | --------
9 | - Compatible with Sublime Text 2 and Sublime Text 3.
10 | - Runs SBT as an interactive REPL inside Sublime Text's output panel.
11 | - Detects compile errors and test failures in SBT's output and highlights the
12 | errant lines in the source code.
13 | - Lists compile errors and test failures in a quick panel for easy navigation
14 | to the errant lines.
15 | - Cycles through errors for even faster navigation.
16 | - Displays the error text in an output panel for easy reading.
17 | - Detects Play Framework projects and runs SBT using `play` instead of `sbt`.
18 | - Supports project-specific configuration.
19 |
20 | Installing
21 | ----------
22 | Download [Package Control](http://wbond.net/sublime_packages/package_control)
23 | and use the *Package Control: Install Package* command from the command palette.
24 | Using Package Control ensures SublimeSBT will stay up to date automatically.
25 |
26 | Using
27 | -----
28 | SublimeSBT is mostly used through the Command Palette. To open the pallete,
29 | press `ctrl+shift+p` (Windows, Linux) or `cmd+shift+p` (OS X). The SublimeSBT
30 | commands are all prefixed with `SBT:`. The commands only show up in the command
31 | palette if SublimeSBT detects that your project is an SBT project.
32 |
33 | **Start SBT**
34 |
35 | - Start an SBT session for the current project. If the project looks like a
36 | Play Framework project, the `play` command is used instead of the `sbt`
37 | command.
38 |
39 | **Stop SBT**
40 |
41 | - Stop the currently running SBT session.
42 |
43 | **Kill SBT**
44 |
45 | - Force the currently running SBT session to stop.
46 |
47 | **Show SBT Output**
48 |
49 | - Show the SBT output panel if it's not already showing. This also focuses
50 | the output panel and puts the cursor at the end.
51 |
52 | **Compile, Test, Run, Package, Start Console**
53 |
54 | - Run the `compile`, `test`, `run`, `package`, or `console` SBT command. If
55 | SBT is currently running the command is run in interactive mode, otherwise
56 | it's run in batch mode.
57 |
58 | **Start Continuous Compiling, Start Continuous Testing**
59 |
60 | - Run `~ compile` or `~ test`. If SBT is currently running the command is run
61 | in interactive mode, otherwise it's run in batch mode.
62 |
63 | **Test-Only, Test-Quick**
64 |
65 | - Run the `test-only` or `test-quick` command, prompting for an argument. If
66 | SBT is currently running the command is run in interactive mode, otherwise
67 | it's run in batch mode.
68 |
69 | **Start Continuous Test-Only, Start Continuous Test-Quick**
70 |
71 | - Run `~ test-only` or `~ test-quick`, prompting for an argument. If SBT is
72 | currently running the command is run in interactive mode, otherwise it's
73 | run in batch mode.
74 |
75 | **Reload**
76 |
77 | - Run the `reload` command if SBT is currently running.
78 |
79 | **Show Error List**
80 |
81 | - Show a quick panel with any compile errors or test failures. Selecting an
82 | error opens the file at the line with the error and shows the error text in
83 | the error output panel.
84 |
85 | **Show Next Error**
86 |
87 | - Jump to the next error in the error list. Opens the file at the line with
88 | the error and shows the error text in the error output panel.
89 |
90 | **Show Error Output**
91 |
92 | - Show the error output panel if it's not already showing.
93 |
94 | **Clear Errors**
95 |
96 | - Clear any compile errors or test failures and remove any highlighting
97 | thereof.
98 |
99 | **Show History**
100 |
101 | - Show a quick panel with the history of submitted commands. Selecting a
102 | command submits it again.
103 |
104 | **Show History and Edit**
105 |
106 | - As for Show History but also provides an opportunity to edit the selected
107 | command before it is re-submitted.
108 |
109 | **Clear History**
110 |
111 | - Clear the command history.
112 |
113 | Configuring
114 | -----------
115 | The default settings can be viewed by accessing the ***Preferences >
116 | Package Settings > SublimeSBT > Settings – Default*** menu entry. To ensure
117 | settings are not lost when the package is upgraded, make sure all edits are
118 | saved to ***Settings – User***.
119 |
120 | **sbt_command**
121 |
122 | - An array representing the command line to use to start sbt. Depending on
123 | your setup you may need to put the full path to the file here.
124 |
125 | **play_command**
126 |
127 | - An array representing the command line to use to start sbt for a Play
128 | Framework project. Depending on your setup you may need to put the full
129 | path to the file here.
130 |
131 | **test_command**
132 |
133 | - A string representing the sbt command to use to run tests.
134 |
135 | **run_command**
136 |
137 | - A string representing the sbt command to use to run the project.
138 |
139 | **error\_marking, failure\_marking, warning\_marking**
140 |
141 | - How to mark errors, failures, and warnings in the source code:
142 |
143 | **style**
144 |
145 | - The mark style to use. Valid values are "dot", "outline", or "both".
146 |
147 | **scope**
148 |
149 | - The scope to use to color the outline.
150 |
151 | **color_scheme**
152 |
153 | - The color scheme to use for the output panel.
154 |
155 | Project-specific settings can be configured by accessing the ***Project > Edit
156 | Project*** menu entry and putting settings in a "SublimeSBT" object inside of
157 | "settings" in your project file, e.g.:
158 |
159 | {
160 | "folders":
161 | [
162 | {
163 | "path": "/Users/jarhart/scalakoansexercises"
164 | }
165 | ],
166 | "settings":
167 | {
168 | "SublimeSBT":
169 | {
170 | "sbt_command": ["./sbt"],
171 | "test_command": "test-only org.functionalkoans.forscala.Koans"
172 | }
173 | }
174 | }
175 |
176 | **history**
177 |
178 | - An array that contains commands that should be added to the command
179 | history when a project is opened.
180 |
181 | **history_length**
182 |
183 | - The maximum number of unique commands to keep in the command history.
184 | The default setting is 20.
185 |
186 | Project-Specific Configuring
187 | ----------------------------
188 | If the file `.SublimeSBT_history` exists at the top level of a project, each
189 | line of that file will be added to the command history when the project is
190 | opened.
191 |
192 | Contributors
193 | ------------
194 | - [Jason Arhart](https://github.com/jarhart)
195 | - [Alexey Alekhin](https://github.com/laughedelic)
196 | - [Colt Frederickson](https://github.com/coltfred)
197 | - [Bryan Head](https://github.com/qiemem)
198 | - [Tony Sloane](https://github.com/inkytonik)
199 | - [Tim Gautier](https://github.com/timgautier)
200 |
201 | Contributing
202 | ------------
203 |
204 | 1. Fork it
205 | 2. Create your feature branch (`git checkout -b my-new-feature`)
206 | 3. Commit your changes (`git commit -am 'Added some feature'`)
207 | 4. Push to the branch (`git push origin my-new-feature`)
208 | 5. Create new Pull Request
209 |
210 | Copyright
211 | ---------
212 |
213 | Copyright (c) 2012 Jason Arhart. See LICENSE for further details.
214 |
--------------------------------------------------------------------------------
/sbtrunner.py:
--------------------------------------------------------------------------------
1 | import sublime
2 |
3 | try:
4 | from .project import Project
5 | from .util import OnePerWindow
6 | except(ValueError):
7 | from project import Project
8 | from util import OnePerWindow
9 |
10 | import os
11 | import pipes
12 | import signal
13 | import subprocess
14 | import threading
15 |
16 |
17 | class SbtRunner(OnePerWindow):
18 |
19 | @classmethod
20 | def is_sbt_running_for(cls, window):
21 | return cls(window).is_sbt_running()
22 |
23 | def __init__(self, window):
24 | self._project = Project(window)
25 | self._proc = None
26 | self.init_history()
27 |
28 | def project_root(self):
29 | return self._project.project_root()
30 |
31 | def sbt_command(self, command):
32 | cmdline = self._project.sbt_command()
33 | if command is not None:
34 | cmdline.append(command)
35 | return cmdline
36 |
37 | def start_sbt(self, command, on_start, on_stop, on_stdout, on_stderr):
38 | if self.project_root() and not self.is_sbt_running():
39 | self._proc = self._try_start_sbt_proc(self.sbt_command(command),
40 | on_start,
41 | on_stop,
42 | on_stdout,
43 | on_stderr)
44 |
45 | def stop_sbt(self):
46 | if self.is_sbt_running():
47 | self._proc.terminate()
48 |
49 | def kill_sbt(self):
50 | if self.is_sbt_running():
51 | self._proc.kill()
52 |
53 | def is_sbt_running(self):
54 | return (self._proc is not None) and self._proc.is_running()
55 |
56 | def send_to_sbt(self, input):
57 | if self.is_sbt_running():
58 | self.add_to_history(input)
59 | self._proc.send(input)
60 |
61 | def _try_start_sbt_proc(self, cmdline, *handlers):
62 | try:
63 | return SbtProcess.start(cmdline,
64 | self.project_root(),
65 | self._project.settings,
66 | *handlers)
67 | except OSError:
68 | msg = ('Unable to find "%s".\n\n'
69 | 'You may need to specify the full path to your sbt command.'
70 | % cmdline[0])
71 | sublime.error_message(msg)
72 |
73 | def init_history(self):
74 | self._history = []
75 | if self.project_root():
76 | path = os.path.join(self.project_root(), '.SublimeSBT_history')
77 | if os.path.exists(path):
78 | for line in open(path):
79 | self._history.append(line.rstrip('\n\r'))
80 | global_cmds = self._project.settings.get('history') or []
81 | for cmd in global_cmds:
82 | self._history.append(cmd)
83 |
84 | def add_to_history(self, input):
85 | if input != '' and not input.isspace ():
86 | input = input.rstrip('\n\r')
87 | self._history = [cmd for cmd in self._history if cmd != input]
88 | self._history.insert (0, input)
89 | history_length = self._project.settings.get('history_length') or 20
90 | del self._history[history_length:]
91 |
92 | def clear_history(self):
93 | self._history.clear ()
94 |
95 | def get_history(self):
96 | return self._history
97 |
98 |
99 | class SbtProcess(object):
100 |
101 | @staticmethod
102 | def start(*args, **kwargs):
103 | if sublime.platform() == 'windows':
104 | return SbtWindowsProcess._start(*args, **kwargs)
105 | else:
106 | return SbtUnixProcess._start(*args, **kwargs)
107 |
108 | @classmethod
109 | def _start(cls, cmdline, cwd, settings, *handlers):
110 | return cls(cls._start_proc(cmdline, cwd, settings), settings, *handlers)
111 |
112 | @classmethod
113 | def _start_proc(cls, cmdline, cwd, settings):
114 | return cls._popen(cmdline,
115 | env=cls._sbt_env(settings),
116 | stdin=subprocess.PIPE,
117 | stdout=subprocess.PIPE,
118 | stderr=subprocess.PIPE,
119 | cwd=cwd)
120 |
121 | @classmethod
122 | def _sbt_env(cls, settings):
123 | return dict(list(os.environ.items()) +
124 | [cls._append_opts('SBT_OPTS', cls._sbt_opts(settings))])
125 |
126 | @classmethod
127 | def _sbt_opts(cls, settings):
128 | return [
129 | str('-Dfile.encoding=%s' % (settings.get('encoding') or 'UTF-8'))
130 | ]
131 |
132 | @classmethod
133 | def _append_opts(cls, name, opts):
134 | existing_opts = os.environ.get(name, None)
135 | if existing_opts:
136 | opts = [existing_opts] + opts
137 | return [name, ' '.join(opts)]
138 |
139 | def __init__(self, proc, settings, on_start, on_stop, on_stdout, on_stderr):
140 | self._proc = proc
141 | self._encoding = settings.get('encoding') or 'UTF-8'
142 | on_start()
143 | if self._proc.stdout:
144 | self._start_thread(self._monitor_output,
145 | (self._proc.stdout, on_stdout))
146 | if self._proc.stderr:
147 | self._start_thread(self._monitor_output,
148 | (self._proc.stderr, on_stderr))
149 | self._start_thread(self._monitor_proc, (on_stop,))
150 |
151 | def is_running(self):
152 | return self._proc.returncode is None
153 |
154 | def send(self, input):
155 | self._proc.stdin.write(input.encode())
156 | self._proc.stdin.flush()
157 |
158 | def _monitor_output(self, pipe, handle_output):
159 | while True:
160 | output = os.read(pipe.fileno(), 2 ** 15).decode(self._encoding)
161 | if output != "":
162 | handle_output(output)
163 | else:
164 | pipe.close()
165 | return
166 |
167 | def _monitor_proc(self, handle_stop):
168 | self._proc.wait()
169 | sublime.set_timeout(handle_stop, 0)
170 |
171 | def _start_thread(self, target, args):
172 | threading.Thread(target=target, args=args).start()
173 |
174 |
175 | class SbtUnixProcess(SbtProcess):
176 |
177 | @classmethod
178 | def _popen(cls, cmdline, **kwargs):
179 | return subprocess.Popen(cls._shell_cmdline(cmdline),
180 | preexec_fn=os.setpgrp,
181 | **kwargs)
182 |
183 | @classmethod
184 | def _shell_cmdline(cls, cmdline):
185 | shell = os.environ.get('SHELL', '/bin/bash')
186 | opts = '-ic' if shell.endswith('csh') else '-lic'
187 | cmd = ' '.join(map(pipes.quote, cmdline))
188 | return [shell, opts, cmd]
189 |
190 | def terminate(self):
191 | os.killpg(self._proc.pid, signal.SIGTERM)
192 |
193 | def kill(self):
194 | os.killpg(self._proc.pid, signal.SIGKILL)
195 |
196 |
197 | class SbtWindowsProcess(SbtProcess):
198 |
199 | @classmethod
200 | def _popen(cls, cmdline, **kwargs):
201 | return subprocess.Popen(cmdline, shell=True, **kwargs)
202 |
203 | def terminate(self):
204 | self.kill()
205 |
206 | def kill(self):
207 | cmdline = ['taskkill', '/T', '/F', '/PID', str(self._proc.pid)]
208 | si = subprocess.STARTUPINFO()
209 | si.dwFlags = subprocess.STARTF_USESHOWWINDOW
210 | si.wShowWindow = subprocess.SW_HIDE
211 | subprocess.call(cmdline, startupinfo=si)
212 |
--------------------------------------------------------------------------------
/sublimesbt.py:
--------------------------------------------------------------------------------
1 | import sublime_plugin
2 | import sublime
3 |
4 | import re
5 |
6 | try:
7 | from .project import Project
8 | from .sbtrunner import SbtRunner
9 | from .sbtview import SbtView
10 | from .errorview import ErrorView
11 | from .outputmon import BuildOutputMonitor
12 | from .util import delayed, maybe
13 | except(ValueError):
14 | from project import Project
15 | from sbtrunner import SbtRunner
16 | from sbtview import SbtView
17 | from errorview import ErrorView
18 | from outputmon import BuildOutputMonitor
19 | from util import delayed, maybe
20 |
21 | class SbtWindowCommand(sublime_plugin.WindowCommand):
22 |
23 | def __init__(self, *args):
24 | super(SbtWindowCommand, self).__init__(*args)
25 | self._project = Project(self.window)
26 | self._runner = SbtRunner(self.window)
27 | self._sbt_view = SbtView(self.window)
28 | self._error_view = ErrorView(self.window)
29 | self._error_reporter = self._project.error_reporter
30 | self._error_report = self._project.error_report
31 | self._monitor_compile_output = BuildOutputMonitor(self._project)
32 |
33 | def is_sbt_project(self):
34 | return self._project.is_sbt_project()
35 |
36 | def is_play_project(self):
37 | return self._project.is_play_project()
38 |
39 | def is_sbt_running(self):
40 | return self._runner.is_sbt_running()
41 |
42 | def start_sbt(self, command=None):
43 | self._runner.start_sbt(command,
44 | on_start=self._sbt_view.start,
45 | on_stop=self._sbt_view.finish,
46 | on_stdout=self._on_stdout,
47 | on_stderr=self._on_stderr)
48 |
49 | def stop_sbt(self):
50 | self._runner.stop_sbt()
51 |
52 | def kill_sbt(self):
53 | self._runner.kill_sbt()
54 |
55 | def show_sbt(self):
56 | self._sbt_view.show()
57 |
58 | def hide_sbt(self):
59 | self._sbt_view.hide()
60 |
61 | def focus_sbt(self):
62 | self._sbt_view.focus()
63 |
64 | def take_input(self):
65 | return self._sbt_view.take_input()
66 |
67 | def send_to_sbt(self, cmd):
68 | self.window.run_command('clear_sbt_errors')
69 | self._runner.send_to_sbt(cmd)
70 |
71 | @delayed(0)
72 | def show_error(self, error):
73 | self._error_report.focus_error(error)
74 | self._error_reporter.show_errors()
75 | self._error_view.show_error(error)
76 | self.goto_error(error)
77 |
78 | @delayed(0)
79 | def goto_error(self, error):
80 | self.window.open_file(error.encoded_position(), sublime.ENCODED_POSITION)
81 |
82 | def show_error_output(self):
83 | self._error_view.show()
84 |
85 | def setting(self, name):
86 | return self._project.setting(name)
87 |
88 | def _on_stdout(self, output):
89 | self._monitor_compile_output(output)
90 | self._show_output(output)
91 |
92 | def _on_stderr(self, output):
93 | self._show_output(output)
94 |
95 | @delayed(0)
96 | def _show_output(self, output):
97 | output = self._work_around_JLine_bug(output)
98 | self._sbt_view.show_output(output)
99 |
100 | # If we have a single character, space, CR then we are probably seeing a
101 | # JLine bug which has inserted the space, CR at column 80 of a prompt
102 | # line. Delete the space and CR so that it doesn't mess up the display
103 | # of the prompt line (ie. hide the stuff before the CR). Just remove
104 | # the space, CR pair.
105 | def _work_around_JLine_bug(self, output):
106 | return re.sub(r'^(.) \r$', r'\1', output)
107 |
108 |
109 | class StartSbtCommand(SbtWindowCommand):
110 |
111 | def run(self):
112 | self.start_sbt()
113 |
114 | def is_enabled(self):
115 | return self.is_sbt_project() and not self.is_sbt_running()
116 |
117 |
118 | class StopSbtCommand(SbtWindowCommand):
119 |
120 | def run(self):
121 | self.stop_sbt()
122 |
123 | def is_enabled(self):
124 | return self.is_sbt_running()
125 |
126 |
127 | class KillSbtCommand(SbtWindowCommand):
128 |
129 | def run(self):
130 | self.kill_sbt()
131 |
132 | def is_enabled(self):
133 | return self.is_sbt_running()
134 |
135 |
136 | class ShowSbtCommand(SbtWindowCommand):
137 |
138 | def run(self):
139 | self.show_sbt()
140 | self.focus_sbt()
141 |
142 | def is_enabled(self):
143 | return self.is_sbt_project()
144 |
145 |
146 | class SbtSubmitCommand(SbtWindowCommand):
147 |
148 | def run(self):
149 | self.send_to_sbt(self.take_input() + '\n')
150 |
151 | def is_enabled(self):
152 | return self.is_sbt_running()
153 |
154 |
155 | class SbtCommand(SbtWindowCommand):
156 |
157 | def run(self, command):
158 | if self.is_sbt_running():
159 | self.send_to_sbt(command + '\n')
160 | else:
161 | self.start_sbt(command)
162 |
163 | def is_enabled(self):
164 | return self.is_sbt_project()
165 |
166 |
167 | class SbtTestCommand(SbtCommand):
168 |
169 | def run(self):
170 | super(SbtTestCommand, self).run(self.test_command())
171 |
172 | def test_command(self):
173 | return self.setting('test_command')
174 |
175 |
176 | class SbtContinuousTestCommand(SbtTestCommand):
177 |
178 | def test_command(self):
179 | return '~ ' + super(SbtContinuousTestCommand, self).test_command()
180 |
181 |
182 | test_only_arg = '*'
183 |
184 |
185 | class SbtTestOnlyCommand(SbtCommand):
186 |
187 | base_command = 'test-only'
188 |
189 | def run(self):
190 | self.window.show_input_panel(self.prompt(), test_only_arg,
191 | self.test_only, None, None)
192 |
193 | def test_only(self, arg):
194 | global test_only_arg
195 | test_only_arg = arg
196 | super(SbtTestOnlyCommand, self).run(self.test_command(arg))
197 |
198 | def prompt(self):
199 | return 'SBT: %s' % self.base_command
200 |
201 | def test_command(self, arg):
202 | return '%s %s' % (self.base_command, arg)
203 |
204 |
205 | class SbtContinuousTestOnlyCommand(SbtTestOnlyCommand):
206 |
207 | def test_command(self, arg):
208 | return '~ ' + super(SbtContinuousTestOnlyCommand, self).test_command(arg)
209 |
210 |
211 | class SbtTestQuickCommand(SbtTestOnlyCommand):
212 |
213 | base_command = 'test-quick'
214 |
215 |
216 | class SbtContinuousTestQuickCommand(SbtTestQuickCommand):
217 |
218 | def test_command(self, arg):
219 | return '~ ' + super(SbtContinuousTestQuickCommand, self).test_command(arg)
220 |
221 |
222 | class SbtRunCommand(SbtCommand):
223 |
224 | def run(self):
225 | super(SbtRunCommand, self).run(self.setting('run_command'))
226 |
227 |
228 | class SbtReloadCommand(SbtCommand):
229 |
230 | def run(self):
231 | super(SbtReloadCommand, self).run('reload')
232 |
233 | def is_enabled(self):
234 | return self.is_sbt_running()
235 |
236 |
237 | class SbtErrorsCommand(SbtWindowCommand):
238 |
239 | def is_enabled(self):
240 | return self.is_sbt_project() and self._error_report.has_errors()
241 |
242 |
243 | class ClearSbtErrorsCommand(SbtErrorsCommand):
244 |
245 | def run(self):
246 | self._error_reporter.clear()
247 | self._error_view.clear()
248 |
249 |
250 | class ListSbtErrorsCommand(SbtErrorsCommand):
251 |
252 | def run(self):
253 | errors = list(self._error_report.all_errors())
254 | list_items = [e.list_item() for e in errors]
255 |
256 | def goto_error(index):
257 | if index >= 0:
258 | self.show_error(errors[index])
259 |
260 | self.window.show_quick_panel(list_items, goto_error)
261 |
262 |
263 | class NextSbtErrorCommand(SbtErrorsCommand):
264 |
265 | def run(self):
266 | self.show_error(self._error_report.next_error())
267 |
268 |
269 | class ShowSbtErrorOutputCommand(SbtErrorsCommand):
270 |
271 | def run(self):
272 | self.show_error_output()
273 |
274 |
275 | class SbtEotCommand(SbtWindowCommand):
276 |
277 | def run(self):
278 | if sublime.platform() == 'windows':
279 | self.send_to_sbt('\032')
280 | else:
281 | self.send_to_sbt('\004')
282 |
283 | def is_enabled(self):
284 | return self.is_sbt_running()
285 |
286 |
287 | class SbtDeleteLeftCommand(SbtWindowCommand):
288 |
289 | def run(self):
290 | self._sbt_view.delete_left()
291 |
292 |
293 | class SbtDeleteBolCommand(SbtWindowCommand):
294 |
295 | def run(self):
296 | self._sbt_view.delete_bol()
297 |
298 |
299 | class SbtDeleteWordLeftCommand(SbtWindowCommand):
300 |
301 | def run(self):
302 | self._sbt_view.delete_word_left()
303 |
304 |
305 | class SbtDeleteWordRightCommand(SbtWindowCommand):
306 |
307 | def run(self):
308 | self._sbt_view.delete_word_right()
309 |
310 |
311 | class SbtShowHistoryCommand(SbtWindowCommand):
312 |
313 | def run(self, editable=False):
314 | def get_command(index):
315 | if index >= 0:
316 | cmd = self._runner.get_history()[index]
317 | if editable:
318 | self.window.show_input_panel("Command:", cmd,
319 | self.run_command,
320 | None, None)
321 | else:
322 | self.run_command(cmd)
323 | history = self._runner.get_history()
324 | if history == []:
325 | sublime.error_message('There is no SBT command history to display.')
326 | else:
327 | self.window.show_quick_panel(history, get_command)
328 |
329 | def run_command(self, cmd):
330 | self.send_to_sbt (cmd + '\n')
331 |
332 | def is_enabled(self):
333 | return self.is_sbt_running()
334 |
335 |
336 | class SbtClearHistoryCommand(SbtWindowCommand):
337 |
338 | def run(self):
339 | self._runner.clear_history()
340 |
341 |
342 | class SbtListener(sublime_plugin.EventListener):
343 |
344 | def on_clone(self, view):
345 | for reporter in maybe(self._reporter(view)):
346 | reporter.show_errors_in(view.file_name())
347 |
348 | def on_load(self, view):
349 | for reporter in maybe(self._reporter(view)):
350 | reporter.show_errors_in(view.file_name())
351 |
352 | def on_post_save(self, view):
353 | for reporter in maybe(self._reporter(view)):
354 | reporter.hide_errors_in(view.file_name())
355 |
356 | def on_modified(self, view):
357 | for reporter in maybe(self._reporter(view)):
358 | reporter.show_errors_in(view.file_name())
359 |
360 | def on_selection_modified(self, view):
361 | if SbtView.is_sbt_view(view):
362 | SbtView(view.window()).update_writability()
363 | else:
364 | for reporter in maybe(self._reporter(view)):
365 | reporter.update_status_now()
366 |
367 | def on_activated(self, view):
368 | for reporter in maybe(self._reporter(view)):
369 | reporter.show_errors_in(view.file_name())
370 |
371 | def on_query_context(self, view, key, operator, operand, match_all):
372 | if key == "in_sbt_view":
373 | if SbtView.is_sbt_view(view):
374 | return SbtRunner.is_sbt_running_for(view.window())
375 | else:
376 | return False
377 |
378 | def _reporter(self, view):
379 | for window in maybe(view.window()):
380 | return Project(window).error_reporter
381 |
--------------------------------------------------------------------------------