~ubuntu-branches/ubuntu/vivid/frescobaldi/vivid

« back to all changes in this revision

Viewing changes to frescobaldi_app/snippet/insert.py

  • Committer: Package Import Robot
  • Author(s): Ryan Kavanagh
  • Date: 2012-01-03 16:20:11 UTC
  • mfrom: (1.4.1)
  • Revision ID: package-import@ubuntu.com-20120103162011-tsjkwl4sntwmprea
Tags: 2.0.0-1
* New upstream release 
* Drop the following uneeded patches:
  + 01_checkmodules_no_python-kde4_build-dep.diff
  + 02_no_pyc.diff
  + 04_no_binary_lilypond_upgrades.diff
* Needs new dependency python-poppler-qt4
* Update debian/watch for new download path
* Update copyright file with new holders and years

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
 
2
#
 
3
# Copyright (c) 2008 - 2011 by Wilbert Berendsen
 
4
#
 
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.
 
9
#
 
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.
 
14
#
 
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.
 
19
 
 
20
"""
 
21
Insert snippets into a Document.
 
22
"""
 
23
 
 
24
from __future__ import unicode_literals
 
25
 
 
26
from PyQt4.QtCore import QSettings
 
27
from PyQt4.QtGui import QTextCursor, QMessageBox
 
28
 
 
29
import cursortools
 
30
import indent
 
31
import tokeniter
 
32
 
 
33
from . import snippets
 
34
from . import expand
 
35
 
 
36
 
 
37
def insert(name, view):
 
38
    """Insert named snippet into the view."""
 
39
    text, variables = snippets.get(name)
 
40
    cursor = view.textCursor()
 
41
    
 
42
    selection = variables.get('selection', '')
 
43
    if 'yes' in selection and not cursor.hasSelection():
 
44
        return
 
45
    if 'strip' in selection:
 
46
        cursortools.stripSelection(cursor)
 
47
    
 
48
    pos = cursor.selectionStart()
 
49
    line = cursor.document().findBlock(pos).blockNumber()
 
50
    with cursortools.editBlock(cursor):
 
51
        
 
52
        # insert the snippet, might return a new cursor
 
53
        if 'python' in variables:
 
54
            new = insert_python(text, cursor, name, view)
 
55
        else:
 
56
            new = insert_snippet(text, cursor, variables)
 
57
        
 
58
        # QTextBlocks the snippet starts and ends
 
59
        block = cursor.document().findBlockByNumber(line)
 
60
        last = cursor.block()
 
61
        
 
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
 
65
            while True:
 
66
                block = block.next()
 
67
                if indent.setIndent(block, indent.computeIndent(block)):
 
68
                    tokeniter.update(block)
 
69
                if block == last:
 
70
                    break
 
71
    
 
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)
 
77
 
 
78
 
 
79
def insert_snippet(text, cursor, variables):
 
80
    """Inserts a normal text snippet.
 
81
    
 
82
    After the insert, the cursor points to the end of the inserted snippet.
 
83
    
 
84
    If this function returns a cursor it must be set as the cursor for the view
 
85
    after the snippet has been inserted.
 
86
    
 
87
    """
 
88
    exp_base = expand.Expander(cursor)
 
89
    
 
90
    evs = [] # make a list of events, either text or a constant
 
91
    for text, key in snippets.expand(text):
 
92
        if text:
 
93
            evs.append(text)
 
94
        if key == '$':
 
95
            evs.append('$')
 
96
        elif key:
 
97
            # basic variables
 
98
            func = getattr(exp_base, key, None)
 
99
            if func:
 
100
                evs.append(func())
 
101
    
 
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
 
111
                break
 
112
        for j in range(i+1, len(evs)):
 
113
            if evs[j] not in expand.constants:
 
114
                evs[j] = space + evs[j].lstrip()
 
115
                break
 
116
    # now insert the text
 
117
    ins = QTextCursor(cursor)
 
118
    selectionUsed and ins.setPosition(cursor.selectionStart())
 
119
    a, c = -1, -1
 
120
    for e in evs:
 
