print errors in regexp triggers

This commit is contained in:
Stanislav Seletskiy 2016-04-27 20:25:41 +06:00
parent 8250d33bca
commit 6cd168c5b4
3 changed files with 97 additions and 78 deletions

View File

@ -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

View File

@ -11,16 +11,19 @@ import textwrap
from UltiSnips import _vim from UltiSnips import _vim
from UltiSnips.compatibility import as_unicode from UltiSnips.compatibility import as_unicode
from UltiSnips.indent_util import IndentUtil from UltiSnips.indent_util import IndentUtil
from UltiSnips.position import Position
from UltiSnips.text import escape from UltiSnips.text import escape
from UltiSnips.text_objects import SnippetInstance 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") __WHITESPACE_SPLIT = re.compile(r"\s")
def split_at_whitespace(string): def split_at_whitespace(string):
"""Like string.split(), but keeps empty words as empty words.""" """Like string.split(), but keeps empty words as empty words."""
return re.split(__WHITESPACE_SPLIT, string) return re.split(__WHITESPACE_SPLIT, string)
def _words_for_line(trigger, before, num_words=None): def _words_for_line(trigger, before, num_words=None):
"""Gets the final 'num_words' words from 'before'. """Gets the final 'num_words' words from 'before'.
@ -131,27 +134,7 @@ class SnippetDefinition(object):
try: try:
exec(code, {'snip': snip}) exec(code, {'snip': snip})
except Exception as e: except Exception as e:
e.snippet_info = textwrap.dedent(""" self._make_debug_exception(e, code)
Defined in: {}
Trigger: {}
Description: {}
Context: {}
Pre-expand: {}
Post-expand: {}
""").format(
self._location,
self._trigger,
self._description,
self._context_code if self._context_code else '<none>',
self._actions['pre_expand'] if 'pre_expand' in self._actions
else '<none>',
self._actions['post_expand'] if 'post_expand' in self._actions
else '<none>',
code,
)
e.snippet_code = code
raise raise
return snip return snip
@ -200,6 +183,28 @@ class SnippetDefinition(object):
return snip 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 '<none>',
self._actions['pre_expand'] if 'pre_expand' in self._actions
else '<none>',
self._actions['post_expand'] if 'post_expand' in self._actions
else '<none>',
code,
)
e.snippet_code = code
def has_option(self, opt): def has_option(self, opt):
"""Check if the named option is set.""" """Check if the named option is set."""
return opt in self._opts return opt in self._opts
@ -247,7 +252,12 @@ class SnippetDefinition(object):
words = _words_for_line(self._trigger, before) words = _words_for_line(self._trigger, before)
if 'r' in self._opts: if 'r' in self._opts:
try:
match = self._re_match(before) match = self._re_match(before)
except Exception as e:
self._make_debug_exception(e)
raise
elif 'w' in self._opts: elif 'w' in self._opts:
words_len = len(self._trigger) words_len = len(self._trigger)
words_prefix = words[:-words_len] words_prefix = words[:-words_len]

View File

