PrEditor 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- preditor/__init__.py +315 -0
- preditor/__main__.py +13 -0
- preditor/about_module.py +165 -0
- preditor/cli.py +192 -0
- preditor/config.py +318 -0
- preditor/constants.py +13 -0
- preditor/contexts.py +210 -0
- preditor/cores/__init__.py +0 -0
- preditor/cores/core.py +20 -0
- preditor/dccs/.hab.json +10 -0
- preditor/dccs/maya/PrEditor_maya.mod +1 -0
- preditor/dccs/maya/README.md +22 -0
- preditor/dccs/maya/plug-ins/PrEditor_maya.py +141 -0
- preditor/dccs/studiomax/PackageContents.xml +32 -0
- preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr +8 -0
- preditor/dccs/studiomax/README.md +17 -0
- preditor/dccs/studiomax/preditor.ms +16 -0
- preditor/dccs/studiomax/preditor_menu.mnx +7 -0
- preditor/debug.py +149 -0
- preditor/delayable_engine/__init__.py +302 -0
- preditor/delayable_engine/delayables.py +85 -0
- preditor/enum.py +728 -0
- preditor/excepthooks.py +165 -0
- preditor/gui/__init__.py +56 -0
- preditor/gui/app.py +163 -0
- preditor/gui/codehighlighter.py +289 -0
- preditor/gui/completer.py +237 -0
- preditor/gui/console.py +605 -0
- preditor/gui/console_base.py +911 -0
- preditor/gui/dialog.py +181 -0
- preditor/gui/drag_tab_bar.py +625 -0
- preditor/gui/editor_chooser.py +57 -0
- preditor/gui/errordialog.py +69 -0
- preditor/gui/find_files.py +137 -0
- preditor/gui/fuzzy_search/__init__.py +0 -0
- preditor/gui/fuzzy_search/fuzzy_search.py +97 -0
- preditor/gui/group_tab_widget/__init__.py +0 -0
- preditor/gui/group_tab_widget/group_tab_widget.py +528 -0
- preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
- preditor/gui/group_tab_widget/grouped_tab_models.py +107 -0
- preditor/gui/group_tab_widget/grouped_tab_widget.py +223 -0
- preditor/gui/group_tab_widget/one_tab_widget.py +96 -0
- preditor/gui/level_buttons.py +358 -0
- preditor/gui/logger_window_handler.py +77 -0
- preditor/gui/logger_window_plugin.py +35 -0
- preditor/gui/loggerwindow.py +2405 -0
- preditor/gui/newtabwidget.py +69 -0
- preditor/gui/output_console.py +11 -0
- preditor/gui/qtdesigner/__init__.py +21 -0
- preditor/gui/qtdesigner/_log_plugin.py +29 -0
- preditor/gui/qtdesigner/console_base_plugin.py +48 -0
- preditor/gui/qtdesigner/console_predit_plugin.py +48 -0
- preditor/gui/set_text_editor_path_dialog.py +61 -0
- preditor/gui/status_label.py +99 -0
- preditor/gui/suggest_path_quotes_dialog.py +50 -0
- preditor/gui/ui/editor_chooser.ui +93 -0
- preditor/gui/ui/errordialog.ui +74 -0
- preditor/gui/ui/find_files.ui +140 -0
- preditor/gui/ui/loggerwindow.ui +1909 -0
- preditor/gui/ui/set_text_editor_path_dialog.ui +189 -0
- preditor/gui/ui/suggest_path_quotes_dialog.ui +225 -0
- preditor/gui/window.py +161 -0
- preditor/gui/workbox_mixin.py +1139 -0
- preditor/gui/workbox_text_edit.py +136 -0
- preditor/gui/workboxwidget.py +315 -0
- preditor/logging_config.py +55 -0
- preditor/osystem.py +401 -0
- preditor/plugins.py +118 -0
- preditor/prefs.py +381 -0
- preditor/resource/environment_variables.html +26 -0
- preditor/resource/error_mail.html +85 -0
- preditor/resource/error_mail_inline.html +41 -0
- preditor/resource/img/README.md +17 -0
- preditor/resource/img/arrow_forward.png +0 -0
- preditor/resource/img/check-bold.png +0 -0
- preditor/resource/img/chevron-down.png +0 -0
- preditor/resource/img/chevron-up.png +0 -0
- preditor/resource/img/close-thick.png +0 -0
- preditor/resource/img/comment-edit.png +0 -0
- preditor/resource/img/content-copy.png +0 -0
- preditor/resource/img/content-cut.png +0 -0
- preditor/resource/img/content-duplicate.png +0 -0
- preditor/resource/img/content-paste.png +0 -0
- preditor/resource/img/content-save.png +0 -0
- preditor/resource/img/debug_disabled.png +0 -0
- preditor/resource/img/eye-check.png +0 -0
- preditor/resource/img/file-plus.png +0 -0
- preditor/resource/img/file-remove.png +0 -0
- preditor/resource/img/format-align-left.png +0 -0
- preditor/resource/img/format-letter-case-lower.png +0 -0
- preditor/resource/img/format-letter-case-upper.png +0 -0
- preditor/resource/img/format-letter-case.svg +1 -0
- preditor/resource/img/information.png +0 -0
- preditor/resource/img/logging_critical.png +0 -0
- preditor/resource/img/logging_custom.png +0 -0
- preditor/resource/img/logging_debug.png +0 -0
- preditor/resource/img/logging_error.png +0 -0
- preditor/resource/img/logging_info.png +0 -0
- preditor/resource/img/logging_not_set.png +0 -0
- preditor/resource/img/logging_warning.png +0 -0
- preditor/resource/img/marker.png +0 -0
- preditor/resource/img/play.png +0 -0
- preditor/resource/img/playlist-play.png +0 -0
- preditor/resource/img/plus-minus-variant.png +0 -0
- preditor/resource/img/preditor.ico +0 -0
- preditor/resource/img/preditor.png +0 -0
- preditor/resource/img/preditor.psd +0 -0
- preditor/resource/img/preditor.svg +44 -0
- preditor/resource/img/regex.svg +1 -0
- preditor/resource/img/restart.svg +1 -0
- preditor/resource/img/skip-forward-outline.png +0 -0
- preditor/resource/img/skip-next-outline.png +0 -0
- preditor/resource/img/skip-next.png +0 -0
- preditor/resource/img/skip-previous.png +0 -0
- preditor/resource/img/subdirectory-arrow-right.png +0 -0
- preditor/resource/img/text-search-variant.png +0 -0
- preditor/resource/img/warning-big.png +0 -0
- preditor/resource/lang/python.json +30 -0
- preditor/resource/pref_updates/pref_updates.json +17 -0
- preditor/resource/settings.ini +25 -0
- preditor/resource/stylesheet/Bright.css +76 -0
- preditor/resource/stylesheet/Dark.css +210 -0
- preditor/scintilla/__init__.py +40 -0
- preditor/scintilla/delayables/__init__.py +11 -0
- preditor/scintilla/delayables/smart_highlight.py +97 -0
- preditor/scintilla/delayables/spell_check.py +174 -0
- preditor/scintilla/documenteditor.py +1924 -0
- preditor/scintilla/finddialog.py +68 -0
- preditor/scintilla/lang/__init__.py +80 -0
- preditor/scintilla/lang/config/bash.ini +15 -0
- preditor/scintilla/lang/config/batch.ini +14 -0
- preditor/scintilla/lang/config/cpp.ini +19 -0
- preditor/scintilla/lang/config/css.ini +19 -0
- preditor/scintilla/lang/config/eyeonscript.ini +17 -0
- preditor/scintilla/lang/config/html.ini +21 -0
- preditor/scintilla/lang/config/javascript.ini +24 -0
- preditor/scintilla/lang/config/lua.ini +16 -0
- preditor/scintilla/lang/config/maxscript.ini +20 -0
- preditor/scintilla/lang/config/mel.ini +18 -0
- preditor/scintilla/lang/config/mu.ini +22 -0
- preditor/scintilla/lang/config/nsi.ini +19 -0
- preditor/scintilla/lang/config/perl.ini +19 -0
- preditor/scintilla/lang/config/puppet.ini +19 -0
- preditor/scintilla/lang/config/python.ini +28 -0
- preditor/scintilla/lang/config/ruby.ini +19 -0
- preditor/scintilla/lang/config/sql.ini +7 -0
- preditor/scintilla/lang/config/xml.ini +21 -0
- preditor/scintilla/lang/config/yaml.ini +18 -0
- preditor/scintilla/lang/language.py +240 -0
- preditor/scintilla/lexers/__init__.py +0 -0
- preditor/scintilla/lexers/cpplexer.py +22 -0
- preditor/scintilla/lexers/javascriptlexer.py +27 -0
- preditor/scintilla/lexers/maxscriptlexer.py +235 -0
- preditor/scintilla/lexers/mellexer.py +369 -0
- preditor/scintilla/lexers/mulexer.py +33 -0
- preditor/scintilla/lexers/pythonlexer.py +42 -0
- preditor/scintilla/ui/finddialog.ui +160 -0
- preditor/settings.py +71 -0
- preditor/stream/__init__.py +72 -0
- preditor/stream/console_handler.py +169 -0
- preditor/stream/director.py +144 -0
- preditor/stream/manager.py +97 -0
- preditor/streamhandler_helper.py +46 -0
- preditor/utils/__init__.py +191 -0
- preditor/utils/call_stack.py +86 -0
- preditor/utils/cute.py +106 -0
- preditor/utils/stylesheets.py +54 -0
- preditor/utils/text_search.py +338 -0
- preditor/version.py +34 -0
- preditor/weakref.py +363 -0
- preditor-2.1.0.dist-info/METADATA +308 -0
- preditor-2.1.0.dist-info/RECORD +179 -0
- preditor-2.1.0.dist-info/WHEEL +5 -0
- preditor-2.1.0.dist-info/entry_points.txt +19 -0
- preditor-2.1.0.dist-info/licenses/LICENSE +165 -0
- preditor-2.1.0.dist-info/top_level.txt +3 -0
- tests/encodings/test_ecoding.py +33 -0
- tests/find_files/test_find_files.py +74 -0
- tests/ide/test_delayable_engine.py +171 -0
preditor/gui/console.py
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import string
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import traceback
|
|
8
|
+
from builtins import str as text
|
|
9
|
+
from functools import partial
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import __main__
|
|
13
|
+
from Qt.QtCore import QPoint, Qt, QTimer
|
|
14
|
+
from Qt.QtGui import QKeySequence, QTextCursor, QTextDocument
|
|
15
|
+
from Qt.QtWidgets import QAbstractItemView, QAction, QApplication, QWidget
|
|
16
|
+
|
|
17
|
+
from .. import settings
|
|
18
|
+
from ..constants import StreamType
|
|
19
|
+
from ..utils import Truncate
|
|
20
|
+
from ..utils.cute import QtPropertyInit
|
|
21
|
+
from .completer import PythonCompleter
|
|
22
|
+
from .console_base import ConsoleBase
|
|
23
|
+
from .loggerwindow import LoggerWindow
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ConsolePrEdit(ConsoleBase):
|
|
27
|
+
# Ensure the error prompt only shows up once.
|
|
28
|
+
_errorPrompted = False
|
|
29
|
+
|
|
30
|
+
_consolePrompt = '>>> '
|
|
31
|
+
|
|
32
|
+
# Note: Changing _outputPrompt may require updating resource\lang\python.xml
|
|
33
|
+
# If still using a #
|
|
34
|
+
_outputPrompt = '#Result: '
|
|
35
|
+
|
|
36
|
+
def __init__(self, parent: QWidget, controller: Optional[LoggerWindow] = None):
|
|
37
|
+
super(ConsolePrEdit, self).__init__(parent, controller=controller)
|
|
38
|
+
|
|
39
|
+
# Method used to update the gui when code is executed
|
|
40
|
+
self.clearExecutionTime = None
|
|
41
|
+
self.reportExecutionTime = None
|
|
42
|
+
|
|
43
|
+
# store the error buffer
|
|
44
|
+
self._completer = None
|
|
45
|
+
|
|
46
|
+
# When executing code, that takes longer than this seconds, flash the window
|
|
47
|
+
self.flash_window = None
|
|
48
|
+
|
|
49
|
+
# Store previous commands to retrieve easily
|
|
50
|
+
self._prevCommands = []
|
|
51
|
+
self._prevCommandIndex = 0
|
|
52
|
+
self._prevCommandsMax = 100
|
|
53
|
+
|
|
54
|
+
# create the completer
|
|
55
|
+
self.setCompleter(PythonCompleter(self))
|
|
56
|
+
|
|
57
|
+
self.uiClearToLastPromptACT = QAction('Clear to Last', self)
|
|
58
|
+
self.uiClearToLastPromptACT.triggered.connect(self.clearToLastPrompt)
|
|
59
|
+
self.uiClearToLastPromptACT.setShortcut(
|
|
60
|
+
QKeySequence(Qt.Modifier.CTRL | Qt.Modifier.SHIFT | Qt.Key.Key_Backspace)
|
|
61
|
+
)
|
|
62
|
+
self.addAction(self.uiClearToLastPromptACT)
|
|
63
|
+
|
|
64
|
+
# Make sure console cursor is visible. It can get it's width set to 0 with
|
|
65
|
+
# unusual(ie not 100%) os display scaling.
|
|
66
|
+
if not self.cursorWidth():
|
|
67
|
+
self.setCursorWidth(1)
|
|
68
|
+
|
|
69
|
+
# The act of changing from no scroll bar to a scroll bar can add up to 1
|
|
70
|
+
# second of time to the process of outputting text, so, just always have
|
|
71
|
+
# it on.
|
|
72
|
+
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
|
73
|
+
|
|
74
|
+
def doubleSingleShotSetScrollValue(self, origPercent):
|
|
75
|
+
"""This double QTimer.singleShot monkey business seems to be the only way
|
|
76
|
+
to get scroll.maximum() to update properly so that we calc newValue
|
|
77
|
+
correctly. It's quite silly. Apparently, the important part is that
|
|
78
|
+
calling scroll.maximum() has had a pause since the font had been set.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def singleShotSetScrollValue(self, origPercent):
|
|
82
|
+
scroll = self.verticalScrollBar()
|
|
83
|
+
maximum = scroll.maximum()
|
|
84
|
+
if maximum is not None:
|
|
85
|
+
newValue = round(origPercent * maximum)
|
|
86
|
+
QTimer.singleShot(1, partial(scroll.setValue, newValue))
|
|
87
|
+
|
|
88
|
+
# The 100 ms timer amount is somewhat arbitrary. It must be more than
|
|
89
|
+
# some value to work, but what that value is is unknown, and may change
|
|
90
|
+
# under various circumstances. Briefly disable updates for smoother transition.
|
|
91
|
+
self.setUpdatesEnabled(False)
|
|
92
|
+
try:
|
|
93
|
+
QTimer.singleShot(100, partial(singleShotSetScrollValue, self, origPercent))
|
|
94
|
+
finally:
|
|
95
|
+
self.setUpdatesEnabled(True)
|
|
96
|
+
|
|
97
|
+
def keyReleaseEvent(self, event):
|
|
98
|
+
"""Override of keyReleaseEvent to determine when to end navigation of
|
|
99
|
+
previous commands
|
|
100
|
+
"""
|
|
101
|
+
if event.key() == Qt.Key.Key_Alt:
|
|
102
|
+
self._prevCommandIndex = 0
|
|
103
|
+
else:
|
|
104
|
+
event.ignore()
|
|
105
|
+
|
|
106
|
+
def getPrevCommand(self):
|
|
107
|
+
"""Find and display the previous command in stack"""
|
|
108
|
+
self._prevCommandIndex -= 1
|
|
109
|
+
|
|
110
|
+
if abs(self._prevCommandIndex) > len(self._prevCommands):
|
|
111
|
+
self._prevCommandIndex += 1
|
|
112
|
+
|
|
113
|
+
if self._prevCommands:
|
|
114
|
+
self.setCommand()
|
|
115
|
+
|
|
116
|
+
def getNextCommand(self):
|
|
117
|
+
"""Find and display the next command in stack"""
|
|
118
|
+
self._prevCommandIndex += 1
|
|
119
|
+
self._prevCommandIndex = min(self._prevCommandIndex, 0)
|
|
120
|
+
|
|
121
|
+
if self._prevCommands:
|
|
122
|
+
self.setCommand()
|
|
123
|
+
|
|
124
|
+
def setCommand(self):
|
|
125
|
+
"""Do the displaying of currently chosen command"""
|
|
126
|
+
prevCommand = ''
|
|
127
|
+
if self._prevCommandIndex:
|
|
128
|
+
prevCommand = self._prevCommands[self._prevCommandIndex]
|
|
129
|
+
|
|
130
|
+
cursor = self.textCursor()
|
|
131
|
+
cursor.select(QTextCursor.SelectionType.LineUnderCursor)
|
|
132
|
+
if cursor.selectedText().startswith(self._consolePrompt):
|
|
133
|
+
prevCommand = "{}{}".format(self._consolePrompt, prevCommand)
|
|
134
|
+
cursor.insertText(prevCommand)
|
|
135
|
+
self.setTextCursor(cursor)
|
|
136
|
+
|
|
137
|
+
def clear(self):
|
|
138
|
+
"""clears the text in the editor"""
|
|
139
|
+
super(ConsoleBase, self).clear()
|
|
140
|
+
self.startInputLine()
|
|
141
|
+
# Note: Don't use the regular `super()` call here as it would result
|
|
142
|
+
# in multiple calls to repaint, just call the base Qt class's clear.
|
|
143
|
+
self.maybeRepaint(force=True)
|
|
144
|
+
|
|
145
|
+
def clearToLastPrompt(self):
|
|
146
|
+
# store the current cursor position so we can restore when we are done
|
|
147
|
+
currentCursor = self.textCursor()
|
|
148
|
+
# move to the end of the document so we can search backwards
|
|
149
|
+
cursor = self.textCursor()
|
|
150
|
+
cursor.movePosition(QTextCursor.MoveOperation.End)
|
|
151
|
+
self.setTextCursor(cursor)
|
|
152
|
+
# Check if the last line is a empty prompt. If so, then preform two finds so we
|
|
153
|
+
# find the prompt we are looking for instead of this empty prompt
|
|
154
|
+
findCount = (
|
|
155
|
+
2 if self.toPlainText()[-len(self.prompt()) :] == self.prompt() else 1
|
|
156
|
+
)
|
|
157
|
+
for _ in range(findCount):
|
|
158
|
+
self.find(self.prompt(), QTextDocument.FindFlag.FindBackward)
|
|
159
|
+
# move to the end of the found line, select the rest of the text and remove it
|
|
160
|
+
# preserving history if there is anything to remove.
|
|
161
|
+
cursor = self.textCursor()
|
|
162
|
+
cursor.movePosition(QTextCursor.MoveOperation.EndOfLine)
|
|
163
|
+
cursor.movePosition(
|
|
164
|
+
QTextCursor.MoveOperation.End, QTextCursor.MoveMode.KeepAnchor
|
|
165
|
+
)
|
|
166
|
+
txt = cursor.selectedText()
|
|
167
|
+
if txt:
|
|
168
|
+
self.setTextCursor(cursor)
|
|
169
|
+
self.insertPlainText('')
|
|
170
|
+
# Restore the cursor position to its original location
|
|
171
|
+
self.setTextCursor(currentCursor)
|
|
172
|
+
|
|
173
|
+
def completer(self):
|
|
174
|
+
"""returns the completer instance that is associated with this editor"""
|
|
175
|
+
return self._completer
|
|
176
|
+
|
|
177
|
+
def executeString(
|
|
178
|
+
self,
|
|
179
|
+
commandText,
|
|
180
|
+
consoleLine=None,
|
|
181
|
+
filename='<ConsolePrEdit>',
|
|
182
|
+
extraPrint=True,
|
|
183
|
+
echoResult=False,
|
|
184
|
+
truncate=False,
|
|
185
|
+
):
|
|
186
|
+
# These vars helps with faking code lines in tracebacks for stdin input, which
|
|
187
|
+
# workboxes are, and py3 doesn't include in the traceback
|
|
188
|
+
self.consoleLine = consoleLine or ""
|
|
189
|
+
|
|
190
|
+
if self.clearExecutionTime is not None:
|
|
191
|
+
self.clearExecutionTime()
|
|
192
|
+
cursor = self.textCursor()
|
|
193
|
+
cursor.select(QTextCursor.SelectionType.BlockUnderCursor)
|
|
194
|
+
line = cursor.selectedText()
|
|
195
|
+
if line and line[0] not in string.printable:
|
|
196
|
+
line = line[1:]
|
|
197
|
+
|
|
198
|
+
if line.startswith(self.prompt()) and extraPrint:
|
|
199
|
+
self.write("\n", stream_type=StreamType.RESULT)
|
|
200
|
+
|
|
201
|
+
cmdresult = None
|
|
202
|
+
# https://stackoverflow.com/a/29456463
|
|
203
|
+
# If you want to get the result of the code, you have to call eval
|
|
204
|
+
# however eval does not accept multiple statements. For that you need
|
|
205
|
+
# exec which has no Return.
|
|
206
|
+
wasEval = False
|
|
207
|
+
startTime = time.time()
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
compiled = compile(commandText, filename, 'eval')
|
|
211
|
+
wasEval = True
|
|
212
|
+
except Exception:
|
|
213
|
+
compiled = compile(commandText, filename, 'exec')
|
|
214
|
+
|
|
215
|
+
# We wrap in try / finally so that elapsed time gets updated, even when an
|
|
216
|
+
# exception is raised.
|
|
217
|
+
try:
|
|
218
|
+
if wasEval:
|
|
219
|
+
cmdresult = eval(compiled, __main__.__dict__, __main__.__dict__)
|
|
220
|
+
else:
|
|
221
|
+
exec(compiled, __main__.__dict__, __main__.__dict__)
|
|
222
|
+
finally:
|
|
223
|
+
# Report the total time it took to execute this code.
|
|
224
|
+
if self.reportExecutionTime is not None:
|
|
225
|
+
delta = time.time() - startTime
|
|
226
|
+
self.reportExecutionTime((delta, commandText))
|
|
227
|
+
|
|
228
|
+
# Provide user feedback when running long code execution.
|
|
229
|
+
if self.controller:
|
|
230
|
+
flash_time = self.controller.uiFlashTimeSPIN.value()
|
|
231
|
+
if self.flash_window and flash_time and delta >= flash_time:
|
|
232
|
+
if settings.OS_TYPE == "Windows":
|
|
233
|
+
try:
|
|
234
|
+
from casement import utils
|
|
235
|
+
except ImportError:
|
|
236
|
+
# If casement is not installed, flash window is disabled
|
|
237
|
+
pass
|
|
238
|
+
else:
|
|
239
|
+
hwnd = int(self.flash_window.winId())
|
|
240
|
+
utils.flash_window(hwnd)
|
|
241
|
+
|
|
242
|
+
if echoResult and wasEval:
|
|
243
|
+
# If the selected code was a statement print the result of the statement.
|
|
244
|
+
ret = repr(cmdresult)
|
|
245
|
+
self.startOutputLine()
|
|
246
|
+
if truncate:
|
|
247
|
+
self.write(
|
|
248
|
+
Truncate(ret).middle(100) + "\n", stream_type=StreamType.RESULT
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
self.write(ret + "\n", stream_type=StreamType.RESULT)
|
|
252
|
+
|
|
253
|
+
return cmdresult, wasEval
|
|
254
|
+
|
|
255
|
+
def executeCommand(self):
|
|
256
|
+
"""executes the current line of code"""
|
|
257
|
+
|
|
258
|
+
# Not using workbox, so clear this
|
|
259
|
+
self.consoleLine = ""
|
|
260
|
+
|
|
261
|
+
# grab the command from the line
|
|
262
|
+
block = self.textCursor().block().text()
|
|
263
|
+
p = '{prompt}(.*)'.format(prompt=re.escape(self.prompt()))
|
|
264
|
+
results = re.search(p, block)
|
|
265
|
+
if results:
|
|
266
|
+
commandText = results.groups()[0]
|
|
267
|
+
# if the cursor position is at the end of the line
|
|
268
|
+
if self.textCursor().atEnd():
|
|
269
|
+
# insert a new line
|
|
270
|
+
self.insertPlainText('\n')
|
|
271
|
+
|
|
272
|
+
# update prevCommands list, but only if commandText is not the most
|
|
273
|
+
# recent prevCommand, or there are no previous commands
|
|
274
|
+
hasText = len(commandText) > 0
|
|
275
|
+
prevCmds = self._prevCommands
|
|
276
|
+
notPrevCmd = not prevCmds or prevCmds[-1] != commandText
|
|
277
|
+
if hasText and notPrevCmd:
|
|
278
|
+
self._prevCommands.append(commandText)
|
|
279
|
+
# limit length of prevCommand list to max number of prev commands
|
|
280
|
+
self._prevCommands = self._prevCommands[-1 * self._prevCommandsMax :]
|
|
281
|
+
|
|
282
|
+
# evaluate the command
|
|
283
|
+
cmdresult, wasEval = self.executeString(
|
|
284
|
+
commandText, consoleLine=commandText
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# print the resulting commands
|
|
288
|
+
if cmdresult is not None:
|
|
289
|
+
# When writing to additional stdout's not including a new line
|
|
290
|
+
# makes the output not match the formatting you get inside the
|
|
291
|
+
# console.
|
|
292
|
+
self.write(u'{}\n'.format(cmdresult))
|
|
293
|
+
# NOTE: I am using u'' above so unicode strings in python 2
|
|
294
|
+
# don't get converted to str objects.
|
|
295
|
+
|
|
296
|
+
self.startInputLine()
|
|
297
|
+
|
|
298
|
+
# otherwise, move the command to the end of the line
|
|
299
|
+
else:
|
|
300
|
+
self.startInputLine()
|
|
301
|
+
self.insertPlainText(commandText)
|
|
302
|
+
|
|
303
|
+
# if no command, then start a new line
|
|
304
|
+
else:
|
|
305
|
+
self.startInputLine()
|
|
306
|
+
|
|
307
|
+
def flush(self):
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
def focusInEvent(self, event):
|
|
311
|
+
"""overload the focus in event to ensure the completer has the proper widget"""
|
|
312
|
+
if self.completer():
|
|
313
|
+
self.completer().setWidget(self)
|
|
314
|
+
super().focusInEvent(event)
|
|
315
|
+
|
|
316
|
+
def insertCompletion(self, completion):
|
|
317
|
+
"""inserts the completion text into the editor"""
|
|
318
|
+
if self.completer().widget() == self:
|
|
319
|
+
cursor = self.textCursor()
|
|
320
|
+
cursor.select(QTextCursor.SelectionType.WordUnderCursor)
|
|
321
|
+
cursor.insertText(completion)
|
|
322
|
+
self.setTextCursor(cursor)
|
|
323
|
+
|
|
324
|
+
def insertFromMimeData(self, mimeData):
|
|
325
|
+
html = False
|
|
326
|
+
if mimeData.hasHtml():
|
|
327
|
+
txt = mimeData.html()
|
|
328
|
+
html = True
|
|
329
|
+
else:
|
|
330
|
+
txt = mimeData.text()
|
|
331
|
+
|
|
332
|
+
doc = QTextDocument()
|
|
333
|
+
|
|
334
|
+
if html:
|
|
335
|
+
doc.setHtml(txt)
|
|
336
|
+
else:
|
|
337
|
+
doc.setPlainText(txt)
|
|
338
|
+
|
|
339
|
+
txt = doc.toPlainText()
|
|
340
|
+
|
|
341
|
+
exp = re.compile(
|
|
342
|
+
(
|
|
343
|
+
r'[^A-Za-z0-9\~\!\@\#\$\%\^\&\*\(\)\_\+\{\}\|\:'
|
|
344
|
+
r'\"\<\>\?\`\-\=\[\]\\\;\'\,\.\/ \t\n]'
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
newText = text(txt)
|
|
348
|
+
for each in exp.findall(newText):
|
|
349
|
+
newText = newText.replace(each, '?')
|
|
350
|
+
|
|
351
|
+
self.insertPlainText(newText)
|
|
352
|
+
|
|
353
|
+
def isatty(self):
|
|
354
|
+
"""Return True if the stream is interactive (i.e., connected to a terminal/tty
|
|
355
|
+
device).
|
|
356
|
+
"""
|
|
357
|
+
# This method is required for pytest to run in a DCC. Returns False so the
|
|
358
|
+
# output does not contain cursor control characters that disrupt the visual
|
|
359
|
+
# display of the output.
|
|
360
|
+
return False
|
|
361
|
+
|
|
362
|
+
def lastError(self):
|
|
363
|
+
try:
|
|
364
|
+
return ''.join(
|
|
365
|
+
traceback.format_exception(
|
|
366
|
+
sys.last_type, sys.last_value, sys.last_traceback
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
except AttributeError:
|
|
370
|
+
# last_traceback, last_type and last_value do not always exist
|
|
371
|
+
return ''
|
|
372
|
+
|
|
373
|
+
def keyPressEvent(self, event):
|
|
374
|
+
"""overload the key press event to handle custom events"""
|
|
375
|
+
|
|
376
|
+
completer = self.completer()
|
|
377
|
+
|
|
378
|
+
# Define prefix so we can determine if the exact prefix is in
|
|
379
|
+
# completions and highlight it. We must manually add the currently typed
|
|
380
|
+
# character, or remove it if backspace or delete has just been pressed.
|
|
381
|
+
key = event.text()
|
|
382
|
+
_, prefix = completer.currentObject(scope=__main__.__dict__)
|
|
383
|
+
isBackspaceOrDel = event.key() in (Qt.Key.Key_Backspace, Qt.Key.Key_Delete)
|
|
384
|
+
if key.isalnum() or key in ("-", "_"):
|
|
385
|
+
prefix += str(key)
|
|
386
|
+
elif isBackspaceOrDel and prefix:
|
|
387
|
+
prefix = prefix[:-1]
|
|
388
|
+
|
|
389
|
+
if completer and event.key() in (
|
|
390
|
+
Qt.Key.Key_Backspace,
|
|
391
|
+
Qt.Key.Key_Delete,
|
|
392
|
+
Qt.Key.Key_Escape,
|
|
393
|
+
):
|
|
394
|
+
completer.hideDocumentation()
|
|
395
|
+
|
|
396
|
+
# enter || return keys will execute the command
|
|
397
|
+
if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
|
398
|
+
if completer.popup().isVisible():
|
|
399
|
+
completer.clear()
|
|
400
|
+
event.ignore()
|
|
401
|
+
else:
|
|
402
|
+
self.executeCommand()
|
|
403
|
+
|
|
404
|
+
# home key will move the cursor to home
|
|
405
|
+
elif event.key() == Qt.Key.Key_Home:
|
|
406
|
+
self.moveToHome()
|
|
407
|
+
|
|
408
|
+
# otherwise, ignore the event for completion events
|
|
409
|
+
elif event.key() in (Qt.Key.Key_Tab, Qt.Key.Key_Backtab):
|
|
410
|
+
if not completer.popup().isVisible():
|
|
411
|
+
# The completer does not get updated if its not visible while typing.
|
|
412
|
+
# We are about to complete the text using it so ensure its updated.
|
|
413
|
+
completer.refreshList(scope=__main__.__dict__)
|
|
414
|
+
completer.popup().setCurrentIndex(
|
|
415
|
+
completer.completionModel().index(0, 0)
|
|
416
|
+
)
|
|
417
|
+
# Insert the correct text and clear the completion model
|
|
418
|
+
index = completer.popup().currentIndex()
|
|
419
|
+
self.insertCompletion(index.data(Qt.ItemDataRole.DisplayRole))
|
|
420
|
+
completer.clear()
|
|
421
|
+
|
|
422
|
+
elif event.key() == Qt.Key.Key_Escape and completer.popup().isVisible():
|
|
423
|
+
completer.clear()
|
|
424
|
+
|
|
425
|
+
# other wise handle the keypress
|
|
426
|
+
else:
|
|
427
|
+
# define special key sequences
|
|
428
|
+
modifiers = QApplication.instance().keyboardModifiers()
|
|
429
|
+
ctrlSpace = (
|
|
430
|
+
event.key() == Qt.Key.Key_Space
|
|
431
|
+
and modifiers == Qt.KeyboardModifier.ControlModifier
|
|
432
|
+
)
|
|
433
|
+
ctrlM = (
|
|
434
|
+
event.key() == Qt.Key.Key_M
|
|
435
|
+
and modifiers == Qt.KeyboardModifier.ControlModifier
|
|
436
|
+
)
|
|
437
|
+
ctrlI = (
|
|
438
|
+
event.key() == Qt.Key.Key_I
|
|
439
|
+
and modifiers == Qt.KeyboardModifier.ControlModifier
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Process all events we do not want to override
|
|
443
|
+
if not (ctrlSpace or ctrlM or ctrlI):
|
|
444
|
+
super().keyPressEvent(event)
|
|
445
|
+
|
|
446
|
+
if self.controller:
|
|
447
|
+
if ctrlI:
|
|
448
|
+
self.controller.toggleCaseSensitive()
|
|
449
|
+
if ctrlM:
|
|
450
|
+
self.controller.cycleCompleterMode()
|
|
451
|
+
|
|
452
|
+
# check for particular events for the completion
|
|
453
|
+
if completer:
|
|
454
|
+
# look for documentation popups
|
|
455
|
+
if event.key() == Qt.Key.Key_ParenLeft:
|
|
456
|
+
rect = self.cursorRect()
|
|
457
|
+
point = self.mapToGlobal(QPoint(rect.x(), rect.y()))
|
|
458
|
+
completer.showDocumentation(pos=point, scope=__main__.__dict__)
|
|
459
|
+
|
|
460
|
+
# hide documentation popups
|
|
461
|
+
elif event.key() == Qt.Key.Key_ParenRight:
|
|
462
|
+
completer.hideDocumentation()
|
|
463
|
+
|
|
464
|
+
# determine if we need to show the popup or if it already is visible, we
|
|
465
|
+
# need to update it
|
|
466
|
+
elif (
|
|
467
|
+
event.key() == Qt.Key.Key_Period
|
|
468
|
+
or event.key() == Qt.Key.Key_Escape
|
|
469
|
+
or completer.popup().isVisible()
|
|
470
|
+
or ctrlSpace
|
|
471
|
+
or ctrlI
|
|
472
|
+
or ctrlM
|
|
473
|
+
or completer.wasCompletingCounter
|
|
474
|
+
):
|
|
475
|
+
completer.refreshList(scope=__main__.__dict__)
|
|
476
|
+
|
|
477
|
+
model = completer.completionModel()
|
|
478
|
+
index = model.index(0, 0)
|
|
479
|
+
|
|
480
|
+
# If option chosen, if the exact prefix exists in the
|
|
481
|
+
# possible completions, highlight it, even if it's not the
|
|
482
|
+
# topmost completion.
|
|
483
|
+
if (
|
|
484
|
+
self.controller
|
|
485
|
+
and self.controller.uiHighlightExactCompletionCHK.isChecked()
|
|
486
|
+
):
|
|
487
|
+
for i in range(completer.completionCount()):
|
|
488
|
+
completer.setCurrentRow(i)
|
|
489
|
+
curCompletion = completer.currentCompletion()
|
|
490
|
+
if prefix == curCompletion:
|
|
491
|
+
index = model.index(i, 0)
|
|
492
|
+
break
|
|
493
|
+
elif prefix == curCompletion.lower():
|
|
494
|
+
index = model.index(i, 0)
|
|
495
|
+
break
|
|
496
|
+
|
|
497
|
+
# Set completer current Row, so finishing the completer will use
|
|
498
|
+
# correct text
|
|
499
|
+
completer.setCurrentRow(index.row())
|
|
500
|
+
|
|
501
|
+
# Make sure that current selection is visible, ie scroll to it
|
|
502
|
+
completer.popup().scrollTo(
|
|
503
|
+
index, QAbstractItemView.ScrollHint.EnsureVisible
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# show the completer for the rect
|
|
507
|
+
rect = self.cursorRect()
|
|
508
|
+
rect.setWidth(
|
|
509
|
+
completer.popup().sizeHintForColumn(0)
|
|
510
|
+
+ completer.popup().verticalScrollBar().sizeHint().width()
|
|
511
|
+
)
|
|
512
|
+
completer.complete(rect)
|
|
513
|
+
|
|
514
|
+
if completer.popup().isVisible():
|
|
515
|
+
completer.wasCompleting = True
|
|
516
|
+
completer.wasCompletingCounter = 0
|
|
517
|
+
|
|
518
|
+
if completer.wasCompleting and not completer.popup().isVisible():
|
|
519
|
+
wasCompletingCounterMax = completer.wasCompletingCounterMax
|
|
520
|
+
if completer.wasCompletingCounter <= wasCompletingCounterMax:
|
|
521
|
+
if event.key() not in (Qt.Key.Key_Backspace, Qt.Key.Key_Left):
|
|
522
|
+
completer.wasCompletingCounter += 1
|
|
523
|
+
else:
|
|
524
|
+
completer.wasCompletingCounter = 0
|
|
525
|
+
completer.wasCompleting = False
|
|
526
|
+
|
|
527
|
+
def moveToHome(self):
|
|
528
|
+
"""moves the cursor to the home location"""
|
|
529
|
+
mode = QTextCursor.MoveMode.MoveAnchor
|
|
530
|
+
# select the home
|
|
531
|
+
if (
|
|
532
|
+
QApplication.instance().keyboardModifiers()
|
|
533
|
+
== Qt.KeyboardModifier.ShiftModifier
|
|
534
|
+
):
|
|
535
|
+
mode = QTextCursor.MoveMode.KeepAnchor
|
|
536
|
+
# grab the cursor
|
|
537
|
+
cursor = self.textCursor()
|
|
538
|
+
if (
|
|
539
|
+
QApplication.instance().keyboardModifiers()
|
|
540
|
+
== Qt.KeyboardModifier.ControlModifier
|
|
541
|
+
):
|
|
542
|
+
# move to the top of the document if control is pressed
|
|
543
|
+
cursor.movePosition(QTextCursor.MoveOperation.Start)
|
|
544
|
+
else:
|
|
545
|
+
# Otherwise just move it to the start of the line
|
|
546
|
+
cursor.movePosition(QTextCursor.MoveOperation.StartOfBlock, mode)
|
|
547
|
+
# move the cursor to the end of the prompt.
|
|
548
|
+
cursor.movePosition(QTextCursor.MoveOperation.Right, mode, len(self.prompt()))
|
|
549
|
+
self.setTextCursor(cursor)
|
|
550
|
+
|
|
551
|
+
def onFirstShow(self, event) -> bool:
|
|
552
|
+
if not super().onFirstShow(event):
|
|
553
|
+
# It's already been shown, nothing to do.
|
|
554
|
+
return False
|
|
555
|
+
|
|
556
|
+
# This is the first showing of this widget, ensure the first input
|
|
557
|
+
# prompt is styled by any active stylesheet
|
|
558
|
+
self.startInputLine()
|
|
559
|
+
return True
|
|
560
|
+
|
|
561
|
+
def outputPrompt(self):
|
|
562
|
+
"""The prompt used to output a result."""
|
|
563
|
+
return self._outputPrompt
|
|
564
|
+
|
|
565
|
+
def prompt(self):
|
|
566
|
+
return self._consolePrompt
|
|
567
|
+
|
|
568
|
+
def setCompleter(self, completer):
|
|
569
|
+
"""sets the completer instance for this widget"""
|
|
570
|
+
if completer:
|
|
571
|
+
self._completer = completer
|
|
572
|
+
completer.setWidget(self)
|
|
573
|
+
completer.activated.connect(self.insertCompletion)
|
|
574
|
+
|
|
575
|
+
def startInputLine(self):
|
|
576
|
+
"""create a new command prompt line"""
|
|
577
|
+
self.startPrompt(self.prompt())
|
|
578
|
+
self._prevCommandIndex = 0
|
|
579
|
+
|
|
580
|
+
def startOutputLine(self):
|
|
581
|
+
"""Create a new line to show output text."""
|
|
582
|
+
self.startPrompt(self._outputPrompt)
|
|
583
|
+
|
|
584
|
+
def removeCurrentLine(self):
|
|
585
|
+
self.moveCursor(QTextCursor.MoveOperation.End, QTextCursor.MoveMode.MoveAnchor)
|
|
586
|
+
self.moveCursor(
|
|
587
|
+
QTextCursor.MoveOperation.StartOfLine, QTextCursor.MoveMode.MoveAnchor
|
|
588
|
+
)
|
|
589
|
+
self.moveCursor(QTextCursor.MoveOperation.End, QTextCursor.MoveMode.KeepAnchor)
|
|
590
|
+
self.textCursor().removeSelectedText()
|
|
591
|
+
self.textCursor().deletePreviousChar()
|
|
592
|
+
self.insertPlainText("\n")
|
|
593
|
+
|
|
594
|
+
# This main console should have these settings enabled by default
|
|
595
|
+
stream_clear = QtPropertyInit('_stream_clear', True)
|
|
596
|
+
stream_disable_writes = QtPropertyInit('_stream_disable_writes', True)
|
|
597
|
+
stream_replay = QtPropertyInit('_stream_replay', True)
|
|
598
|
+
stream_echo_stderr = QtPropertyInit(
|
|
599
|
+
'_stream_echo_stderr', True, callback=ConsoleBase.update_streams
|
|
600
|
+
)
|
|
601
|
+
stream_echo_stdout = QtPropertyInit(
|
|
602
|
+
'_stream_echo_stdout', True, callback=ConsoleBase.update_streams
|
|
603
|
+
)
|
|
604
|
+
stream_echo_result = QtPropertyInit("_stream_echo_result", True)
|
|
605
|
+
"""Enable StreamType.RESULT output when running code using PrEditor."""
|