1# -*- coding: utf-8 -*- 2 3# pythonconsole.py -- Console widget 4# 5# Copyright (C) 2006 - Steve Frécinaux 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2, or (at your option) 10# any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program; if not, write to the Free Software 19# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 21# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) 22# Copyright (C), 1998 James Henstridge <james@daa.com.au> 23# Copyright (C), 2005 Adam Hooper <adamh@densi.com> 24# Bits from gedit Python Console Plugin 25# Copyrignt (C), 2005 Raphaël Slinckx 26 27import string 28import sys 29import re 30import traceback 31 32from gi.repository import GLib, Gio, Gtk, Gdk, Pango 33 34__all__ = ('PythonConsole', 'OutFile') 35 36 37class PythonConsole(Gtk.ScrolledWindow): 38 39 __gsignals__ = { 40 'grab-focus' : 'override', 41 } 42 43 DEFAULT_FONT = "Monospace 11" 44 45 CONSOLE_KEY_BASE = 'org.gnome.eog.plugins.pythonconsole' 46 SETTINGS_INTERFACE_DIR = "org.gnome.desktop.interface" 47 48 CONSOLE_KEY_COMMAND_COLOR = 'command-color' 49 CONSOLE_KEY_ERROR_COLOR = 'error-color' 50 51 def __init__(self, namespace = {}): 52 Gtk.ScrolledWindow.__init__(self) 53 54 self._settings = Gio.Settings.new(self.CONSOLE_KEY_BASE) 55 self._settings.connect("changed", self.on_color_settings_changed) 56 57 self._interface_settings = Gio.Settings.new(self.SETTINGS_INTERFACE_DIR) 58 self._interface_settings.connect("changed", self.on_settings_changed) 59 60 self._profile_settings = self.get_profile_settings() 61 self._profile_settings.connect("changed", self.on_settings_changed) 62 63 self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) 64 self.set_shadow_type(Gtk.ShadowType.IN) 65 self.view = Gtk.TextView() 66 self.reconfigure() 67 self.view.set_editable(True) 68 self.view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) 69 self.add(self.view) 70 self.view.show() 71 72 buf = self.view.get_buffer() 73 self.normal = buf.create_tag("normal") 74 self.error = buf.create_tag("error") 75 self.command = buf.create_tag("command") 76 77 # Load the default settings 78 self.on_color_settings_changed(self._settings, None) 79 80 self.__spaces_pattern = re.compile(r'^\s+') 81 self.namespace = namespace 82 83 self.block_command = False 84 85 # Init first line 86 buf.create_mark("input-line", buf.get_end_iter(), True) 87 buf.insert(buf.get_end_iter(), ">>> ") 88 buf.create_mark("input", buf.get_end_iter(), True) 89 90 # Init history 91 self.history = [''] 92 self.history_pos = 0 93 self.current_command = '' 94 self.namespace['__history__'] = self.history 95 96 # Set up hooks for standard output. 97 self.stdout = OutFile(self, sys.stdout.fileno(), self.normal) 98 self.stderr = OutFile(self, sys.stderr.fileno(), self.error) 99 100 # Signals 101 self.view.connect("key-press-event", self.__key_press_event_cb) 102 buf.connect("mark-set", self.__mark_set_cb) 103 104 def get_profile_settings(self): 105 #FIXME return either the gnome-terminal settings or the eog one 106 return Gio.Settings.new(self.CONSOLE_KEY_BASE) 107 108 def do_grab_focus(self): 109 self.view.grab_focus() 110 111 def reconfigure(self): 112 # Font 113 font_desc = None 114 system_font = self._interface_settings.get_string("monospace-font-name") 115 116 if self._profile_settings.get_boolean("use-system-font"): 117 font_name = system_font 118 else: 119 font_name = self._profile_settings.get_string("font") 120 121 try: 122 font_desc = Pango.FontDescription(font_name) 123 except: 124 if font_name != self.DEFAULT_FONT: 125 if font_name != system_font: 126 try: 127 font_desc = Pango.FontDescription(system_font) 128 except: 129 pass 130 131 if font_desc == None: 132 try: 133 font_desc = Pango.FontDescription(self.DEFAULT_FONT) 134 except: 135 pass 136 137 if font_desc != None: 138 self.view.modify_font(font_desc) 139 140 def on_settings_changed(self, settings, key): 141 self.reconfigure() 142 143 def on_color_settings_changed(self, settings, key): 144 self.error.set_property("foreground", settings.get_string(self.CONSOLE_KEY_ERROR_COLOR)) 145 self.command.set_property("foreground", settings.get_string(self.CONSOLE_KEY_COMMAND_COLOR)) 146 147 def stop(self): 148 self.namespace = None 149 150 def __key_press_event_cb(self, view, event): 151 modifier_mask = Gtk.accelerator_get_default_mod_mask() 152 event_state = event.state & modifier_mask 153 154 if event.keyval == Gdk.KEY_D and event_state == Gdk.ModifierType.CONTROL_MASK: 155 self.destroy() 156 157 elif event.keyval == Gdk.KEY_Return and event_state == Gdk.ModifierType.CONTROL_MASK: 158 # Get the command 159 buf = view.get_buffer() 160 inp_mark = buf.get_mark("input") 161 inp = buf.get_iter_at_mark(inp_mark) 162 cur = buf.get_end_iter() 163 line = buf.get_text(inp, cur, False) 164 self.current_command = self.current_command + line + "\n" 165 self.history_add(line) 166 167 # Prepare the new line 168 cur = buf.get_end_iter() 169 buf.insert(cur, "\n... ") 170 cur = buf.get_end_iter() 171 buf.move_mark(inp_mark, cur) 172 173 # Keep indentation of precendent line 174 spaces = re.match(self.__spaces_pattern, line) 175 if spaces is not None: 176 buf.insert(cur, line[spaces.start() : spaces.end()]) 177 cur = buf.get_end_iter() 178 179 buf.place_cursor(cur) 180 GLib.idle_add(self.scroll_to_end) 181 return True 182 183 elif event.keyval == Gdk.KEY_Return: 184 # Get the marks 185 buf = view.get_buffer() 186 lin_mark = buf.get_mark("input-line") 187 inp_mark = buf.get_mark("input") 188 189 # Get the command line 190 inp = buf.get_iter_at_mark(inp_mark) 191 cur = buf.get_end_iter() 192 line = buf.get_text(inp, cur, False) 193 self.current_command = self.current_command + line + "\n" 194 self.history_add(line) 195 196 # Make the line blue 197 lin = buf.get_iter_at_mark(lin_mark) 198 buf.apply_tag(self.command, lin, cur) 199 buf.insert(cur, "\n") 200 201 cur_strip = self.current_command.rstrip() 202 203 if cur_strip.endswith(":") \ 204 or (self.current_command[-2:] != "\n\n" and self.block_command): 205 # Unfinished block command 206 self.block_command = True 207 com_mark = "... " 208 elif cur_strip.endswith("\\"): 209 com_mark = "... " 210 else: 211 # Eval the command 212 self.__run(self.current_command) 213 self.current_command = '' 214 self.block_command = False 215 com_mark = ">>> " 216 217 # Prepare the new line 218 cur = buf.get_end_iter() 219 buf.move_mark(lin_mark, cur) 220 buf.insert(cur, com_mark) 221 cur = buf.get_end_iter() 222 buf.move_mark(inp_mark, cur) 223 buf.place_cursor(cur) 224 GLib.idle_add(self.scroll_to_end) 225 return True 226 227 elif event.keyval == Gdk.KEY_KP_Down or event.keyval == Gdk.KEY_Down: 228 # Next entry from history 229 view.stop_emission_by_name("key_press_event") 230 self.history_down() 231 GLib.idle_add(self.scroll_to_end) 232 return True 233 234 elif event.keyval == Gdk.KEY_KP_Up or event.keyval == Gdk.KEY_Up: 235 # Previous entry from history 236 view.stop_emission_by_name("key_press_event") 237 self.history_up() 238 GLib.idle_add(self.scroll_to_end) 239 return True 240 241 elif event.keyval == Gdk.KEY_KP_Left or event.keyval == Gdk.KEY_Left or \ 242 event.keyval == Gdk.KEY_BackSpace: 243 buf = view.get_buffer() 244 inp = buf.get_iter_at_mark(buf.get_mark("input")) 245 cur = buf.get_iter_at_mark(buf.get_insert()) 246 if inp.compare(cur) == 0: 247 if not event_state: 248 buf.place_cursor(inp) 249 return True 250 return False 251 252 # For the console we enable smart/home end behavior incoditionally 253 # since it is useful when editing python 254 255 elif (event.keyval == Gdk.KEY_KP_Home or event.keyval == Gdk.KEY_Home) and \ 256 event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK): 257 # Go to the begin of the command instead of the begin of the line 258 buf = view.get_buffer() 259 it = buf.get_iter_at_mark(buf.get_mark("input")) 260 ins = buf.get_iter_at_mark(buf.get_insert()) 261 262 while it.get_char().isspace(): 263 it.forward_char() 264 265 if it.equal(ins): 266 it = buf.get_iter_at_mark(buf.get_mark("input")) 267 268 if event_state & Gdk.ModifierType.SHIFT_MASK: 269 buf.move_mark_by_name("insert", it) 270 else: 271 buf.place_cursor(it) 272 return True 273 274 elif (event.keyval == Gdk.KEY_KP_End or event.keyval == Gdk.KEY_End) and \ 275 event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK): 276 277 buf = view.get_buffer() 278 it = buf.get_end_iter() 279 ins = buf.get_iter_at_mark(buf.get_insert()) 280 281 it.backward_char() 282 283 while it.get_char().isspace(): 284 it.backward_char() 285 286 it.forward_char() 287 288 if it.equal(ins): 289 it = buf.get_end_iter() 290 291 if event_state & Gdk.ModifierType.SHIFT_MASK: 292 buf.move_mark_by_name("insert", it) 293 else: 294 buf.place_cursor(it) 295 return True 296 297 def __mark_set_cb(self, buf, it, name): 298 input = buf.get_iter_at_mark(buf.get_mark("input")) 299 pos = buf.get_iter_at_mark(buf.get_insert()) 300 self.view.set_editable(pos.compare(input) != -1) 301 302 def get_command_line(self): 303 buf = self.view.get_buffer() 304 inp = buf.get_iter_at_mark(buf.get_mark("input")) 305 cur = buf.get_end_iter() 306 return buf.get_text(inp, cur, False) 307 308 def set_command_line(self, command): 309 buf = self.view.get_buffer() 310 mark = buf.get_mark("input") 311 inp = buf.get_iter_at_mark(mark) 312 cur = buf.get_end_iter() 313 buf.delete(inp, cur) 314 buf.insert(inp, command) 315 self.view.grab_focus() 316 317 def history_add(self, line): 318 if line.strip() != '': 319 self.history_pos = len(self.history) 320 self.history[self.history_pos - 1] = line 321 self.history.append('') 322 323 def history_up(self): 324 if self.history_pos > 0: 325 self.history[self.history_pos] = self.get_command_line() 326 self.history_pos = self.history_pos - 1 327 self.set_command_line(self.history[self.history_pos]) 328 329 def history_down(self): 330 if self.history_pos < len(self.history) - 1: 331 self.history[self.history_pos] = self.get_command_line() 332 self.history_pos = self.history_pos + 1 333 self.set_command_line(self.history[self.history_pos]) 334 335 def scroll_to_end(self): 336 i = self.view.get_buffer().get_end_iter() 337 self.view.scroll_to_iter(i, 0.0, False, 0.5, 0.5) 338 return False 339 340 def write(self, text, tag = None): 341 buf = self.view.get_buffer() 342 if tag is None: 343 buf.insert(buf.get_end_iter(), text) 344 else: 345 buf.insert_with_tags(buf.get_end_iter(), text, tag) 346 347 GLib.idle_add(self.scroll_to_end) 348 349 def eval(self, command, display_command = False): 350 buf = self.view.get_buffer() 351 lin = buf.get_mark("input-line") 352 buf.delete(buf.get_iter_at_mark(lin), 353 buf.get_end_iter()) 354 355 if isinstance(command, list) or isinstance(command, tuple): 356 for c in command: 357 if display_command: 358 self.write(">>> " + c + "\n", self.command) 359 self.__run(c) 360 else: 361 if display_command: 362 self.write(">>> " + c + "\n", self.command) 363 self.__run(command) 364 365 cur = buf.get_end_iter() 366 buf.move_mark_by_name("input-line", cur) 367 buf.insert(cur, ">>> ") 368 cur = buf.get_end_iter() 369 buf.move_mark_by_name("input", cur) 370 self.view.scroll_to_iter(buf.get_end_iter(), 0.0, False, 0.5, 0.5) 371 372 def __run(self, command): 373 sys.stdout, self.stdout = self.stdout, sys.stdout 374 sys.stderr, self.stderr = self.stderr, sys.stderr 375 376 try: 377 try: 378 r = eval(command, self.namespace, self.namespace) 379 if r is not None: 380 print(r) 381 except SyntaxError: 382 exec(command, self.namespace) 383 except: 384 if hasattr(sys, 'last_type') and sys.last_type == SystemExit: 385 self.destroy() 386 else: 387 traceback.print_exc() 388 389 sys.stdout, self.stdout = self.stdout, sys.stdout 390 sys.stderr, self.stderr = self.stderr, sys.stderr 391 392 def destroy(self): 393 pass 394 #gtk.ScrolledWindow.destroy(self) 395 396 397class OutFile: 398 """A fake output file object. It sends output to a TK test widget, 399 and if asked for a file number, returns one set on instance creation""" 400 def __init__(self, console, fn, tag): 401 self.fn = fn 402 self.console = console 403 self.tag = tag 404 def close(self): pass 405 def flush(self): pass 406 def fileno(self): return self.fn 407 def isatty(self): return 0 408 def read(self, a): return '' 409 def readline(self): return '' 410 def readlines(self): return [] 411 def write(self, s): self.console.write(s, self.tag) 412 def writelines(self, l): self.console.write(l, self.tag) 413 def seek(self, a): raise IOError((29, 'Illegal seek')) 414 def tell(self): raise IOError((29, 'Illegal seek')) 415 truncate = tell 416