diff --git a/Context.sublime-menu b/Context.sublime-menu new file mode 100644 index 00000000..b7c8fbcc --- /dev/null +++ b/Context.sublime-menu @@ -0,0 +1,117 @@ +// XXX +// run/test/bench with args? +// +[ + { + "caption": "Rust", + "id": "rust_context", + "children": [ + { + "caption": "Clean", + "command": "cargo_exec", + "args": { + "command": "clean" + } + }, + { + "caption": "Check", + "command": "cargo_exec", + "args": { + "command": "check" + } + }, + { + "caption": "Clippy", + "command": "cargo_exec", + "args": { + "command": "clippy" + } + }, + { + "caption": "-", + }, + { + "caption": "Test Here", + "command": "cargo_test_here", + }, + { + "caption": "Test Current File", + "command": "cargo_test_current_file", + }, + { + "caption": "Test All", + "command": "cargo_exec", + "args": { + "command": "test" + } + }, + { + "caption": "-", + }, + { + "caption": "Bench Here", + "command": "cargo_bench_here", + }, + { + "caption": "Bench Current File", + "command": "cargo_bench_current_file", + }, + { + "caption": "Bench All", + "command": "cargo_exec", + "args": { + "command": "bench" + } + }, + { + "caption": "-", + }, + { + "caption": "Run This File", + "command": "cargo_run_current_file", + }, + { + "caption": "Doc", + "command": "cargo_exec", + "args": { + "command": "doc", + } + }, + { + "caption": "Cancel Build", + "command": "rust_cancel" + }, + { + "caption": "-", + }, + { + "caption": "Clear Messages", + "command": "rust_dismiss_messages", + }, + { + "caption": "List All Messages", + "command": "rust_list_messages", + }, + { + "caption": "-", + }, + { + "caption": "Open Settings", + "command": "edit_settings", + "args": + { + "base_file": "${packages}/Rust Enhanced/RustEnhanced.sublime-settings", + "default": "{\n\t$0\n}\n" + } + }, + { + "caption": "Configure Cargo Build", + "command": "cargo_configure", + }, + { + "caption": "On-Save Checking", + "command": "toggle_rust_syntax_setting", + }, + ] + } +] diff --git a/README.md b/README.md index 7d3f094a..85840114 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,10 @@ Projects with multiple build targets are supported too (--lib, --bin, --example, } ``` +## Context Menu +The Sublime context menu includes a Rust entry with a variety of commands. +See [context menu docs](docs/context.md) for more information. + ## Settings To customize the settings, use the command from the Sublime menu: diff --git a/RustEnhanced.sublime-commands b/RustEnhanced.sublime-commands index 40410573..f9206b9c 100644 --- a/RustEnhanced.sublime-commands +++ b/RustEnhanced.sublime-commands @@ -31,5 +31,25 @@ "base_file": "${packages}/Rust Enhanced/RustEnhanced.sublime-settings", "default": "{\n\t$0\n}\n" } - } + }, + { + "caption": "Rust: List All Messages", + "command": "rust_list_messages" + }, + { + "caption": "Rust: Run Test At Cursor", + "command": "cargo_test_at_cursor" + }, + { + "caption": "Rust: Run Tests In Current File", + "command": "cargo_test_current_file" + }, + { + "caption": "Rust: Run Benchmark At Cursor", + "command": "cargo_bench_at_cursor" + }, + { + "caption": "Rust: Run Benchmarks In Current File", + "command": "cargo_bench_current_file" + }, ] diff --git a/cargo_build.py b/cargo_build.py index 50e3c415..e7ac75b8 100644 --- a/cargo_build.py +++ b/cargo_build.py @@ -284,3 +284,183 @@ class RustDismissMessagesCommand(sublime_plugin.WindowCommand): def run(self): messages.clear_messages(self.window) + + +class RustListMessagesCommand(sublime_plugin.WindowCommand): + + """Shows a quick panel with a list of all messages.""" + + def run(self): + messages.list_messages(self.window) + + +# Patterns used to help find test function names. +# This is far from perfect, but should be good enough. +SPACE = r'[ \t]' +OPT_COMMENT = r"""(?: + (?: [ \t]* //.*) + | (?: [ \t]* /\*.*\*/ [ \t]* ) +)?""" +IDENT = r"""(?: + [a-z A-Z] [a-z A-Z 0-9 _]* + | _ [a-z A-Z 0-9 _]+ +)""" +TEST_PATTERN = r"""(?x) + {SPACE}* \# {SPACE}* \[ {SPACE}* {WHAT} {SPACE}* \] {SPACE}* + (?: + (?: {SPACE}* \#\[ [^]]+ \] {OPT_COMMENT} \n ) + | (?: {OPT_COMMENT} \n ) + )* + .* fn {SPACE}+ ({IDENT}+) +""" + + +def _target_to_test(what, view, on_done): + """Helper used to determine build target from given view.""" + td = target_detect.TargetDetector(view.window()) + targets = td.determine_targets(view.file_name()) + if len(targets) == 0: + sublime.error_message('Error: Could not determine target to %s.' % what) + elif len(targets) == 1: + on_done(' '.join(targets[0][1])) + else: + # Can't determine a single target, let the user choose one. + display_items = [' '.join(x[1]) for x in targets] + + def quick_on_done(idx): + on_done(targets[idx][1]) + + view.window().show_quick_panel(display_items, quick_on_done) + + +def _pt_to_test_name(what, pt, view): + """Helper used to convert Sublime point to a test/bench function name.""" + fn_names = [] + pat = TEST_PATTERN.format(WHAT=what, **globals()) + regions = view.find_all(pat, 0, r'\1', fn_names) + if not regions: + sublime.error_message('Could not find a Rust %s function.' % what) + return None + # Assuming regions are in ascending order. + indices = [i for (i, r) in enumerate(regions) if r.a <= pt] + if not indices: + sublime.error_message('No %s functions found about the current point.' % what) + return None + return fn_names[indices[-1]] + + +def _cargo_test_pt(what, pt, view): + """Helper used to run a test for a given point in the given view.""" + def do_test(target): + test_fn_name = _pt_to_test_name(what, pt, view) + if test_fn_name: + view.window().run_command('cargo_exec', args={ + 'command': what, + 'settings': { + 'target': target, + 'extra_run_args': '--exact ' + test_fn_name + } + }) + + _target_to_test(what, view, do_test) + + +class CargoHere(sublime_plugin.WindowCommand): + + """Base class for mouse-here commands. + + Subclasses set `what` attribute. + """ + + what = None + + def run(self, event): + view = self.window.active_view() + if not view: + return + pt = view.window_to_text((event['x'], event['y'])) + _cargo_test_pt(self.what, pt, view) + + def want_event(self): + return True + + +class CargoTestHereCommand(CargoHere): + + """Determines the test name at the current mouse position, and runs just + that test.""" + + what = 'test' + + +class CargoBenchHereCommand(CargoHere): + + """Determines the benchmark at the current mouse position, and runs just + that benchmark.""" + + what = 'bench' + + +class CargoTestAtCursorCommand(sublime_plugin.TextCommand): + + """Determines the test name at the current cursor position, and runs just + that test.""" + + def run(self, edit): + pt = self.view.sel()[0].begin() + _cargo_test_pt('test', pt, self.view) + + +class CargoCurrentFile(sublime_plugin.WindowCommand): + + """Base class for current file commands. + + Subclasses set `what` attribute. + """ + + what = None + + def run(self): + print('current file') + def _test_file(target): + print('target is %r' % target) + self.window.run_command('cargo_exec', args={ + 'command': self.what, + 'settings': { + 'target': target + } + }) + + view = self.window.active_view() + _target_to_test(self.what, view, _test_file) + + +class CargoTestCurrentFileCommand(CargoCurrentFile): + + """Runs all tests in the current file.""" + + what = 'test' + + +class CargoBenchCurrentFileCommand(CargoCurrentFile): + + """Runs all benchmarks in the current file.""" + + what = 'bench' + + +class CargoRunCurrentFileCommand(CargoCurrentFile): + + """Runs the current file.""" + + what = 'run' + + +class CargoBenchAtCursorCommand(sublime_plugin.TextCommand): + + """Determines the benchmark name at the current cursor position, and runs + just that benchmark.""" + + def run(self, edit): + pt = self.view.sel()[0].begin() + _cargo_test_pt('bench', pt, self.view) diff --git a/docs/context.md b/docs/context.md new file mode 100644 index 00000000..4b8ebaab --- /dev/null +++ b/docs/context.md @@ -0,0 +1,44 @@ +# Rust Context Menu + +You can access Sublime's context menu with a right click. + +![Rust Context Menu](rust_context.png "Rust Context Menu") + +## Cargo Commands +A variety of Cargo commands are available here for quick access so you don't +have to frequently switch your build system variant. Some of them are +context-sensitive, based on where you click the mouse. + +* **Clean**: `cargo clean` to remove build artifacts. +* **Check**: `cargo check` to quickly check your package for errors. +* **Clippy**: `cargo clippy` to run + [Clippy](https://github.com/rust-lang-nursery/rust-clippy) on your source. +--- +* **Test Here**: Runs just the one test underneath the cursor. A similar + command is also available in the Sublime Command Palette as "Rust: Run Test + At Cursor". +* **Test Current File**: Runs all tests in the current file. +* **Test All**: Runs all tests in the package. +--- +* **Bench Here**: Runs just the one benchmark underneath the cursor. +* **Bench Current File**: Runs all benchmarks in the current file. +* **Bench All**: Runs all benchmarks in the package. +--- +* **Run This File**: `cargo run` the current file. +* **Doc**: `cargo doc` to generate documentation. +* **Cancel Build**: Cancel the current build. Also available with keyboard + shortcuts, see [build docs](build.md). + +## Message Commands +* **Clear Messages**: Remove inline error/warning messages. Also available + with the Esc key, or click the × symbol in the message. +* **List All Messages**: Show a quick panel popup with a list of all current + messages. + +## Settings Commands +* **Open Settings**: Open the Rust Enhanced settings. +* **Configure Cargo Build**: Run the command to interactively configure Cargo + build settings stored in your Sublime project file. See + [Configure Command](build.md#configure-command). +* **On-Save Checking**: Toggle `cargo check` running every time you save a + Rust file. diff --git a/docs/rust_context.png b/docs/rust_context.png new file mode 100644 index 00000000..bac4311c Binary files /dev/null and b/docs/rust_context.png differ diff --git a/rust/messages.py b/rust/messages.py index 8fabf599..31f76801 100644 --- a/rust/messages.py +++ b/rust/messages.py @@ -19,10 +19,18 @@ # } # `path` is the absolute path to the file. # Each msg_dict has the following: -# - `level` -# - `span` -# - `is_main` -# - `message` +# - `level`: Message level as a string such as "error", or "info". +# - `span`: Location of the message (0-based): +# `((line_start, col_start), (line_end, col_end))` +# May be `None` to indicate no particular spot. +# - `is_main`: If True, this is a top-level message. False is used for +# attached detailed diagnostic information, child notes, etc. +# - `path`: Absolute path to the file. +# - `text`: The raw text of the message without any minihtml markup. +# - `phantom_text`: The string used for showing phantoms that includes the +# minihtml markup. +# - `output_panel_region`: Optional Sublime Region object that indicates the +# region in the build output panel that corresponds with this message. WINDOW_MESSAGES = {} @@ -128,7 +136,7 @@ def add_message(window, path, span, level, is_main, text, markup_text, msg_cb): 'text': text, 'phantom_text': phantom_text, } - if to_add in messages: + if _is_duplicate(to_add, messages): # Don't add duplicates. return messages.append(to_add) @@ -139,6 +147,17 @@ def add_message(window, path, span, level, is_main, text, markup_text, msg_cb): msg_cb(to_add) +def _is_duplicate(to_add, messages): + # Primarily to avoid comparing the `output_panel_region` key. + for message in messages: + for key, value in to_add.items(): + if message[key] != value: + break + else: + return True + return False + + def has_message_for_path(window, path): paths = WINDOW_MESSAGES.get(window.id(), {}).get('paths', {}) return path in paths @@ -317,15 +336,15 @@ def _sort_messages(window): def show_next_message(window, levels): current_idx = _advance_next_message(window, levels) - _show_message(window, levels, current_idx) + _show_message(window, current_idx) def show_prev_message(window, levels): current_idx = _advance_prev_message(window, levels) - _show_message(window, levels, current_idx) + _show_message(window, current_idx) -def _show_message(window, levels, current_idx): +def _show_message(window, current_idx, transient=False, force_open=False): if current_idx is None: return try: @@ -334,26 +353,39 @@ def _show_message(window, levels, current_idx): return paths = window_info['paths'] path, messages = _ith_iter_item(paths.items(), current_idx[0]) - view = window.find_open_file(path) msg = messages[current_idx[1]] _scroll_build_panel(window, msg) - if view: - _scroll_to_message(view, msg) - else: - # show_at_center is buggy with newly opened views (see - # https://github.com/SublimeTextIssues/Core/issues/538). - # ENCODED_POSITION is 1-based. - row, col = msg['span'][0] + view = None + if not transient and not force_open: + view = window.find_open_file(path) + if view: + _scroll_to_message(view, msg, transient) + if not view: + flags = sublime.ENCODED_POSITION + if transient: + # FORCE_GROUP is undocumented. It forces the view to open in the + # current group, even if the view is already open in another + # group. This is necessary to prevent the quick panel from losing + # focus. See: + # https://github.com/SublimeTextIssues/Core/issues/1041 + flags |= sublime.TRANSIENT | sublime.FORCE_GROUP + if msg['span']: + # show_at_center is buggy with newly opened views (see + # https://github.com/SublimeTextIssues/Core/issues/538). + # ENCODED_POSITION is 1-based. + row, col = msg['span'][0] + else: + row, col = (999999999, 1) view = window.open_file('%s:%d:%d' % (path, row + 1, col + 1), - sublime.ENCODED_POSITION) + flags) # Block until the view is loaded. - _show_message_wait(view, levels, messages, current_idx) + _show_message_wait(view, messages, current_idx) -def _show_message_wait(view, levels, messages, current_idx): +def _show_message_wait(view, messages, current_idx): if view.is_loading(): def f(): - _show_message_wait(view, levels, messages, current_idx) + _show_message_wait(view, messages, current_idx) sublime.set_timeout(f, 10) # The on_load event handler will call show_messages_for_view which # should handle displaying the messages. @@ -376,9 +408,10 @@ def _scroll_build_panel(window, message): view.erase_regions('bug') -def _scroll_to_message(view, message): +def _scroll_to_message(view, message, transient): """Scroll view to the message.""" - view.window().focus_view(view) + if not transient: + view.window().focus_view(view) r = _span_to_region(view, message['span']) view.sel().clear() view.sel().add(r.a) @@ -504,6 +537,48 @@ def _is_matching_level(levels, msg_dict): return False +def _relative_path(window, path): + """Convert an absolute path to a relative path used for a truncated + display.""" + for folder in window.folders(): + if path.startswith(folder): + return os.path.relpath(path, folder) + return path + + +def list_messages(window): + """Show a list of all messages.""" + try: + win_info = WINDOW_MESSAGES[window.id()] + except KeyError: + # XXX: Or dialog? + window.show_quick_panel(["No messages available"], None) + return + panel_items = [] + jump_to = [] + for path_idx, (path, msgs) in enumerate(win_info['paths'].items()): + for msg_idx, msg_dict in enumerate(msgs): + if not msg_dict['is_main']: + continue + jump_to.append((path_idx, msg_idx)) + if msg_dict['span']: + path_label = '%s:%s' % ( + _relative_path(window, path), + msg_dict['span'][0][0] + 1) + else: + path_label = _relative_path(window, path) + item = [msg_dict['text'], path_label] + panel_items.append(item) + + def on_done(idx): + _show_message(window, jump_to[idx], force_open=True) + + def on_highlighted(idx): + _show_message(window, jump_to[idx], transient=True) + + window.show_quick_panel(panel_items, on_done, 0, 0, on_highlighted) + + def add_rust_messages(window, cwd, info, target_path, msg_cb): """Add messages from Rust JSON to Sublime views. diff --git a/tests/multi-targets/benches/bench_context.rs b/tests/multi-targets/benches/bench_context.rs new file mode 100644 index 00000000..e9260885 --- /dev/null +++ b/tests/multi-targets/benches/bench_context.rs @@ -0,0 +1,17 @@ +// Tests for "bench here" commands. + +#![feature(test)] + +extern crate test; + +use test::Bencher; + + #[bench] // comment +fn bench1(b: &mut Bencher) { + b.iter(|| 1); +} + +#[bench] +fn bench2(b: &mut Bencher) { + b.iter(|| 2); +} diff --git a/tests/multi-targets/tests/test_context.rs b/tests/multi-targets/tests/test_context.rs new file mode 100644 index 00000000..844eb74d --- /dev/null +++ b/tests/multi-targets/tests/test_context.rs @@ -0,0 +1,38 @@ +// Tests for "test here" command. + + #[test] +fn test1() { + +} + +# [ test ] //comment + +#[should_panic(expected="xyz")] /* comment */ + +pub fn expected_panic1() { + panic!("xyz"); +} + +#[test] +fn test2() { + fn inner() { + + } + inner(); +}#[test] +fn test3() { + +} + +// #[test] +// fn test4() { +// } + +/*#[test] +fn test5() { +} +*/ + +#[test] fn test6() { + +} diff --git a/tests/rust_test_common.py b/tests/rust_test_common.py index 1afe28a6..100148db 100644 --- a/tests/rust_test_common.py +++ b/tests/rust_test_common.py @@ -99,6 +99,11 @@ def _run_build_wait(self, command='build', **kwargs): # Wait for it to finish. self._get_rust_thread().join() + def _get_build_output(self, window): + opanel = window.find_output_panel(plugin.rust.opanel.PANEL_NAME) + output = opanel.substr(sublime.Region(0, opanel.size())) + return output + def _with_open_file(self, filename, f, **kwargs): """Opens filename (relative to the plugin) in a new view, calls f(view) to perform the tests. diff --git a/tests/test_cargo_build.py b/tests/test_cargo_build.py index 1ff82ba0..852027e1 100644 --- a/tests/test_cargo_build.py +++ b/tests/test_cargo_build.py @@ -21,11 +21,6 @@ def exe(s): class TestCargoBuild(TestBase): - def _get_build_output(self, window): - opanel = window.find_output_panel(plugin.rust.opanel.PANEL_NAME) - output = opanel.substr(sublime.Region(0, opanel.size())) - return output - def setUp(self): super(TestCargoBuild, self).setUp() self._cargo_clean(multi_target_root) @@ -430,7 +425,7 @@ def _quick_panel(self, items, on_done, flags=0, def _test_ambiguous_auto_build2(self, view): window = view.window() - self.quick_panel_items = ['--test test1', '--test test2'] + self.quick_panel_items = ['--test test1', '--test test2', '--test test_context'] self.quick_panel_index = 0 self._run_build_wait('auto') output = self._get_build_output(window) diff --git a/tests/test_context.py b/tests/test_context.py new file mode 100644 index 00000000..d0b37847 --- /dev/null +++ b/tests/test_context.py @@ -0,0 +1,159 @@ +"""Tests for the context commands.""" + + +from rust_test_common import * + + +class TestContext(TestBase): + + def test_pt_to_test_name(self): + self._with_open_file('tests/multi-targets/tests/test_context.rs', + self._test_pt_to_test_name) + + def _test_pt_to_test_name(self, view): + expected = [ + ('test1', (3, 1), (7, 1)), + ('expected_panic1', (8, 1), (15, 1)), + ('test2', (16, 1), (22, 1)), + ('test3', (22, 2), (26, 1)), + ('test6', (36, 1), (39, 1)), + ] + for fn_name, (start_row, start_col), (end_row, end_col) in expected: + start_pt = view.text_point(start_row - 1, start_col - 1) + end_pt = view.text_point(end_row - 1, end_col - 1) + for pt in range(start_pt, end_pt): + name = plugin.cargo_build._pt_to_test_name('test', pt, view) + self.assertEqual(name, fn_name, + 'rowcol=%r' % (view.rowcol(pt),)) + + def test_cargo_test_here(self): + self._with_open_file('tests/multi-targets/tests/test_context.rs', + self._test_cargo_test_here) + + def _test_cargo_test_here(self, view): + pt = view.text_point(4, 0) + x, y = view.text_to_window(pt) + view.window().run_command('cargo_test_here', args={ + 'event': {'x': x, 'y': y} + }) + self._get_rust_thread().join() + output = self._get_build_output(view.window()) + self.assertRegex(output, + r'\[Running: cargo test --test test_context --message-format=json -- --exact test1\]') + + def test_cargo_test_at_cursor(self): + self._with_open_file('tests/multi-targets/tests/test_context.rs', + self._test_cargo_test_at_cursor) + + def _test_cargo_test_at_cursor(self, view): + pt = view.text_point(12, 0) + sel = view.sel() + sel.clear() + sel.add(sublime.Region(pt)) + view.run_command('cargo_test_at_cursor') + self._get_rust_thread().join() + output = self._get_build_output(view.window()) + self.assertRegex(output, + r'\[Running: cargo test --test test_context --message-format=json -- --exact expected_panic1\]') + + def test_cargo_test_current_file(self): + self._with_open_file('tests/multi-targets/tests/test_context.rs', + self._test_cargo_test_current_file) + + def _test_cargo_test_current_file(self, view): + view.window().run_command('cargo_test_current_file') + self._get_rust_thread().join() + output = self._get_build_output(view.window()) + self.assertRegex(output, + r'\[Running: cargo test --test test_context --message-format=json\]') + + def test_cargo_bench_here(self): + self._with_open_file('tests/multi-targets/benches/bench_context.rs', + self._test_cargo_bench_here) + + def _test_cargo_bench_here(self, view): + pt = view.text_point(15, 0) + x, y = view.text_to_window(pt) + view.window().run_command('cargo_bench_here', args={ + 'event': {'x': x, 'y': y} + }) + self._get_rust_thread().join() + output = self._get_build_output(view.window()) + self.assertRegex(output, + r'\[Running: cargo bench --bench bench_context --message-format=json -- --exact bench2\]') + + def test_cargo_bench_at_cursor(self): + self._with_open_file('tests/multi-targets/benches/bench_context.rs', + self._test_cargo_bench_at_cursor) + + def _test_cargo_bench_at_cursor(self, view): + pt = view.text_point(15, 0) + sel = view.sel() + sel.clear() + sel.add(sublime.Region(pt)) + view.run_command('cargo_bench_at_cursor') + self._get_rust_thread().join() + output = self._get_build_output(view.window()) + self.assertRegex(output, + r'\[Running: cargo bench --bench bench_context --message-format=json -- --exact bench2\]') + + def test_cargo_bench_current_file(self): + self._with_open_file('tests/multi-targets/benches/bench_context.rs', + self._test_cargo_bench_current_file) + + def _test_cargo_bench_current_file(self, view): + view.window().run_command('cargo_bench_current_file') + self._get_rust_thread().join() + output = self._get_build_output(view.window()) + self.assertRegex(output, + r'\[Running: cargo bench --bench bench_context --message-format=json\]') + + def test_cargo_run_current_file(self): + self._with_open_file('tests/multi-targets/examples/ex1.rs', + self._test_cargo_run_current_file) + + def _test_cargo_run_current_file(self, view): + view.window().run_command('cargo_run_current_file') + self._get_rust_thread().join() + output = self._get_build_output(view.window()) + self.assertRegex(output, + r'\[Running: cargo run --example ex1 --message-format=json\]') + + def test_rust_list_messages(self): + self._with_open_file('tests/message-order/examples/ex_warning1.rs', + self._test_rust_list_messages) + + def _test_rust_list_messages(self, view): + window = view.window() + self._cargo_clean(view) + window.run_command('cargo_exec', args={'command': 'auto'}) + self._get_rust_thread().join() + sqp = window.__class__.show_quick_panel + window.__class__.show_quick_panel = self._quick_panel + try: + self._test_rust_list_messages2(view) + finally: + window.__class__.show_quick_panel = sqp + + def _quick_panel(self, items, on_done, flags=0, + selected_index=-1, on_highlighted=None): + self.assertEqual(items, self.quick_panel_items) + on_done(self.quick_panel_index) + + def _test_rust_list_messages2(self, view): + window = view.window() + self.quick_panel_items = [ + ['function is never used: `unused_a`', + os.path.join('tests', 'message-order', 'examples', 'warning1.rs') + ':1'], + ['function is never used: `unused_b`', + os.path.join('tests', 'message-order', 'examples', 'warning1.rs') + ':5'], + ['function is never used: `unused_in_2`', + os.path.join('tests', 'message-order', 'examples', 'warning2.rs') + ':82'], + ] + self.quick_panel_index = 2 + window.run_command('rust_list_messages') + new_view = window.active_view() + expected_path = os.path.normpath( + os.path.join(plugin_path, 'tests/message-order/examples/warning2.rs')) + self.assertEqual(new_view.file_name(), expected_path) + new_view.run_command('close_file') diff --git a/tests/test_target_detect.py b/tests/test_target_detect.py index 83ec4650..2a65744d 100644 --- a/tests/test_target_detect.py +++ b/tests/test_target_detect.py @@ -32,7 +32,8 @@ def test_multi_targets(self): # Shared module in test, not possible to easily determine which # test it belongs to. ('tests/common/helpers.rs', [('tests/test1.rs', '--test test1'), - ('tests/test2.rs', '--test test2')]), + ('tests/test2.rs', '--test test2'), + ('tests/test_context.rs', '--test test_context')]), # proc-macro kind ('pmacro/src/lib.rs', [('pmacro/src/lib.rs', '--lib')]), # Different lib types. diff --git a/tests/test_toggle_setting.py b/tests/test_toggle_setting.py new file mode 100644 index 00000000..5b42d69d --- /dev/null +++ b/tests/test_toggle_setting.py @@ -0,0 +1,21 @@ +"""Tests for toggle command.""" + + +from rust_test_common import * + + +class TestToggle(TestBase): + + def test_toggle(self): + window = sublime.active_window() + self.assertEqual( + util.get_setting('rust_syntax_checking', True), + True) + window.run_command('toggle_rust_syntax_setting') + self.assertEqual( + util.get_setting('rust_syntax_checking', True), + False) + window.run_command('toggle_rust_syntax_setting') + self.assertEqual( + util.get_setting('rust_syntax_checking', True), + True) diff --git a/toggle_setting.py b/toggle_setting.py index 15a9eba1..7e5dd67d 100644 --- a/toggle_setting.py +++ b/toggle_setting.py @@ -1,9 +1,22 @@ -import sublime, sublime_plugin +import sublime_plugin +from .rust import (util, messages) -class ToggleRustSyntaxSettingCommand(sublime_plugin.TextCommand): - def run(self, setting): - # Grab the setting and reserse it - current_state = self.view.settings().get('rust_syntax_checking') - self.view.settings().set('rust_syntax_checking', not current_state) - self.view.window().status_message("Rust syntax checking is now " + ("inactive" if current_state else "active")) +class ToggleRustSyntaxSettingCommand(sublime_plugin.WindowCommand): + + """Toggles on-save checking for the current window.""" + + def run(self): + # Grab the setting and reverse it. + window = self.window + current_state = util.get_setting('rust_syntax_checking', True) + new_state = not current_state + pdata = window.project_data() + pdata.setdefault('settings', {})['rust_syntax_checking'] = new_state + if not new_state: + messages.clear_messages(window) + window.status_message("Rust syntax checking is now " + ("inactive" if current_state else "active")) + window.set_project_data(pdata) + + def is_checked(self): + return util.get_setting('rust_syntax_checking', True)