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