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.
Files changed (179) hide show
  1. preditor/__init__.py +315 -0
  2. preditor/__main__.py +13 -0
  3. preditor/about_module.py +165 -0
  4. preditor/cli.py +192 -0
  5. preditor/config.py +318 -0
  6. preditor/constants.py +13 -0
  7. preditor/contexts.py +210 -0
  8. preditor/cores/__init__.py +0 -0
  9. preditor/cores/core.py +20 -0
  10. preditor/dccs/.hab.json +10 -0
  11. preditor/dccs/maya/PrEditor_maya.mod +1 -0
  12. preditor/dccs/maya/README.md +22 -0
  13. preditor/dccs/maya/plug-ins/PrEditor_maya.py +141 -0
  14. preditor/dccs/studiomax/PackageContents.xml +32 -0
  15. preditor/dccs/studiomax/PrEditor-PrEditor_Show.mcr +8 -0
  16. preditor/dccs/studiomax/README.md +17 -0
  17. preditor/dccs/studiomax/preditor.ms +16 -0
  18. preditor/dccs/studiomax/preditor_menu.mnx +7 -0
  19. preditor/debug.py +149 -0
  20. preditor/delayable_engine/__init__.py +302 -0
  21. preditor/delayable_engine/delayables.py +85 -0
  22. preditor/enum.py +728 -0
  23. preditor/excepthooks.py +165 -0
  24. preditor/gui/__init__.py +56 -0
  25. preditor/gui/app.py +163 -0
  26. preditor/gui/codehighlighter.py +289 -0
  27. preditor/gui/completer.py +237 -0
  28. preditor/gui/console.py +605 -0
  29. preditor/gui/console_base.py +911 -0
  30. preditor/gui/dialog.py +181 -0
  31. preditor/gui/drag_tab_bar.py +625 -0
  32. preditor/gui/editor_chooser.py +57 -0
  33. preditor/gui/errordialog.py +69 -0
  34. preditor/gui/find_files.py +137 -0
  35. preditor/gui/fuzzy_search/__init__.py +0 -0
  36. preditor/gui/fuzzy_search/fuzzy_search.py +97 -0
  37. preditor/gui/group_tab_widget/__init__.py +0 -0
  38. preditor/gui/group_tab_widget/group_tab_widget.py +528 -0
  39. preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
  40. preditor/gui/group_tab_widget/grouped_tab_models.py +107 -0
  41. preditor/gui/group_tab_widget/grouped_tab_widget.py +223 -0
  42. preditor/gui/group_tab_widget/one_tab_widget.py +96 -0
  43. preditor/gui/level_buttons.py +358 -0
  44. preditor/gui/logger_window_handler.py +77 -0
  45. preditor/gui/logger_window_plugin.py +35 -0
  46. preditor/gui/loggerwindow.py +2405 -0
  47. preditor/gui/newtabwidget.py +69 -0
  48. preditor/gui/output_console.py +11 -0
  49. preditor/gui/qtdesigner/__init__.py +21 -0
  50. preditor/gui/qtdesigner/_log_plugin.py +29 -0
  51. preditor/gui/qtdesigner/console_base_plugin.py +48 -0
  52. preditor/gui/qtdesigner/console_predit_plugin.py +48 -0
  53. preditor/gui/set_text_editor_path_dialog.py +61 -0
  54. preditor/gui/status_label.py +99 -0
  55. preditor/gui/suggest_path_quotes_dialog.py +50 -0
  56. preditor/gui/ui/editor_chooser.ui +93 -0
  57. preditor/gui/ui/errordialog.ui +74 -0
  58. preditor/gui/ui/find_files.ui +140 -0
  59. preditor/gui/ui/loggerwindow.ui +1909 -0
  60. preditor/gui/ui/set_text_editor_path_dialog.ui +189 -0
  61. preditor/gui/ui/suggest_path_quotes_dialog.ui +225 -0
  62. preditor/gui/window.py +161 -0
  63. preditor/gui/workbox_mixin.py +1139 -0
  64. preditor/gui/workbox_text_edit.py +136 -0
  65. preditor/gui/workboxwidget.py +315 -0
  66. preditor/logging_config.py +55 -0
  67. preditor/osystem.py +401 -0
  68. preditor/plugins.py +118 -0
  69. preditor/prefs.py +381 -0
  70. preditor/resource/environment_variables.html +26 -0
  71. preditor/resource/error_mail.html +85 -0
  72. preditor/resource/error_mail_inline.html +41 -0
  73. preditor/resource/img/README.md +17 -0
  74. preditor/resource/img/arrow_forward.png +0 -0
  75. preditor/resource/img/check-bold.png +0 -0
  76. preditor/resource/img/chevron-down.png +0 -0
  77. preditor/resource/img/chevron-up.png +0 -0
  78. preditor/resource/img/close-thick.png +0 -0
  79. preditor/resource/img/comment-edit.png +0 -0
  80. preditor/resource/img/content-copy.png +0 -0
  81. preditor/resource/img/content-cut.png +0 -0
  82. preditor/resource/img/content-duplicate.png +0 -0
  83. preditor/resource/img/content-paste.png +0 -0
  84. preditor/resource/img/content-save.png +0 -0
  85. preditor/resource/img/debug_disabled.png +0 -0
  86. preditor/resource/img/eye-check.png +0 -0
  87. preditor/resource/img/file-plus.png +0 -0
  88. preditor/resource/img/file-remove.png +0 -0
  89. preditor/resource/img/format-align-left.png +0 -0
  90. preditor/resource/img/format-letter-case-lower.png +0 -0
  91. preditor/resource/img/format-letter-case-upper.png +0 -0
  92. preditor/resource/img/format-letter-case.svg +1 -0
  93. preditor/resource/img/information.png +0 -0
  94. preditor/resource/img/logging_critical.png +0 -0
  95. preditor/resource/img/logging_custom.png +0 -0
  96. preditor/resource/img/logging_debug.png +0 -0
  97. preditor/resource/img/logging_error.png +0 -0
  98. preditor/resource/img/logging_info.png +0 -0
  99. preditor/resource/img/logging_not_set.png +0 -0
  100. preditor/resource/img/logging_warning.png +0 -0
  101. preditor/resource/img/marker.png +0 -0
  102. preditor/resource/img/play.png +0 -0
  103. preditor/resource/img/playlist-play.png +0 -0
  104. preditor/resource/img/plus-minus-variant.png +0 -0
  105. preditor/resource/img/preditor.ico +0 -0
  106. preditor/resource/img/preditor.png +0 -0
  107. preditor/resource/img/preditor.psd +0 -0
  108. preditor/resource/img/preditor.svg +44 -0
  109. preditor/resource/img/regex.svg +1 -0
  110. preditor/resource/img/restart.svg +1 -0
  111. preditor/resource/img/skip-forward-outline.png +0 -0
  112. preditor/resource/img/skip-next-outline.png +0 -0
  113. preditor/resource/img/skip-next.png +0 -0
  114. preditor/resource/img/skip-previous.png +0 -0
  115. preditor/resource/img/subdirectory-arrow-right.png +0 -0
  116. preditor/resource/img/text-search-variant.png +0 -0
  117. preditor/resource/img/warning-big.png +0 -0
  118. preditor/resource/lang/python.json +30 -0
  119. preditor/resource/pref_updates/pref_updates.json +17 -0
  120. preditor/resource/settings.ini +25 -0
  121. preditor/resource/stylesheet/Bright.css +76 -0
  122. preditor/resource/stylesheet/Dark.css +210 -0
  123. preditor/scintilla/__init__.py +40 -0
  124. preditor/scintilla/delayables/__init__.py +11 -0
  125. preditor/scintilla/delayables/smart_highlight.py +97 -0
  126. preditor/scintilla/delayables/spell_check.py +174 -0
  127. preditor/scintilla/documenteditor.py +1924 -0
  128. preditor/scintilla/finddialog.py +68 -0
  129. preditor/scintilla/lang/__init__.py +80 -0
  130. preditor/scintilla/lang/config/bash.ini +15 -0
  131. preditor/scintilla/lang/config/batch.ini +14 -0
  132. preditor/scintilla/lang/config/cpp.ini +19 -0
  133. preditor/scintilla/lang/config/css.ini +19 -0
  134. preditor/scintilla/lang/config/eyeonscript.ini +17 -0
  135. preditor/scintilla/lang/config/html.ini +21 -0
  136. preditor/scintilla/lang/config/javascript.ini +24 -0
  137. preditor/scintilla/lang/config/lua.ini +16 -0
  138. preditor/scintilla/lang/config/maxscript.ini +20 -0
  139. preditor/scintilla/lang/config/mel.ini +18 -0
  140. preditor/scintilla/lang/config/mu.ini +22 -0
  141. preditor/scintilla/lang/config/nsi.ini +19 -0
  142. preditor/scintilla/lang/config/perl.ini +19 -0
  143. preditor/scintilla/lang/config/puppet.ini +19 -0
  144. preditor/scintilla/lang/config/python.ini +28 -0
  145. preditor/scintilla/lang/config/ruby.ini +19 -0
  146. preditor/scintilla/lang/config/sql.ini +7 -0
  147. preditor/scintilla/lang/config/xml.ini +21 -0
  148. preditor/scintilla/lang/config/yaml.ini +18 -0
  149. preditor/scintilla/lang/language.py +240 -0
  150. preditor/scintilla/lexers/__init__.py +0 -0
  151. preditor/scintilla/lexers/cpplexer.py +22 -0
  152. preditor/scintilla/lexers/javascriptlexer.py +27 -0
  153. preditor/scintilla/lexers/maxscriptlexer.py +235 -0
  154. preditor/scintilla/lexers/mellexer.py +369 -0
  155. preditor/scintilla/lexers/mulexer.py +33 -0
  156. preditor/scintilla/lexers/pythonlexer.py +42 -0
  157. preditor/scintilla/ui/finddialog.ui +160 -0
  158. preditor/settings.py +71 -0
  159. preditor/stream/__init__.py +72 -0
  160. preditor/stream/console_handler.py +169 -0
  161. preditor/stream/director.py +144 -0
  162. preditor/stream/manager.py +97 -0
  163. preditor/streamhandler_helper.py +46 -0
  164. preditor/utils/__init__.py +191 -0
  165. preditor/utils/call_stack.py +86 -0
  166. preditor/utils/cute.py +106 -0
  167. preditor/utils/stylesheets.py +54 -0
  168. preditor/utils/text_search.py +338 -0
  169. preditor/version.py +34 -0
  170. preditor/weakref.py +363 -0
  171. preditor-2.1.0.dist-info/METADATA +308 -0
  172. preditor-2.1.0.dist-info/RECORD +179 -0
  173. preditor-2.1.0.dist-info/WHEEL +5 -0
  174. preditor-2.1.0.dist-info/entry_points.txt +19 -0
  175. preditor-2.1.0.dist-info/licenses/LICENSE +165 -0
  176. preditor-2.1.0.dist-info/top_level.txt +3 -0
  177. tests/encodings/test_ecoding.py +33 -0
  178. tests/find_files/test_find_files.py +74 -0
  179. tests/ide/test_delayable_engine.py +171 -0
