GnuCash c935c2f+
Loading...
Searching...
No Matches
console.py
1#! /usr/bin/env python3
2#
3# Copyright (c) 2008, Nicolas Rougier
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright
12# notice, this list of conditions and the following disclaimer in the
13# documentation and/or other materials provided with the distribution.
14# * Neither the name of the University of California, Berkeley nor the
15# names of its contributors may be used to endorse or promote products
16# derived from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
19# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS BE LIABLE FOR ANY
22# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import os
30import sys
31import re
32import tempfile
33import readline
34import gi
35gi.require_version('Gtk', '3.0')
36from gi.repository import GObject
37from gi.repository import Gtk
38from gi.repository import Gdk
39from gi.repository import Pango
40import io
41import pycons.shell as shell
42try: import pycons.ishell as ishell
43except: pass
44
45ansi_colors = {'0;30': '#2E3436',
46 '0;31': '#CC0000',
47 '0;32': '#4E9A06',
48 '0;33': '#C4A000',
49 '0;34': '#3465A4',
50 '0;35': '#75507B',
51 '0;36': '#06989A',
52 '0;37': '#D3D7CF',
53 '1;30': '#555753',
54 '1;31': '#EF2929',
55 '1;32': '#8AE234',
56 '1;33': '#FCE94F',
57 '1;34': '#729FCF',
58 '1;35': '#AD7FA8',
59 '1;36': '#34E2E2',
60 '1;37': '#EEEEEC'}
61
62# ------------------------------------------------------------- class ConsoleOut
64 """
65 A fake output file object. It sends output to the console widget,
66 and if asked for a file number, returns one set on instance creation
67 """
68
69 def __init__(self, console, fn=-1, style=None):
70 self.fn = fn
71 self.console = console
72 self.style = style
73 def close(self): pass
74 flush = close
75 def fileno(self): return self.fn
76 def isatty(self): return False
77 def read(self, a): return ''
78 def readline(self): return ''
79 def readlines(self): return []
80 def write(self, s):
81 self.console.write (s, self.style)
82 def writelines(self, l):
83 for s in l:
84 self.console.write (s, self.style)
85 def seek(self, a): raise IOError(29, 'Illegal seek')
86 def tell(self): raise IOError(29, 'Illegal seek')
87 truncate = tell
88
89
90# -------------------------------------------------------------- class ConsoleIn
92 """
93 A fake input file object. It receives input from a GTK TextView widget,
94 and if asked for a file number, returns one set on instance creation
95 """
96 def __init__(self, console, fn=-1):
97 self.fn = fn
98 self.console = console
99 def close(self): pass
100 flush = close
101 def fileno(self): return self.fn
102 def isatty(self): return False
103 def read(self, a): return self.readline()
104 def readline(self):
105 self.console.input_mode = True
106 buffer = self.console.buffer
107 #console.write('\n')
108 iter = buffer.get_iter_at_mark(buffer.get_insert())
109 buffer.move_mark (buffer.get_mark('linestart'), iter)
110 while self.console.input_mode:
111 #while Gtk.events_pending():
112 Gtk.main_iteration()
113 s = self.console.input
114 self.console.input = ''
115 return s+'\n'
116 def readlines(self): return []
117 def write(self, s): return None
118 def writelines(self, l): return None
119 def seek(self, a): raise IOError(29, 'Illegal seek')
120 def tell(self): raise IOError(29, 'Illegal seek')
121 truncate = tell
122
123
124# ---------------------------------------------------------------- class Console
125class Console (Gtk.ScrolledWindow):
126 """ GTK python console """
127
128 def __init__(self, argv=[], shelltype='python', banner=[],
129 filename=None, size=100, user_local_ns=None, user_global_ns=None):
130
131 """ Console interface building + initialization"""
132
133 # GTK interface
134 self.do_quit = False
135 GObject.GObject.__init__(self)
136 self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
137 self.set_shadow_type (Gtk.ShadowType.NONE)
138 self.set_border_width(0)
139 self.view = Gtk.TextView()
140 self.view.modify_font (Pango.FontDescription("Mono 10"))
141 self.view.set_editable (True)
142 self.view.set_wrap_mode(True)
143 self.view.set_left_margin(0)
144 self.view.set_right_margin(0)
145 self.buffer = self.view.get_buffer()
146 self.buffer.create_tag ('title',
147 indent = 2,
148 weight=Pango.Weight.BOLD,
149 foreground='blue',
150 font='Mono 12')
151 self.buffer.create_tag ('subtitle',
152 indent = 2,
153 foreground='blue',
154 font='Mono 8')
155 self.buffer.create_tag ('output',
156 foreground = 'blue',
157 font='Mono 10')
158 self.buffer.create_tag ('error',
159 foreground='red',
160 style=Pango.Style.OBLIQUE,
161 font='Mono 10')
162 self.buffer.create_tag ('prompt',
163 foreground='blue',
164 weight=Pango.Weight.BOLD,
165 font='Mono 10')
166 self.buffer.create_tag('0')
167 self.color_pat = re.compile(r'\x01?\x1b\[(.*?)m\x02?')
168 for code in ansi_colors:
169 self.buffer.create_tag(code,
170 foreground=ansi_colors[code],
171 weight=700)
172 for text, style in banner:
173 self.write (text, style)
174 iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
175 self.buffer.create_mark ('linestart', iter, True)
176 self.view.add_events(Gdk.EventMask.KEY_PRESS_MASK)
177 self.view.connect ('key-press-event', self.key_press_eventkey_press_event)
178 self.add(self.view)
179 self.show_all()
180 self.killbuffer = None
181
182 # Console stuff
183 self.argv = argv
184 self.history_init(filename, size)
185 self.cout = io.StringIO()
186 self.cout.truncate(0)
187
188 if not user_local_ns:
189 user_local_ns = locals()
190 if not user_global_ns:
191 user_global_ns = globals()
192
193 if shelltype=='ipython':
194 self.shell = ishell.Shell(argv,user_local_ns, user_global_ns,
195 cout=self.cout, cerr=self.cout,
196 input_func=self.raw_input)
197 else:
198 self.shell = shell.Shell(user_local_ns,user_global_ns)
199 self.interrupt = False
200 self.input_mode = False
201 self.input = None
202 self.stdout = ConsoleOut (self, sys.stdout.fileno(), 'output')
203 self.stderr = ConsoleOut (self, sys.stderr.fileno(), 'error')
204 self.stdin = ConsoleIn (self, sys.stdin.fileno())
205
206 # Create a named pipe for system stdout/stderr redirection
207 self.fifoname = tempfile.mktemp()
208 if not os.path.exists (self.fifoname):
209 os.mkfifo (self.fifoname)
210 self.piperead = os.open (self.fifoname, os.O_RDONLY | os.O_NONBLOCK)
211 self.pipewrite = os.open (self.fifoname, os.O_WRONLY | os.O_NONBLOCK)
212 self.shell.eval(self)
213 self.cout.truncate(0)
214
215 def history_init(self, filename, size):
216 self.history_file = filename
217 self.history_size = size
218 if filename and os.path.exists(filename):
219 readline.read_history_file(filename)
220 readline.set_history_length(size)
221 self.history_reset()
222
223 def history_save(self):
224 if self.history_file:
225 readline.write_history_file(self.history_file)
226
227 def history_add(self, item):
228 if len(item):
229 readline.add_history (item)
230 self.history_reset()
231
232 def history_reset(self):
233 self.history_index = readline.get_current_history_length()+1
234
235 def history_next(self):
236 self.history_index += 1
237 if self.history_index <= readline.get_current_history_length():
238 return '' or readline.get_history_item (self.history_index)
239 self.history_index = readline.get_current_history_length()+1
240 return ''
241
242 def history_prev(self):
243 if self.history_index > 1:
244 self.history_index -= 1
245 else:
246 self.history_index = 1
247 return '' or readline.get_history_item (self.history_index)
248
249 def raw_input(self, prompt=''):
250 if self.interrupt:
251 self.interrupt = False
252 raise KeyboardInterrupt
253 return self.last_line()
254
255 def grab_focus (self):
256 """ Give focus to the TextView """
257
258 self.view.grab_focus()
259
260 def write (self, text, style=None):
261 """ Write text using given style (if any) """
262 segments = self.color_pat.split(text)
263 segment = segments.pop(0)
264 start,end = self.buffer.get_bounds()
265 if style==None:
266 self.buffer.insert(end, segment)
267 else:
268 self.buffer.insert_with_tags_by_name(end, segment, style)
269 if segments:
270 ansi_tags = self.color_pat.findall(text)
271 for tag in ansi_tags:
272 i = segments.index(tag)
273 self.buffer.insert_with_tags_by_name(self.buffer.get_end_iter(),
274 segments[i+1], tag)
275 segments.pop(i)
276 self.view.scroll_mark_onscreen(self.buffer.get_insert())
277
278 def overwrite (self, text, style=None):
279 """ Overwrite text after prompt with text """
280
281 mark = self.buffer.get_mark('linestart')
282 start = self.buffer.get_iter_at_mark(mark)
283 end = self.buffer.get_end_iter()
284 self.buffer.delete (start,end)
285 self.write (text, style)
286
287 def last_line (self):
288 """ Get last line (without prompt) """
289
290 mark = self.buffer.get_mark('linestart')
291 start = self.buffer.get_iter_at_mark(mark)
292 end = self.buffer.get_end_iter()
293 return self.buffer.get_text (start,end,True)
294
295
296 def prompt (self, style=None):
297 """ Display prompt """
298
299 iter = self.buffer.get_end_iter()
300 self.buffer.place_cursor (iter)
301 self.write (self.shell.prompt, style)
302 iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
303 self.buffer.move_mark (self.buffer.get_mark('linestart'), iter)
304 self.history_reset()
305 self.view.scroll_mark_onscreen(self.buffer.get_insert())
306 while Gtk.events_pending():
307 Gtk.main_iteration()
308
309 def key_press_event (self, widget, event):
310 """ Handle key press event """
311
312 keyname = Gdk.keyval_name (event.keyval)
313
314 # New command
315 if keyname in ['Return', 'KP_Enter']:
316 line = self.last_line()
317 self.history_add (line)
318 if self.input_mode:
319 self.input_mode = False
320 self.input = self.last_line()
321 self.write('\n')
322 else:
323 self.execute()
324 return True
325
326 # Prevent cursor to go back past prompt
327 elif keyname in ['Left', 'BackSpace']:
328 mark = self.buffer.get_mark('linestart')
329 linestart = self.buffer.get_iter_at_mark(mark)
330 iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
331 if iter.compare(linestart) <= 0:
332 return True
333
334 elif keyname == 'Right':
335 return False
336
337 # Next history item
338 elif keyname == 'Down':
339 self.overwrite (self.history_next())
340 return True
341
342 # Previous history item
343 elif keyname == 'Up':
344 self.overwrite (self.history_prev())
345 return True
346
347 # Move cursor just after prompt
348 elif keyname == 'Home':
349 mark = self.buffer.get_mark('linestart')
350 linestart = self.buffer.get_iter_at_mark(mark)
351 self.buffer.place_cursor (linestart)
352 return True
353
354 # Completion if line not empty
355 elif keyname == 'Tab':
356 line = self.last_line()
357 if not line.strip():
358 return False
359 completed, possibilities = self.shell.complete(line)
360 if len(possibilities) > 1:
361 slice = line
362 self.write('\n')
363 for symbol in possibilities:
364 self.write(symbol+'\n')
365 self.prompt('prompt')
366 self.overwrite(completed or slice)
367 return True
368
369 # Controls
370 elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
371 if keyname in ['a','A']:
372 mark = self.buffer.get_mark('linestart')
373 linestart = self.buffer.get_iter_at_mark(mark)
374 self.buffer.place_cursor (linestart)
375 return True
376 elif keyname in ['e','E']:
377 end = self.buffer.get_end_iter()
378 self.buffer.place_cursor (end)
379 return True
380 elif keyname in ['k','K']:
381 start = self.buffer.get_iter_at_mark (self.buffer.get_insert())
382 end = self.buffer.get_end_iter()
383 self.killbuffer = self.buffer.get_text(start,end)
384 self.buffer.delete(start,end)
385 return True
386 elif keyname in ['y','Y']:
387 if self.killbuffer:
388 iter = self.buffer.get_iter_at_mark (self.buffer.get_insert())
389 self.buffer.insert(iter, self.killbuffer)
390 return True
391 elif keyname in ['l', 'L']:
392 start = self.buffer.get_start_iter()
393 end = self.buffer.get_end_iter()
394 end.backward_sentence_start()
395 self.buffer.delete (start,end)
396 elif keyname in ['d', 'D']:
397 if not len(self.last_line().strip()):
398 self.quit()
399
400 # Editing before prompt is forbidden
401 else:
402 mark = self.buffer.get_mark('linestart')
403 linestart = self.buffer.get_iter_at_mark(mark)
404 iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
405 if iter.compare(linestart) < 0:
406 iter = self.buffer.get_end_iter()
407 self.buffer.place_cursor (iter)
408 return False
409
410
411 def execute (self):
412 # Python stdout, stderr, stdin redirection
413 sys.stdout, self.stdout = self.stdout, sys.stdout
414 sys.stderr, self.stderr = self.stderr, sys.stderr
415 sys.stdin, self.stdin = self.stdin, sys.stdin
416
417 # System stdout, stderr redirection
418 sys_stdout = os.dup(1)
419 sys_stderr = os.dup(2)
420 os.dup2 (self.pipewrite, 1)
421 os.dup2 (self.pipewrite, 2)
422
423 self.shell.eval(self)
424 self.view.scroll_mark_onscreen(self.buffer.get_insert())
425 while Gtk.events_pending():
426 Gtk.main_iteration()
427
428 # Get system output and remove system redirection
429 os.dup2 (sys_stdout, 1)
430 os.dup2 (sys_stderr, 2)
431 os.close (sys_stdout)
432 os.close (sys_stderr)
433
434 # Remove python redirection
435 sys.stdout, self.stdout = self.stdout, sys.stdout
436 sys.stderr, self.stderr = self.stderr, sys.stderr
437 sys.stdin, self.stdin = self.stdin, sys.stdin
438
439
440 def quit(self):
441 """ Quit console """
442
443 self.history_save()
444 try:
445 os.close (self.piperead)
446 os.close (self.pipewrite)
447 except:
448 pass
449 if os.path.exists (self.fifoname):
450 os.remove (self.fifoname)
451 self.do_quit = True
__init__(self, argv=[], shelltype='python', banner=[], filename=None, size=100, user_local_ns=None, user_global_ns=None)
Definition console.py:129
overwrite(self, text, style=None)
Definition console.py:278
prompt(self, style=None)
Definition console.py:296
key_press_event(self, widget, event)
Definition console.py:309
raw_input(self, prompt='')
Definition console.py:249
history_add(self, item)
Definition console.py:227
write(self, text, style=None)
Definition console.py:260
history_init(self, filename, size)
Definition console.py:215