121
        if e == expand.ANCHOR:
 
122
            a = ins.position()
 
123
        elif e == expand.CURSOR:
 
124
            c = ins.position()
 
125
        elif e == expand.SELECTION:
 
126
            ins.setPosition(cursor.selectionEnd())
 
127
        else:
 
128
            ins.insertText(e)
 
129
    cursor.setPosition(ins.position())
 
130
    # return a new cursor if requested
 
131
    if (a, c) != (-1, -1):
 
132
        new = QTextCursor(cursor)
 
133
        if a != -1:
 
134
            new.setPosition(a)
 
135
        if c != -1:
 
136
            new.setPosition(c, QTextCursor.KeepAnchor if a != -1 else QTextCursor.MoveAnchor)
 
137
        return new
 
138
 
 
139
 
 
140
def insert_python(text, cursor, name, view):
 
141
    """Regards the text as Python code, and exec it.
 
142
    
 
143
    name and view are given in case an exception occurs.
 
144
    
 
145
    The following variables are available:
 
146
    
 
147
    - text: contains selection or '', set it to insert new text
 
148
    - state: contains simplestate for the cursor position
 
149
    - cursor: the QTextCursor
 
150
 
 
151
    After the insert, the cursor points to the end of the inserted snippet.
 
152
    
 
153
    """
 
154
    namespace = {
 
155
        'cursor': QTextCursor(cursor),
 
156
        'state': state(cursor),
 
157
        'text': cursor.selection().toPlainText(),
 
158
        'view': view,
 
159
        'ANCHOR': 1,
 
160
        'CURSOR': 2,
 
161
    }
 
162
    try:
 
163
        code = compile(text, "<snippet>", "exec")
 
164
        exec code in namespace
 
165
    except Exception:
 
166
        handle_exception(name, view)
 
167
    else:
 
168
        text = namespace.get('text', '')
 
169
        if isinstance(text, (tuple, list)):
 
170
            ANCHOR = namespace.get('ANCHOR', 1)
 
171
            CURSOR = namespace.get('CURSOR', 2)
 
172
            a, c = -1, -1
 
173
            for t in text:
 
174
                if t == ANCHOR:
 
175
                    a = cursor.selectionStart()
 
176
                elif t == CURSOR:
 
177
                    c = cursor.selectionStart()
 
178
                else:
 
179
                    cursor.insertText(t)
 
180
            if (a, c) != (-1, -1):
 
181
                new = QTextCursor(cursor)
 
182
                if a != -1:
 
183
                    new.setPosition(a)
 
184
                if c != -1:
 
185
                    new.setPosition(c, QTextCursor.KeepAnchor if a != -1 else QTextCursor.MoveAnchor)
 
186
                return new
 
187
        else:
 
188
            cursor.insertText(namespace['text'])
 
189
 
 
190
 
 
191
def state(cursor):
 
192
    """Returns the simplestate string for the position of the cursor."""
 
193
    import simplestate
 
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()
 
199
    for t in tokens:
 
200
        if t.end > column:
 
201
            break
 
202
        state.follow(t)
 
203
    return simplestate.state(state)
 
204
 
 
205
 
 
206
def handle_exception(name, view):
 
207
    """Called when a snippet raises a Python exception.
 
208
    
 
209
    Shows the error message and offers the option to edit the offending snippet.
 
210
    
 
211
    """
 
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>":
 
216
        del tb[0]
 
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:
 
225
        return
 
226
    
 
227
    # determine line number
 
228
    if exc_type is SyntaxError:
 
229
        lineno = exc_value.lineno
 
230
    elif tb:
 
231
        lineno = tb[0][1]
 
232
    else:
 
233
        lineno = None
 
234
    
 
235
    import panels
 
236
    from . import edit
 
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('-*- '):
 
243
                lineno += 1
 
244
            else:
 
245
                break
 
246
        block = textedit.document().findBlockByNumber(lineno-1)
 
247
        if block.isValid():
 
248
            textedit.setTextCursor(QTextCursor(block))
 
249
 
 
250