PrEditor 1.0.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.
Potentially problematic release.
This version of PrEditor might be problematic. Click here for more details.
- preditor/__init__.py +322 -0
- preditor/__main__.py +13 -0
- preditor/about_module.py +161 -0
- preditor/cli.py +192 -0
- preditor/config.py +302 -0
- preditor/contexts.py +119 -0
- preditor/cores/__init__.py +0 -0
- preditor/cores/core.py +20 -0
- preditor/dccs/maya/PrEditor_maya.mod +2 -0
- preditor/dccs/maya/plug-ins/PrEditor_maya.py +110 -0
- preditor/debug.py +144 -0
- preditor/delayable_engine/__init__.py +302 -0
- preditor/delayable_engine/delayables.py +85 -0
- preditor/enum.py +728 -0
- preditor/excepthooks.py +131 -0
- preditor/gui/__init__.py +93 -0
- preditor/gui/app.py +160 -0
- preditor/gui/codehighlighter.py +209 -0
- preditor/gui/completer.py +226 -0
- preditor/gui/console.py +867 -0
- preditor/gui/dialog.py +178 -0
- preditor/gui/drag_tab_bar.py +190 -0
- preditor/gui/editor_chooser.py +57 -0
- preditor/gui/errordialog.py +68 -0
- preditor/gui/find_files.py +125 -0
- preditor/gui/fuzzy_search/__init__.py +0 -0
- preditor/gui/fuzzy_search/fuzzy_search.py +93 -0
- preditor/gui/group_tab_widget/__init__.py +325 -0
- preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
- preditor/gui/group_tab_widget/grouped_tab_models.py +108 -0
- preditor/gui/group_tab_widget/grouped_tab_widget.py +78 -0
- preditor/gui/group_tab_widget/one_tab_widget.py +54 -0
- preditor/gui/level_buttons.py +343 -0
- preditor/gui/logger_window_handler.py +48 -0
- preditor/gui/logger_window_plugin.py +32 -0
- preditor/gui/loggerwindow.py +1385 -0
- preditor/gui/newtabwidget.py +69 -0
- preditor/gui/set_text_editor_path_dialog.py +59 -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 +1105 -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 +389 -0
- preditor/gui/workbox_text_edit.py +137 -0
- preditor/gui/workboxwidget.py +298 -0
- preditor/logging_config.py +52 -0
- preditor/osystem.py +401 -0
- preditor/plugins.py +118 -0
- preditor/prefs.py +74 -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/settings.ini +25 -0
- preditor/resource/stylesheet/Bright.css +65 -0
- preditor/resource/stylesheet/Dark.css +199 -0
- preditor/scintilla/__init__.py +22 -0
- preditor/scintilla/delayables/__init__.py +11 -0
- preditor/scintilla/delayables/smart_highlight.py +94 -0
- preditor/scintilla/delayables/spell_check.py +173 -0
- preditor/scintilla/documenteditor.py +2038 -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 +21 -0
- preditor/scintilla/lexers/javascriptlexer.py +25 -0
- preditor/scintilla/lexers/maxscriptlexer.py +234 -0
- preditor/scintilla/lexers/mellexer.py +368 -0
- preditor/scintilla/lexers/mulexer.py +32 -0
- preditor/scintilla/lexers/pythonlexer.py +41 -0
- preditor/scintilla/ui/finddialog.ui +160 -0
- preditor/settings.py +71 -0
- preditor/stream/__init__.py +80 -0
- preditor/stream/director.py +73 -0
- preditor/stream/manager.py +74 -0
- preditor/streamhandler_helper.py +46 -0
- preditor/utils/__init__.py +0 -0
- preditor/utils/cute.py +30 -0
- preditor/utils/stylesheets.py +54 -0
- preditor/utils/text_search.py +342 -0
- preditor/version.py +21 -0
- preditor/weakref.py +363 -0
- preditor-1.0.0.dist-info/METADATA +224 -0
- preditor-1.0.0.dist-info/RECORD +158 -0
- preditor-1.0.0.dist-info/WHEEL +5 -0
- preditor-1.0.0.dist-info/entry_points.txt +18 -0
- preditor-1.0.0.dist-info/licenses/LICENSE +165 -0
- preditor-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import textwrap
|
|
6
|
+
|
|
7
|
+
from Qt.QtCore import Qt
|
|
8
|
+
from Qt.QtWidgets import QStackedWidget
|
|
9
|
+
|
|
10
|
+
from ..prefs import prefs_path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WorkboxMixin(object):
|
|
14
|
+
_warning_text = None
|
|
15
|
+
"""When a user is picking this Workbox class, show a warning with this text."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self, parent=None, tempfile=None, filename=None, core_name=None, **kwargs
|
|
19
|
+
):
|
|
20
|
+
super(WorkboxMixin, self).__init__(parent=parent, **kwargs)
|
|
21
|
+
self._filename_pref = filename
|
|
22
|
+
self._is_loaded = False
|
|
23
|
+
self._tempdir = None
|
|
24
|
+
self._tempfile = tempfile
|
|
25
|
+
self.core_name = core_name
|
|
26
|
+
|
|
27
|
+
def __auto_complete_enabled__(self):
|
|
28
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
29
|
+
|
|
30
|
+
def __set_auto_complete_enabled__(self, state):
|
|
31
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
32
|
+
|
|
33
|
+
def __clear__(self):
|
|
34
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
35
|
+
|
|
36
|
+
def __close__(self):
|
|
37
|
+
"""Called just before the LoggerWindow is closed to allow for workbox cleanup"""
|
|
38
|
+
|
|
39
|
+
def __comment_toggle__(self):
|
|
40
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
41
|
+
|
|
42
|
+
def __console__(self):
|
|
43
|
+
"""Returns the PrEditor console to code is executed in if set."""
|
|
44
|
+
try:
|
|
45
|
+
return self._console
|
|
46
|
+
except AttributeError:
|
|
47
|
+
self._console = None
|
|
48
|
+
|
|
49
|
+
def __set_console__(self, console):
|
|
50
|
+
self._console = console
|
|
51
|
+
|
|
52
|
+
def __copy_indents_as_spaces__(self):
|
|
53
|
+
"""When copying code, should it convert leading tabs to spaces?"""
|
|
54
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
55
|
+
|
|
56
|
+
def __set_copy_indents_as_spaces__(self, state):
|
|
57
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
58
|
+
|
|
59
|
+
def __cursor_position__(self):
|
|
60
|
+
"""Returns the line and index of the cursor."""
|
|
61
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
62
|
+
|
|
63
|
+
def __set_cursor_position__(self, line, index):
|
|
64
|
+
"""Set the cursor to this line number and index"""
|
|
65
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
66
|
+
|
|
67
|
+
def __exec_all__(self):
|
|
68
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
69
|
+
|
|
70
|
+
def __exec_selected__(self, truncate=True):
|
|
71
|
+
txt, line = self.__selected_text__()
|
|
72
|
+
|
|
73
|
+
# Remove any leading white space shared across all lines
|
|
74
|
+
txt = textwrap.dedent(txt)
|
|
75
|
+
|
|
76
|
+
# Get rid of pesky \r's
|
|
77
|
+
txt = self.__unix_end_lines__(txt)
|
|
78
|
+
|
|
79
|
+
# Make workbox line numbers match the workbox line numbers, by adding
|
|
80
|
+
# the appropriate number of newlines to mimic it's original position in
|
|
81
|
+
# the workbox.
|
|
82
|
+
txt = '\n' * line + txt
|
|
83
|
+
|
|
84
|
+
# execute the code
|
|
85
|
+
filename = self.__workbox_filename__(selection=True)
|
|
86
|
+
ret, was_eval = self.__console__().executeString(txt, filename=filename)
|
|
87
|
+
if was_eval:
|
|
88
|
+
# If the selected code was a statement print the result of the statement.
|
|
89
|
+
ret = repr(ret)
|
|
90
|
+
self.__console__().startOutputLine()
|
|
91
|
+
if truncate:
|
|
92
|
+
print(self.truncate_middle(ret, 100))
|
|
93
|
+
else:
|
|
94
|
+
print(ret)
|
|
95
|
+
|
|
96
|
+
def __file_monitoring_enabled__(self):
|
|
97
|
+
"""Returns True if this workbox supports file monitoring.
|
|
98
|
+
This allows the editor to update its text if the linked
|
|
99
|
+
file is changed on disk."""
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
def __set_file_monitoring_enabled__(self, state):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
def __filename__(self):
|
|
106
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
107
|
+
|
|
108
|
+
def __font__(self):
|
|
109
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
110
|
+
|
|
111
|
+
def __set_font__(self, font):
|
|
112
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
113
|
+
|
|
114
|
+
def __group_tab_index__(self):
|
|
115
|
+
"""Returns the group and editor indexes if this editor is being used in
|
|
116
|
+
a GroupTabWidget.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
group, editor: The index of the group tab and the index of the
|
|
120
|
+
editor's tab under the group tab. -1 is returned for both if
|
|
121
|
+
this isn't parent to a GroupTabWidget.
|
|
122
|
+
"""
|
|
123
|
+
group = editor = -1
|
|
124
|
+
|
|
125
|
+
# This widget's parent should be a stacked widget and we can get the
|
|
126
|
+
# editors index from that
|
|
127
|
+
stack = self.parent()
|
|
128
|
+
if stack and isinstance(stack, QStackedWidget):
|
|
129
|
+
editor = stack.indexOf(self)
|
|
130
|
+
else:
|
|
131
|
+
return -1, -1
|
|
132
|
+
|
|
133
|
+
# The parent of the stacked widget should be a tab widget, get its parent
|
|
134
|
+
editor_tab = stack.parent()
|
|
135
|
+
if not editor_tab:
|
|
136
|
+
return -1, -1
|
|
137
|
+
|
|
138
|
+
# This should be a stacked widget under a tab widget, we can get group
|
|
139
|
+
# from it without needing to get its parent.
|
|
140
|
+
stack = editor_tab.parent()
|
|
141
|
+
if stack and isinstance(stack, QStackedWidget):
|
|
142
|
+
group = stack.indexOf(editor_tab)
|
|
143
|
+
|
|
144
|
+
return group, editor
|
|
145
|
+
|
|
146
|
+
def __workbox_filename__(self, selection=False):
|
|
147
|
+
title = "WorkboxSelection" if selection else "Workbox"
|
|
148
|
+
group, editor = self.__group_tab_index__()
|
|
149
|
+
if group == -1 or editor == -1:
|
|
150
|
+
return '<{}>'.format(title)
|
|
151
|
+
else:
|
|
152
|
+
return '<{}>:{},{}'.format(title, group, editor)
|
|
153
|
+
|
|
154
|
+
def __goto_line__(self, line):
|
|
155
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
156
|
+
|
|
157
|
+
def __indentations_use_tabs__(self):
|
|
158
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
159
|
+
|
|
160
|
+
def __set_indentations_use_tabs__(self, state):
|
|
161
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
162
|
+
|
|
163
|
+
def __insert_text__(self, txt):
|
|
164
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
165
|
+
|
|
166
|
+
def __load__(self, filename):
|
|
167
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
168
|
+
|
|
169
|
+
def __margins_font__(self):
|
|
170
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
171
|
+
|
|
172
|
+
def __set_margins_font__(self, font):
|
|
173
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
174
|
+
|
|
175
|
+
def __marker_add__(self, line):
|
|
176
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
177
|
+
|
|
178
|
+
def __marker_clear_all__(self):
|
|
179
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
180
|
+
|
|
181
|
+
def __reload_file__(self):
|
|
182
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
183
|
+
|
|
184
|
+
def __remove_selected_text__(self):
|
|
185
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
186
|
+
|
|
187
|
+
def __save__(self):
|
|
188
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
189
|
+
|
|
190
|
+
def __selected_text__(self, start_of_line=False, selectText=False):
|
|
191
|
+
"""Returns selected text or the current line of text, plus the line
|
|
192
|
+
number of the begining of selection / cursor position.
|
|
193
|
+
|
|
194
|
+
If text is selected, it is returned. If nothing is selected, returns the
|
|
195
|
+
entire line of text the cursor is currently on.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
start_of_line (bool, optional): If text is selected, include any
|
|
199
|
+
leading text from the first line of the selection.
|
|
200
|
+
selectText (bool): If expanding to the entire line from the cursor,
|
|
201
|
+
indicates whether to select that line of text
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
str: The requested text
|
|
205
|
+
line (int): plus the line number of the beginning of selection / cursor
|
|
206
|
+
position.
|
|
207
|
+
"""
|
|
208
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
209
|
+
|
|
210
|
+
def __tab_width__(self):
|
|
211
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
212
|
+
|
|
213
|
+
def __set_tab_width__(self, width):
|
|
214
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
215
|
+
|
|
216
|
+
def __text__(self, line=None, start=None, end=None):
|
|
217
|
+
"""Returns the text in this widget, possibly limited in scope.
|
|
218
|
+
|
|
219
|
+
Note: Only pass line, or (start and end) to this method.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
line (int, optional): Limit the returned scope to just this line number.
|
|
223
|
+
start (int, optional): Limit the scope to text between this and end.
|
|
224
|
+
end (int, optional): Limit the scope to text between start and this.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
str: The requested text.
|
|
228
|
+
"""
|
|
229
|
+
raise NotImplementedError("Mixin method not overridden.")
|
|
230
|
+
|
|
231
|
+
def __set_text__(self, txt):
|
|
232
|
+
"""Replace all of the current text with txt. This method should be overridden
|
|
233
|
+
by sub-classes, and call super to mark the widget as having been loaded.
|
|
234
|
+
If text is being set on the widget, it most likely should be marked as
|
|
235
|
+
having been loaded.
|
|
236
|
+
"""
|
|
237
|
+
self._is_loaded = True
|
|
238
|
+
|
|
239
|
+
def truncate_middle(self, s, n, sep=' ... '):
|
|
240
|
+
"""Truncates the provided text to a fixed length, putting the sep in the middle.
|
|
241
|
+
https://www.xormedia.com/string-truncate-middle-with-ellipsis/
|
|
242
|
+
"""
|
|
243
|
+
if len(s) <= n:
|
|
244
|
+
# string is already short-enough
|
|
245
|
+
return s
|
|
246
|
+
# half of the size, minus the seperator
|
|
247
|
+
n_2 = int(n) // 2 - len(sep)
|
|
248
|
+
# whatever's left
|
|
249
|
+
n_1 = n - n_2 - len(sep)
|
|
250
|
+
return '{0}{1}{2}'.format(s[:n_1], sep, s[-n_2:])
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def __unix_end_lines__(cls, txt):
|
|
254
|
+
"""Replaces all windows and then mac line endings with unix line endings."""
|
|
255
|
+
return txt.replace('\r\n', '\n').replace('\r', '\n')
|
|
256
|
+
|
|
257
|
+
def __restore_prefs__(self, prefs):
|
|
258
|
+
self._filename_pref = prefs.get('filename')
|
|
259
|
+
self._tempfile = prefs.get('tempfile')
|
|
260
|
+
|
|
261
|
+
def __save_prefs__(self, name, current=None):
|
|
262
|
+
ret = {}
|
|
263
|
+
# Hopefully the alphabetical sorting of this dict is preserved in py3
|
|
264
|
+
# to make it easy to diff the json pref file if ever required.
|
|
265
|
+
if current is not None:
|
|
266
|
+
ret['current'] = current
|
|
267
|
+
ret['filename'] = self._filename_pref
|
|
268
|
+
ret['name'] = name
|
|
269
|
+
ret['tempfile'] = self._tempfile
|
|
270
|
+
|
|
271
|
+
if not self._is_loaded:
|
|
272
|
+
return ret
|
|
273
|
+
|
|
274
|
+
if self._filename_pref:
|
|
275
|
+
self.__save__()
|
|
276
|
+
else:
|
|
277
|
+
if not self._tempfile:
|
|
278
|
+
self._tempfile = self.__create_tempfile__()
|
|
279
|
+
ret['tempfile'] = self._tempfile
|
|
280
|
+
self.__write_file__(
|
|
281
|
+
self.__tempfile__(create=True),
|
|
282
|
+
self.__text__(),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return ret
|
|
286
|
+
|
|
287
|
+
def __tempdir__(self, create=False):
|
|
288
|
+
if self._tempdir is None:
|
|
289
|
+
self._tempdir = prefs_path('workboxes', core_name=self.core_name)
|
|
290
|
+
|
|
291
|
+
if create and not os.path.exists(self._tempdir):
|
|
292
|
+
os.makedirs(self._tempdir)
|
|
293
|
+
|
|
294
|
+
return self._tempdir
|
|
295
|
+
|
|
296
|
+
def __tempfile__(self, create=False):
|
|
297
|
+
if self._tempfile:
|
|
298
|
+
return os.path.join(self.__tempdir__(create=create), self._tempfile)
|
|
299
|
+
|
|
300
|
+
def __create_tempfile__(self):
|
|
301
|
+
"""Creates a temporary file to be used by `__tempfile__` to store this
|
|
302
|
+
editors text contents stored in `__tempdir__`."""
|
|
303
|
+
with tempfile.NamedTemporaryFile(
|
|
304
|
+
prefix='workbox_',
|
|
305
|
+
suffix='.py',
|
|
306
|
+
dir=self.__tempdir__(create=True),
|
|
307
|
+
delete=False,
|
|
308
|
+
) as fle:
|
|
309
|
+
name = fle.name
|
|
310
|
+
|
|
311
|
+
return os.path.basename(name)
|
|
312
|
+
|
|
313
|
+
def __remove_tempfile__(self):
|
|
314
|
+
"""Removes `__tempfile__` if it is being used."""
|
|
315
|
+
tempfile = self.__tempfile__()
|
|
316
|
+
if tempfile and os.path.exists(tempfile):
|
|
317
|
+
os.remove(tempfile)
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def __open_file__(cls, filename):
|
|
321
|
+
with open(filename) as fle:
|
|
322
|
+
return fle.read()
|
|
323
|
+
return ""
|
|
324
|
+
|
|
325
|
+
@classmethod
|
|
326
|
+
def __write_file__(cls, filename, txt):
|
|
327
|
+
with open(filename, 'w') as fle:
|
|
328
|
+
fle.write(txt)
|
|
329
|
+
|
|
330
|
+
def __show__(self):
|
|
331
|
+
if self._is_loaded:
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
self._is_loaded = True
|
|
335
|
+
if self._filename_pref:
|
|
336
|
+
self.__load__(self._filename_pref)
|
|
337
|
+
elif self._tempfile:
|
|
338
|
+
txt = self.__open_file__(self.__tempfile__())
|
|
339
|
+
self.__set_text__(txt)
|
|
340
|
+
|
|
341
|
+
def process_shortcut(self, event, run=True):
|
|
342
|
+
"""Check for workbox shortcuts and optionally call them.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
event (QEvent): The keyPressEvent to process.
|
|
346
|
+
run (bool, optional): Run the expected action if possible.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
str or False: Returns False if the key press was not handled, indicating
|
|
350
|
+
that the subclass needs to handle it(or call super). If a known
|
|
351
|
+
shortcut was detected, a string indicating the action is returned
|
|
352
|
+
after running the action if enabled and supported.
|
|
353
|
+
|
|
354
|
+
Known actions:
|
|
355
|
+
__exec_selected__: If the user pressed Shift + Return or pressed the
|
|
356
|
+
number pad enter key calling `__exec_selected__`.
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
# Number pad enter, or Shift + Return pressed, execute selected
|
|
360
|
+
# Ctrl+ Shift+Return pressed, execute selected without truncating output
|
|
361
|
+
if run:
|
|
362
|
+
# self.__exec_selected__()
|
|
363
|
+
# Collect what was pressed
|
|
364
|
+
key = event.key()
|
|
365
|
+
modifiers = event.modifiers()
|
|
366
|
+
|
|
367
|
+
# Determine which relevant combos are pressed
|
|
368
|
+
ret = key == Qt.Key_Return
|
|
369
|
+
enter = key == Qt.Key_Enter
|
|
370
|
+
shift = modifiers == Qt.ShiftModifier
|
|
371
|
+
ctrlShift = modifiers == Qt.ControlModifier | Qt.ShiftModifier
|
|
372
|
+
|
|
373
|
+
# Determine which actions to take
|
|
374
|
+
evalTrunc = enter or (ret and shift)
|
|
375
|
+
evalNoTrunc = ret and ctrlShift
|
|
376
|
+
|
|
377
|
+
if evalTrunc:
|
|
378
|
+
# Execute with truncation
|
|
379
|
+
self.window().execSelected()
|
|
380
|
+
elif evalNoTrunc:
|
|
381
|
+
# Execute without truncation
|
|
382
|
+
self.window().execSelected(truncate=False)
|
|
383
|
+
|
|
384
|
+
if evalTrunc or evalNoTrunc:
|
|
385
|
+
if self.window().uiAutoPromptACT.isChecked():
|
|
386
|
+
self.__console__().startInputLine()
|
|
387
|
+
return '__exec_selected__'
|
|
388
|
+
else:
|
|
389
|
+
return False
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from Qt.QtGui import QFont, QFontMetrics, QTextCursor
|
|
6
|
+
from Qt.QtWidgets import QTextEdit
|
|
7
|
+
|
|
8
|
+
from .codehighlighter import CodeHighlighter
|
|
9
|
+
from .workbox_mixin import WorkboxMixin
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WorkboxTextEdit(WorkboxMixin, QTextEdit):
|
|
15
|
+
"""A very simple multi-line text editor without any bells and whistles.
|
|
16
|
+
|
|
17
|
+
It's better than nothing, but not by much.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
_warning_text = (
|
|
21
|
+
"This is a bare bones workbox, if you have another option, it's probably"
|
|
22
|
+
"a better option."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self, parent=None, console=None, delayable_engine='default', core_name=None
|
|
27
|
+
):
|
|
28
|
+
super(WorkboxTextEdit, self).__init__(parent=parent, core_name=core_name)
|
|
29
|
+
self._filename = None
|
|
30
|
+
self.__set_console__(console)
|
|
31
|
+
highlight = CodeHighlighter(self)
|
|
32
|
+
highlight.setLanguage('Python')
|
|
33
|
+
self.uiCodeHighlighter = highlight
|
|
34
|
+
|
|
35
|
+
def __auto_complete_enabled__(self):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def __set_auto_complete_enabled__(self, state):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def __copy_indents_as_spaces__(self):
|
|
42
|
+
"""When copying code, should it convert leading tabs to spaces?"""
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
def __set_copy_indents_as_spaces__(self, state):
|
|
46
|
+
logger.info(
|
|
47
|
+
"WorkboxTextEdit does not support converting indents to spaces on copy."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def __cursor_position__(self):
|
|
51
|
+
"""Returns the line and index of the cursor."""
|
|
52
|
+
cursor = self.textCursor()
|
|
53
|
+
sc = QTextCursor(self.document())
|
|
54
|
+
sc.setPosition(cursor.selectionStart())
|
|
55
|
+
return sc.blockNumber(), sc.positionInBlock()
|
|
56
|
+
|
|
57
|
+
def __exec_all__(self):
|
|
58
|
+
txt = self.__text__().rstrip()
|
|
59
|
+
filename = self.__workbox_filename__()
|
|
60
|
+
self.__console__().executeString(txt, filename=filename)
|
|
61
|
+
|
|
62
|
+
def __font__(self):
|
|
63
|
+
return self.font()
|
|
64
|
+
|
|
65
|
+
def __set_font__(self, font):
|
|
66
|
+
metrics = QFontMetrics(font)
|
|
67
|
+
self.setTabStopDistance(metrics.width(" ") * 4)
|
|
68
|
+
super(WorkboxTextEdit, self).setFont(font)
|
|
69
|
+
|
|
70
|
+
def __goto_line__(self, line):
|
|
71
|
+
cursor = QTextCursor(self.document().findBlockByLineNumber(line - 1))
|
|
72
|
+
self.setTextCursor(cursor)
|
|
73
|
+
|
|
74
|
+
def __indentations_use_tabs__(self):
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
def __set_indentations_use_tabs__(self, state):
|
|
78
|
+
logger.info("WorkboxTextEdit does not support using spaces for tabs.")
|
|
79
|
+
|
|
80
|
+
def __load__(self, filename):
|
|
81
|
+
self._filename = filename
|
|
82
|
+
txt = self.__open_file__(self._filename)
|
|
83
|
+
self.__set_text__(txt)
|
|
84
|
+
|
|
85
|
+
def __margins_font__(self):
|
|
86
|
+
return QFont()
|
|
87
|
+
|
|
88
|
+
def __set_margins_font__(self, font):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
def __tab_width__(self):
|
|
92
|
+
# TODO: Implement custom tab widths
|
|
93
|
+
return 4
|
|
94
|
+
|
|
95
|
+
def __text__(self, line=None, start=None, end=None):
|
|
96
|
+
return self.toPlainText()
|
|
97
|
+
|
|
98
|
+
def __set_text__(self, text):
|
|
99
|
+
super(WorkboxTextEdit, self).__set_text__(text)
|
|
100
|
+
self.setPlainText(text)
|
|
101
|
+
|
|
102
|
+
def __selected_text__(self, start_of_line=False, selectText=False):
|
|
103
|
+
cursor = self.textCursor()
|
|
104
|
+
|
|
105
|
+
# Get starting line number. Must set the cursor's position to the start of the
|
|
106
|
+
# selection, otherwise we may instead get the ending line number.
|
|
107
|
+
tempCursor = self.textCursor()
|
|
108
|
+
tempCursor.setPosition(tempCursor.selectionStart())
|
|
109
|
+
line = tempCursor.block().firstLineNumber()
|
|
110
|
+
|
|
111
|
+
# If no selection, return the current line
|
|
112
|
+
if cursor.selection().isEmpty():
|
|
113
|
+
text = cursor.block().text()
|
|
114
|
+
|
|
115
|
+
selectText = self.window().uiSelectTextACT.isChecked() or selectText
|
|
116
|
+
if selectText:
|
|
117
|
+
cursor.select(QTextCursor.LineUnderCursor)
|
|
118
|
+
self.setTextCursor(cursor)
|
|
119
|
+
|
|
120
|
+
return text, line
|
|
121
|
+
|
|
122
|
+
# Otherwise return the selected text
|
|
123
|
+
if start_of_line:
|
|
124
|
+
sc = QTextCursor(self.document())
|
|
125
|
+
sc.setPosition(cursor.selectionStart())
|
|
126
|
+
sc.movePosition(cursor.StartOfLine, sc.MoveAnchor)
|
|
127
|
+
sc.setPosition(cursor.selectionEnd(), sc.KeepAnchor)
|
|
128
|
+
|
|
129
|
+
return sc.selection().toPlainText(), line
|
|
130
|
+
|
|
131
|
+
return self.textCursor().selection().toPlainText(), line
|
|
132
|
+
|
|
133
|
+
def keyPressEvent(self, event):
|
|
134
|
+
if self.process_shortcut(event):
|
|
135
|
+
return
|
|
136
|
+
else:
|
|
137
|
+
super(WorkboxTextEdit, self).keyPressEvent(event)
|