1
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
3
# Copyright (c) 2008 - 2011 by Wilbert Berendsen
5
# This program is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU General Public License
7
# as published by the Free Software Foundation; either version 2
8
# of the License, or (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# See http://www.gnu.org/licenses/ for more information.
21
Insert snippets into a Document.
24
from __future__ import unicode_literals
26
from PyQt4.QtCore import QSettings
27
from PyQt4.QtGui import QTextCursor, QMessageBox
33
from . import snippets
37
def insert(name, view):
38
"""Insert named snippet into the view."""
39
text, variables = snippets.get(name)
40
cursor = view.textCursor()
42
selection = variables.get('selection', '')
43
if 'yes' in selection and not cursor.hasSelection():
45
if 'strip' in selection:
46
cursortools.stripSelection(cursor)
48
pos = cursor.selectionStart()
49
line = cursor.document().findBlock(pos).blockNumber()
50
with cursortools.editBlock(cursor):
52
# insert the snippet, might return a new cursor
53
if 'python' in variables:
54
new = insert_python(text, cursor, name, view)
56
new = insert_snippet(text, cursor, variables)
58
# QTextBlocks the snippet starts and ends
59
block = cursor.document().findBlockByNumber(line)
62
# re-indent if not explicitly suppressed by a 'indent: no' variable
63
if last != block and 'no' not in variables.get('indent', ''):
64
tokeniter.update(block) # tokenize inserted lines
67
if indent.setIndent(block, indent.computeIndent(block)):
68
tokeniter.update(block)
72
if not new and 'keep' in selection:
73
end = cursor.position()
74
cursor.setPosition(pos)
75
cursor.setPosition(end, QTextCursor.KeepAnchor)
76
view.setTextCursor(new or cursor)
79
def insert_snippet(text, cursor, variables):
80
"""Inserts a normal text snippet.
82
After the insert, the cursor points to the end of the inserted snippet.
84
If this function returns a cursor it must be set as the cursor for the view
85
after the snippet has been inserted.
88
exp_base = expand.Expander(cursor)
90
evs = [] # make a list of events, either text or a constant
91
for text, key in snippets.expand(text):
98
func = getattr(exp_base, key, None)
102
selectionUsed = expand.SELECTION in evs
103
# do the padding if 'selection: strip;' is used
104
if selectionUsed and 'strip' in variables.get('selection', ''):
105
space = '\n' if '\n' in cursor.selection().toPlainText() else ' '
106
# change whitespace in previous and next piece of text
107
i = evs.index(expand.SELECTION)
108
for j in range(i-1, -i, -1):
109
if evs[j] not in expand.constants:
110
evs[j] = evs[j].rstrip() + space
112
for j in range(i+1, len(evs)):
113
if evs[j] not in expand.constants:
114
evs[j] = space + evs[j].lstrip()
116
# now insert the text
117
ins = QTextCursor(cursor)
118
selectionUsed and ins.setPosition(cursor.selectionStart())
121
if e == expand.ANCHOR:
123
elif e == expand.CURSOR:
125
elif e == expand.SELECTION:
126
ins.setPosition(cursor.selectionEnd())
129
cursor.setPosition(ins.position())
130
# return a new cursor if requested
131
if (a, c) != (-1, -1):
132
new = QTextCursor(cursor)
136
new.setPosition(c, QTextCursor.KeepAnchor if a != -1 else QTextCursor.MoveAnchor)
140
def insert_python(text, cursor, name, view):
141
"""Regards the text as Python code, and exec it.
143
name and view are given in case an exception occurs.
145
The following variables are available:
147
- text: contains selection or '', set it to insert new text
148
- state: contains simplestate for the cursor position
149
- cursor: the QTextCursor
151
After the insert, the cursor points to the end of the inserted snippet.
155
'cursor': QTextCursor(cursor),
156
'state': state(cursor),
157
'text': cursor.selection().toPlainText(),
163
code = compile(text, "<snippet>", "exec")
164
exec code in namespace
166
handle_exception(name, view)
168
text = namespace.get('text', '')
169
if isinstance(text, (tuple, list)):
170
ANCHOR = namespace.get('ANCHOR', 1)
171
CURSOR = namespace.get('CURSOR', 2)
175
a = cursor.selectionStart()
177
c = cursor.selectionStart()
180
if (a, c) != (-1, -1):
181
new = QTextCursor(cursor)
185
new.setPosition(c, QTextCursor.KeepAnchor if a != -1 else QTextCursor.MoveAnchor)
188
cursor.insertText(namespace['text'])
192
"""Returns the simplestate string for the position of the cursor."""
194
pos = cursor.selectionStart()
195
block = cursor.document().findBlock(pos)
196
tokens = tokeniter.tokens(block)
197
state = tokeniter.state(block)
198
column = pos - block.position()
203
return simplestate.state(state)
206
def handle_exception(name, view):
207
"""Called when a snippet raises a Python exception.
209
Shows the error message and offers the option to edit the offending snippet.
212
import sys, traceback
213
exc_type, exc_value, exc_traceback = sys.exc_info()
214
tb = traceback.extract_tb(exc_traceback)
215
while tb and tb[0][0] != "<snippet>":
217
msg = ''.join(traceback.format_list(tb) +
218
traceback.format_exception_only(exc_type, exc_value))
219
dlg = QMessageBox(QMessageBox.Critical, _("Snippet error"), msg,
220
QMessageBox.Ok | QMessageBox.Cancel)
221
dlg.button(QMessageBox.Ok).setText(_("Edit Snippet"))
222
dlg.setDefaultButton(QMessageBox.Cancel)
223
dlg.setEscapeButton(QMessageBox.Cancel)
224
if dlg.exec_() != QMessageBox.Ok:
227
# determine line number
228
if exc_type is SyntaxError:
229
lineno = exc_value.lineno
237
widget = panels.manager(view.window()).snippettool.widget()
238
textedit = edit.Edit(widget, name).text
239
if lineno is not None:
240
# convert to line number in full snippet text
241
for block in cursortools.allBlocks(textedit.document()):
242
if block.text().startswith('-*- '):
246
block = textedit.document().findBlockByNumber(lineno-1)
248
textedit.setTextCursor(QTextCursor(block))