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
preditor/debug.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FileLogger:
|
|
12
|
+
def __init__(self, stdhandle, logfile, _print=True, clearLog=True):
|
|
13
|
+
self._stdhandle = stdhandle
|
|
14
|
+
self._logfile = logfile
|
|
15
|
+
self._print = _print
|
|
16
|
+
if clearLog:
|
|
17
|
+
# clear the log file
|
|
18
|
+
self.clear()
|
|
19
|
+
|
|
20
|
+
def clear(self, stamp=False):
|
|
21
|
+
"""Removes the contents of the log file."""
|
|
22
|
+
open(self._logfile, 'w').close()
|
|
23
|
+
if stamp:
|
|
24
|
+
msg = '--------- Date: {today} Version: {version} ---------'
|
|
25
|
+
print(msg.format(today=datetime.datetime.today(), version=sys.version))
|
|
26
|
+
|
|
27
|
+
def flush(self):
|
|
28
|
+
self._stdhandle.flush()
|
|
29
|
+
|
|
30
|
+
def write(self, msg):
|
|
31
|
+
f = open(self._logfile, 'a')
|
|
32
|
+
f.write(msg)
|
|
33
|
+
f.close()
|
|
34
|
+
if self._print:
|
|
35
|
+
self._stdhandle.write(msg)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def logToFile(path, stdout=True, stderr=True, useOldStd=True, clearLog=True):
|
|
39
|
+
"""Redirect all stdout and/or stderr output to a log file.
|
|
40
|
+
|
|
41
|
+
Creates a FileLogger class for stdout and stderr and installs itself in python.
|
|
42
|
+
All output will be logged to the file path. Prints the current datetime and
|
|
43
|
+
sys.version info when stdout is True.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
path (str): File path to log output to.
|
|
47
|
+
|
|
48
|
+
stdout (bool): If True(default) override sys.stdout.
|
|
49
|
+
|
|
50
|
+
stderr (bool): If True(default) override sys.stderr.
|
|
51
|
+
|
|
52
|
+
useOldStd (bool): If True, messages will be written to the FileLogger
|
|
53
|
+
and the previous sys.stdout/sys.stderr.
|
|
54
|
+
|
|
55
|
+
clearLog (bool): If True(default) clear the log file when this command is
|
|
56
|
+
called.
|
|
57
|
+
"""
|
|
58
|
+
if stderr:
|
|
59
|
+
sys.stderr = FileLogger(sys.stderr, path, useOldStd, clearLog=clearLog)
|
|
60
|
+
if stdout:
|
|
61
|
+
sys.stdout = FileLogger(sys.stdout, path, useOldStd, clearLog=False)
|
|
62
|
+
if clearLog:
|
|
63
|
+
sys.stdout.clear(stamp=True)
|
|
64
|
+
|
|
65
|
+
from .streamhandler_helper import StreamHandlerHelper
|
|
66
|
+
|
|
67
|
+
# Update any StreamHandler's that were setup using the old stdout/err
|
|
68
|
+
if stdout:
|
|
69
|
+
StreamHandlerHelper.replace_stream(sys.stdout._stdhandle, sys.stdout)
|
|
70
|
+
if stderr:
|
|
71
|
+
StreamHandlerHelper.replace_stream(sys.stderr._stdhandle, sys.stderr)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def printCallingFunction(compact=False):
|
|
75
|
+
"""Prints and returns info about the calling function
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
compact (bool): If set to True, prints a more compact printout
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
str: Info on the calling function.
|
|
82
|
+
"""
|
|
83
|
+
import inspect
|
|
84
|
+
|
|
85
|
+
current = inspect.currentframe().f_back
|
|
86
|
+
try:
|
|
87
|
+
parent = current.f_back
|
|
88
|
+
except AttributeError:
|
|
89
|
+
print('No Calling function found')
|
|
90
|
+
return
|
|
91
|
+
currentInfo = inspect.getframeinfo(current)
|
|
92
|
+
parentInfo = inspect.getframeinfo(parent)
|
|
93
|
+
if parentInfo[3] is not None:
|
|
94
|
+
context = ', '.join(parentInfo[3]).strip('\t').rstrip()
|
|
95
|
+
else:
|
|
96
|
+
context = 'No context to return'
|
|
97
|
+
if compact:
|
|
98
|
+
output = '# %s Calling Function: %s Filename: %s Line: %i Context: %s' % (
|
|
99
|
+
currentInfo[2],
|
|
100
|
+
parentInfo[2],
|
|
101
|
+
parentInfo[0],
|
|
102
|
+
parentInfo[1],
|
|
103
|
+
context,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
output = ["Function: '%s' in file '%s'" % (currentInfo[2], currentInfo[0])]
|
|
107
|
+
output.append(
|
|
108
|
+
" Calling Function: '%s' in file '%s'" % (parentInfo[2], parentInfo[0])
|
|
109
|
+
)
|
|
110
|
+
output.append(" Line: '%i'" % parentInfo[1])
|
|
111
|
+
output.append(" Context: '%s'" % context)
|
|
112
|
+
output = '\n'.join(output)
|
|
113
|
+
print(output)
|
|
114
|
+
return output
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def mroDump(obj, nice=True, joinString='\n'):
|
|
118
|
+
"""Formats inspect.getmro into text.
|
|
119
|
+
|
|
120
|
+
For the given class object or instance of a class, use inspect to return the Method
|
|
121
|
+
Resolution Order.
|
|
122
|
+
|
|
123
|
+
Args: obj (object): The object to return the mro of. This can be a class object or
|
|
124
|
+
instance.1
|
|
125
|
+
|
|
126
|
+
nice (bool): Returns the same module names as help(object) if True, otherwise
|
|
127
|
+
repr(object).
|
|
128
|
+
|
|
129
|
+
joinString (str, optional): The repr of each class is joined by this string.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
str: A string showing the Method Resolution Order of the given object.
|
|
133
|
+
"""
|
|
134
|
+
import pydoc
|
|
135
|
+
|
|
136
|
+
# getmro requires a class, turn instances into a class
|
|
137
|
+
if not inspect.isclass(obj):
|
|
138
|
+
obj = type(obj)
|
|
139
|
+
classes = inspect.getmro(obj)
|
|
140
|
+
if nice:
|
|
141
|
+
ret = [pydoc.classname(x, obj.__module__) for x in (classes)]
|
|
142
|
+
else:
|
|
143
|
+
ret = [repr(x) for x in (classes)]
|
|
144
|
+
return joinString.join(ret)
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import warnings
|
|
5
|
+
import weakref
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from collections.abc import MutableSet
|
|
8
|
+
|
|
9
|
+
from Qt import QtCompat
|
|
10
|
+
from Qt.QtCore import QObject, QTimer, Signal
|
|
11
|
+
|
|
12
|
+
from .delayables import Delayable
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# https://stackoverflow.com/a/7829569
|
|
16
|
+
class OrderedSet(MutableSet):
|
|
17
|
+
def __init__(self, values=()):
|
|
18
|
+
self._od = OrderedDict().fromkeys(values)
|
|
19
|
+
|
|
20
|
+
def __len__(self):
|
|
21
|
+
return len(self._od)
|
|
22
|
+
|
|
23
|
+
def __iter__(self):
|
|
24
|
+
return iter(self._od)
|
|
25
|
+
|
|
26
|
+
def __contains__(self, value):
|
|
27
|
+
return value in self._od
|
|
28
|
+
|
|
29
|
+
def add(self, value):
|
|
30
|
+
self._od[value] = None
|
|
31
|
+
|
|
32
|
+
def discard(self, value):
|
|
33
|
+
self._od.pop(value, None)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OrderedWeakrefSet(weakref.WeakSet):
|
|
37
|
+
def __init__(self, values=()):
|
|
38
|
+
super(OrderedWeakrefSet, self).__init__()
|
|
39
|
+
self.data = OrderedSet()
|
|
40
|
+
for elem in values:
|
|
41
|
+
self.add(elem)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DelayableEngine(QObject):
|
|
45
|
+
"""Provides a way for multiple DocumentEditors to run code over
|
|
46
|
+
multiple Qt event loops in chunks preventing locking up the ui.
|
|
47
|
+
|
|
48
|
+
Signals:
|
|
49
|
+
processing_finished (int, float): Emitted when the engine finishes
|
|
50
|
+
processing successfully.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
_instance = {}
|
|
54
|
+
processing_finished = Signal()
|
|
55
|
+
|
|
56
|
+
def __init__(self, name, parent=None, interval=0):
|
|
57
|
+
super(DelayableEngine, self).__init__()
|
|
58
|
+
self.name = name
|
|
59
|
+
self.documents = OrderedWeakrefSet()
|
|
60
|
+
self.delayables = {}
|
|
61
|
+
self.maxLoopTime = 0.01
|
|
62
|
+
self.start_time = time.time()
|
|
63
|
+
# It's likely we wont' finish processing all documents and all delayables
|
|
64
|
+
# before we run out of time. These variables keep track of where we stopped.
|
|
65
|
+
self.document_index = 0
|
|
66
|
+
self.delayable_index = 0
|
|
67
|
+
|
|
68
|
+
self.timer = QTimer(self)
|
|
69
|
+
self.timer.setInterval(interval)
|
|
70
|
+
self.timer.timeout.connect(self.loop)
|
|
71
|
+
|
|
72
|
+
# These values are reset when enqueue needs to start self.timer
|
|
73
|
+
# Each of these lists have a item added when self.loop exits
|
|
74
|
+
# (this can be it finished or ran out of time)
|
|
75
|
+
# Number of documents that had a delayable.loop called on them this loop
|
|
76
|
+
self.processed = []
|
|
77
|
+
# Time spent processing delayables for this self.loop
|
|
78
|
+
self.processing_time = []
|
|
79
|
+
# Number of nonVisible items that were skipped for this self.loop
|
|
80
|
+
self.skipped = []
|
|
81
|
+
|
|
82
|
+
def __repr__(self):
|
|
83
|
+
return '{}.{}("{}")'.format(
|
|
84
|
+
self.__module__,
|
|
85
|
+
self.__class__.__name__,
|
|
86
|
+
self.name,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def __str__(self):
|
|
90
|
+
return '{}("{}")'.format(self.__class__.__name__, self.name)
|
|
91
|
+
|
|
92
|
+
def add_delayable(self, delayable):
|
|
93
|
+
"""Add a Delayable subclass instance for processing in this engine.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
delayable (Delayable or str): A Delayable instance or the key identifier.
|
|
97
|
+
If a Delayable instance is passed, it will replace any previous
|
|
98
|
+
instances. If a string is passed it will not replace previous instance.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
KeyError: A invalid key identifier string was passed.
|
|
102
|
+
"""
|
|
103
|
+
if isinstance(delayable, str):
|
|
104
|
+
if delayable in self.delayables:
|
|
105
|
+
# Don't replace the instance if a string is passed
|
|
106
|
+
return
|
|
107
|
+
for cls in Delayable._all_subclasses():
|
|
108
|
+
if cls.key == delayable:
|
|
109
|
+
delayable = cls(self)
|
|
110
|
+
break
|
|
111
|
+
else:
|
|
112
|
+
raise KeyError('No Delayable found with key: "{}"'.format(delayable))
|
|
113
|
+
elif delayable.key in self.delayables:
|
|
114
|
+
# Remove the old delayable if it exists so we can replace it.
|
|
115
|
+
self.remove_delayable(self.delayables[delayable.key])
|
|
116
|
+
|
|
117
|
+
self.delayables[delayable.key] = delayable
|
|
118
|
+
for document in self.documents:
|
|
119
|
+
delayable.add_document(document)
|
|
120
|
+
|
|
121
|
+
def add_document(self, document):
|
|
122
|
+
self.documents.add(document)
|
|
123
|
+
document.delayable_engine = self
|
|
124
|
+
for delayable in self.delayables:
|
|
125
|
+
self.delayables[delayable].add_document(document)
|
|
126
|
+
|
|
127
|
+
def add_supported_delayables(self, name):
|
|
128
|
+
"""Add all valid Delayable subclasses that have name in their supports."""
|
|
129
|
+
for delayable in Delayable._all_subclasses():
|
|
130
|
+
if delayable.key not in self.delayables:
|
|
131
|
+
if name in delayable.supports and delayable.key != 'invalid':
|
|
132
|
+
self.add_delayable(delayable(self))
|
|
133
|
+
|
|
134
|
+
def delayable_enabled(self, delayable):
|
|
135
|
+
"""Returns True if delayable is currently added.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
delayable (Delayable or str): A Delayable instance or the key identifier.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
bool: Is the given delayable installed for this engine.
|
|
142
|
+
"""
|
|
143
|
+
if isinstance(delayable, Delayable):
|
|
144
|
+
delayable = delayable.key
|
|
145
|
+
return delayable in self.delayables
|
|
146
|
+
|
|
147
|
+
def enqueue(self, document, key, *args):
|
|
148
|
+
# Only add a item to be processed if we have a delayable that can
|
|
149
|
+
# process the requested key.
|
|
150
|
+
if key in self.delayables:
|
|
151
|
+
# There is only ever one instance of a delayable class processed
|
|
152
|
+
# If we already have a class enqueued, merge the arguments so we
|
|
153
|
+
# don't end up loosing some processing.
|
|
154
|
+
if key in document.delayable_info:
|
|
155
|
+
args = self.delayables[key].merge_args(
|
|
156
|
+
document.delayable_info[key], args
|
|
157
|
+
)
|
|
158
|
+
document.delayable_info[key] = args
|
|
159
|
+
if not self.timer.isActive():
|
|
160
|
+
self.timer.start()
|
|
161
|
+
self.processed = []
|
|
162
|
+
self.processing_time = []
|
|
163
|
+
self.skipped = []
|
|
164
|
+
return True
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
def expired(self):
|
|
168
|
+
return time.time() - self.start_time > self.maxLoopTime
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def instance(cls, name, parent=None, interval=0):
|
|
172
|
+
"""Returns a shared instance of DelayableEngine, creating the instance
|
|
173
|
+
if needed.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
name (str): The name of the delayable engine to get the instance of.
|
|
177
|
+
parent (QWidget, optional): If a new instance is created, use this
|
|
178
|
+
as its parent. Ignored otherwise.
|
|
179
|
+
interval (int, optional): If a new instance is created, use this as
|
|
180
|
+
its interval value. Defaults to zero.
|
|
181
|
+
"""
|
|
182
|
+
if name not in cls._instance:
|
|
183
|
+
cls._instance[name] = cls(name, parent=parent, interval=interval)
|
|
184
|
+
return cls._instance[name]
|
|
185
|
+
|
|
186
|
+
def loop(self): # noqa C901
|
|
187
|
+
self.start_time = time.time()
|
|
188
|
+
documents = list(self.documents)
|
|
189
|
+
# offset documents by the document_index so we can pickup where we left off
|
|
190
|
+
documents = documents[self.document_index :] + documents[: self.document_index]
|
|
191
|
+
|
|
192
|
+
count = 0
|
|
193
|
+
skipped = 0
|
|
194
|
+
finished = True
|
|
195
|
+
first_loop = True
|
|
196
|
+
while not self.expired():
|
|
197
|
+
for document in documents:
|
|
198
|
+
self.document_index += 1
|
|
199
|
+
if self.document_index >= len(documents):
|
|
200
|
+
self.document_index = 0
|
|
201
|
+
|
|
202
|
+
if not QtCompat.isValid(document):
|
|
203
|
+
if document in self.documents:
|
|
204
|
+
self.documents.remove(document)
|
|
205
|
+
print('Removing deleted document')
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
if not document.delayable_info:
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
if not document.isVisible() and first_loop:
|
|
212
|
+
skipped += 1
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
keys = list(document.delayable_info.keys())
|
|
216
|
+
keys = keys[self.delayable_index :] + keys[: self.delayable_index]
|
|
217
|
+
for key in keys:
|
|
218
|
+
self.delayable_index += 1
|
|
219
|
+
if self.delayable_index > len(keys):
|
|
220
|
+
self.delayable_index = 0
|
|
221
|
+
|
|
222
|
+
# delayable_info should only have keys for delayables we can access.
|
|
223
|
+
delayable = self.delayables[key]
|
|
224
|
+
|
|
225
|
+
args = document.delayable_info[key]
|
|
226
|
+
try:
|
|
227
|
+
args = delayable.loop(document, *args)
|
|
228
|
+
except Exception:
|
|
229
|
+
warnings.warn('Error processing {}, canceling it'.format(key))
|
|
230
|
+
del document.delayable_info[key]
|
|
231
|
+
raise
|
|
232
|
+
if args:
|
|
233
|
+
document.delayable_info[key] = args
|
|
234
|
+
# We need to process more items
|
|
235
|
+
finished = False
|
|
236
|
+
else:
|
|
237
|
+
del document.delayable_info[key]
|
|
238
|
+
count += 1
|
|
239
|
+
if self.expired():
|
|
240
|
+
self.processed.append(count)
|
|
241
|
+
self.processing_time.append(time.time() - self.start_time)
|
|
242
|
+
self.skipped.append(skipped)
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
first_loop = False
|
|
246
|
+
|
|
247
|
+
self.processed.append(count)
|
|
248
|
+
self.processing_time.append(time.time() - self.start_time)
|
|
249
|
+
self.skipped.append(skipped)
|
|
250
|
+
if finished:
|
|
251
|
+
# Nothing else to do for now, just exit
|
|
252
|
+
self.timer.stop()
|
|
253
|
+
self.processing_finished.emit()
|
|
254
|
+
|
|
255
|
+
def remove_document(self, document):
|
|
256
|
+
"""Removes a document from being processed"""
|
|
257
|
+
if document in self.documents:
|
|
258
|
+
for delayable in self.delayables:
|
|
259
|
+
self.delayables[delayable].remove_document(document)
|
|
260
|
+
|
|
261
|
+
self.documents.remove(document)
|
|
262
|
+
document.delayable_engine = type(self).instance('default')
|
|
263
|
+
|
|
264
|
+
def remove_delayable(self, delayable):
|
|
265
|
+
"""Removes a Delayable subclass instance for processing in this engine.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
delayable (Delayable or str): A Delayable instance or the key identifier.
|
|
269
|
+
Remove this delayable from the current documents if it was added.
|
|
270
|
+
"""
|
|
271
|
+
if isinstance(delayable, str):
|
|
272
|
+
if delayable not in self.delayables:
|
|
273
|
+
return
|
|
274
|
+
delayable = self.delayables[delayable]
|
|
275
|
+
if delayable:
|
|
276
|
+
for document in self.documents:
|
|
277
|
+
delayable.remove_document(document)
|
|
278
|
+
# Stop processing this delayable if it's currently processing.
|
|
279
|
+
try:
|
|
280
|
+
del document.delayable_info[delayable.key]
|
|
281
|
+
except KeyError:
|
|
282
|
+
pass
|
|
283
|
+
self.delayables.pop(delayable.key)
|
|
284
|
+
|
|
285
|
+
def set_delayable_enabled(self, delayable, enabled):
|
|
286
|
+
"""Add or remove the delayable provided.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
delayable (Delayable or str): A Delayable instance or the key identifier.
|
|
290
|
+
enabled (bool): If True installs delayable, if False removes delayable.
|
|
291
|
+
|
|
292
|
+
See Also:
|
|
293
|
+
:py:meth:`DelayableEngine.add_delayable` and
|
|
294
|
+
:py:meth:`DelayableEngine.remove_delayable`
|
|
295
|
+
|
|
296
|
+
Raises:
|
|
297
|
+
KeyError: A invalid key identifier string was passed.
|
|
298
|
+
"""
|
|
299
|
+
if enabled:
|
|
300
|
+
self.add_delayable(delayable)
|
|
301
|
+
else:
|
|
302
|
+
self.remove_delayable(delayable)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
|
2
|
+
|
|
3
|
+
from Qt.QtCore import QObject
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Delayable(QObject):
|
|
7
|
+
key = 'invalid'
|
|
8
|
+
supports = ('ide', 'workbox')
|
|
9
|
+
|
|
10
|
+
def __init__(self, engine):
|
|
11
|
+
self.engine = engine
|
|
12
|
+
super(Delayable, self).__init__()
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def _all_subclasses(cls):
|
|
16
|
+
return cls.__subclasses__() + [
|
|
17
|
+
g for s in cls.__subclasses__() for g in s._all_subclasses()
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
def add_document(self, document):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def loop(self, document, *args):
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
def merge_args(self, args1, args2):
|
|
27
|
+
return args2
|
|
28
|
+
|
|
29
|
+
def remove_document(self, document):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RangeDelayable(Delayable):
|
|
34
|
+
"""Delayable designed to take a start and stop range as its first arguments."""
|
|
35
|
+
|
|
36
|
+
def merge_args(self, args1, args2):
|
|
37
|
+
"""Uses the lowest start argument value. The end argument returns None if
|
|
38
|
+
one of them is None otherwise the largest is returned. All other arguments
|
|
39
|
+
of from args2 are used.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
args1 (tuple): The old arguments.
|
|
43
|
+
args2 (tuple): The new arguments.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
tuple: args2 with its first two arguments modified to the largest range.
|
|
47
|
+
"""
|
|
48
|
+
start1 = args1[0]
|
|
49
|
+
start2 = args2[0]
|
|
50
|
+
start = min(start1, start2)
|
|
51
|
+
|
|
52
|
+
end1 = args1[1]
|
|
53
|
+
end2 = args2[1]
|
|
54
|
+
if end1 is None or end2 is None:
|
|
55
|
+
# Always prefer None for the end. It indicates that we want to
|
|
56
|
+
# go to the end of the document.
|
|
57
|
+
end = None
|
|
58
|
+
else:
|
|
59
|
+
end = max(end1, end2)
|
|
60
|
+
return (start, end) + args2[2:]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SearchDelayable(Delayable):
|
|
64
|
+
def loop(self, document, find_state):
|
|
65
|
+
start, end = document.find_text(find_state)
|
|
66
|
+
if find_state.wrapped:
|
|
67
|
+
# once we have wrapped, disable wrap
|
|
68
|
+
find_state.wrap = False
|
|
69
|
+
if start != -1:
|
|
70
|
+
self.text_found(document, start, end, find_state)
|
|
71
|
+
return (find_state,)
|
|
72
|
+
|
|
73
|
+
def search_from_position(self, document, find_state, position, *args):
|
|
74
|
+
if position is None:
|
|
75
|
+
position = document.positionFromLineIndex(*document.getCursorPosition())
|
|
76
|
+
# Start searching from position, wrap past the end and stop where we started
|
|
77
|
+
find_state.start_pos = position
|
|
78
|
+
find_state.start_pos_original = position
|
|
79
|
+
find_state.wrap = True
|
|
80
|
+
find_state.wrapped = False
|
|
81
|
+
self.engine.enqueue(document, self.key, find_state, *args)
|
|
82
|
+
|
|
83
|
+
def text_found(self, document, start, end, find_state):
|
|
84
|
+
"""Called each time text is found."""
|
|
85
|
+
raise NotImplementedError('SearchDelayable.text_found should be subclassed.')
|