@ -14,6 +14,7 @@ import re
from contextlib import contextmanager from contextlib import contextmanager
from UltiSnips import _vim from UltiSnips import _vim
from UltiSnips import err_to_scratch_buffer
from UltiSnips._diff import diff, guess_edit from UltiSnips._diff import diff, guess_edit
from UltiSnips.compatibility import as_unicode from UltiSnips.compatibility import as_unicode
from UltiSnips.position import Position from UltiSnips.position import Position
@ -52,49 +53,6 @@ def _ask_snippets(snippets):
return _ask_user(snippets, display) 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 # 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. # facing methods, most of the private methods should be moved outside of it.
class SnippetManager(object): class SnippetManager(object):
@ -141,7 +99,7 @@ class SnippetManager(object):
self._reinit() self._reinit()
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def jump_forwards(self): def jump_forwards(self):
"""Jumps to the next tabstop.""" """Jumps to the next tabstop."""
_vim.command('let g:ulti_jump_forwards_res = 1') _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') _vim.command('let g:ulti_jump_forwards_res = 0')
return self._handle_failure(self.forward_trigger) return self._handle_failure(self.forward_trigger)
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def jump_backwards(self): def jump_backwards(self):
"""Jumps to the previous tabstop.""" """Jumps to the previous tabstop."""
_vim.command('let g:ulti_jump_backwards_res = 1') _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') _vim.command('let g:ulti_jump_backwards_res = 0')
return self._handle_failure(self.backward_trigger) return self._handle_failure(self.backward_trigger)
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def expand(self): def expand(self):
"""Try to expand a snippet at the current position.""" """Try to expand a snippet at the current position."""
_vim.command('let g:ulti_expand_res = 1') _vim.command('let g:ulti_expand_res = 1')
@ -167,7 +125,7 @@ class SnippetManager(object):
_vim.command('let g:ulti_expand_res = 0') _vim.command('let g:ulti_expand_res = 0')
self._handle_failure(self.expand_trigger) self._handle_failure(self.expand_trigger)
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def expand_or_jump(self): def expand_or_jump(self):
"""This function is used for people who wants to have the same trigger """This function is used for people who wants to have the same trigger
for expansion and forward jumping. for expansion and forward jumping.
@ -185,7 +143,7 @@ class SnippetManager(object):
_vim.command('let g:ulti_expand_or_jump_res = 0') _vim.command('let g:ulti_expand_or_jump_res = 0')
self._handle_failure(self.expand_trigger) self._handle_failure(self.expand_trigger)
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def snippets_in_current_scope(self, searchAll): def snippets_in_current_scope(self, searchAll):
"""Returns the snippets that could be expanded to Vim as a global """Returns the snippets that could be expanded to Vim as a global
variable.""" variable."""
@ -226,7 +184,7 @@ class SnippetManager(object):
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def list_snippets(self): def list_snippets(self):
"""Shows the snippets that could be expanded to the User and let her """Shows the snippets that could be expanded to the User and let her
select one.""" select one."""
@ -251,7 +209,7 @@ class SnippetManager(object):
return True return True
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def add_snippet(self, trigger, value, description, def add_snippet(self, trigger, value, description,
options, ft='all', priority=0, context=None, actions={}): options, ft='all', priority=0, context=None, actions={}):
"""Add a snippet to the list of known snippets of the given 'ft'.""" """Add a snippet to the list of known snippets of the given 'ft'."""
@ -260,7 +218,7 @@ class SnippetManager(object):
description, options, {}, 'added', description, options, {}, 'added',
context, actions)) context, actions))
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def expand_anon( def expand_anon(
self, value, trigger='', description='', options='', self, value, trigger='', description='', options='',
context=None, actions={} context=None, actions={}
@ -317,7 +275,7 @@ class SnippetManager(object):
self._buffer_filetypes[_vim.buf.number].insert(idx + 1, ft) self._buffer_filetypes[_vim.buf.number].insert(idx + 1, ft)
idx += 1 idx += 1
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def _cursor_moved(self): def _cursor_moved(self):
"""Called whenever the cursor moved.""" """Called whenever the cursor moved."""
self._should_update_textobjects = False self._should_update_textobjects = False
@ -446,7 +404,7 @@ class SnippetManager(object):
# are back in our buffer # are back in our buffer
pass pass
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def _save_last_visual_selection(self): def _save_last_visual_selection(self):
"""This is called when the expand trigger is pressed in visual mode. """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. Our job is to remember everything between '< and '> and pass it on to.
@ -876,7 +834,7 @@ class SnippetManager(object):
finally: finally:
self._inside_action = old_flag self._inside_action = old_flag
@err_to_scratch_buffer @err_to_scratch_buffer.wrap
def _track_change(self): def _track_change(self):
self._should_update_textobjects = True self._should_update_textobjects = True