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.

Files changed (158) hide show
  1. preditor/__init__.py +322 -0
  2. preditor/__main__.py +13 -0
  3. preditor/about_module.py +161 -0
  4. preditor/cli.py +192 -0
  5. preditor/config.py +302 -0
  6. preditor/contexts.py +119 -0
  7. preditor/cores/__init__.py +0 -0
  8. preditor/cores/core.py +20 -0
  9. preditor/dccs/maya/PrEditor_maya.mod +2 -0
  10. preditor/dccs/maya/plug-ins/PrEditor_maya.py +110 -0
  11. preditor/debug.py +144 -0
  12. preditor/delayable_engine/__init__.py +302 -0
  13. preditor/delayable_engine/delayables.py +85 -0
  14. preditor/enum.py +728 -0
  15. preditor/excepthooks.py +131 -0
  16. preditor/gui/__init__.py +93 -0
  17. preditor/gui/app.py +160 -0
  18. preditor/gui/codehighlighter.py +209 -0
  19. preditor/gui/completer.py +226 -0
  20. preditor/gui/console.py +867 -0
  21. preditor/gui/dialog.py +178 -0
  22. preditor/gui/drag_tab_bar.py +190 -0
  23. preditor/gui/editor_chooser.py +57 -0
  24. preditor/gui/errordialog.py +68 -0
  25. preditor/gui/find_files.py +125 -0
  26. preditor/gui/fuzzy_search/__init__.py +0 -0
  27. preditor/gui/fuzzy_search/fuzzy_search.py +93 -0
  28. preditor/gui/group_tab_widget/__init__.py +325 -0
  29. preditor/gui/group_tab_widget/grouped_tab_menu.py +35 -0
  30. preditor/gui/group_tab_widget/grouped_tab_models.py +108 -0
  31. preditor/gui/group_tab_widget/grouped_tab_widget.py +78 -0
  32. preditor/gui/group_tab_widget/one_tab_widget.py +54 -0
  33. preditor/gui/level_buttons.py +343 -0
  34. preditor/gui/logger_window_handler.py +48 -0
  35. preditor/gui/logger_window_plugin.py +32 -0
  36. preditor/gui/loggerwindow.py +1385 -0
  37. preditor/gui/newtabwidget.py +69 -0
  38. preditor/gui/set_text_editor_path_dialog.py +59 -0
  39. preditor/gui/status_label.py +99 -0
  40. preditor/gui/suggest_path_quotes_dialog.py +50 -0
  41. preditor/gui/ui/editor_chooser.ui +93 -0
  42. preditor/gui/ui/errordialog.ui +74 -0
  43. preditor/gui/ui/find_files.ui +140 -0
  44. preditor/gui/ui/loggerwindow.ui +1105 -0
  45. preditor/gui/ui/set_text_editor_path_dialog.ui +189 -0
  46. preditor/gui/ui/suggest_path_quotes_dialog.ui +225 -0
  47. preditor/gui/window.py +161 -0
  48. preditor/gui/workbox_mixin.py +389 -0
  49. preditor/gui/workbox_text_edit.py +137 -0
  50. preditor/gui/workboxwidget.py +298 -0
  51. preditor/logging_config.py +52 -0
  52. preditor/osystem.py +401 -0
  53. preditor/plugins.py +118 -0
  54. preditor/prefs.py +74 -0
  55. preditor/resource/environment_variables.html +26 -0
  56. preditor/resource/error_mail.html +85 -0
  57. preditor/resource/error_mail_inline.html +41 -0
  58. preditor/resource/img/README.md +17 -0
  59. preditor/resource/img/arrow_forward.png +0 -0
  60. preditor/resource/img/check-bold.png +0 -0
  61. preditor/resource/img/chevron-down.png +0 -0
  62. preditor/resource/img/chevron-up.png +0 -0
  63. preditor/resource/img/close-thick.png +0 -0
  64. preditor/resource/img/comment-edit.png +0 -0
  65. preditor/resource/img/content-copy.png +0 -0
  66. preditor/resource/img/content-cut.png +0 -0
  67. preditor/resource/img/content-duplicate.png +0 -0
  68. preditor/resource/img/content-paste.png +0 -0
  69. preditor/resource/img/content-save.png +0 -0
  70. preditor/resource/img/debug_disabled.png +0 -0
  71. preditor/resource/img/eye-check.png +0 -0
  72. preditor/resource/img/file-plus.png +0 -0
  73. preditor/resource/img/file-remove.png +0 -0
  74. preditor/resource/img/format-align-left.png +0 -0
  75. preditor/resource/img/format-letter-case-lower.png +0 -0
  76. preditor/resource/img/format-letter-case-upper.png +0 -0
  77. preditor/resource/img/format-letter-case.svg +1 -0
  78. preditor/resource/img/information.png +0 -0
  79. preditor/resource/img/logging_critical.png +0 -0
  80. preditor/resource/img/logging_custom.png +0 -0
  81. preditor/resource/img/logging_debug.png +0 -0
  82. preditor/resource/img/logging_error.png +0 -0
  83. preditor/resource/img/logging_info.png +0 -0
  84. preditor/resource/img/logging_not_set.png +0 -0
  85. preditor/resource/img/logging_warning.png +0 -0
  86. preditor/resource/img/marker.png +0 -0
  87. preditor/resource/img/play.png +0 -0
  88. preditor/resource/img/playlist-play.png +0 -0
  89. preditor/resource/img/plus-minus-variant.png +0 -0
  90. preditor/resource/img/preditor.ico +0 -0
  91. preditor/resource/img/preditor.png +0 -0
  92. preditor/resource/img/preditor.psd +0 -0
  93. preditor/resource/img/preditor.svg +44 -0
  94. preditor/resource/img/regex.svg +1 -0
  95. preditor/resource/img/restart.svg +1 -0
  96. preditor/resource/img/skip-forward-outline.png +0 -0
  97. preditor/resource/img/skip-next-outline.png +0 -0
  98. preditor/resource/img/skip-next.png +0 -0
  99. preditor/resource/img/skip-previous.png +0 -0
  100. preditor/resource/img/subdirectory-arrow-right.png +0 -0
  101. preditor/resource/img/text-search-variant.png +0 -0
  102. preditor/resource/img/warning-big.png +0 -0
  103. preditor/resource/lang/python.json +30 -0
  104. preditor/resource/settings.ini +25 -0
  105. preditor/resource/stylesheet/Bright.css +65 -0
  106. preditor/resource/stylesheet/Dark.css +199 -0
  107. preditor/scintilla/__init__.py +22 -0
  108. preditor/scintilla/delayables/__init__.py +11 -0
  109. preditor/scintilla/delayables/smart_highlight.py +94 -0
  110. preditor/scintilla/delayables/spell_check.py +173 -0
  111. preditor/scintilla/documenteditor.py +2038 -0
  112. preditor/scintilla/finddialog.py +68 -0
  113. preditor/scintilla/lang/__init__.py +80 -0
  114. preditor/scintilla/lang/config/bash.ini +15 -0
  115. preditor/scintilla/lang/config/batch.ini +14 -0
  116. preditor/scintilla/lang/config/cpp.ini +19 -0
  117. preditor/scintilla/lang/config/css.ini +19 -0
  118. preditor/scintilla/lang/config/eyeonscript.ini +17 -0
  119. preditor/scintilla/lang/config/html.ini +21 -0
  120. preditor/scintilla/lang/config/javascript.ini +24 -0
  121. preditor/scintilla/lang/config/lua.ini +16 -0
  122. preditor/scintilla/lang/config/maxscript.ini +20 -0
  123. preditor/scintilla/lang/config/mel.ini +18 -0
  124. preditor/scintilla/lang/config/mu.ini +22 -0
  125. preditor/scintilla/lang/config/nsi.ini +19 -0
  126. preditor/scintilla/lang/config/perl.ini +19 -0
  127. preditor/scintilla/lang/config/puppet.ini +19 -0
  128. preditor/scintilla/lang/config/python.ini +28 -0
  129. preditor/scintilla/lang/config/ruby.ini +19 -0
  130. preditor/scintilla/lang/config/sql.ini +7 -0
  131. preditor/scintilla/lang/config/xml.ini +21 -0
  132. preditor/scintilla/lang/config/yaml.ini +18 -0
  133. preditor/scintilla/lang/language.py +240 -0
  134. preditor/scintilla/lexers/__init__.py +0 -0
  135. preditor/scintilla/lexers/cpplexer.py +21 -0
  136. preditor/scintilla/lexers/javascriptlexer.py +25 -0
  137. preditor/scintilla/lexers/maxscriptlexer.py +234 -0
  138. preditor/scintilla/lexers/mellexer.py +368 -0
  139. preditor/scintilla/lexers/mulexer.py +32 -0
  140. preditor/scintilla/lexers/pythonlexer.py +41 -0
  141. preditor/scintilla/ui/finddialog.ui +160 -0
  142. preditor/settings.py +71 -0
  143. preditor/stream/__init__.py +80 -0
  144. preditor/stream/director.py +73 -0
  145. preditor/stream/manager.py +74 -0
  146. preditor/streamhandler_helper.py +46 -0
  147. preditor/utils/__init__.py +0 -0
  148. preditor/utils/cute.py +30 -0
  149. preditor/utils/stylesheets.py +54 -0
  150. preditor/utils/text_search.py +342 -0
  151. preditor/version.py +21 -0
  152. preditor/weakref.py +363 -0
  153. preditor-1.0.0.dist-info/METADATA +224 -0
  154. preditor-1.0.0.dist-info/RECORD +158 -0
  155. preditor-1.0.0.dist-info/WHEEL +5 -0
  156. preditor-1.0.0.dist-info/entry_points.txt +18 -0
  157. preditor-1.0.0.dist-info/licenses/LICENSE +165 -0
  158. 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.')