|
22 | 22 | COMPLETION_ERROR_MSG = "[Ycmd][Completion] Error {}" |
23 | 23 | COMPLETION_NOT_AVAILABLE_MSG = "[Ycmd] No completion available" |
24 | 24 | ERROR_MESSAGE_TEMPLATE = "[{kind}] {text}" |
| 25 | +PANEL_ERROR_MESSAGE_TEMPLATE = "{:<5} {}" |
25 | 26 | GET_PATH_ERROR_MSG = "[Ycmd][Path] Failed to replace '{}' -> '{}'" |
26 | 27 | NO_HMAC_MESSAGE = "[Ycmd] You should generate HMAC throug the menu before using plugin" |
27 | 28 | NOTIFY_ERROR_MSG = "[Ycmd][Notify] Error {}" |
@@ -223,6 +224,9 @@ class YcmdCompletionEventListener(sublime_plugin.EventListener): |
223 | 224 | view_line = dict() |
224 | 225 |
|
225 | 226 | def on_selection_modified_async(self, view): |
| 227 | + if view.id() == ERROR_PANEL.id(): |
| 228 | + ERROR_PANEL.show_code_for_error() |
| 229 | + return |
226 | 230 | if lang(view) is None or view.is_scratch(): |
227 | 231 | return |
228 | 232 | self.update_statusbar(view) |
@@ -250,6 +254,11 @@ def on_pre_close(self, view): |
250 | 254 | if view_id in self.view_cache: |
251 | 255 | del self.view_cache[view_id] |
252 | 256 |
|
| 257 | + def on_activated_async(self, view): |
| 258 | + if lang(view) is None or view.is_scratch(): |
| 259 | + return |
| 260 | + ERROR_PANEL.update(self.view_cache) |
| 261 | + |
253 | 262 | def on_query_completions(self, view, prefix, locations): |
254 | 263 | '''Sublime Text autocompletion event handler''' |
255 | 264 | filetype = lang(view) |
@@ -306,6 +315,7 @@ def _on_errors(self, data): |
306 | 315 | [_ for _ in data |
307 | 316 | if get_file_path(_['location']['filepath']) == filepath]) |
308 | 317 | self.update_statusbar(active_view(), force=True) |
| 318 | + ERROR_PANEL.update(self.view_cache) |
309 | 319 |
|
310 | 320 | def update_statusbar(self, view, force=False): |
311 | 321 | row, col = get_selected_pos(view) |
@@ -390,3 +400,91 @@ def _completer_cb(self, data, command): |
390 | 400 | sublime.ENCODED_POSITION) |
391 | 401 | else: |
392 | 402 | print_status("[Ycmd][{}]: {}".format(command, jsonResp.get('message', ''))) |
| 403 | + |
| 404 | +class YcmdErrorPanelRefresh(sublime_plugin.TextCommand): |
| 405 | + def run(self, edit, data): |
| 406 | + self.view.erase(edit, sublime.Region(0, self.view.size())) |
| 407 | + self.view.insert(edit, 0, data) |
| 408 | + |
| 409 | +class YcmdErrorPanel(object): |
| 410 | + # view of this error panel |
| 411 | + view = None |
| 412 | + # text currently in panel |
| 413 | + text = "" |
| 414 | + # view with code, for with panel show errors |
| 415 | + code_view = None |
| 416 | + lines_to_errors = [] |
| 417 | + |
| 418 | + def id(self): |
| 419 | + if self.view != None: |
| 420 | + return self.view.id() |
| 421 | + else: |
| 422 | + return None |
| 423 | + |
| 424 | + def update_async(self, view_cache, view=None): |
| 425 | + t = Thread(None, self.update, 'PanelUpdateAsync', [view_cache, view]) |
| 426 | + t.daemon = True |
| 427 | + t.start() |
| 428 | + |
| 429 | + def update(self, view_cache, view=None): |
| 430 | + if view == None: |
| 431 | + view = active_view() |
| 432 | + |
| 433 | + messages = [] |
| 434 | + self.lines_to_errors = [] |
| 435 | + lines = view_cache.get(view.id(), {}) |
| 436 | + for line_num, line_regions in sorted(lines.items()): |
| 437 | + for region, msg in line_regions.items(): |
| 438 | + messages.append(PANEL_ERROR_MESSAGE_TEMPLATE.format(str(line_num)+':', msg)) |
| 439 | + self.lines_to_errors.append(region) |
| 440 | + self.text = '\n'.join(messages) |
| 441 | + self.code_view = view |
| 442 | + if self.is_visible(): |
| 443 | + self._refresh() |
| 444 | + |
| 445 | + def is_visible(self): |
| 446 | + return self.view != None and self.view.window() != None |
| 447 | + |
| 448 | + def _refresh(self): |
| 449 | + self.view.set_read_only(False) |
| 450 | + self.view.set_scratch(True) |
| 451 | + self.view.run_command("ycmd_error_panel_refresh", {"data": self.text}) |
| 452 | + self.view.set_read_only(True) |
| 453 | + |
| 454 | + def show_code_for_error(self): |
| 455 | + if not self.is_visible() or self.code_view == None: |
| 456 | + return |
| 457 | + |
| 458 | + # get rid of false positive (non-user interaction) |
| 459 | + last_command_name, _, _ = self.view.command_history(0, False) |
| 460 | + if last_command_name == 'ycmd_error_panel_refresh': |
| 461 | + return |
| 462 | + |
| 463 | + row, _ = get_selected_pos(self.view) |
| 464 | + if row < len(self.lines_to_errors): |
| 465 | + region = self.lines_to_errors[row] |
| 466 | + # we must create sublime region, because cached region is just tuple |
| 467 | + sublime_region = sublime.Region(region[0], region[1]) |
| 468 | + self.code_view.show_at_center(sublime_region) |
| 469 | + |
| 470 | + def open(self): |
| 471 | + window = sublime.active_window() |
| 472 | + if not self.is_visible(): |
| 473 | + self.view = window.create_output_panel("clang-errors") |
| 474 | + syntax_file = "Packages/YcmdCompletion/ErrorPanel.tmLanguage" |
| 475 | + self.view.set_syntax_file(syntax_file) |
| 476 | + self._refresh() |
| 477 | + window.run_command("show_panel", {"panel": "output.clang-errors"}) |
| 478 | + |
| 479 | + def close(self): |
| 480 | + sublime.active_window().run_command("hide_panel", {"panel": "output.clang-errors"}) |
| 481 | + |
| 482 | +ERROR_PANEL = YcmdErrorPanel() |
| 483 | + |
| 484 | +class YcmdErrorPanelShow(sublime_plugin.WindowCommand): |
| 485 | + def run(self): |
| 486 | + ERROR_PANEL.open() |
| 487 | + |
| 488 | +class YcmdErrorPanelHide(sublime_plugin.WindowCommand): |
| 489 | + def run(self): |
| 490 | + ERROR_PANEL.close() |
0 commit comments