preditor/weakref.py ADDED
@@ -0,0 +1,363 @@
1
+ # coding: utf-8
2
+ # /*##########################################################################
3
+ #
4
+ # Copyright (c) 2016-2018 European Synchrotron Radiation Facility
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #
24
+ # ###########################################################################*/
25
+ # Code pulled from the silx codebase
26
+ # https://github.com/silx-kit/silx/blob/master/src/silx/utils/weakref.py
27
+ """Weakref utils for compatibility between Python 2 and Python 3 or for
28
+ extended features.
29
+ """
30
+ from __future__ import absolute_import
31
+
32
+ __authors__ = ["V. Valls"]
33
+ __license__ = "MIT"
34
+ __date__ = "15/09/2016"
35
+
36
+
37
+ import inspect
38
+ import types
39
+ import weakref
40
+
41
+
42
+ def ref(object, callback=None):
43
+ """Returns a weak reference to object. The original object can be retrieved
44
+ by calling the reference object if the referent is still alive. If the
45
+ referent is no longer alive, calling the reference object will cause None
46
+ to be returned.
47
+
48
+ The signature is the same as the standard `weakref` library, but it returns
49
+ `WeakMethod` if the object is a bound method.
50
+
51
+ :param object: An object
52
+ :param func callback: If provided, and the returned weakref object is
53
+ still alive, the callback will be called when the object is about to
54
+ be finalized. The weak reference object will be passed as the only
55
+ parameter to the callback. Then the referent will no longer be
56
+ available.
57
+ :return: A weak reference to the object
58
+ """
59
+ if inspect.ismethod(object):
60
+ return WeakMethod(object, callback)
61
+ else:
62
+ return weakref.ref(object, callback)
63
+
64
+
65
+ def proxy(object, callback=None):
66
+ """Return a proxy to object which uses a weak reference. This supports use
67
+ of the proxy in most contexts instead of requiring the explicit
68
+ dereferencing used with weak reference objects.
69
+
70
+ The signature is the same as the standard `weakref` library, but it returns
71
+ `WeakMethodProxy` if the object is a bound method.
72
+
73
+ :param object: An object
74
+ :param func callback: If provided, and the returned weakref object is
75
+ still alive, the callback will be called when the object is about to
76
+ be finalized. The weak reference object will be passed as the only
77
+ parameter to the callback. Then the referent will no longer be
78
+ available.
79
+ :return: A proxy to a weak reference of the object
80
+ """
81
+ if inspect.ismethod(object):
82
+ return WeakMethodProxy(object, callback)
83
+ else:
84
+ return weakref.proxy(object, callback)
85
+
86
+
87
+ class WeakMethod(object):
88
+ """Wraps a callable object like a function or a bound method.
89
+ Feature callback when the object is about to be finalized.
90
+ Provids the same interface as a normal weak reference.
91
+ """
92
+
93
+ def __init__(self, function, callback=None):
94
+ """
95
+ Constructor
96
+ :param function: Function/method to be called
97
+ :param callback: If callback is provided and not None,
98
+ and the returned weakref object is still alive, the
99
+ callback will be called when the object is about to
100
+ be finalized; the weak reference object will be passed
101
+ as the only parameter to the callback; the referent will
102
+ no longer be available
103
+ """
104
+ self.__callback = callback
105
+
106
+ if inspect.ismethod(function):
107
+ # it is a bound method
108
+ self.__obj = weakref.ref(function.__self__, self.__call_callback)
109
+ self.__method = weakref.ref(function.__func__, self.__call_callback)
110
+ else:
111
+ self.__obj = None
112
+ self.__method = weakref.ref(function, self.__call_callback)
113
+
114
+ def __call_callback(self, ref):
115
+ """Called when the object is about to be finalized"""
116
+ if not self.is_alive():
117
+ return
118
+ self.__obj = None
119
+ self.__method = None
120
+ if self.__callback is not None:
121
+ self.__callback(self)
122
+
123
+ def __call__(self):
124
+ """Return a callable function or None if the WeakMethod is dead."""
125
+ if self.__obj is not None:
126
+ method = self.__method()
127
+ obj = self.__obj()
128
+ if method is None or obj is None:
129
+ return None
130
+ return types.MethodType(method, obj)
131
+ elif self.__method is not None:
132
+ return self.__method()
133
+ else:
134
+ return None
135
+
136
+ def is_alive(self):
137
+ """True if the WeakMethod is still alive"""
138
+ return self.__method is not None
139
+
140
+ def __eq__(self, other):
141
+ """Check it another obect is equal to this.
142
+
143
+ :param object other: Object to compare with
144
+ """
145
+ if isinstance(other, WeakMethod):
146
+ if not self.is_alive():
147
+ return False
148
+ return self.__obj == other.__obj and self.__method == other.__method
149
+ return False
150
+
151
+ def __ne__(self, other):
152
+ """Check it another obect is not equal to this.
153
+
154
+ :param object other: Object to compare with
155
+ """
156
+ if isinstance(other, WeakMethod):
157
+ if not self.is_alive():
158
+ return False
159
+ return self.__obj != other.__obj or self.__method != other.__method
160
+ return True
161
+
162
+ def __hash__(self):
163
+ """Returns the hash for the object."""
164
+ return self.__obj.__hash__() ^ self.__method.__hash__()
165
+
166
+
167
+ class WeakMethodProxy(WeakMethod):
168
+ """Wraps a callable object like a function or a bound method
169
+ with a weakref proxy.
170
+ """
171
+
172
+ def __call__(self, *args, **kwargs):
173
+ """Dereference the method and call it if the method is still alive.
174
+ Else raises an ReferenceError.
175
+
176
+ :raises: ReferenceError, if the method is not alive
177
+ """
178
+ fn = super(WeakMethodProxy, self).__call__()
179
+ if fn is None:
180
+ raise ReferenceError("weakly-referenced object no longer exists")
181
+ return fn(*args, **kwargs)
182
+
183
+
184
+ class WeakList(list):
185
+ """Manage a list of weaked references.
186
+ When an object is dead, the list is flaged as invalid.
187
+ If expected the list is cleaned up to remove dead objects.
188
+ """
189
+
190
+ def __init__(self, enumerator=()):
191
+ """Create a WeakList
192
+
193
+ :param iterator enumerator: A list of object to initialize the
194
+ list
195
+ """
196
+ list.__init__(self)
197
+ self.__list = []
198
+ self.__is_valid = True
199
+ for obj in enumerator:
200
+ self.append(obj)
201
+
202
+ def __invalidate(self, ref):
203
+ """Flag the list as invalidated. The list contains dead references."""
204
+ self.__is_valid = False
205
+
206
+ def __create_ref(self, obj):
207
+ """Create a weakref from an object. It uses the `ref` module function."""
208
+ return ref(obj, self.__invalidate)
209
+
210
+ def __clean(self):
211
+ """Clean the list from dead references"""
212
+ if self.__is_valid:
213
+ return
214
+ self.__list = [ref for ref in self.__list if ref() is not None]
215
+ self.__is_valid = True
216
+
217
+ def __iter__(self):
218
+ """Iterate over objects of the list"""
219
+ for ref in self.__list:
220
+ obj = ref()
221
+ if obj is not None:
222
+ yield obj
223
+
224
+ def __len__(self):
225
+ """Count item on the list"""
226
+ self.__clean()
227
+ return len(self.__list)
228
+
229
+ def __getitem__(self, key):
230
+ """Returns the object at the requested index
231
+
232
+ :param key: Indexes to get
233
+ :type key: int or slice
234
+ """
235
+ self.__clean()
236
+ data = self.__list[key]
237
+ if isinstance(data, list):
238
+ result = [ref() for ref in data]
239
+ else:
240
+ result = data()
241
+ return result
242
+
243
+ def __setitem__(self, key, obj):
244
+ """Set an item at an index
245
+
246
+ :param key: Indexes to set
247
+ :type key: int or slice
248
+ """
249
+ self.__clean()
250
+ if isinstance(key, slice):
251
+ objs = [self.__create_ref(o) for o in obj]
252
+ self.__list[key] = objs
253
+ else:
254
+ obj_ref = self.__create_ref(obj)
255
+ self.__list[key] = obj_ref
256
+
257
+ def __delitem__(self, key):
258
+ """Delete an Indexes item of this list
259
+
260
+ :param key: Index to delete
261
+ :type key: int or slice
262
+ """
263
+ self.__clean()
264
+ del self.__list[key]
265
+
266
+ def __delslice__(self, i, j):
267
+ """Looks to be used in Python 2.7"""
268
+ self.__delitem__(slice(i, j, None))
269
+
270
+ def __setslice__(self, i, j, sequence):
271
+ """Looks to be used in Python 2.7"""
272
+ self.__setitem__(slice(i, j, None), sequence)
273
+
274
+ def __getslice__(self, i, j):
275
+ """Looks to be used in Python 2.7"""
276
+ return self.__getitem__(slice(i, j, None))
277
+
278
+ def __reversed__(self):
279
+ """Returns a copy of the reverted list"""
280
+ reversed_list = reversed(list(self))
281
+ return WeakList(reversed_list)
282
+
283
+ def __contains__(self, obj):
284
+ """Returns true if the object is in the list"""
285
+ ref = self.__create_ref(obj)
286
+ return ref in self.__list
287
+
288
+ def __add__(self, other):
289
+ """Returns a WeakList containing this list an the other"""
290
+ l = WeakList(self) # noqa: E741
291
+ l.extend(other)
292
+ return l
293
+
294
+ def __iadd__(self, other):
295
+ """Add objects to this list inplace"""
296
+ self.extend(other)
297
+ return self
298
+
299
+ def __mul__(self, n):
300
+ """Returns a WeakList containing n-duplication object of this list"""
301
+ return WeakList(list(self) * n)
302
+
303
+ def __imul__(self, n):
304
+ """N-duplication of the objects to this list inplace"""
305
+ self.__list *= n
306
+ return self
307
+
308
+ def append(self, obj):
309
+ """Add an object at the end of the list"""
310
+ ref = self.__create_ref(obj)
311
+ self.__list.append(ref)
312
+
313
+ def count(self, obj):
314
+ """Returns the number of occurencies of an object"""
315
+ ref = self.__create_ref(obj)
316
+ return self.__list.count(ref)
317
+
318
+ def extend(self, other):
319
+ """Append the list with all objects from another list"""
320
+ for obj in other:
321
+ self.append(obj)
322
+
323
+ def index(self, obj):
324
+ """Returns the index of an object"""
325
+ ref = self.__create_ref(obj)
326
+ return self.__list.index(ref)
327
+
328
+ def insert(self, index, obj):
329
+ """Insert an object at the requested index"""
330
+ ref = self.__create_ref(obj)
331
+ self.__list.insert(index, ref)
332
+
333
+ def pop(self, index=-1):
334
+ """Remove and return an object at the requested index"""
335
+ self.__clean()
336
+ obj = self.__list.pop(index)()
337
+ return obj
338
+
339
+ def remove(self, obj):
340
+ """Remove an object from the list"""
341
+ ref = self.__create_ref(obj)
342
+ self.__list.remove(ref)
343
+
344
+ def reverse(self):
345
+ """Reverse the list inplace"""
346
+ self.__list.reverse()
347
+
348
+ def sort(self, key=None, reverse=False):
349
+ """Sort the list inplace.
350
+ Not very efficient.
351
+ """
352
+ sorted_list = list(self)
353
+ sorted_list.sort(key=key, reverse=reverse)
354
+ self.__list = []
355
+ self.extend(sorted_list)
356
+
357
+ def __str__(self):
358
+ unref_list = list(self)
359
+ return "WeakList(%s)" % str(unref_list)
360
+
361
+ def __repr__(self):
362
+ unref_list = list(self)
363
+ return "WeakList(%s)" % repr(unref_list)
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: PrEditor
3
+ Version: 2.1.0
4
+ Summary: A python REPL and Editor and console based on Qt.
5
+ Author-email: Blur Studio <opensource@blur.com>
6
+ License: LGPL-3.0
7
+ Project-URL: Homepage, https://github.com/blurstudio/PrEditor
8
+ Project-URL: Source, https://github.com/blurstudio/PrEditor
9
+ Project-URL: Tracker, https://github.com/blurstudio/PrEditor/issues
10
+ Platform: any
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: Implementation :: CPython
17
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
18
+ Requires-Python: >=3.7
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: charset-normalizer
22
+ Requires-Dist: configparser>=4.0.2
23
+ Requires-Dist: future>=0.18.2
24
+ Requires-Dist: importlib-metadata>=4.8.3
25
+ Requires-Dist: Qt.py>=1.4.4
26
+ Requires-Dist: signalslot>=0.1.2
27
+ Provides-Extra: cli
28
+ Requires-Dist: click>=7.1.2; extra == "cli"
29
+ Requires-Dist: click-default-group; extra == "cli"
30
+ Provides-Extra: dev
31
+ Requires-Dist: black; extra == "dev"
32
+ Requires-Dist: build; extra == "dev"
33
+ Requires-Dist: covdefaults; extra == "dev"
34
+ Requires-Dist: coverage; extra == "dev"
35
+ Requires-Dist: flake8; extra == "dev"
36
+ Requires-Dist: flake8-bugbear; extra == "dev"
37
+ Requires-Dist: Flake8-pyproject; extra == "dev"
38
+ Requires-Dist: pep8-naming; extra == "dev"
39
+ Requires-Dist: pytest; extra == "dev"
40
+ Requires-Dist: tox; extra == "dev"
41
+ Provides-Extra: qsci5
42
+ Requires-Dist: QScintilla; extra == "qsci5"
43
+ Provides-Extra: qsci6
44
+ Requires-Dist: PyQt6-QScintilla; extra == "qsci6"
45
+ Provides-Extra: shortcut
46
+ Requires-Dist: casement>=0.1.0; platform_system == "Windows" and extra == "shortcut"
47
+ Dynamic: license-file
48
+
49
+ # PrEditor
50
+
51
+ A python REPL, editor and console based on Qt. It allows you to interact
52
+ directly with the current python session and write/run complex code in workbox's.
53
+ It also has an interface for configuring python logging.
54
+
55
+ # Use and Features
56
+
57
+ ![preview](https://github.com/blurstudio/PrEditor/assets/2424292/5425aa5f-0f9b-4b04-8e98-5a58546eb93c)
58
+
59
+ * **Console:** The top section is a python REPL allowing you to run code like you
60
+ are in the python interactive shell. However, you can't use code
61
+ blocks([...](https://docs.python.org/3/glossary.html#term-...)), use the workbox instead.
62
+ * Python's stdout and stderr are written here including exceptions.
63
+ * If the cursor is at the very end of the last line, and that line starts with
64
+ a prompt (`>>> ` this includes 1 space) the code is executed when you press return.
65
+ Pressing return on any other prompt line copies that line to the end ready to
66
+ execute.
67
+ * Pressing `Ctrl + Up/Down` will cycle through previous command history.
68
+ * The console is a text edit and you can edit any of the text so you can fix
69
+ your mistakes as you make them
70
+ * **Workbox:** The workbox is a place to write complex multi-line code. The contents
71
+ of all workboxes are saved when PrEditor is closed or pressing `Ctrl + S`.
72
+ * Workboxes are grouped into tabs of workboxes. You can drag and drop
73
+ individual workboxes between groups and re-order them.
74
+ * `Ctrl + Return` runs all code inside of the current workbox.
75
+ * `Shift + Return` or the `Number-pad Return` executes the selected text or
76
+ the line the cursor is on.
77
+ * `run_workbox("group/tab")` This command is added allowing you to run the
78
+ contents of a workbox. Pass the name of the group and workbox tabs separated
79
+ by a forward slash.
80
+ * **Logging Level button:** Tools for managing python loggers.
81
+ * This button shows all known python loggers and lets you view/change their
82
+ logging levels.
83
+ * You can install logging handlers that have had PrEditor plugins written for them.
84
+ * Known python logger levels are saved and restored.
85
+ * **OutputConsole:** Selectively shows output from stdout, stderr, specific python
86
+ loggers, and tracebacks(not using stderr). This can be used in various widgets to
87
+ show selected output. See [examples/output_console.py](examples/output_console.py)
88
+ for an example of the various modes.
89
+ * All code is run in `__main__`. In code you can add objects to it for inspection in PrEditor.
90
+ * `Ctrl + Shift + PgUp/PgDown` changes focus between the console and workbox.
91
+ * `Ctrl + Alt + Shift + PgUp/PgDown` changes focus and copies the current prompt
92
+ line of the console, or the current line of the workbox to the other.
93
+
94
+
95
+ # Examples
96
+
97
+ See [examples](examples) for more complete examples of using PrEditor.
98
+
99
+ For simple standalone applications that only exist for the life of the main window
100
+ you can simply call `connect_preditor` in your class `__init__` and optionally add
101
+ the created QAction into your GUI's menu. All `sys.stdout` and `sys.stderr` output
102
+ written after `connect_preditor` is called, will be shown in the PrEditor window
103
+ if it shown. If a exception is raised, and PrEditor is not visible, the user will
104
+ be notified and can easily show PrEditor.
105
+ ```py
106
+ import preditor
107
+
108
+ # Create a keyboard shortcut(F2) to launch PrEditor and start capturing sys.stdout
109
+ # and sys.stderr writes. The name argument makes this instance use it for prefs
110
+ action = preditor.connect_preditor(window, name="Example")
111
+
112
+ # Add the newly created action to a menu
113
+ window.menuBar().actions()[0].menu.addAction(action)
114
+ ```
115
+
116
+ Steps for initialization of a more complex application where you don't have
117
+ control over the initialization of the Gui(like Maya).
118
+ See [examples/add_to_app.py](examples/add_to_app.py) for a simple implementation.
119
+
120
+
121
+ ```py
122
+ # Step 1: Capture sys.stdout and sys.stderr output to a buffer as early as
123
+ # possible without creating the gui. Add this code to a plugin that gets loaded
124
+ # as early as possible. This can even be run before the gui is created.
125
+ import preditor
126
+ # The name "maya" specifies the core_name that will be used to load/save prefs.
127
+ preditor.configure("maya")
128
+
129
+ # Step 2: Add a way for the user to trigger calling launch to show the PrEditor
130
+ # gui. This is the first time the PrEditor GUI is initialized.
131
+ preditor.launch()
132
+
133
+ # Step 3: When closing the application, calling this will ensure that the
134
+ # current PrEditor gui's state is saved. It's safe and fast to call this even
135
+ # if the gui was never created.
136
+ preditor.shutdown()
137
+ ```
138
+
139
+ Up to the point where the PrEditor instance is created you can update the config
140
+ data set by `preditor.configure`. For example you can change the name(used to load
141
+ a set of user prefs) by calling `preditor.config.name = 'NewName'`. This is useful
142
+ for configuring PrEditor before you import your specific setup code that implements
143
+ a better `parent_callback`.
144
+
145
+ # Installing
146
+
147
+ `pip install preditor`
148
+
149
+ ## Installing Qt
150
+
151
+ PrEditor is built on Qt, but uses [Qt.py](https://github.com/mottosso/Qt.py) so
152
+ you can choose to use PySide6, PySide2, PyQt6 or PyQt5. We have elected to not
153
+ directly depend on either of these packages so that you can use PrEditor inside
154
+ of existing applications like Maya or Houdini that already come with PySide
155
+ installed. If you are using it externally add them to your pip install command.
156
+
157
+ - PySide6: `pip install preditor PySide6`
158
+ - PyQt6: `pip install preditor PyQt6`
159
+
160
+ ## Cli
161
+
162
+ PrEditor is intended to be installed inside existing applications like Maya,
163
+ Houdini, Nuke etc, so it doesn't make sense to require installing packages like
164
+ click for those installs. If you are setting up a system wide install and want
165
+ to use the cli interface, you will need to install the cli optional dependencies.
166
+
167
+ `pip install preditor[cli]`
168
+
169
+ ### Creating shortcuts
170
+
171
+ If you want to be able to create desktop shortcuts from the cli to launch
172
+ PrEditor, you will also need to include the `shortcut` dependencies. Currently
173
+ this is only useful for windows.
174
+
175
+ - `pip install preditor[cli,shortcut]`
176
+
177
+ ## QScintilla workbox
178
+
179
+ The more mature QScintilla workbox requires a few extra dependencies that must
180
+ be passed manually. We have added it as pip `optional-dependencies`. QScintilla
181
+ only works with PyQt5/6 and it is a little hard to get PyQt working inside of
182
+ DCC's that ship with PySide2/6 by default. Here is the python 3 pip install command.
183
+
184
+ - PyQt6: `pip install preditor[qsci6] PyQt6, aspell-python-py3`
185
+ - PyQt5: `pip install preditor[qsci5] PyQt5, aspell-python-py3`
186
+
187
+ The aspell-python-py3 requirement is optional to enable spell check.
188
+
189
+ You may need to set the `QT_PREFERRED_BINDING` or `QT_PREFERRED_BINDING_JSON`
190
+ [environment variable](https://github.com/mottosso/Qt.py?tab=readme-ov-file#override-preferred-choice) to ensure that PrEditor can use PyQt5/PyQt6.
191
+
192
+ # DCC Integration
193
+
194
+ Here are several example integrations for DCC's included in PrEditor. These
195
+ require some setup to manage installing all pip requirements. These will require
196
+ you to follow the [Setup](#setup) instructions below.
197
+
198
+ - [Maya](/preditor/dccs/maya/README.md)
199
+ - [3ds Max](/preditor/dccs/studiomax/README.md)
200
+
201
+ If you are using hab, you can simply add the path to the [preditor](/preditor) folder to your site's `distro_paths`. [See .hab.json](/preditor/dccs/.hab.json)
202
+
203
+ ## Setup
204
+
205
+ PrEditor has many python pip requirements. The easiest way to get access to all
206
+ of them inside an DCC is to create a virtualenv and pip install the requirements.
207
+ You can possibly use the python included with DCC(mayapy), but this guide covers
208
+ using a system install of python.
209
+
210
+ 1. Identify the minor version of python that the dcc is using. Running `sys.version_info[:2]` in the DCC returns the major and minor version of python.
211
+ 2. Download and install the required version of python. Note, you likely only need to match the major and minor version of python(3.11 not 3.11.12). It's recommended that you don't use the windows store to install python as it has had issues when used to create virtualenvs.
212
+ 3. Create a virtualenv using that version of python. On windows you can use `py.exe -3.11` or call the correct python.exe file. Change `-3.11` to match the major and minor version returned by step 1. Note that you should create separate venvs for a given python minor version and potentially for minor versions of Qt if you are using PyQt.
213
+ ```batch
214
+ cd c:\path\to\venv\parent
215
+ py -3.11 -m virtualenv preditor_311
216
+ ```
217
+ 4. Use the newly created pip exe to install PrEditor and its dependencies.
218
+ * This example shows using PySide and the simple TextEdit workbox in a minimal configuration.
219
+ ```batch
220
+ c:\path\to\venv\parent\preditor_311\Scripts\pip install PrEditor
221
+ ```
222
+ * This example shows using QScintilla in PyQt6 for a better editing experience. Note that you need to match the PyQt version used by the DCC, This may require matching the exact version of PyQt.
223
+ ```batch
224
+ c:\path\to\venv\parent\preditor_311\Scripts\pip install PrEditor[qsci6] PyQt6==6.5.3
225
+ ```
226
+
227
+ ### Editable install
228
+
229
+ You should skip this section unless you want to develop PrEditor's code from an git repo using python's editable pip install.
230
+
231
+ Due to how editable installs work you will need to set an environment variable
232
+ specifying the site-packages directory of the virtualenv you created in the
233
+ previous step. On windows this should be the `lib\site-packages` folder inside
234
+ of the venv you just created. Store this in the `PREDITOR_SITE`, this can be done
235
+ permanently or temporarily(via `set "PREDITOR_SITE=c:\path\to\venv\parent\preditor_311\lib\site-packages"`).
236
+
237
+ This is required because you are going to use the path to your git repo's preditor
238
+ folder in the module/plugin loading methods for the the DCC you are using, but
239
+ there is no way to automatically find the virtualenv that your random git repo
240
+ is installed in. In fact, you may have have your git repo installed into multiple
241
+ virtualenvs at once.
242
+
243
+ # Plugins
244
+
245
+ PrEditor is can be extended using entry point plugins defined by other pip packages.
246
+
247
+ * `preditor.plug.about_module`: Used to add information about various packages
248
+ like version and install location to the output of `preditor.about_preditor()`.
249
+ This is what generates the text shown by Help menu -> About PrEditor. See
250
+ sub-classes of `AboutModule` in `preditor.about_module` and how those are
251
+ added in [setup.cfg](setup.cfg).
252
+
253
+ * `preditor.plug.editors`: Used to add new workbox editors to PrEditor. See
254
+ [workbox_text_edit.py](preditor/gui/workbox_text_edit.py) for an example of
255
+ implementing a workbox. See [workbox_mixin.py](preditor/gui/workbox_mixin.py)
256
+ for the full interface to implement all features of an editor.
257
+
258
+ * `preditor.plug.loggerwindow`: Used to customize the LoggerWindow instance when
259
+ the LoggerWindow is created. For example, this can be used to create extra Toolbars
260
+ or add menu items. When using this plugin, make sure to use the
261
+ `preditor.gui.logger_window_plugin.LoggerWindowPlugin` class for your base class.
262
+
263
+ * `preditor.plug.logging_handlers`: Used to add custom python logging handlers
264
+ to the LoggingLevelButton's handlers sub-menus. This allows you to install a
265
+ handler instance on a specific logging object.
266
+
267
+ # Qt Designer integration
268
+
269
+ PrEditor includes some reusable widgets that are useful to integrate into your
270
+ own custom interfaces. These can be directly imported and added but PrEditor also
271
+ defines Qt Designer plugins so you can directly add them to your designer files.
272
+
273
+ Note: This has currently only been tested using [PyQt5](https://pypi.org/project/pyqt5-tools/)/[PyQt6](https://pypi.org/project/pyqt6-tools/).
274
+
275
+ To load the plugins append the path to [preditor/gui/qtdesigner](/preditor/gui/qtdesigner)
276
+ to the `PYQTDESIGNERPATH` environment variable.
277
+
278
+ # Change Log
279
+
280
+ * **2.0.0:** OutputConsole, link files to workboxe tabs and Workbox editing history
281
+ * New reusable `OutputConsole` widget that optionally shows stdout, stderr, logging messages, and tracebacks. See the [example](/examples/output_console.py) implementation for details.
282
+ * Workbox tabs can be linked to files and edited externally
283
+ * Workbox content change history is versioned and you can quickly switch between the versions.
284
+ * Recently closed workbox tabs can be be re-opened
285
+ * Workbox preference saving has been re-worked. It will automatically migrate
286
+ to the new setup so you won't loose your current workbox tab contents. However
287
+ after upgrading if you want to switch back to the v1.X release see details below.
288
+
289
+ <details>
290
+
291
+ In the rare case that you must revert to older Preditor (v1.X), you will only
292
+ see the workboxes you had when you updated to PrEditor v2.0. When you switch
293
+ back to v2.0 again, you still may only see those same workboxes. This can be
294
+ fixed with these steps, which in summary is to replace `preditor_pref.json`
295
+ with one of the backups of that file.
296
+
297
+ * Options Menu > Preferences
298
+ * In the `Prefs files on disk` section, click Browse. An Explorer window opens.
299
+ * Close PrEditor
300
+ * Go into the `prefs_bak` folder
301
+ * Sort by name or by date, so most recent files are at the top
302
+ * Look for a backup that is at least slightly larger than recent ones. If they are all the same size, go with the latest one.
303
+ * Copy that into the parent directory (ie PrEditor)
304
+ * Remove `preditor_pref.json`
305
+ * Rename the `preditor_pref<timestamp>.json` file you copied, so it is `preditor_pref.json`
306
+ * Restart PrEditor. Check if it has all your workboxes.
307
+ * If it still isn't correct, do a little sleuthing or trial and error to find the correct backup to use.
308
+ </details>