1import sys 2import traceback 3import re 4 5from PyQt4.QtCore import QObject, Qt 6from PyQt4.QtGui import QTextCursor, qApp, QApplication, QPlainTextEdit 7 8 9from highlighter import PythonHighlighter, QtScriptHighlighter 10 11 12 13 14from PyQt4.QtScript import ( 15 QScriptEngine, QScriptValue, QScriptValueIterator) 16 17 18class OutputWidget(QPlainTextEdit): 19 20 21 def __init__(self, parent=None, readonly=True, max_rows=1000, echo=True): 22 QPlainTextEdit.__init__(self, parent) 23 self.echo = echo 24 self.setReadOnly(readonly) 25 self.document().setMaximumBlockCount(max_rows) 26 self.attach() 27 28 29 def attach(self): 30 sys.stdout = sys.stderr = self 31 32 33 def __del__(self): 34 self.detach() 35 36 37 def detach(self): 38 sys.stdout = sys.__stdout__ 39 sys.stderr = sys.__stderr__ 40 41 42 def write(self, s): 43 if self.echo: 44 sys.__stdout__.write(s) 45 doc = self.document() 46 cursor = QTextCursor(doc) 47 cursor.clearSelection() 48 cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) 49 cursor.insertText(s) 50 cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) 51 cursor.clearSelection() 52 self.ensureCursorVisible() 53 qApp.processEvents() 54 55 56 def writelines(self, lines): 57 self.write("\n".join(lines)) 58 59 60 61class ConsoleWidget(OutputWidget): 62 63 64 def __init__(self, parent=None, ps1="?", ps2=">"): 65 OutputWidget.__init__(self, parent, readonly=False) 66 self.setTabChangesFocus(False) 67 self.ps1 = ps1 68 self.ps2 = ps2 69 self.history_index = 0 70 self.history = [""] 71 self.tab_state = -1 72 print self.ps1, 73 74 75 def focusInEvent(self, event): 76 self.attach() 77 OutputWidget.focusInEvent(self, event) 78 79 80 def mousePressEvent(self, event): 81 self.setFocus() 82 83 84 def push(self, line): 85 return True 86 87 88 def keyPressEvent(self, event): 89 def remove_line(): 90 cursor = self.textCursor() 91 cursor.select(QTextCursor.BlockUnderCursor) 92 cursor.removeSelectedText() 93 key = event.key() 94 modifiers = event.modifiers() 95 l = len(self.ps1) 96 line = unicode(self.document().end().previous().text()) 97 ps1orps2, line = line[:l-1], line[l:] 98 99 100 if not key in [Qt.Key_Tab, Qt.Key_Backtab] and \ 101 len(event.text()): 102 self.tab_state = -1 103 if key == Qt.Key_Up: 104 if self.history_index + 1 < len(self.history): 105 self.history_index += 1 106 remove_line() 107 print 108 print ps1orps2, self.history[self.history_index], 109 elif key == Qt.Key_Down: 110 if self.history_index > 0: 111 self.history_index -= 1 112 remove_line() 113 print 114 print ps1orps2, self.history[self.history_index], 115 elif key == Qt.Key_Tab: 116 if modifiers & Qt.ControlModifier: 117 print " " * 4, 118 else: 119 self.tab_state += 1 120 remove_line() 121 print 122 print ps1orps2, 123 print self.completer.complete(line, self.tab_state) or line, 124 elif key == Qt.Key_Backtab: 125 if self.tab_state >= 0: 126 self.tab_state -= 1 127 remove_line() 128 print 129 print ps1orps2, 130 print self.completer.complete(line, self.tab_state) or line, 131 elif key in [Qt.Key_Backspace, Qt.Key_Left]: 132 if self.textCursor().columnNumber() > len(ps1orps2) + 1: 133 return OutputWidget.keyPressEvent(self, event) 134 elif key == Qt.Key_Return: 135 self.moveCursor(QTextCursor.EndOfLine, QTextCursor.MoveAnchor) 136 print 137 if self.push(line): 138 print self.ps2, 139 else: 140 print self.ps1, 141 if line and line != self.history[self.history_index]: 142 self.history.insert(1, line) 143 self.history_index = 0 144 else: 145 return OutputWidget.keyPressEvent(self, event) 146 147 148 149class PythonInterpreter(object): 150 151 152 def __init__(self, name="<pyqtshell>", locals=None): 153 self.name = name 154 self.locals = locals or {} 155 self.locals["__name__"] = self.name 156 self.lines = [] 157 158 159 def run(self, source, locals=None): 160 if locals == None: 161 locals = self.locals 162 code = compile(source, self.name, "exec") 163 try: 164 exec code in locals 165 except: 166 self.showtraceback() 167 try: 168 Scripter.activeWindow.redraw = True 169 Scripter.activeWindow.update() 170 except: pass 171 172 173 def push(self, line): 174 if self.lines: 175 if line: 176 self.lines.append(line) 177 return 1 # want more! 178 else: 179 line = "\n".join(self.lines) + "\n" 180 else: 181 if not line: 182 return 0 183 try: 184 code = compile(line, self.name, "single") 185 self.lines = [] 186 except SyntaxError, why: 187 if why[0] == "unexpected EOF while parsing": 188 self.lines.append(line) 189 return 1 # want more! 190 else: 191 self.showtraceback() 192 except: 193 self.showtraceback() 194 else: 195 try: 196 exec code in self.locals 197 except: 198 self.showtraceback() 199 try: 200 Scripter.activeWindow.redraw = True 201 Scripter.activeWindow.update() 202 except: pass 203 return 0 204 205 206 def showtraceback(self): 207 self.lines = [] 208 if sys.exc_type == SyntaxError: # and len(sys.exc_value) == 2: 209 print " File \"%s\", line %d" % (self.name, sys.exc_value[1][1]) 210 print " " * (sys.exc_value[1][2] + 2) + "^" 211 print str(sys.exc_type) + ":", sys.exc_value[0] 212 else: 213 traceback.print_tb(sys.exc_traceback, None) 214 print sys.exc_type.__name__ + ":", sys.exc_value 215 216 217 218 219class PythonCompleter(object): 220 221 222 def __init__(self, namespace): 223 self.namespace = namespace 224 225 226 def complete(self, text, state): 227 if state == 0: 228 if "." in text: 229 self.matches = self.attr_matches(text) 230 else: 231 self.matches = self.global_matches(text) 232 try: 233 return self.matches[state] 234 except IndexError: 235 return None 236 237 238 def global_matches(self, text): 239 import keyword, __builtin__ 240 matches = [] 241 n = len(text) 242 for list in [keyword.kwlist, 243 __builtin__.__dict__, 244 self.namespace]: 245 for word in list: 246 if word[:n] == text and word != "__builtins__": 247 matches.append(word) 248 return matches 249 250 251 def attr_matches(self, text): 252 def get_class_members(cls): 253 ret = dir(cls) 254 if hasattr(cls,'__bases__'): 255 for base in cls.__bases__: 256 ret = ret + get_class_members(base) 257 return ret 258 import re 259 m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) 260 if not m: 261 return 262 expr, attr = m.group(1, 3) 263 object = eval(expr, self.namespace) 264 words = dir(object) 265 if hasattr(object,'__class__'): 266 words.append('__class__') 267 words = words + get_class_members(object.__class__) 268 matches = [] 269 n = len(attr) 270 for word in words: 271 if word[:n] == attr and word != "__builtins__": 272 matches.append("%s.%s" % (expr, word)) 273 return matches 274 275 276 277 278 279 280 281class PythonConsole(ConsoleWidget): 282 283 284 def __init__(self, parent=None, namespace=None): 285 ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ") 286 self.highlighter = PythonHighlighter(self) 287 self.inter = PythonInterpreter(locals=namespace) 288 self.namespace = self.inter.locals 289 self.completer = PythonCompleter(self.namespace) 290 #print "Python", sys.version 291 #print "Autocomplete with (Shift+)Tab, insert spaces with Ctrl+Tab" 292 self.push("pass") 293 294 295 def push(self, line): 296 return self.inter.push(line) 297 298 299 def clear(self): 300 doc = self.document() 301 doc.setPlainText(self.ps1) 302 303 304 305 306class QtScriptInterpreter(object): 307 308 309 def __init__(self, locals): 310 self.locals = locals 311 self.engine = self.newEngine() 312 self.code = "" 313 self.state = 0 314 315 316 def newEngine(self): 317 engine = QScriptEngine() 318 ns = engine.globalObject() 319 for name, value in self.locals.items(): 320 if isinstance(value, QObject): 321 value = engine.newQObject(value) 322 elif callable(value): 323 value = engine.newFunction(value) 324 ns.setProperty(name, value) 325 return engine 326 327 328 def execute(self, code): 329 self.execute_code(code, self.engine) 330 331 332 def execute_code(self, code, engine=None): 333 engine = engine or self.newEngine() 334 result = engine.evaluate(code) 335 try: 336 Scripter.activeWindow.redraw = True 337 Scripter.activeWindow.update() 338 except: pass 339 if engine.hasUncaughtException(): 340 bt = engine.uncaughtExceptionBacktrace() 341 print "Traceback:" 342 print "\n".join([" %s" % l for l in list(bt)]) 343 print engine.uncaughtException().toString() 344 else: 345 if not result.isUndefined(): 346 print result.toString() 347 348 349 def push(self, line): 350 if not line.strip(): 351 return self.state 352 self.code = self.code + line + "\n" 353 if self.engine.canEvaluate(self.code): 354 self.execute(self.code) 355 self.code = "" 356 self.state = 0 357 else: 358 self.state = 1 359 return self.state 360 361 362js_words = [ 363 'break', 364 'for', 365 'throw', 366 'case', 367 'function', 368 'try', 369 'catch', 370 'if', 371 'typeof', 372 'continue', 373 'in', 374 'var', 375 'default', 376 'instanceof', 377 'void', 378 'delete', 379 'new', 380 'undefined', 381 'do', 382 'return', 383 'while', 384 'else', 385 'switch', 386 'with', 387 'finally', 388 'this', 389 'NaN', 390 'Infinity', 391 'undefined', 392 'print', 393 'parseInt', 394 'parseFloat', 395 'isNaN', 396 'isFinite', 397 'decodeURI', 398 'decodeURIComponent', 399 'encodeURI', 400 'encodeURIComponent', 401 'escape', 402 'unescape', 403 'version', 404 'gc', 405 'Object', 406 'Function', 407 'Number', 408 'Boolean', 409 'String', 410 'Date', 411 'Array', 412 'RegExp', 413 'Error', 414 'EvalError', 415 'RangeError', 416 'ReferenceError', 417 'SyntaxError', 418 'TypeError', 419 'URIError', 420 'eval', 421 'Math', 422 'Enumeration', 423 'Variant', 424 'QObject', 425 'QMetaObject'] 426 427 428 429class QtScriptCompleter(object): 430 431 432 def __init__(self, engine): 433 self.engine = engine 434 435 436 def complete(self, text, state): 437 if state == 0: 438 if "." in text: 439 self.matches = self.attr_matches(text) 440 else: 441 self.matches = self.global_matches(text) 442 try: 443 return self.matches[state] 444 except IndexError: 445 return None 446 447 448 449 def attr_matches(self, text): 450 return [] 451 452 453 454 def iter_obj(self, obj): 455 it = QScriptValueIterator(self.engine.globalObject()) 456 while it.hasNext(): 457 yield str(it.name()) 458 it.next() 459 460 461 def global_matches(self, text): 462 words = list(self.iter_obj(self.engine.globalObject())) 463 words.extend(js_words) 464 l = [] 465 n = len(text) 466 for w in words: 467 if w[:n] == text: 468 l.append(w) 469 return l 470 471 472 473 474 475class QtScriptConsole(ConsoleWidget): 476 477 478 def __init__(self, parent=None, namespace=None): 479 ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ") 480 self.highlighter = QtScriptHighlighter(self) 481 namespace = namespace or {} 482 def console_print(context, engine): 483 for i in range(context.argumentCount()): 484 print context.argument(i).toString(), 485 print 486 return QScriptValue() 487 def dir_context(context, engine): 488 if context.argumentCount() == 0: 489 obj = context.thisObject() 490 else: 491 obj = context.argument(0) 492 l = [] 493 it = QScriptValueIterator(obj) 494 while it.hasNext(): 495 it.next() 496 l.append(str(it.name())) 497 return QScriptValue(engine, repr(l)) 498 namespace["print"] = console_print 499 namespace["dir"] = dir_context 500 namespace["Application"] = qApp 501 try: 502 namespace["Scripter"] = Scripter.qt 503 except: pass 504 self.inter = QtScriptInterpreter(namespace) 505 self.completer = QtScriptCompleter(self.inter.engine) 506 507 508 509 def push(self, line): 510 return self.inter.push(line) 511 512 513 514if __name__ == "__main__": 515 app = QApplication(sys.argv) 516 o = QtScriptConsole() 517 #o = PythonConsole() 518 o.resize(640,480) 519 o.attach() 520 o.show() 521 app.exec_() 522