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.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 '<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
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 '<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):
"""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:
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]

View File

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