diff --git a/pythonx/UltiSnips/err_to_scratch_buffer.py b/pythonx/UltiSnips/err_to_scratch_buffer.py new file mode 100644 index 0000000..daec4df --- /dev/null +++ b/pythonx/UltiSnips/err_to_scratch_buffer.py @@ -0,0 +1,51 @@ +# coding=utf8 + +from functools import wraps +import traceback +import re +import sys + +from UltiSnips import _vim + +def wrap(func): + """Decorator that will catch any Exception that 'func' throws and displays + it in a new Vim scratch buffer.""" + @wraps(func) + def wrapper(self, *args, **kwds): + try: + return func(self, *args, **kwds) + except Exception as e: # pylint: disable=bare-except + msg = \ + """An error occured. This is either a bug in UltiSnips or a bug in a +snippet definition. If you think this is a bug, please report it to +https://github.com/SirVer/ultisnips/issues/new. + +Following is the full stack trace: +""" + + msg += traceback.format_exc() + if hasattr(e, 'snippet_info'): + msg += "\nSnippet, caused error:\n" + msg += re.sub( + '^(?=\S)', ' ', e.snippet_info, flags=re.MULTILINE + ) + # snippet_code comes from _python_code.py, it's set manually for + # providing error message with stacktrace of failed python code + # inside of the snippet. + if hasattr(e, 'snippet_code'): + _, _, tb = sys.exc_info() + tb_top = traceback.extract_tb(tb)[-1] + msg += "\nExecuted snippet code:\n" + lines = e.snippet_code.split("\n") + for number, line in enumerate(lines, 1): + msg += str(number).rjust(3) + prefix = " " if line else "" + if tb_top[1] == number: + prefix = " > " + msg += prefix + line + "\n" + + # Vim sends no WinLeave msg here. + if hasattr(self, '_leaving_buffer'): + self._leaving_buffer() # pylint:disable=protected-access + _vim.new_scratch_buffer(msg) + return wrapper diff --git a/pythonx/UltiSnips/snippet/definition/_base.py b/pythonx/UltiSnips/snippet/definition/_base.py index 6d7b13f..b7f4bbe 100644 --- a/pythonx/UltiSnips/snippet/definition/_base.py +++ b/pythonx/UltiSnips/snippet/definition/_base.py @@ -11,16 +11,19 @@ import textwrap from UltiSnips import _vim from UltiSnips.compatibility import as_unicode from UltiSnips.indent_util import IndentUtil -from UltiSnips.position import Position from UltiSnips.text import escape from UltiSnips.text_objects import SnippetInstance -from UltiSnips.text_objects._python_code import SnippetUtilCursor, SnippetUtilForAction +from UltiSnips.text_objects._python_code import \ + SnippetUtilCursor, SnippetUtilForAction __WHITESPACE_SPLIT = re.compile(r"\s") + + def split_at_whitespace(string): """Like string.split(), but keeps empty words as empty words.""" return re.split(__WHITESPACE_SPLIT, string) + def _words_for_line(trigger, before, num_words=None): """Gets the final 'num_words' words from 'before'. @@ -131,27 +134,7 @@ class SnippetDefinition(object): try: exec(code, {'snip': snip}) except Exception as e: - e.snippet_info = textwrap.dedent(""" - Defined in: {} - Trigger: {} - Description: {} - Context: {} - Pre-expand: {} - Post-expand: {} - """).format( - self._location, - self._trigger, - self._description, - self._context_code if self._context_code else '', - self._actions['pre_expand'] if 'pre_expand' in self._actions - else '', - self._actions['post_expand'] if 'post_expand' in self._actions - else '', - code, - ) - - e.snippet_code = code - + self._make_debug_exception(e, code) raise return snip @@ -200,6 +183,28 @@ class SnippetDefinition(object): return snip + def _make_debug_exception(self, e, code=''): + e.snippet_info = textwrap.dedent(""" + Defined in: {} + Trigger: {} + Description: {} + Context: {} + Pre-expand: {} + Post-expand: {} + """).format( + self._location, + self._trigger, + self._description, + self._context_code if self._context_code else '', + self._actions['pre_expand'] if 'pre_expand' in self._actions + else '', + self._actions['post_expand'] if 'post_expand' in self._actions + else '', + code, + ) + + e.snippet_code = code + def has_option(self, opt): """Check if the named option is set.""" return opt in self._opts @@ -247,7 +252,12 @@ class SnippetDefinition(object): words = _words_for_line(self._trigger, before) if 'r' in self._opts: - match = self._re_match(before) + try: + match = self._re_match(before) + except Exception as e: + self._make_debug_exception(e) + raise + elif 'w' in self._opts: words_len = len(self._trigger) words_prefix = words[:-words_len] diff --git a/pythonx/UltiSnips/snippet_manager.py b/pythonx/UltiSnips/snippet_manager.py index a93b2c7..aed93fa 100644 --- a/pythonx/UltiSnips/snippet_manager.py +++ b/pythonx/UltiSnips/snippet_manager.py @@ -14,6 +14,7 @@ import re from contextlib import contextmanager from UltiSnips import _vim +from UltiSnips import err_to_scratch_buffer from UltiSnips._diff import diff, guess_edit from UltiSnips.compatibility import as_unicode from UltiSnips.position import Position @@ -52,49 +53,6 @@ def _ask_snippets(snippets): return _ask_user(snippets, display) -def err_to_scratch_buffer(func): - """Decorator that will catch any Exception that 'func' throws and displays - it in a new Vim scratch buffer.""" - @wraps(func) - def wrapper(self, *args, **kwds): - try: - return func(self, *args, **kwds) - except Exception as e: # pylint: disable=bare-except - msg = \ - """An error occured. This is either a bug in UltiSnips or a bug in a -snippet definition. If you think this is a bug, please report it to -https://github.com/SirVer/ultisnips/issues/new. - -Following is the full stack trace: -""" - - msg += traceback.format_exc() - if hasattr(e, 'snippet_info'): - msg += "\nSnippet, caused error:\n" - msg += re.sub( - '^(?=\S)', ' ', e.snippet_info, flags=re.MULTILINE - ) - # snippet_code comes from _python_code.py, it's set manually for - # providing error message with stacktrace of failed python code - # inside of the snippet. - if hasattr(e, 'snippet_code'): - _, _, tb = sys.exc_info() - tb_top = traceback.extract_tb(tb)[-1] - msg += "\nExecuted snippet code:\n" - lines = e.snippet_code.split("\n") - for number, line in enumerate(lines, 1): - msg += str(number).rjust(3) - prefix = " " if line else "" - if tb_top[1] == number: - prefix = " > " - msg += prefix + line + "\n" - - # Vim sends no WinLeave msg here. - self._leaving_buffer() # pylint:disable=protected-access - _vim.new_scratch_buffer(msg) - return wrapper - - # TODO(sirver): This class is still too long. It should only contain public # facing methods, most of the private methods should be moved outside of it. class SnippetManager(object): @@ -141,7 +99,7 @@ class SnippetManager(object): self._reinit() - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def jump_forwards(self): """Jumps to the next tabstop.""" _vim.command('let g:ulti_jump_forwards_res = 1') @@ -150,7 +108,7 @@ class SnippetManager(object): _vim.command('let g:ulti_jump_forwards_res = 0') return self._handle_failure(self.forward_trigger) - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def jump_backwards(self): """Jumps to the previous tabstop.""" _vim.command('let g:ulti_jump_backwards_res = 1') @@ -159,7 +117,7 @@ class SnippetManager(object): _vim.command('let g:ulti_jump_backwards_res = 0') return self._handle_failure(self.backward_trigger) - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def expand(self): """Try to expand a snippet at the current position.""" _vim.command('let g:ulti_expand_res = 1') @@ -167,7 +125,7 @@ class SnippetManager(object): _vim.command('let g:ulti_expand_res = 0') self._handle_failure(self.expand_trigger) - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def expand_or_jump(self): """This function is used for people who wants to have the same trigger for expansion and forward jumping. @@ -185,7 +143,7 @@ class SnippetManager(object): _vim.command('let g:ulti_expand_or_jump_res = 0') self._handle_failure(self.expand_trigger) - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def snippets_in_current_scope(self, searchAll): """Returns the snippets that could be expanded to Vim as a global variable.""" @@ -226,7 +184,7 @@ class SnippetManager(object): - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def list_snippets(self): """Shows the snippets that could be expanded to the User and let her select one.""" @@ -251,7 +209,7 @@ class SnippetManager(object): return True - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def add_snippet(self, trigger, value, description, options, ft='all', priority=0, context=None, actions={}): """Add a snippet to the list of known snippets of the given 'ft'.""" @@ -260,7 +218,7 @@ class SnippetManager(object): description, options, {}, 'added', context, actions)) - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def expand_anon( self, value, trigger='', description='', options='', context=None, actions={} @@ -317,7 +275,7 @@ class SnippetManager(object): self._buffer_filetypes[_vim.buf.number].insert(idx + 1, ft) idx += 1 - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def _cursor_moved(self): """Called whenever the cursor moved.""" self._should_update_textobjects = False @@ -446,7 +404,7 @@ class SnippetManager(object): # are back in our buffer pass - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def _save_last_visual_selection(self): """This is called when the expand trigger is pressed in visual mode. Our job is to remember everything between '< and '> and pass it on to. @@ -876,7 +834,7 @@ class SnippetManager(object): finally: self._inside_action = old_flag - @err_to_scratch_buffer + @err_to_scratch_buffer.wrap def _track_change(self): self._should_update_textobjects = True