~ubuntu-branches/ubuntu/vivid/gedit-plugins/vivid-proposed

« back to all changes in this revision

Viewing changes to plugins/commander/commander/entry.py

  • Committer: Package Import Robot
  • Author(s): Robert Ancell
  • Date: 2012-08-20 11:28:57 UTC
  • mfrom: (1.4.9)
  • Revision ID: package-import@ubuntu.com-20120820112857-b9o0cpx9enivzy8u
Tags: 3.5.1-0ubuntu1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
41
41
import traceback
42
42
 
43
43
class Entry(Gtk.EventBox):
44
 
        __gtype_name__ = "CommanderEntry"
45
 
 
46
 
        def __init__(self, view):
47
 
                Gtk.EventBox.__init__(self)
48
 
                self._view = view
49
 
 
50
 
                self.set_visible_window(False)
51
 
 
52
 
                hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 3)
53
 
                hbox.show()
54
 
                hbox.set_border_width(3)
55
 
 
56
 
                # context for the view
57
 
                self._entry = Gtk.Entry()
58
 
                self._entry.set_has_frame(False)
59
 
                self._entry.set_name('gedit-commander-entry')
60
 
                self._entry.show()
61
 
 
62
 
                css = Gtk.CssProvider()
63
 
                css.load_from_data("""
 
44
    __gtype_name__ = "CommanderEntry"
 
45
 
 
46
    def __init__(self, view):
 
47
        Gtk.EventBox.__init__(self)
 
48
        self._view = view
 
49
 
 
50
        self.set_visible_window(False)
 
51
 
 
52
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 3)
 
53
        hbox.show()
 
54
        hbox.set_border_width(3)
 
55
 
 
56
        # context for the view
 
57
        self._entry = Gtk.Entry()
 
58
        self._entry.set_has_frame(False)
 
59
        self._entry.set_name('gedit-commander-entry')
 
60
        self._entry.show()
 
61
 
 
62
        css = Gtk.CssProvider()
 
63
        css.load_from_data("""
64
64
@binding-set terminal-like-bindings {
65
 
        unbind "<Control>A";
 
65
    unbind "<Control>A";
66
66
 
67
 
        bind "<Control>W" { "delete-from-cursor" (word-ends, -1) };
68
 
        bind "<Control>A" { "move-cursor" (buffer-ends, -1, 0) };
69
 
        bind "<Control>U" { "delete-from-cursor" (display-line-ends, -1) };
70
 
        bind "<Control>K" { "delete-from-cursor" (display-line-ends, 1) };
71
 
        bind "<Control>E" { "move-cursor" (buffer-ends, 1, 0) };
72
 
        bind "Escape" { "delete-from-cursor" (display-lines, 1) };
 
67
    bind "<Control>W" { "delete-from-cursor" (word-ends, -1) };
 
68
    bind "<Control>A" { "move-cursor" (buffer-ends, -1, 0) };
 
69
    bind "<Control>U" { "delete-from-cursor" (display-line-ends, -1) };
 
70
    bind "<Control>K" { "delete-from-cursor" (display-line-ends, 1) };
 
71
    bind "<Control>E" { "move-cursor" (buffer-ends, 1, 0) };
 
72
    bind "Escape" { "delete-from-cursor" (display-lines, 1) };
73
73
}
74
74
 
75
75
GtkEntry#gedit-commander-entry {
76
 
        gtk-key-bindings: terminal-like-bindings;
 
76
    gtk-key-bindings: terminal-like-bindings;
 
77
 
 
78
    /* Override background to anything. This is weird, but doing this we can
 
79
       then in code use widget.override_background to set the color dynamically
 
80
       to the same color as the gedit view */
 
81
    background: transparent;
 
82
    border-width: 0;
 
83
    box-shadow: 0 0 transparent;
77
84
}
78
85
""")
79
86
 
80
 
                # FIXME: remove hardcopy of 600 (GTK_STYLE_PROVIDER_PRIORITY_APPLICATION)
81
 
                # https://bugzilla.gnome.org/show_bug.cgi?id=646860
82
 
                self._entry.get_style_context().add_provider(css, 600)
83
 
 
84
 
                self._prompt_label = Gtk.Label(label='<b>&gt;&gt;&gt;</b>', use_markup=True)
85
 
                self._prompt_label.show()
86
 
 
87
 
                self._entry.connect('focus-out-event', self.on_entry_focus_out)
88
 
                self._entry.connect('key-press-event', self.on_entry_key_press)
89
 
 
90
 
                self._history = History(os.path.join(GLib.get_user_config_dir(), 'gedit/commander/history'))
91
 
                self._prompt = None
92
 
 
93
 
                self._accel_group = None
94
 
 
95
 
                hbox.pack_start(self._prompt_label, False, False, 0)
96
 
                hbox.pack_start(self._entry, True, True, 0)
97
 
 
98
 
                self.copy_style_from_view()
99
 
                self.view_style_updated_id = self._view.connect('style-updated', self.on_view_style_updated)
100
 
 
101
 
                self.add(hbox)
102
 
                self.attach()
103
 
                self._entry.grab_focus()
104
 
 
105
 
                self._wait_timeout = 0
106
 
                self._info_window = None
107
 
 
108
 
                self.connect('destroy', self.on_destroy)
109
 
                self.connect_after('size-allocate', self.on_size_allocate)
110
 
                self.view_draw_id = self._view.connect_after('draw', self.on_draw)
111
 
 
112
 
                self._history_prefix = None
113
 
                self._suspended = None
114
 
                self._handlers = [
115
 
                        [0, Gdk.KEY_Up, self.on_history_move, -1],
116
 
                        [0, Gdk.KEY_Down, self.on_history_move, 1],
117
 
                        [None, Gdk.KEY_Return, self.on_execute, None],
118
 
                        [None, Gdk.KEY_KP_Enter, self.on_execute, None],
119
 
                        [0, Gdk.KEY_Tab, self.on_complete, None],
120
 
                        [0, Gdk.KEY_ISO_Left_Tab, self.on_complete, None]
121
 
                ]
122
 
 
123
 
                self._re_complete = re.compile('("((?:\\\\"|[^"])*)"?|\'((?:\\\\\'|[^\'])*)\'?|[^\s]+)')
124
 
                self._command_state = commands.Commands.State()
125
 
 
126
 
        def on_view_style_updated(self, widget):
127
 
                self.copy_style_from_view()
128
 
 
129
 
        def get_border_color(self):
130
 
                color = self.get_background_color().copy()
131
 
                color.red = 1 - color.red
132
 
                color.green = 1 - color.green
133
 
                color.blue = 1 - color.blue
134
 
                color.alpha = 0.5
135
 
 
136
 
                return color
137
 
 
138
 
        def get_background_color(self):
139
 
                context = self._view.get_style_context()
140
 
                return context.get_background_color(Gtk.StateFlags.NORMAL)
141
 
        
142
 
        def get_foreground_color(self):
143
 
                context = self._view.get_style_context()
144
 
                return context.get_color(Gtk.StateFlags.NORMAL)
145
 
 
146
 
        def get_font(self):
147
 
                context = self._view.get_style_context()
148
 
                return context.get_font(Gtk.StateFlags.NORMAL)
149
 
 
150
 
        def copy_style_from_view(self, widget=None):
151
 
                if widget != None:
152
 
                        context = self._view.get_style_context()
153
 
                        font = context.get_font(Gtk.StateFlags.NORMAL)
154
 
 
155
 
                        widget.override_color(Gtk.StateFlags.NORMAL, self.get_foreground_color())
156
 
                        widget.override_background_color(Gtk.StateFlags.NORMAL, self.get_background_color())
157
 
                        widget.override_font(self.get_font())
158
 
                else:
159
 
                        if self._entry:
160
 
                                self.copy_style_from_view(self._entry)
161
 
 
162
 
                        if self._prompt_label:
163
 
                                self.copy_style_from_view(self._prompt_label)
164
 
 
165
 
        def view(self):
166
 
                return self._view
167
 
 
168
 
        def on_size_allocate(self, widget, alloc):
169
 
                alloc = self.get_allocation()
170
 
                self._view.set_border_window_size(Gtk.TextWindowType.BOTTOM, alloc.height)
171
 
 
172
 
                win = self._view.get_window(Gtk.TextWindowType.BOTTOM)
173
 
                self.set_size_request(win.get_width(), -1)
174
 
 
175
 
                # NOTE: we need to do this explicitly somehow, otherwise the window
176
 
                # size will not be updated unless something else happens, not exactly
177
 
                # sure what. This might be caused by the multi notebook, or custom
178
 
                # animation layouting?
179
 
                self._view.get_parent().resize_children()
180
 
 
181
 
        def attach(self):
182
 
                # Attach ourselves in the text view, and position just above the
183
 
                # text window
184
 
                win = self._view.get_window(Gtk.TextWindowType.TEXT)
185
 
                alloc = self.get_allocation()
186
 
 
187
 
                self._view.set_border_window_size(Gtk.TextWindowType.BOTTOM, max(alloc.height, 1))
188
 
                self._view.add_child_in_window(self, Gtk.TextWindowType.BOTTOM, 0, 0)
189
 
 
190
 
                win = self._view.get_window(Gtk.TextWindowType.BOTTOM)
191
 
 
192
 
                # Set same color as gutter, i.e. bg color of the view
193
 
                context = self._view.get_style_context()
194
 
                color = context.get_background_color(Gtk.StateFlags.NORMAL)
195
 
                win.set_background_rgba(color)
196
 
 
197
 
                self.show()
198
 
                self.set_size_request(win.get_width(), -1)
199
 
 
200
 
        def on_entry_focus_out(self, widget, evnt):
201
 
                if self._entry.get_sensitive():
202
 
                        self.destroy()
203
 
 
204
 
        def on_entry_key_press(self, widget, evnt):
205
 
                state = evnt.state & Gtk.accelerator_get_default_mod_mask()
206
 
                text = self._entry.get_text()
207
 
 
208
 
                if evnt.keyval == Gdk.KEY_Escape:
209
 
                        if self._info_window:
210
 
                                if self._suspended:
211
 
                                        self._suspended.resume()
212
 
 
213
 
                                if self._info_window:
214
 
                                        self._info_window.destroy()
215
 
 
216
 
                                self._entry.set_sensitive(True)
217
 
                        elif self._accel_group:
218
 
                                self._accel_group = self._accel_group.parent
219
 
 
220
 
                                if not self._accel_group or not self._accel_group.parent:
221
 
                                        self._entry.set_editable(True)
222
 
                                        self._accel_group = None
223
 
 
224
 
                                self.prompt()
225
 
                        elif text:
226
 
                                self._entry.set_text('')
227
 
                        elif self._command_state:
228
 
                                self._command_state.clear()
229
 
                                self.prompt()
230
 
                        else:
231
 
                                self._view.grab_focus()
232
 
                                self.destroy()
233
 
 
234
 
                        return True
235
 
 
236
 
                if state or self._accel_group:
237
 
                        # Check if it should be handled by the accel group
238
 
                        group = self._accel_group
239
 
 
240
 
                        if not self._accel_group:
241
 
                                group = commands.Commands().accelerator_group()
242
 
 
243
 
                        accel = group.activate(evnt.keyval, state)
244
 
 
245
 
                        if isinstance(accel, commands.accel_group.AccelGroup):
246
 
                                self._accel_group = accel
247
 
                                self._entry.set_text('')
248
 
                                self._entry.set_editable(False)
249
 
                                self.prompt()
250
 
 
251
 
                                return True
252
 
                        elif isinstance(accel, commands.accel_group.AccelCallback):
253
 
                                self._entry.set_editable(True)
254
 
                                self.run_command(lambda: accel.activate(self._command_state, self))
255
 
                                return True
256
 
 
257
 
                if not self._entry.get_editable():
258
 
                        return True
259
 
 
260
 
                for handler in self._handlers:
261
 
                        if (handler[0] == None or handler[0] == state) and evnt.keyval == handler[1] and handler[2](handler[3], state):
262
 
                                return True
263
 
 
264
 
                if self._info_window and self._info_window.empty():
265
 
                        self._info_window.destroy()
266
 
 
267
 
                self._history_prefix = None
268
 
                return False
269
 
 
270
 
        def on_history_move(self, direction, modifier):
271
 
                pos = self._entry.get_position()
272
 
 
273
 
                self._history.update(self._entry.get_text())
274
 
 
275
 
                if self._history_prefix == None:
276
 
                        if len(self._entry.get_text()) == pos:
277
 
                                self._history_prefix = self._entry.get_chars(0, pos)
278
 
                        else:
279
 
                                self._history_prefix = ''
280
 
 
281
 
                if self._history_prefix == None:
282
 
                        hist = ''
283
 
                else:
284
 
                        hist = self._history_prefix
285
 
 
286
 
                next = self._history.move(direction, hist)
287
 
 
288
 
                if next != None:
289
 
                        self._entry.set_text(next)
290
 
                        self._entry.set_position(-1)
291
 
 
292
 
                return True
293
 
 
294
 
        def prompt(self, pr=''):
295
 
                self._prompt = pr
296
 
 
297
 
                if self._accel_group != None:
298
 
                        pr = '<i>%s</i>' % (saxutils.escape(self._accel_group.full_name()),)
299
 
 
300
 
                if not pr:
301
 
                        pr = ''
302
 
                else:
303
 
                        pr = ' ' + pr
304
 
 
305
 
                self._prompt_label.set_markup('<b>&gt;&gt;&gt;</b>%s' % pr)
306
 
 
307
 
        def make_info(self):
308
 
                if self._info_window == None:
309
 
                        self._info_window = Info(self)
310
 
                        self._info_window.show()
311
 
 
312
 
                        self._info_window.connect('destroy', self.on_info_window_destroy)
313
 
 
314
 
        def on_info_window_destroy(self, widget):
315
 
                self._info_window = None
316
 
 
317
 
        def info_show(self, text='', use_markup=False):
318
 
                self.make_info()
319
 
                self._info_window.add_lines(text, use_markup)
320
 
 
321
 
        def info_status(self, text):
322
 
                self.make_info()
323
 
                self._info_window.status(text)
324
 
 
325
 
        def info_add_action(self, stock, callback, data=None):
326
 
                self.make_info()
327
 
                return self._info_window.add_action(stock, callback, data)
328
 
 
329
 
        def command_history_done(self):
330
 
                self._history.add(self._entry.get_text())
331
 
                self._history_prefix = None
332
 
                self._entry.set_text('')
333
 
 
334
 
        def on_wait_cancel(self):
335
 
                if self._suspended:
336
 
                        self._suspended.resume()
337
 
 
338
 
                if self._cancel_button:
339
 
                        self._cancel_button.destroy()
340
 
 
341
 
                if self._info_window and self._info_window.empty():
342
 
                        self._info_window.destroy()
343
 
                        self._entry.grab_focus()
344
 
                        self._entry.set_sensitive(True)
345
 
 
346
 
        def _show_wait_cancel(self):
347
 
                self._cancel_button = self.info_add_action(Gtk.STOCK_STOP, self.on_wait_cancel)
348
 
                self.info_status('<i>Waiting to finish...</i>')
349
 
 
350
 
                self._wait_timeout = 0
351
 
                return False
352
 
 
353
 
        def _complete_word_match(self, match):
354
 
                for i in (3, 2, 0):
355
 
                        if match.group(i) != None:
356
 
                                return [match.group(i), match.start(i), match.end(i)]
357
 
 
358
 
        def on_suspend_resume(self):
359
 
                if self._wait_timeout:
360
 
                        GObject.source_remove(self._wait_timeout)
361
 
                        self._wait_timeout = 0
362
 
                else:
363
 
                        self._cancel_button.destroy()
364
 
                        self._cancel_button = None
365
 
                        self.info_status(None)
366
 
 
367
 
                self._entry.set_sensitive(True)
368
 
                self.command_history_done()
369
 
 
370
 
                if self._entry.props.has_focus or (self._info_window and not self._info_window.empty()):
371
 
                        self._entry.grab_focus()
372
 
 
373
 
                self.on_execute(None, 0)
374
 
 
375
 
        def ellipsize(self, s, size):
376
 
                if len(s) <= size:
377
 
                        return s
378
 
 
379
 
                mid = (size - 4) / 2
380
 
                return s[:mid] + '...' + s[-mid:]
381
 
 
382
 
        def destroy(self):
383
 
                self.hide()
384
 
                Gtk.EventBox.destroy(self)
385
 
 
386
 
        def run_command(self, cb):
387
 
                self._suspended = None
388
 
 
389
 
                try:
390
 
                        ret = cb()
391
 
                except Exception, e:
392
 
                        self.command_history_done()
393
 
                        self._command_state.clear()
394
 
 
395
 
                        self.prompt()
396
 
 
397
 
                        # Show error in info
398
 
                        self.info_show('<b><span color="#f66">Error:</span></b> ' + saxutils.escape(str(e)), True)
399
 
 
400
 
                        if not isinstance(e, commands.exceptions.Execute):
401
 
                                self.info_show(self.format_trace(), False)
402
 
 
403
 
                        return None
404
 
 
405
 
                if ret == commands.result.Result.SUSPEND:
406
 
                        # Wait for it...
407
 
                        self._suspended = ret
408
 
                        ret.register(self.on_suspend_resume)
409
 
 
410
 
                        self._wait_timeout = GObject.timeout_add(500, self._show_wait_cancel)
411
 
                        self._entry.set_sensitive(False)
412
 
                else:
413
 
                        self.command_history_done()
414
 
                        self.prompt('')
415
 
 
416
 
                        if ret == commands.result.Result.PROMPT:
417
 
                                self.prompt(ret.prompt)
418
 
                        elif (ret == None or ret == commands.result.HIDE) and not self._prompt and (not self._info_window or self._info_window.empty()):
419
 
                                self._command_state.clear()
420
 
                                self._view.grab_focus()
421
 
                                self.destroy()
422
 
                        else:
423
 
                                self._entry.grab_focus()
424
 
 
425
 
                return ret
426
 
 
427
 
        def format_trace(self):
428
 
                tp, val, tb = sys.exc_info()
429
 
 
430
 
                origtb = tb
431
 
 
432
 
                thisdir = os.path.dirname(__file__)
433
 
 
434
 
                # Skip frames up until after the last entry.py...
435
 
                while True:
436
 
                        filename = tb.tb_frame.f_code.co_filename
437
 
 
438
 
                        dname = os.path.dirname(filename)
439
 
 
440
 
                        if not dname.startswith(thisdir):
441
 
                                break
442
 
 
443
 
                        tb = tb.tb_next
444
 
 
445
 
                msg = traceback.format_exception(tp, val, tb)
446
 
                r = ''.join(msg[0:-1])
447
 
 
448
 
                # This is done to prevent cyclic references, see python
449
 
                # documentation on sys.exc_info
450
 
                del origtb
451
 
 
452
 
                return r
453
 
 
454
 
        def on_execute(self, dummy, modifier):
455
 
                if self._info_window and not self._suspended:
456
 
                        self._info_window.destroy()
457
 
 
458
 
                text = self._entry.get_text().strip()
459
 
                words = list(self._re_complete.finditer(text))
460
 
                wordsstr = []
461
 
 
462
 
                for word in words:
463
 
                        spec = self._complete_word_match(word)
464
 
                        wordsstr.append(spec[0])
465
 
 
466
 
                if not wordsstr and not self._command_state:
467
 
                        self._entry.set_text('')
468
 
                        return
469
 
 
470
 
                self.run_command(lambda: commands.Commands().execute(self._command_state, text, words, wordsstr, self, modifier))
471
 
 
472
 
                return True
473
 
 
474
 
        def on_complete(self, dummy, modifier):
475
 
                # First split all the text in words
476
 
                text = self._entry.get_text()
477
 
                pos = self._entry.get_position()
478
 
 
479
 
                words = list(self._re_complete.finditer(text))
480
 
                wordsstr = []
481
 
 
482
 
                for word in words:
483
 
                        spec = self._complete_word_match(word)
484
 
                        wordsstr.append(spec[0])
485
 
 
486
 
                # Find out at which word the cursor actually is
487
 
                # Examples:
488
 
                #  * hello world|
489
 
                #  * hello| world
490
 
                #  * |hello world
491
 
                #  * hello wor|ld
492
 
                #  * hello  |  world
493
 
                #  * "hello world|"
494
 
                posidx = None
495
 
 
496
 
                for idx in xrange(0, len(words)):
497
 
                        spec = self._complete_word_match(words[idx])
498
 
 
499
 
                        if words[idx].start(0) > pos:
500
 
                                # Empty space, new completion
501
 
                                wordsstr.insert(idx, '')
502
 
                                words.insert(idx, None)
503
 
                                posidx = idx
504
 
                                break
505
 
                        elif spec[2] == pos:
506
 
                                # At end of word, resume completion
507
 
                                posidx = idx
508
 
                                break
509
 
                        elif spec[1] <= pos and spec[2] > pos:
510
 
                                # In middle of word, do not complete
511
 
                                return True
512
 
 
513
 
                if posidx == None:
514
 
                        wordsstr.append('')
515
 
                        words.append(None)
516
 
                        posidx = len(wordsstr) - 1
517
 
 
518
 
                # First word completes a command, if not in any special 'mode'
519
 
                # otherwise, relay completion to the command, or complete by advice
520
 
                # from the 'mode' (prompt)
521
 
                cmds = commands.Commands()
522
 
 
523
 
                if not self._command_state and posidx == 0:
524
 
                        # Complete the first command
525
 
                        ret = commands.completion.command(words=wordsstr, idx=posidx)
526
 
                else:
527
 
                        complete = None
528
 
                        realidx = posidx
529
 
 
530
 
                        if not self._command_state:
531
 
                                # Get the command first
532
 
                                cmd = commands.completion.single_command(wordsstr, 0)
533
 
                                realidx -= 1
534
 
 
535
 
                                ww = wordsstr[1:]
536
 
                        else:
537
 
                                cmd = self._command_state.top()
538
 
                                ww = wordsstr
539
 
 
540
 
                        if cmd:
541
 
                                complete = cmd.autocomplete_func()
542
 
 
543
 
                        if not complete:
544
 
                                return True
545
 
 
546
 
                        # 'complete' contains a dict with arg -> func to do the completion
547
 
                        # of the named argument the command (or stack item) expects
548
 
                        args, varargs = cmd.args()
549
 
 
550
 
                        # Remove system arguments
551
 
                        s = ['argstr', 'args', 'entry', 'view']
552
 
                        args = filter(lambda x: not x in s, args)
553
 
 
554
 
                        if realidx < len(args):
555
 
                                arg = args[realidx]
556
 
                        elif varargs:
557
 
                                arg = '*'
558
 
                        else:
559
 
                                return True
560
 
 
561
 
                        if not arg in complete:
562
 
                                return True
563
 
 
564
 
                        func = complete[arg]
565
 
 
566
 
                        try:
567
 
                                spec = utils.getargspec(func)
568
 
 
569
 
                                if not ww:
570
 
                                        ww = ['']
571
 
 
572
 
                                kwargs = {
573
 
                                        'words': ww,
574
 
                                        'idx': realidx,
575
 
                                        'view': self._view
576
 
                                }
577
 
 
578
 
                                if not spec.keywords:
579
 
                                        for k in kwargs.keys():
580
 
                                                if not k in spec.args:
581
 
                                                        del kwargs[k]
582
 
 
583
 
                                ret = func(**kwargs)
584
 
                        except Exception, e:
585
 
                                # Can be number of arguments, or return values or simply buggy
586
 
                                # modules
587
 
                                print e
588
 
                                traceback.print_exc()
589
 
                                return True
590
 
 
591
 
                if not ret or not ret[0]:
592
 
                        return True
593
 
 
594
 
                res = ret[0]
595
 
                completed = ret[1]
596
 
 
597
 
                if len(ret) > 2:
598
 
                        after = ret[2]
599
 
                else:
600
 
                        after = ' '
601
 
 
602
 
                # Replace the word
603
 
                if words[posidx] == None:
604
 
                        # At end of everything, just append
605
 
                        spec = None
606
 
 
607
 
                        self._entry.insert_text(completed, self._entry.get_text_length())
608
 
                        self._entry.set_position(-1)
609
 
                else:
610
 
                        spec = self._complete_word_match(words[posidx])
611
 
 
612
 
                        self._entry.delete_text(spec[1], spec[2])
613
 
                        self._entry.insert_text(completed, spec[1])
614
 
                        self._entry.set_position(spec[1] + len(completed))
615
 
 
616
 
                if len(res) == 1:
617
 
                        # Full completion
618
 
                        lastpos = self._entry.get_position()
619
 
 
620
 
                        if not isinstance(res[0], commands.module.Module) or not res[0].commands():
621
 
                                if words[posidx] and after == ' ' and (words[posidx].group(2) != None or words[posidx].group(3) != None):
622
 
                                        lastpos = lastpos + 1
623
 
 
624
 
                                self._entry.insert_text(after, lastpos)
625
 
                                self._entry.set_position(lastpos + 1)
626
 
                        elif completed == wordsstr[posidx] or not res[0].method:
627
 
                                self._entry.insert_text('.', lastpos)
628
 
                                self._entry.set_position(lastpos + 1)
629
 
 
630
 
                        if self._info_window:
631
 
                                self._info_window.destroy()
632
 
                else:
633
 
                        # Show popup with completed items
634
 
                        if self._info_window:
635
 
                                self._info_window.clear()
636
 
 
637
 
                        ret = []
638
 
 
639
 
                        for x in res:
640
 
                                if isinstance(x, commands.method.Method):
641
 
                                        ret.append('<b>' + saxutils.escape(x.name) + '</b> (<i>' + x.oneline_doc() + '</i>)')
642
 
                                else:
643
 
                                        ret.append(str(x))
644
 
 
645
 
                        self.info_show("\n".join(ret), True)
646
 
 
647
 
                return True
648
 
 
649
 
        def on_draw(self, widget, ct):
650
 
                win = widget.get_window(Gtk.TextWindowType.BOTTOM)
651
 
 
652
 
                if not Gtk.cairo_should_draw_window(ct, win):
653
 
                        return False
654
 
 
655
 
                Gtk.cairo_transform_to_window(ct, widget, win)
656
 
 
657
 
                color = self.get_border_color()
658
 
                width = win.get_width()
659
 
 
660
 
                ct.set_source_rgba(color.red, color.green, color.blue, color.alpha)
661
 
                ct.move_to(0, 0)
662
 
                ct.line_to(width, 0)
663
 
                ct.stroke()
664
 
 
665
 
                return False
666
 
 
667
 
        def on_destroy(self, widget):
668
 
                self._view.set_border_window_size(Gtk.TextWindowType.BOTTOM, 0)
669
 
                self._view.disconnect(self.view_style_updated_id)
670
 
                self._view.disconnect(self.view_draw_id)
671
 
 
672
 
                if self._info_window:
673
 
                        self._info_window.destroy()
674
 
 
675
 
                self._history.save()
676
 
 
677
 
# vi:ex:ts=4
 
87
        # FIXME: remove hardcopy of 600 (GTK_STYLE_PROVIDER_PRIORITY_APPLICATION)
 
88
        # https://bugzilla.gnome.org/show_bug.cgi?id=646860
 
89
        self._entry.get_style_context().add_provider(css, 600)
 
90
 
 
91
        self._prompt_label = Gtk.Label(label='<b>&gt;&gt;&gt;</b>', use_markup=True)
 
92
        self._prompt_label.show()
 
93
 
 
94
        self._entry.connect('focus-out-event', self.on_entry_focus_out)
 
95
        self._entry.connect('key-press-event', self.on_entry_key_press)
 
96
 
 
97
        self._history = History(os.path.join(GLib.get_user_config_dir(), 'gedit/commander/history'))
 
98
        self._prompt = None
 
99
 
 
100
        self._accel_group = None
 
101
 
 
102
        hbox.pack_start(self._prompt_label, False, False, 0)
 
103
        hbox.pack_start(self._entry, True, True, 0)
 
104
 
 
105
        self.copy_style_from_view()
 
106
        self.view_style_updated_id = self._view.connect('style-updated', self.on_view_style_updated)
 
107
 
 
108
        self.add(hbox)
 
109
        self.attach()
 
110
        self._entry.grab_focus()
 
111
 
 
112
        self._wait_timeout = 0
 
113
        self._info_window = None
 
114
 
 
115
        self.connect('destroy', self.on_destroy)
 
116
        self.connect_after('size-allocate', self.on_size_allocate)
 
117
        self.view_draw_id = self._view.connect_after('draw', self.on_draw)
 
118
 
 
119
        self._history_prefix = None
 
120
        self._suspended = None
 
121
        self._handlers = [
 
122
            [0, Gdk.KEY_Up, self.on_history_move, -1],
 
123
            [0, Gdk.KEY_Down, self.on_history_move, 1],
 
124
            [None, Gdk.KEY_Return, self.on_execute, None],
 
125
            [None, Gdk.KEY_KP_Enter, self.on_execute, None],
 
126
            [0, Gdk.KEY_Tab, self.on_complete, None],
 
127
            [0, Gdk.KEY_ISO_Left_Tab, self.on_complete, None]
 
128
        ]
 
129
 
 
130
        self._re_complete = re.compile('("((?:\\\\"|[^"])*)"?|\'((?:\\\\\'|[^\'])*)\'?|[^\s]+)')
 
131
        self._command_state = commands.Commands.State()
 
132
 
 
133
    def on_view_style_updated(self, widget):
 
134
        self.copy_style_from_view()
 
135
 
 
136
    def get_border_color(self):
 
137
        color = self.get_background_color().copy()
 
138
        color.red = 1 - color.red
 
139
        color.green = 1 - color.green
 
140
        color.blue = 1 - color.blue
 
141
        color.alpha = 0.5
 
142
 
 
143
        return color
 
144
 
 
145
    def get_background_color(self):
 
146
        context = self._view.get_style_context()
 
147
        return context.get_background_color(Gtk.StateFlags.NORMAL)
 
148
    
 
149
    def get_foreground_color(self):
 
150
        context = self._view.get_style_context()
 
151
        return context.get_color(Gtk.StateFlags.NORMAL)
 
152
 
 
153
    def get_font(self):
 
154
        context = self._view.get_style_context()
 
155
        return context.get_font(Gtk.StateFlags.NORMAL)
 
156
 
 
157
    def copy_style_from_view(self, widget=None):
 
158
        if widget != None:
 
159
            context = self._view.get_style_context()
 
160
            font = context.get_font(Gtk.StateFlags.NORMAL)
 
161
 
 
162
            widget.override_color(Gtk.StateFlags.NORMAL, self.get_foreground_color())
 
163
            widget.override_font(self.get_font())
 
164
        else:
 
165
            if self._entry:
 
166
                self.copy_style_from_view(self._entry)
 
167
 
 
168
            if self._prompt_label:
 
169
                self.copy_style_from_view(self._prompt_label)
 
170
 
 
171
    def view(self):
 
172
        return self._view
 
173
 
 
174
    def on_size_allocate(self, widget, alloc):
 
175
        alloc = self.get_allocation()
 
176
        self._view.set_border_window_size(Gtk.TextWindowType.BOTTOM, alloc.height)
 
177
 
 
178
        win = self._view.get_window(Gtk.TextWindowType.BOTTOM)
 
179
        self.set_size_request(win.get_width(), -1)
 
180
 
 
181
        # NOTE: we need to do this explicitly somehow, otherwise the window
 
182
        # size will not be updated unless something else happens, not exactly
 
183
        # sure what. This might be caused by the multi notebook, or custom
 
184
        # animation layouting?
 
185
        self._view.get_parent().resize_children()
 
186
 
 
187
    def attach(self):
 
188
        # Attach ourselves in the text view, and position just above the
 
189
        # text window
 
190
        win = self._view.get_window(Gtk.TextWindowType.TEXT)
 
191
        alloc = self.get_allocation()
 
192
 
 
193
        self._view.set_border_window_size(Gtk.TextWindowType.BOTTOM, max(alloc.height, 1))
 
194
        self._view.add_child_in_window(self, Gtk.TextWindowType.BOTTOM, 0, 0)
 
195
 
 
196
        win = self._view.get_window(Gtk.TextWindowType.BOTTOM)
 
197
 
 
198
        self.show()
 
199
        self.set_size_request(win.get_width(), -1)
 
200
 
 
201
    def on_entry_focus_out(self, widget, evnt):
 
202
        if self._entry.get_sensitive():
 
203
            self.destroy()
 
204
 
 
205
    def on_entry_key_press(self, widget, evnt):
 
206
        state = evnt.state & Gtk.accelerator_get_default_mod_mask()
 
207
        text = self._entry.get_text()
 
208
 
 
209
        if evnt.keyval == Gdk.KEY_Escape:
 
210
            if self._info_window:
 
211
                if self._suspended:
 
212
                    self._suspended.resume()
 
213
 
 
214
                if self._info_window:
 
215
                    self._info_window.destroy()
 
216
 
 
217
                self._entry.set_sensitive(True)
 
218
            elif self._accel_group:
 
219
                self._accel_group = self._accel_group.parent
 
220
 
 
221
                if not self._accel_group or not self._accel_group.parent:
 
222
                    self._entry.set_editable(True)
 
223
                    self._accel_group = None
 
224
 
 
225
                self.prompt()
 
226
            elif text:
 
227
                self._entry.set_text('')
 
228
            elif self._command_state:
 
229
                self._command_state.clear()
 
230
                self.prompt()
 
231
            else:
 
232
                self._view.grab_focus()
 
233
                self.destroy()
 
234
 
 
235
            return True
 
236
 
 
237
        if state or self._accel_group:
 
238
            # Check if it should be handled by the accel group
 
239
            group = self._accel_group
 
240
 
 
241
            if not self._accel_group:
 
242
                group = commands.Commands().accelerator_group()
 
243
 
 
244
            accel = group.activate(evnt.keyval, state)
 
245
 
 
246
            if isinstance(accel, commands.accel_group.AccelGroup):
 
247
                self._accel_group = accel
 
248
                self._entry.set_text('')
 
249
                self._entry.set_editable(False)
 
250
                self.prompt()
 
251
 
 
252
                return True
 
253
            elif isinstance(accel, commands.accel_group.AccelCallback):
 
254
                self._entry.set_editable(True)
 
255
                self.run_command(lambda: accel.activate(self._command_state, self))
 
256
                return True
 
257
 
 
258
        if not self._entry.get_editable():
 
259
            return True
 
260
 
 
261
        for handler in self._handlers:
 
262
            if (handler[0] == None or handler[0] == state) and evnt.keyval == handler[1] and handler[2](handler[3], state):
 
263
                return True
 
264
 
 
265
        if self._info_window and self._info_window.empty():
 
266
            self._info_window.destroy()
 
267
 
 
268
        self._history_prefix = None
 
269
        return False
 
270
 
 
271
    def on_history_move(self, direction, modifier):
 
272
        pos = self._entry.get_position()
 
273
 
 
274
        self._history.update(self._entry.get_text())
 
275
 
 
276
        if self._history_prefix == None:
 
277
            if len(self._entry.get_text()) == pos:
 
278
                self._history_prefix = self._entry.get_chars(0, pos)
 
279
            else:
 
280
                self._history_prefix = ''
 
281
 
 
282
        if self._history_prefix == None:
 
283
            hist = ''
 
284
        else:
 
285
            hist = self._history_prefix
 
286
 
 
287
        next = self._history.move(direction, hist)
 
288
 
 
289
        if next != None:
 
290
            self._entry.set_text(next)
 
291
            self._entry.set_position(-1)
 
292
 
 
293
        return True
 
294
 
 
295
    def prompt(self, pr=''):
 
296
        self._prompt = pr
 
297
 
 
298
        if self._accel_group != None:
 
299
            pr = '<i>%s</i>' % (saxutils.escape(self._accel_group.full_name()),)
 
300
 
 
301
        if not pr:
 
302
            pr = ''
 
303
        else:
 
304
            pr = ' ' + pr
 
305
 
 
306
        self._prompt_label.set_markup('<b>&gt;&gt;&gt;</b>%s' % pr)
 
307
 
 
308
    def make_info(self):
 
309
        if self._info_window == None:
 
310
            self._info_window = Info(self)
 
311
            self._info_window.show()
 
312
 
 
313
            self._info_window.connect('destroy', self.on_info_window_destroy)
 
314
 
 
315
    def on_info_window_destroy(self, widget):
 
316
        self._info_window = None
 
317
 
 
318
    def info_show(self, text='', use_markup=False):
 
319
        self.make_info()
 
320
        self._info_window.add_lines(text, use_markup)
 
321
 
 
322
    def info_status(self, text):
 
323
        self.make_info()
 
324
        self._info_window.status(text)
 
325
 
 
326
    def info_add_action(self, stock, callback, data=None):
 
327
        self.make_info()
 
328
        return self._info_window.add_action(stock, callback, data)
 
329
 
 
330
    def command_history_done(self):
 
331
        self._history.add(self._entry.get_text())
 
332
        self._history_prefix = None
 
333
        self._entry.set_text('')
 
334
 
 
335
    def on_wait_cancel(self):
 
336
        if self._suspended:
 
337
            self._suspended.resume()
 
338
 
 
339
        if self._cancel_button:
 
340
            self._cancel_button.destroy()
 
341
 
 
342
        if self._info_window and self._info_window.empty():
 
343
            self._info_window.destroy()
 
344
            self._entry.grab_focus()
 
345
            self._entry.set_sensitive(True)
 
346
 
 
347
    def _show_wait_cancel(self):
 
348
        self._cancel_button = self.info_add_action(Gtk.STOCK_STOP, self.on_wait_cancel)
 
349
        self.info_status('<i>Waiting to finish...</i>')
 
350
 
 
351
        self._wait_timeout = 0
 
352
        return False
 
353
 
 
354
    def _complete_word_match(self, match):
 
355
        for i in (3, 2, 0):
 
356
            if match.group(i) != None:
 
357
                return [match.group(i), match.start(i), match.end(i)]
 
358
 
 
359
    def on_suspend_resume(self):
 
360
        if self._wait_timeout:
 
361
            GObject.source_remove(self._wait_timeout)
 
362
            self._wait_timeout = 0
 
363
        else:
 
364
            self._cancel_button.destroy()
 
365
            self._cancel_button = None
 
366
            self.info_status(None)
 
367
 
 
368
        self._entry.set_sensitive(True)
 
369
        self.command_history_done()
 
370
 
 
371
        if self._entry.props.has_focus or (self._info_window and not self._info_window.empty()):
 
372
            self._entry.grab_focus()
 
373
 
 
374
        self.on_execute(None, 0)
 
375
 
 
376
    def ellipsize(self, s, size):
 
377
        if len(s) <= size:
 
378
            return s
 
379
 
 
380
        mid = (size - 4) / 2
 
381
        return s[:mid] + '...' + s[-mid:]
 
382
 
 
383
    def destroy(self):
 
384
        self.hide()
 
385
        Gtk.EventBox.destroy(self)
 
386
 
 
387
    def run_command(self, cb):
 
388
        self._suspended = None
 
389
 
 
390
        try:
 
391
            ret = cb()
 
392
        except Exception, e:
 
393
            self.command_history_done()
 
394
            self._command_state.clear()
 
395
 
 
396
            self.prompt()
 
397
 
 
398
            # Show error in info
 
399
            self.info_show('<b><span color="#f66">Error:</span></b> ' + saxutils.escape(str(e)), True)
 
400
 
 
401
            if not isinstance(e, commands.exceptions.Execute):
 
402
                self.info_show(self.format_trace(), False)
 
403
 
 
404
            return None
 
405
 
 
406
        if ret == commands.result.Result.SUSPEND:
 
407
            # Wait for it...
 
408
            self._suspended = ret
 
409
            ret.register(self.on_suspend_resume)
 
410
 
 
411
            self._wait_timeout = GObject.timeout_add(500, self._show_wait_cancel)
 
412
            self._entry.set_sensitive(False)
 
413
        else:
 
414
            self.command_history_done()
 
415
            self.prompt('')
 
416
 
 
417
            if ret == commands.result.Result.PROMPT:
 
418
                self.prompt(ret.prompt)
 
419
            elif (ret == None or ret == commands.result.HIDE) and not self._prompt and (not self._info_window or self._info_window.empty()):
 
420
                self._command_state.clear()
 
421
                self._view.grab_focus()
 
422
                self.destroy()
 
423
            else:
 
424
                self._entry.grab_focus()
 
425
 
 
426
        return ret
 
427
 
 
428
    def format_trace(self):
 
429
        tp, val, tb = sys.exc_info()
 
430
 
 
431
        origtb = tb
 
432
 
 
433
        thisdir = os.path.dirname(__file__)
 
434
 
 
435
        # Skip frames up until after the last entry.py...
 
436
        while True:
 
437
            filename = tb.tb_frame.f_code.co_filename
 
438
 
 
439
            dname = os.path.dirname(filename)
 
440
 
 
441
            if not dname.startswith(thisdir):
 
442
                break
 
443
 
 
444
            tb = tb.tb_next
 
445
 
 
446
        msg = traceback.format_exception(tp, val, tb)
 
447
        r = ''.join(msg[0:-1])
 
448
 
 
449
        # This is done to prevent cyclic references, see python
 
450
        # documentation on sys.exc_info
 
451
        del origtb
 
452
 
 
453
        return r
 
454
 
 
455
    def on_execute(self, dummy, modifier):
 
456
        if self._info_window and not self._suspended:
 
457
            self._info_window.destroy()
 
458
 
 
459
        text = self._entry.get_text().strip()
 
460
        words = list(self._re_complete.finditer(text))
 
461
        wordsstr = []
 
462
 
 
463
        for word in words:
 
464
            spec = self._complete_word_match(word)
 
465
            wordsstr.append(spec[0])
 
466
 
 
467
        if not wordsstr and not self._command_state:
 
468
            self._entry.set_text('')
 
469
            return
 
470
 
 
471
        self.run_command(lambda: commands.Commands().execute(self._command_state, text, words, wordsstr, self, modifier))
 
472
 
 
473
        return True
 
474
 
 
475
    def on_complete(self, dummy, modifier):
 
476
        # First split all the text in words
 
477
        text = self._entry.get_text()
 
478
        pos = self._entry.get_position()
 
479
 
 
480
        words = list(self._re_complete.finditer(text))
 
481
        wordsstr = []
 
482
 
 
483
        for word in words:
 
484
            spec = self._complete_word_match(word)
 
485
            wordsstr.append(spec[0])
 
486
 
 
487
        # Find out at which word the cursor actually is
 
488
        # Examples:
 
489
        #  * hello world|
 
490
        #  * hello| world
 
491
        #  * |hello world
 
492
        #  * hello wor|ld
 
493
        #  * hello  |  world
 
494
        #  * "hello world|"
 
495
        posidx = None
 
496
 
 
497
        for idx in xrange(0, len(words)):
 
498
            spec = self._complete_word_match(words[idx])
 
499
 
 
500
            if words[idx].start(0) > pos:
 
501
                # Empty space, new completion
 
502
                wordsstr.insert(idx, '')
 
503
                words.insert(idx, None)
 
504
                posidx = idx
 
505
                break
 
506
            elif spec[2] == pos:
 
507
                # At end of word, resume completion
 
508
                posidx = idx
 
509
                break
 
510
            elif spec[1] <= pos and spec[2] > pos:
 
511
                # In middle of word, do not complete
 
512
                return True
 
513
 
 
514
        if posidx == None:
 
515
            wordsstr.append('')
 
516
            words.append(None)
 
517
            posidx = len(wordsstr) - 1
 
518
 
 
519
        # First word completes a command, if not in any special 'mode'
 
520
        # otherwise, relay completion to the command, or complete by advice
 
521
        # from the 'mode' (prompt)
 
522
        cmds = commands.Commands()
 
523
 
 
524
        if not self._command_state and posidx == 0:
 
525
            # Complete the first command
 
526
            ret = commands.completion.command(words=wordsstr, idx=posidx)
 
527
        else:
 
528
            complete = None
 
529
            realidx = posidx
 
530
 
 
531
            if not self._command_state:
 
532
                # Get the command first
 
533
                cmd = commands.completion.single_command(wordsstr, 0)
 
534
                realidx -= 1
 
535
 
 
536
                ww = wordsstr[1:]
 
537
            else:
 
538
                cmd = self._command_state.top()
 
539
                ww = wordsstr
 
540
 
 
541
            if cmd:
 
542
                complete = cmd.autocomplete_func()
 
543
 
 
544
            if not complete:
 
545
                return True
 
546
 
 
547
            # 'complete' contains a dict with arg -> func to do the completion
 
548
            # of the named argument the command (or stack item) expects
 
549
            args, varargs = cmd.args()
 
550
 
 
551
            # Remove system arguments
 
552
            s = ['argstr', 'args', 'entry', 'view']
 
553
            args = filter(lambda x: not x in s, args)
 
554
 
 
555
            if realidx < len(args):
 
556
                arg = args[realidx]
 
557
            elif varargs:
 
558
                arg = '*'
 
559
            else:
 
560
                return True
 
561
 
 
562
            if not arg in complete:
 
563
                return True
 
564
 
 
565
            func = complete[arg]
 
566
 
 
567
            try:
 
568
                spec = utils.getargspec(func)
 
569
 
 
570
                if not ww:
 
571
                    ww = ['']
 
572
 
 
573
                kwargs = {
 
574
                    'words': ww,
 
575
                    'idx': realidx,
 
576
                    'view': self._view
 
577
                }
 
578
 
 
579
                if not spec.keywords:
 
580
                    for k in kwargs.keys():
 
581
                        if not k in spec.args:
 
582
                            del kwargs[k]
 
583
 
 
584
                ret = func(**kwargs)
 
585
            except Exception, e:
 
586
                # Can be number of arguments, or return values or simply buggy
 
587
                # modules
 
588
                print e
 
589
                traceback.print_exc()
 
590
                return True
 
591
 
 
592
        if not ret or not ret[0]:
 
593
            return True
 
594
 
 
595
        res = ret[0]
 
596
        completed = ret[1]
 
597
 
 
598
        if len(ret) > 2:
 
599
            after = ret[2]
 
600
        else:
 
601
            after = ' '
 
602
 
 
603
        # Replace the word
 
604
        if words[posidx] == None:
 
605
            # At end of everything, just append
 
606
            spec = None
 
607
 
 
608
            self._entry.insert_text(completed, self._entry.get_text_length())
 
609
            self._entry.set_position(-1)
 
610
        else:
 
611
            spec = self._complete_word_match(words[posidx])
 
612
 
 
613
            self._entry.delete_text(spec[1], spec[2])
 
614
            self._entry.insert_text(completed, spec[1])
 
615
            self._entry.set_position(spec[1] + len(completed))
 
616
 
 
617
        if len(res) == 1:
 
618
            # Full completion
 
619
            lastpos = self._entry.get_position()
 
620
 
 
621
            if not isinstance(res[0], commands.module.Module) or not res[0].commands():
 
622
                if words[posidx] and after == ' ' and (words[posidx].group(2) != None or words[posidx].group(3) != None):
 
623
                    lastpos = lastpos + 1
 
624
 
 
625
                self._entry.insert_text(after, lastpos)
 
626
                self._entry.set_position(lastpos + 1)
 
627
            elif completed == wordsstr[posidx] or not res[0].method:
 
628
                self._entry.insert_text('.', lastpos)
 
629
                self._entry.set_position(lastpos + 1)
 
630
 
 
631
            if self._info_window:
 
632
                self._info_window.destroy()
 
633
        else:
 
634
            # Show popup with completed items
 
635
            if self._info_window:
 
636
                self._info_window.clear()
 
637
 
 
638
            ret = []
 
639
 
 
640
            for x in res:
 
641
                if isinstance(x, commands.method.Method):
 
642
                    ret.append('<b>' + saxutils.escape(x.name) + '</b> (<i>' + x.oneline_doc() + '</i>)')
 
643
                else:
 
644
                    ret.append(str(x))
 
645
 
 
646
            self.info_show("\n".join(ret), True)
 
647
 
 
648
        return True
 
649
 
 
650
    def on_draw(self, widget, ct):
 
651
        win = widget.get_window(Gtk.TextWindowType.BOTTOM)
 
652
 
 
653
        if not Gtk.cairo_should_draw_window(ct, win):
 
654
            return False
 
655
 
 
656
        Gtk.cairo_transform_to_window(ct, widget, win)
 
657
 
 
658
        color = self.get_border_color()
 
659
        width = win.get_width()
 
660
 
 
661
        ct.set_source_rgba(color.red, color.green, color.blue, color.alpha)
 
662
        ct.move_to(0, 0)
 
663
        ct.line_to(width, 0)
 
664
        ct.stroke()
 
665
 
 
666
        return False
 
667
 
 
668
    def on_destroy(self, widget):
 
669
        self._view.set_border_window_size(Gtk.TextWindowType.BOTTOM, 0)
 
670
        self._view.disconnect(self.view_style_updated_id)
 
671
        self._view.disconnect(self.view_draw_id)
 
672
 
 
673
        if self._info_window:
 
674
            self._info_window.destroy()
 
675
 
 
676
        self._history.save()
 
677
 
 
678
# vi:ex:ts=4:et