summaryrefslogtreecommitdiff
path: root/plugins/snippets/snippets/Snippet.py
blob: 192b036a7112fcd4d475b103fb1b7b3c7cc00991 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
#    Pluma snippets plugin
#    Copyright (C) 2005-2006  Jesse van den Kieboom <jesse@icecrew.nl>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import os
from gi.repository import Gio, Gtk

from Placeholder import *
from Parser import Parser, Token
from Helper import *

class EvalUtilities:
    def __init__(self, view=None):
        self.view = view
        self._init_namespace()

    def _init_namespace(self):
        self.namespace = {
            '__builtins__': __builtins__,
            'align': self.util_align,
            'readfile': self.util_readfile,
            'filesize': self.util_filesize
        }

    def _real_len(self, s, tablen = 0):
        if tablen == 0:
            tablen = self.view.get_tab_width()

        return len(s.expandtabs(tablen))

    def _filename_to_uri(self, filename):
        gfile = Gio.file_new_for_path(filename)

        return gfile.get_uri()

    def util_readfile(self, filename):
        stream = Gio.file_new_for_path(filename).read()

        if not stream:
            return ''

        res = stream.read()
        stream.close()

        return res

    def util_filesize(self, filename):
        gfile = Gio.file_new_for_path(filename)
        info = gfile.query_info(Gio.FILE_ATTRIBUTE_STANDARD_SIZE)

        if not info:
            return 0

        return info.get_size()

    def util_align(self, items):
        maxlen = []
        tablen = self.view.get_tab_width()

        for row in range(0, len(items)):
            for col in range(0, len(items[row]) - 1):
                if row == 0:
                    maxlen.append(0)

                items[row][col] += "\t"
                rl = self._real_len(items[row][col], tablen)

                if (rl > maxlen[col]):
                    maxlen[col] = rl

        result = ''

        for row in range(0, len(items)):
            for col in range(0, len(items[row]) - 1):
                item = items[row][col]

                result += item + ("\t" * ((maxlen[col] - \
                        self._real_len(item, tablen)) / tablen))

            result += items[row][len(items[row]) - 1]

            if row != len(items) - 1:
                result += "\n"

        return result

