From dd9969369683029b7952193a763b0adf41645280 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Tue, 28 Aug 2018 16:02:49 -0700 Subject: [PATCH] On-save check: Show result in window status when done. Includes minor cleanup for tracking message levels. --- SyntaxCheckPlugin.py | 37 +++++++++++++++++++++------- rust/levels.py | 57 ++++++++++++++++++++++++++++++++++++++++++++ rust/messages.py | 51 +++++++++++++++++++-------------------- rust/opanel.py | 6 ++--- rust/rust_proc.py | 7 ++++++ rust/themes.py | 2 +- rust/util.py | 1 + 7 files changed, 123 insertions(+), 38 deletions(-) create mode 100644 rust/levels.py diff --git a/SyntaxCheckPlugin.py b/SyntaxCheckPlugin.py index baeaeedd..cea638eb 100755 --- a/SyntaxCheckPlugin.py +++ b/SyntaxCheckPlugin.py @@ -78,16 +78,31 @@ def run(self): self.update_status() self.this_view_found = False + CHECK_FAIL_MSG = 'Rust check failed, see console or debug log.' try: messages.clear_messages(self.window) try: - self.get_rustc_messages() + rc = self.get_rustc_messages() except rust_proc.ProcessTerminatedError: + self.window.status_message('') return - messages.messages_finished(self.window) + except Exception as e: + self.window.status_message(CHECK_FAIL_MSG) + raise finally: self.done = True - self.window.status_message('') + messages.messages_finished(self.window) + counts = messages.message_counts(self.window) + if counts: + msg = [] + for key, value in sorted(counts.items(), key=lambda x: x[0]): + level = key.plural if value > 1 else key.name + msg.append('%i %s' % (value, level)) + self.window.status_message('Rust check: %s' % (', '.join(msg,))) + elif rc: + self.window.status_message(CHECK_FAIL_MSG) + else: + self.window.status_message('Rust check: success') def update_status(self, count=0): if self.done: @@ -105,6 +120,9 @@ def get_rustc_messages(self): filename. :raises rust_proc.ProcessTerminatedError: Check was canceled. + :raises OSError: Failed to launch the child process. + + :returns: Returns the process return code. """ method = util.get_setting('rust_syntax_checking_method', 'check') settings = cargo_settings.CargoSettings(self.window) @@ -119,8 +137,7 @@ def get_rustc_messages(self): self.msg_rel_path = cmd['msg_rel_path'] p = rust_proc.RustProc() p.run(self.window, cmd['command'], self.cwd, self, env=cmd['env']) - p.wait() - return + return p.wait() if method == 'no-trans': print('rust_syntax_checking_method == "no-trans" is no longer supported.') @@ -129,10 +146,13 @@ def get_rustc_messages(self): if method != 'check': print('Unknown setting for `rust_syntax_checking_method`: %r' % (method,)) - return + return -1 td = target_detect.TargetDetector(self.window) targets = td.determine_targets(self.triggered_file_name) + if not targets: + return -1 + rc = 0 for (target_src, target_args) in targets: cmd = settings.get_command(method, command_info, self.cwd, self.cwd, initial_settings={'target': ' '.join(target_args)}, @@ -149,9 +169,10 @@ def get_rustc_messages(self): p = rust_proc.RustProc() self.current_target_src = target_src p.run(self.window, cmd['command'], self.cwd, self, env=cmd['env']) - p.wait() + rc = p.wait() if self.this_view_found: - break + return rc + return rc ######################################################################### # ProcListner methods diff --git a/rust/levels.py b/rust/levels.py new file mode 100644 index 00000000..742fd7ed --- /dev/null +++ b/rust/levels.py @@ -0,0 +1,57 @@ +import sublime +from . import log + + +class Level: + + def __init__(self, order, name, plural): + self.order = order + self.name = name + self.plural = plural + + def __hash__(self): + return hash(self.name) + + def __eq__(self, other): + if isinstance(other, Level): + return self.name == other.name + elif isinstance(other, str): + return self.name == other + else: + return False + + def __lt__(self, other): + return self.order < other.order + + def __le__(self, other): + return self.order <= other.order + + def __gt__(self, other): + return self.order > other.order + + def __ge__(self, other): + return self.order >= other.order + + def __repr__(self): + return self.name + + +LEVELS = { + 'error': Level(0, 'error', 'errors'), + 'warning': Level(1, 'warning', 'warnings'), + 'note': Level(2, 'note', 'notes'), + 'help': Level(3, 'help', 'help'), +} + + +def level_from_str(level): + if level.startswith('error:'): + # ICE + level = 'error' + try: + return LEVELS[level] + except KeyError: + log.critical(sublime.active_window(), + 'RustEnhanced: Unknown message level %r encountered.', + level) + return LEVELS['error'] diff --git a/rust/messages.py b/rust/messages.py index 151e6b8a..1c19551f 100644 --- a/rust/messages.py +++ b/rust/messages.py @@ -15,6 +15,7 @@ from . import util, themes, log from .batch import * +from .levels import * # Key is window id. # Value is a dictionary: { @@ -42,7 +43,7 @@ class Message: be None if the content is raw markup (such as a minihtml link) or if it is an outline-only region (which happens with things such as dual-region messages added in 1.21). - :ivar level: Message level as a string such as "error", or "info". + :ivar level: Message level as a `Level` object. :ivar span: Location of the message (0-based): `((line_start, col_start), (line_end, col_end))` May be `None` to indicate no particular spot. @@ -258,27 +259,17 @@ def messages_finished(window): def _draw_region_highlights(view, batch): if util.get_setting('rust_region_style') == 'none': return - - # Collect message regions by level. - regions = { - 'error': [], - 'warning': [], - 'note': [], - 'help': [], - } if batch.hidden: return + + # Collect message regions by level. + regions = {level: [] for level in LEVELS.values()} for msg in batch: region = msg.sublime_region(view) - if msg.level not in regions: - log.critical(view.window(), - 'RustEnhanced: Unknown message level %r encountered.', - msg.level) - msg.level = 'error' regions[msg.level].append((msg.region_key, region)) # Do this in reverse order so that errors show on-top. - for level in ['help', 'note', 'warning', 'error']: + for level in reversed(sorted(list(LEVELS.values()))): # Use scope names from color themes to drive the color of the outline. # 'invalid' typically is red. We use 'info' for all other levels, which # is usually not defined in any color theme, and will end up showing as @@ -293,7 +284,7 @@ def _draw_region_highlights(view, batch): scope = 'invalid' else: scope = 'info' - icon = util.icon_path(level) + icon = util.icon_path(level.name) for key, region in regions[level]: _sublime_add_regions( view, key, [region], scope, icon, @@ -468,13 +459,8 @@ def _sort_messages(window): items = [] for path, batches in batches_by_path.items(): for batch in batches: - level = { - 'error': 0, - 'warning': 1, - 'note': 2, - 'help': 3, - }.get(batch.first().level, 0) - items.append((level, path, batch.first().lineno(), batch)) + first = batch.first() + items.append((first.level, path, first.lineno(), batch)) items.sort(key=lambda x: x[:3]) batches_by_path = collections.OrderedDict() for _, path, _, batch in items: @@ -737,6 +723,19 @@ def on_highlighted(idx): window.show_quick_panel(panel_items, on_done, 0, 0, on_highlighted) +def message_counts(window): + result = collections.Counter() + try: + win_info = WINDOW_MESSAGES[window.id()] + except KeyError: + return result + for batches in win_info['paths'].values(): + for batch in batches: + if isinstance(batch, PrimaryBatch): + result[batch.first().level] += 1 + return result + + def add_rust_messages(window, base_path, info, target_path, msg_cb): """Add messages from Rust JSON to Sublime views. @@ -876,13 +875,13 @@ def set_primary_message(span, text): message.path = make_span_path(span) message.span = make_span_region(span) message.text = text - message.level = info['level'] + message.level = level_from_str(info['level']) def add_additional(span, text, level, suggested_replacement=None): child = Message() child.text = text child.suggested_replacement = suggested_replacement - child.level = level + child.level = level_from_str(level) child.primary = False if 'macros>' in span['file_name']: # Nowhere to display this, just send it to the console via msg_cb. @@ -925,7 +924,7 @@ def add_additional(span, text, level, suggested_replacement=None): # put it. if msg_cb: tmp_msg = Message() - tmp_msg.level = info['level'] + tmp_msg.level = level_from_str(info['level']) tmp_msg.text = imsg msg_cb(tmp_msg) diff --git a/rust/opanel.py b/rust/opanel.py index b9464eb7..0381d692 100644 --- a/rust/opanel.py +++ b/rust/opanel.py @@ -4,7 +4,7 @@ import os import re -from . import rust_proc, messages, util, semver, log +from . import rust_proc, messages, util, semver, levels, log # Use the same panel name that Sublime's build system uses so that "Show Build # Results" will open the same panel. I don't see any particular reason why @@ -98,7 +98,7 @@ def on_data(self, proc, data): region_start + m.end()) message.output_panel_region = build_region message.path = path - message.level = 'error' + message.level = levels.level_from_str('error') messages.add_message(self.window, message) def on_error(self, proc, message): @@ -118,7 +118,7 @@ def msg_cb(self, message): if not message.text: # Region-only messages can be ignored. return - region_start = self.output_view.size() + len(message.level) + 2 + region_start = self.output_view.size() + len(message.level.name) + 2 path = message.path if path: if self.base_path and path.startswith(self.base_path): diff --git a/rust/rust_proc.py b/rust/rust_proc.py index 50ca9a70..5d650d18 100644 --- a/rust/rust_proc.py +++ b/rust/rust_proc.py @@ -91,6 +91,8 @@ def slurp_json(window, cmd, cwd): :returns: List of parsed JSON objects. :raises ProcessTermiantedError: Process was terminated by another thread. + :raises OSError: Failed to launch the child process. `FileNotFoundError` + is a typical example if the executable is not found. """ rc, listener = _slurp(window, cmd, cwd) if not listener.json and rc: @@ -109,6 +111,8 @@ def check_output(window, cmd, cwd): :returns: A string of the command's output. :raises ProcessTermiantedError: Process was terminated by another thread. + :raises OSError: Failed to launch the child process. `FileNotFoundError` + is a typical example if the executable is not found. :raises subprocess.CalledProcessError: The command returned a nonzero exit status. """ @@ -160,6 +164,9 @@ def run(self, window, cmd, cwd, listener, env=None, :raises ProcessTermiantedError: Process was terminated by another thread. + :raises OSError: Failed to launch the child process. + `FileNotFoundError` is a typical example if the executable is not + found. """ self.cmd = cmd self.cwd = cwd diff --git a/rust/themes.py b/rust/themes.py index 15f91947..7383e560 100644 --- a/rust/themes.py +++ b/rust/themes.py @@ -104,7 +104,7 @@ def render(self, view, batch, for_popup=False): level_text = '' else: if msg.level == last_level: - level_text = ' ' * (len(msg.level) + 2) + level_text = ' ' * (len(str(msg.level)) + 2) else: level_text = '%s: ' % (msg.level,) last_level = msg.level diff --git a/rust/util.py b/rust/util.py index 08055f9a..f4d925b6 100644 --- a/rust/util.py +++ b/rust/util.py @@ -135,6 +135,7 @@ def get_cargo_metadata(window, cwd, toolchain=None): def icon_path(level, res=None): """Return a path to a message-level icon.""" + level = str(level) if level not in ('error', 'warning', 'note', 'help', 'none'): return '' gutter_style = get_setting('rust_gutter_style', 'shape')