diff --git a/pythonx/UltiSnips/snippet/definition/_base.py b/pythonx/UltiSnips/snippet/definition/_base.py index 62af17e..77c1f63 100644 --- a/pythonx/UltiSnips/snippet/definition/_base.py +++ b/pythonx/UltiSnips/snippet/definition/_base.py @@ -6,6 +6,7 @@ import re import vim +import textwrap from UltiSnips import _vim from UltiSnips.compatibility import as_unicode @@ -116,7 +117,31 @@ class SnippetDefinition(object): snip = SnippetUtilForAction(locals) - exec(code, {'snip': snip}) + 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 + + raise return snip diff --git a/pythonx/UltiSnips/snippet_manager.py b/pythonx/UltiSnips/snippet_manager.py index 448daf8..b4e99c7 100644 --- a/pythonx/UltiSnips/snippet_manager.py +++ b/pythonx/UltiSnips/snippet_manager.py @@ -8,7 +8,9 @@ from functools import wraps import os import platform import traceback +import sys import vim +import re from contextlib import contextmanager from UltiSnips import _vim @@ -57,7 +59,7 @@ def err_to_scratch_buffer(func): def wrapper(self, *args, **kwds): try: return func(self, *args, **kwds) - except: # pylint: disable=bare-except + 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 @@ -65,7 +67,28 @@ 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) diff --git a/pythonx/UltiSnips/text_objects/_python_code.py b/pythonx/UltiSnips/text_objects/_python_code.py index 9427c01..dac036e 100644 --- a/pythonx/UltiSnips/text_objects/_python_code.py +++ b/pythonx/UltiSnips/text_objects/_python_code.py @@ -267,7 +267,11 @@ class PythonCode(NoneditableTextObject): self._snip._reset(ct) # pylint:disable=protected-access for code in self._codes: - exec(code, self._locals) # pylint:disable=exec-used + try: + exec(code, self._locals) # pylint:disable=exec-used + except Exception as e: + e.snippet_code = code + raise rv = as_unicode( self._snip.rv if self._snip._rv_changed # pylint:disable=protected-access diff --git a/test/test_ParseSnippets.py b/test/test_ParseSnippets.py index 6e1450c..f9b4d3b 100644 --- a/test/test_ParseSnippets.py +++ b/test/test_ParseSnippets.py @@ -239,3 +239,78 @@ endsnippet """} keys = 'ab' + EX wanted = 'x first a bob b y' + + +class ParseSnippets_PrintPythonStacktrace(_VimTest): + files = { 'us/all.snippets': r""" + snippet test + `!p abc()` + endsnippet + """} + keys = 'test' + EX + wanted = keys + expected_error = " > abc" + + +class ParseSnippets_PrintPythonStacktraceMultiline(_VimTest): + files = { 'us/all.snippets': r""" + snippet test + `!p if True: + qwe()` + endsnippet + """} + keys = 'test' + EX + wanted = keys + expected_error = " > \s+qwe" + + +class ParseSnippets_PrintErroneousSnippet(_VimTest): + files = { 'us/all.snippets': r""" + snippet test "asd()" e + endsnippet + """} + keys = 'test' + EX + wanted = keys + expected_error = "Trigger: test" + + +class ParseSnippets_PrintErroneousSnippetContext(_VimTest): + files = { 'us/all.snippets': r""" + snippet test "asd()" e + endsnippet + """} + keys = 'test' + EX + wanted = keys + expected_error = "Context: asd" + + +class ParseSnippets_PrintErroneousSnippetPreAction(_VimTest): + files = { 'us/all.snippets': r""" + pre_expand "asd()" + snippet test + endsnippet + """} + keys = 'test' + EX + wanted = keys + expected_error = "Pre-expand: asd" + + +class ParseSnippets_PrintErroneousSnippetPostAction(_VimTest): + files = { 'us/all.snippets': r""" + post_expand "asd()" + snippet test + endsnippet + """} + keys = 'test' + EX + wanted = keys + expected_error = "Post-expand: asd" + +class ParseSnippets_PrintErroneousSnippetLocation(_VimTest): + files = { 'us/all.snippets': r""" + post_expand "asd()" + snippet test + endsnippet + """} + keys = 'test' + EX + wanted = keys + expected_error = "Defined in: .*/all.snippets"