class Snippet:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, prop):
        return self.data[prop]

    def __setitem__(self, prop, value):
        self.data[prop] = value

    def accelerator_display(self):
        accel = self['accelerator']

        if accel:
            keyval, mod = Gtk.accelerator_parse(accel)
            accel = Gtk.accelerator_get_label(keyval, mod)

        return accel or ''

    def display(self):
        nm = markup_escape(self['description'])

        tag = self['tag']
        accel = self.accelerator_display()
        detail = []

        if tag and tag != '':
            detail.append(tag)

        if accel and accel != '':
            detail.append(accel)

        if not detail:
            return nm
        else:
            return nm + ' (<b>' + markup_escape(str.join(', ', detail)) + \
                    '</b>)'

    def _add_placeholder(self, placeholder):
        if placeholder.tabstop in self.placeholders:
            if placeholder.tabstop == -1:
                self.placeholders[-1].append(placeholder)
                self.plugin_data.ordered_placeholders.append(placeholder)
        elif placeholder.tabstop == -1:
            self.placeholders[-1] = [placeholder]
            self.plugin_data.ordered_placeholders.append(placeholder)
        else:
            self.placeholders[placeholder.tabstop] = placeholder
            self.plugin_data.ordered_placeholders.append(placeholder)

    def _insert_text(self, text):
        # Insert text keeping indentation in mind
        indented = unicode.join('\n' + unicode(self._indent), spaces_instead_of_tabs(self._view, text).split('\n'))
        self._view.get_buffer().insert(self._insert_iter(), indented)

    def _insert_iter(self):
        return self._view.get_buffer().get_iter_at_mark(self._insert_mark)

    def _create_environment(self, data):
        val = ((data in os.environ) and os.environ[data]) or ''

        # Get all the current indentation
        all_indent = compute_indentation(self._view, self._insert_iter())

        # Substract initial indentation to get the snippet indentation
        indent = all_indent[len(self._indent):]

        # Keep indentation
        return unicode.join('\n' + unicode(indent), val.split('\n'))

    def _create_placeholder(self, data):
        tabstop = data['tabstop']
        begin = self._insert_iter()

        if tabstop == 0:
            # End placeholder
            return PlaceholderEnd(self._view, begin, data['default'])
        elif tabstop in self.placeholders:
            # Mirror placeholder
            return PlaceholderMirror(self._view, tabstop, begin)
        else:
            # Default placeholder
            return Placeholder(self._view, tabstop, data['default'], begin)

    def _create_shell(self, data):
        begin = self._insert_iter()
        return PlaceholderShell(self._view, data['tabstop'], begin, data['contents'])

    def _create_eval(self, data):
        begin = self._insert_iter()
        return PlaceholderEval(self._view, data['tabstop'], data['dependencies'], begin, data['contents'], self._utils.namespace)

    def _create_regex(self, data):
        begin = self._insert_iter()
        return PlaceholderRegex(self._view, data['tabstop'], begin, data['input'], data['pattern'], data['substitution'], data['modifiers'])

    def _create_text(self, data):
        return data

    def _invalid_placeholder(self, placeholder, remove):
        buf = self._view.get_buffer()

        # Remove the text because this placeholder is invalid
        if placeholder.default and remove:
            buf.delete(placeholder.begin_iter(), placeholder.end_iter())

        placeholder.remove()

        if placeholder.tabstop == -1:
            index = self.placeholders[-1].index(placeholder)
            del self.placeholders[-1][index]
        else:
            del self.placeholders[placeholder.tabstop]

        self.plugin_data.ordered_placeholders.remove(placeholder)

    def _parse(self, plugin_data):
        # Initialize current variables
        self._view = plugin_data.view
        self._indent = compute_indentation(self._view, self._view.get_buffer().get_iter_at_mark(self.begin_mark))
        self._utils = EvalUtilities(self._view)
        self.placeholders = {}
        self._insert_mark = self.end_mark
        self.plugin_data = plugin_data

        # Create parser
        parser = Parser(data=self['text'])

        # Parse tokens
        while (True):
            token = parser.token()

            if not token:
                break

            try:
                val = {'environment': self._create_environment,
                    'placeholder': self._create_placeholder,
                    'shell': self._create_shell,
                    'eval': self._create_eval,
                    'regex': self._create_regex,
                    'text': self._create_text}[token.klass](token.data)
            except:
                sys.stderr.write('Token class not supported: %s\n' % token.klass)
                continue

            if isinstance(val, basestring):
                # Insert text
                self._insert_text(val)
            else:
                # Insert placeholder
                self._add_placeholder(val)

        # Create end placeholder if there isn't one yet
        if 0 not in self.placeholders:
            self.placeholders[0] = PlaceholderEnd(self._view, self.end_iter(), None)
            self.plugin_data.ordered_placeholders.append(self.placeholders[0])

        # Make sure run_last is ran for all placeholders and remove any
        # non `ok` placeholders
        for tabstop in self.placeholders.copy():
            ph = (tabstop == -1 and list(self.placeholders[-1])) or [self.placeholders[tabstop]]

            for placeholder in ph:
                placeholder.run_last(self.placeholders)

                if not placeholder.ok or placeholder.done:
                    self._invalid_placeholder(placeholder, not placeholder.ok)

        # Remove all the Expand placeholders which have a tabstop because
        # they can be used to mirror, but they shouldn't be real tabstops
        # (if they have mirrors installed). This is problably a bit of
        # a dirty hack :)
        if -1 not in self.placeholders:
            self.placeholders[-1] = []

        for tabstop in self.placeholders.copy():
            placeholder = self.placeholders[tabstop]

            if tabstop != -1:
                if isinstance(placeholder, PlaceholderExpand) and \
                   placeholder.has_references:
                    # Add to anonymous placeholders
                    self.placeholders[-1].append(placeholder)

                    # Remove placeholder
                    del self.placeholders[tabstop]

        self.plugin_data = None

    def insert_into(self, plugin_data, insert):
        buf = plugin_data.view.get_buffer()
        last_index = 0

        # Find closest mark at current insertion, so that we may insert
        # our marks in the correct order
        (current, next) = plugin_data.next_placeholder()

        if current:
            # Insert AFTER current
            last_index = plugin_data.placeholders.index(current) + 1
        elif next:
            # Insert BEFORE next
            last_index = plugin_data.placeholders.index(next)
        else:
            # Insert at first position
            last_index = 0

        # lastIndex now contains the position of the last mark
        # Create snippet bounding marks
        self.begin_mark = buf.create_mark(None, insert, True)
        self.end_mark = buf.create_mark(None, insert, False)

        # Now parse the contents of this snippet, create Placeholders
        # and insert the placholder marks in the marks array of plugin_data
        self._parse(plugin_data)

        # So now all of the snippet is in the buffer, we have all our
        # placeholders right here, what's next, put all marks in the
        # plugin_data.marks
        k = self.placeholders.keys()
        k.sort(reverse=True)

        plugin_data.placeholders.insert(last_index, self.placeholders[0])
        last_iter = self.placeholders[0].end_iter()

        for tabstop in k:
            if tabstop != -1 and tabstop != 0:
                placeholder = self.placeholders[tabstop]
                end_iter = placeholder.end_iter()

                if last_iter.compare(end_iter) < 0:
                    last_iter = end_iter

                # Inserting placeholder
                plugin_data.placeholders.insert(last_index, placeholder)

        # Move end mark to last placeholder
        buf.move_mark(self.end_mark, last_iter)

        return self

    def deactivate(self):
        buf = self.begin_mark.get_buffer()

        buf.delete_mark(self.begin_mark)
        buf.delete_mark(self.end_mark)

        self.placeholders = {}

    def begin_iter(self):
        return self.begin_mark.get_buffer().get_iter_at_mark(self.begin_mark)

    def end_iter(self):
        return self.end_mark.get_buffer().get_iter_at_mark(self.end_mark)

# ex:ts=4:et: