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
@@ -0,0 +1,131 @@
1
+ from __future__ import absolute_import, print_function
2
+
3
+ import sys
4
+ import traceback
5
+
6
+ from Qt import QtCompat
7
+
8
+ from . import config, plugins
9
+ from .contexts import ErrorReport
10
+
11
+
12
+ class PreditorExceptHook(object):
13
+ """Replacement for `sys.excepthook` that adds error reporting features.
14
+
15
+ This calls each callable in the `preditor.config.excepthooks` list any time
16
+ `sys.excepthook` is called due to an raised exception.
17
+
18
+ If `config.excepthook` is empty when installing this class, it will
19
+ automatically add the `call_base_excepthook` and `ask_to_show_logger` methods.
20
+ This enables showing the excepthook in parent streams and prompting the user
21
+ to show PrEditor when an error happens. You can disable this by adding `None`
22
+ to the list before this class is initialized.
23
+ """
24
+
25
+ def __init__(self, base_excepthook=None):
26
+ self.base_excepthook = base_excepthook or sys.__excepthook__
27
+
28
+ # Add the default excepthooks
29
+ if not config.excepthooks and config.excepthooks != [None]:
30
+ config.excepthooks.append(self.call_base_excepthook)
31
+ config.excepthooks.append(self.ask_to_show_logger)
32
+
33
+ def __call__(self, *exc_info):
34
+ """Run when an exception is raised and calls all `config.excepthooks`."""
35
+ for plugin in config.excepthooks:
36
+ if plugin is None:
37
+ continue
38
+ plugin(*exc_info)
39
+
40
+ # Clear any ErrorReports that were generated by this exception handling
41
+ ErrorReport.clearReports()
42
+
43
+ def call_base_excepthook(self, *exc_info):
44
+ """Process `base_excepthook` supplied during object instantiation.
45
+
46
+ This is useful for showing the exception in the original excepthook that
47
+ PrEditor replaced when this class was installed.
48
+
49
+ A newline is printed pre-traceback to ensure the first line of output
50
+ is not printed in-line with the prompt. This also provides visual
51
+ separation between tracebacks, when received consecutively.
52
+ """
53
+ print("")
54
+ try:
55
+ self.base_excepthook(*exc_info)
56
+ except (TypeError, NameError):
57
+ sys.__excepthook__(*exc_info)
58
+
59
+ def ask_to_show_logger(self, *exc_info):
60
+ """Show a dialog asking the user how to handle the error."""
61
+ if config.error_dialog_class is True:
62
+ # Default to the base ErrorDialog class
63
+ from .gui.errordialog import ErrorDialog
64
+
65
+ config.error_dialog_class = ErrorDialog
66
+ elif isinstance(config.error_dialog_class, str):
67
+ # If passed an EntryPoint string load the EntryPoint
68
+ config.error_dialog_class = plugins.from_string(config.error_dialog_class)
69
+
70
+ # Handle cases where we shouldn't ask to show the logger.
71
+ if config.error_dialog_class is None:
72
+ return
73
+ elif not config.error_dialog_class.show_prompt(*exc_info):
74
+ return
75
+
76
+ from .gui.console import ConsolePrEdit
77
+ from .gui.loggerwindow import LoggerWindow
78
+
79
+ instance = LoggerWindow.instance(create=False)
80
+
81
+ if instance:
82
+ # logger reference deleted, fallback and print to console
83
+ if not QtCompat.isValid(instance):
84
+ print("[LoggerWindow] LoggerWindow object has been deleted.")
85
+ # TODO: This seems incorrect, what should it be printing?
86
+ print(traceback)
87
+ return
88
+
89
+ # logger is visible and check if it was minimized on windows
90
+ if instance.isVisible() and not instance.isMinimized():
91
+ if instance.uiAutoPromptACT.isChecked():
92
+ instance.console().startInputLine()
93
+ return
94
+
95
+ # error already prompted by exception currently being handled
96
+ if ConsolePrEdit._errorPrompted:
97
+ return
98
+
99
+ # Preemptively marking error as "prompted" (handled) to avoid errors
100
+ # from being raised multiple times due to C++ and/or threading error
101
+ # processing.
102
+ try:
103
+ ConsolePrEdit._errorPrompted = True
104
+ errorDialog = config.error_dialog_class(config.root_window())
105
+ errorDialog.setText(exc_info)
106
+ errorDialog.exec_()
107
+
108
+ # interrupted until dialog closed
109
+ finally:
110
+ ConsolePrEdit._errorPrompted = False
111
+
112
+ @classmethod
113
+ def install(cls, force=False):
114
+ """
115
+ Install PrEditor excepthook override, returning previously implemented
116
+ excepthook function.
117
+
118
+ Arguments:
119
+ force (bool): force re-installation of excepthook override when
120
+ already previously implemented.
121
+
122
+ Returns:
123
+ func: pre-override excepthook function
124
+ """
125
+ ErrorReport.enabled = True
126
+ prev_excepthook = sys.excepthook
127
+
128
+ if not isinstance(prev_excepthook, cls) or force:
129
+ sys.excepthook = cls(prev_excepthook)
130
+
131
+ return prev_excepthook
@@ -0,0 +1,93 @@
1
+ from __future__ import absolute_import
2
+
3
+ from functools import partial
4
+
5
+ from Qt.QtCore import Property
6
+ from Qt.QtWidgets import QStackedWidget
7
+
8
+ from .dialog import Dialog # noqa: F401
9
+ from .window import Window # noqa: F401
10
+
11
+
12
+ def QtPropertyInit(name, default, callback=None, typ=None):
13
+ """Initializes a default Property value with a usable getter and setter.
14
+
15
+ You can optionally pass a function that will get called any time the property
16
+ is set. If using the same callback for multiple properties, you may want to
17
+ use the preditor.decorators.singleShot decorator to prevent your function getting
18
+ called multiple times at once. This callback must accept the attribute name and
19
+ value being set.
20
+
21
+ Example:
22
+ class TestClass(QWidget):
23
+ def __init__(self, *args, **kwargs):
24
+ super(TestClass, self).__init__(*args, **kwargs)
25
+
26
+ stdoutColor = QtPropertyInit('_stdoutColor', QColor(0, 0, 255))
27
+ pyForegroundColor = QtPropertyInit('_pyForegroundColor', QColor(0, 0, 255))
28
+
29
+ Args:
30
+ name(str): The name of internal attribute to store to and lookup from.
31
+ default: The property's default value. This will also define the Property type
32
+ if typ is not set.
33
+ callback(callable): If provided this function is called when the property is
34
+ set.
35
+ typ (class, optional): If not None this value is used to specify the type of
36
+ the Property. This is useful when you need to specify a property as python's
37
+ object but pass a default value of a given class.
38
+
39
+ Returns:
40
+ Property
41
+ """
42
+
43
+ def _getattrDefault(default, self, attrName):
44
+ try:
45
+ value = getattr(self, attrName)
46
+ except AttributeError:
47
+ setattr(self, attrName, default)
48
+ return default
49
+ return value
50
+
51
+ def _setattrCallback(callback, attrName, self, value):
52
+ setattr(self, attrName, value)
53
+ if callback:
54
+ callback(self, attrName, value)
55
+
56
+ ga = partial(_getattrDefault, default)
57
+ sa = partial(_setattrCallback, callback, name)
58
+ # Use the default value's class if typ is not provided.
59
+ if typ is None:
60
+ typ = default.__class__
61
+ return Property(typ, fget=(lambda s: ga(s, name)), fset=(lambda s, v: sa(s, v)))
62
+
63
+
64
+ def loadUi(filename, widget, uiname=''):
65
+ """use's Qt's uic loader to load dynamic interafces onto the inputed widget
66
+
67
+ Args:
68
+ filename (str): The python filename. Its basename will be split off, and a
69
+ ui folder will be added. The file ext will be changed to .ui
70
+ widget (QWidget): The basewidget the ui file will be loaded onto.
71
+ uiname (str, optional): Used instead of the basename. This is useful if
72
+ filename is not the same as the ui file you want to load.
73
+ """
74
+ import os.path
75
+
76
+ from Qt import QtCompat
77
+
78
+ # first, inherit the palette of the parent
79
+ if widget.parent():
80
+ widget.setPalette(widget.parent().palette())
81
+
82
+ if not uiname:
83
+ uiname = os.path.basename(filename).split('.')[0]
84
+
85
+ QtCompat.loadUi(os.path.split(filename)[0] + '/ui/%s.ui' % uiname, widget)
86
+
87
+
88
+ def tab_widget_for_tab(tab_widget):
89
+ """Returns the `QTabWidget` `tab_widget` is parented to or `None`."""
90
+ tab_parent = tab_widget.parent()
91
+ if not isinstance(tab_parent, QStackedWidget):
92
+ return None
93
+ return tab_parent.parent()
preditor/gui/app.py ADDED
@@ -0,0 +1,160 @@
1
+ from __future__ import absolute_import
2
+
3
+ import logging
4
+ import os
5
+
6
+ import Qt
7
+ from Qt.QtGui import QIcon
8
+ from Qt.QtWidgets import QApplication, QDialog, QMainWindow, QSplashScreen
9
+
10
+ from .. import DEFAULT_CORE_NAME, resourcePath, settings
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class App(object):
16
+ """Used to create and configure the QApplication instance.
17
+
18
+ Args:
19
+ name (str, optional): Set the QApplication application name to this value.
20
+ args (list, optional): The arguments used to instantiate the QApplication
21
+ if one isn't already initialized.
22
+ app (QApplication, optional): An instance of a QApplication to use
23
+ instead of creating one.
24
+
25
+ Raises:
26
+ RuntimeError: If DISPLAY is not set when running on linux, or the
27
+ current application isn't a instance of QApplication(ie using a
28
+ QCoreApplication).
29
+ """
30
+
31
+ def __init__(self, name=None, args=None, app=None):
32
+ # Used to track if this instance had to create the QApplication or if it
33
+ # was already created
34
+ self.app_created = False
35
+ # If we made the QApplication, did we call exec_ already?
36
+ self.app_has_exec = False
37
+
38
+ if app is None:
39
+ app = QApplication.instance()
40
+
41
+ self.app = app
42
+
43
+ if not self.app:
44
+ # Check for headless environment's
45
+ if settings.OS_TYPE == 'Linux' and os.environ.get('DISPLAY') is None:
46
+ raise RuntimeError(
47
+ 'The PrEditor gui can not run in a headless environment.'
48
+ )
49
+
50
+ if args is None:
51
+ args = []
52
+ # create a new application
53
+ if self.app is None:
54
+ args.extend(self.dpi_awareness_args())
55
+ self.app = QApplication(args)
56
+ self.app_created = True
57
+ # If we are creating the application, configure it
58
+ self.configure_standalone()
59
+
60
+ if not isinstance(self.app, QApplication):
61
+ raise RuntimeError(
62
+ "PrEditor's gui can only be run using a QApplication instance."
63
+ )
64
+
65
+ if self.app and name:
66
+ # If a application name was passed, update the QApplication's
67
+ # application name.
68
+ self.app.setApplicationName(name)
69
+ self.set_app_id(name)
70
+
71
+ def configure_standalone(self):
72
+ """Update the QApplication for standalone running. Updates the icon and
73
+ sets its style to default_style_name."""
74
+ self.app.setWindowIcon(QIcon(resourcePath('img/preditor.png')))
75
+ self.app.setStyle(self.default_style_name())
76
+
77
+ @staticmethod
78
+ def default_style_name():
79
+ """The default style name used when setting up the QApplication.
80
+
81
+ In Qt4 this is Plastique, in Qt5 this is Fusion.
82
+ """
83
+
84
+ if Qt.IsPyQt4 or Qt.IsPySide:
85
+ return 'Plastique'
86
+ return 'Fusion'
87
+
88
+ @classmethod
89
+ def dpi_awareness_args(cls):
90
+ """On windows sets dpiawareness platform flag to 0 to enable
91
+ per-monitor scaling support in Qt.
92
+
93
+ Returns:
94
+ args: Extend the arguments used to intialize the QApplication.
95
+ """
96
+ if settings.OS_TYPE == "Windows" and Qt.IsPyQt5:
97
+ # Make Qt automatically scale based on the monitor the window is
98
+ # currently located.
99
+ return ["--platform", "windows:dpiawareness=0"]
100
+ return []
101
+
102
+ @classmethod
103
+ def root_window(cls):
104
+ """Returns the currently active window. Attempts to find the top level
105
+ QMainWindow or QDialog for the current Qt application.
106
+ """
107
+ inst = QApplication.instance()
108
+ root_window = None
109
+ if inst:
110
+ root_window = inst.activeWindow()
111
+ # Ignore QSplashScreen's, they should never be considered the root window.
112
+ if isinstance(root_window, QSplashScreen):
113
+ root_window = None
114
+
115
+ # If the application does not have focus try to find A top level widget
116
+ # that doesn't have a parent and is a QMainWindow or QDialog
117
+ if root_window is None:
118
+ windows = []
119
+ dialogs = []
120
+ for w in inst.topLevelWidgets():
121
+ if w.parent() is None:
122
+ if isinstance(w, QMainWindow):
123
+ windows.append(w)
124
+ elif isinstance(w, QDialog):
125
+ dialogs.append(w)
126
+ if windows:
127
+ root_window = windows[0]
128
+ elif dialogs:
129
+ root_window = dialogs[0]
130
+
131
+ # grab the root window
132
+ if root_window:
133
+ while root_window.parent():
134
+ parent = root_window.parent()
135
+ if isinstance(parent, QSplashScreen):
136
+ return root_window
137
+ else:
138
+ root_window = parent
139
+ return root_window
140
+
141
+ @classmethod
142
+ def set_app_id(cls, app_id=DEFAULT_CORE_NAME):
143
+ if settings.OS_TYPE == "Windows":
144
+ # Set the app user model id here not in the window class so it doesn't
145
+ # try to set the app id for applications that already set the app id.
146
+ try:
147
+ from casement.app_id import AppId
148
+ except ImportError:
149
+ logger.debug(
150
+ "Unable to configure taskbar grouping, use `pip install "
151
+ "casement` to enable this."
152
+ )
153
+ else:
154
+ AppId.set_for_application(app_id)
155
+
156
+ def start(self):
157
+ """Exec's the QApplication if it hasn't already been started."""
158
+ if self.app_created and self.app and not self.app_has_exec:
159
+ self.app_has_exec = True
160
+ self.app.exec_()
@@ -0,0 +1,209 @@
1
+ from __future__ import absolute_import
2
+
3
+ import json
4
+ import os
5
+ import re
6
+
7
+ from Qt.QtCore import QRegExp
8
+ from Qt.QtGui import QColor, QSyntaxHighlighter, QTextCharFormat
9
+
10
+ from .. import resourcePath
11
+
12
+
13
+ class CodeHighlighter(QSyntaxHighlighter):
14
+ def __init__(self, widget):
15
+ super(CodeHighlighter, self).__init__(widget)
16
+
17
+ # setup the search rules
18
+ self._keywords = []
19
+ self._strings = []
20
+ self._comments = []
21
+ self._consoleMode = False
22
+ # color storage
23
+ self._commentColor = QColor(0, 206, 52)
24
+ self._keywordColor = QColor(17, 154, 255)
25
+ self._stringColor = QColor(255, 128, 0)
26
+ self._resultColor = QColor(125, 128, 128)
27
+
28
+ # setup the font
29
+ font = widget.font()
30
+ font.setFamily('Courier New')
31
+ widget.setFont(font)
32
+
33
+ def commentColor(self):
34
+ # pull the color from the parent if possible because this doesn't support
35
+ # stylesheets
36
+ parent = self.parent()
37
+ if parent and hasattr(parent, 'commentColor'):
38
+ return parent.commentColor
39
+ return self._commentColor
40
+
41
+ def setCommentColor(self, color):
42
+ # set the color for the parent if possible because this doesn't support
43
+ # stylesheets
44
+ parent = self.parent()
45
+ if parent and hasattr(parent, 'commentColor'):
46
+ parent.commentColor = color
47
+ self._commentColor = color
48
+
49
+ def commentFormat(self):
50
+ """returns the comments QTextCharFormat for this highlighter"""
51
+ format = QTextCharFormat()
52
+ format.setForeground(self.commentColor())
53
+ format.setFontItalic(True)
54
+
55
+ return format
56
+
57
+ def isConsoleMode(self):
58
+ """checks to see if this highlighter is in console mode"""
59
+ return self._consoleMode
60
+
61
+ def highlightBlock(self, text):
62
+ """highlights the inputed text block based on the rules of this code
63
+ highlighter"""
64
+ if not self.isConsoleMode() or str(text).startswith('>>>'):
65
+ # format the result lines
66
+ format = self.resultFormat()
67
+ parent = self.parent()
68
+ if parent and hasattr(parent, 'outputPrompt'):
69
+ self.highlightText(
70
+ text,
71
+ QRegExp('%s[^\\n]*' % re.escape(parent.outputPrompt())),
72
+ format,
73
+ )
74
+
75
+ # format the keywords
76
+ format = self.keywordFormat()
77
+ for kwd in self._keywords:
78
+ self.highlightText(text, QRegExp(r'\b%s\b' % kwd), format)
79
+
80
+ # format the strings
81
+ format = self.stringFormat()
82
+ for string in self._strings:
83
+ self.highlightText(
84
+ text,
85
+ QRegExp('%s[^%s]*' % (string, string)),
86
+ format,
87
+ includeLast=True,
88
+ )
89
+
90
+ # format the comments
91
+ format = self.commentFormat()
92
+ for comment in self._comments:
93
+ self.highlightText(text, QRegExp(comment), format)
94
+
95
+ def highlightText(self, text, expr, format, offset=0, includeLast=False):
96
+ """Highlights a text group with an expression and format
97
+
98
+ Args:
99
+ text (str): text to highlight
100
+ expr (QRegExp): search parameter
101
+ format (QTextCharFormat): formatting rule
102
+ offset (int): number of characters to offset by when highlighting
103
+ includeLast (bool): whether or not the last character should be highlighted
104
+ """
105
+ pos = expr.indexIn(text, 0)
106
+
107
+ # highlight all the given matches to the expression in the text
108
+ while pos != -1:
109
+ pos = expr.pos(offset)
110
+ length = len(expr.cap(offset))
111
+
112
+ # use the last character if desired
113
+ if includeLast:
114
+ length += 1
115
+
116
+ # set the formatting
117
+ self.setFormat(pos, length, format)
118
+
119
+ matched = expr.matchedLength()
120
+ if includeLast:
121
+ matched += 1
122
+
123
+ pos = expr.indexIn(text, pos + matched)
124
+
125
+ def keywordColor(self):
126
+ # pull the color from the parent if possible because this doesn't support
127
+ # stylesheets
128
+ parent = self.parent()
129
+ if parent and hasattr(parent, 'keywordColor'):
130
+ return parent.keywordColor
131
+ return self._keywordColor
132
+
133
+ def setKeywordColor(self, color):
134
+ # set the color for the parent if possible because this doesn't support
135
+ # stylesheets
136
+ parent = self.parent()
137
+ if parent and hasattr(parent, 'keywordColor'):
138
+ parent.keywordColor = color
139
+ self._keywordColor = color
140
+
141
+ def keywordFormat(self):
142
+ """returns the keywords QTextCharFormat for this highlighter"""
143
+ format = QTextCharFormat()
144
+ format.setForeground(self.keywordColor())
145
+
146
+ return format
147
+
148
+ def resultColor(self):
149
+ # pull the color from the parent if possible because this doesn't support
150
+ # stylesheets
151
+ parent = self.parent()
152
+ if parent and hasattr(parent, 'resultColor'):
153
+ return parent.resultColor
154
+ return self._resultColor
155
+
156
+ def setResultColor(self, color):
157
+ # set the color for the parent if possible because this doesn't support
158
+ # stylesheets
159
+ parent = self.parent()
160
+ if parent and hasattr(parent, 'resultColor'):
161
+ parent.resultColor = color
162
+ self._resultColor = color
163
+
164
+ def resultFormat(self):
165
+ """returns the result QTextCharFormat for this highlighter"""
166
+ fmt = QTextCharFormat()
167
+ fmt.setForeground(self.resultColor())
168
+ return fmt
169
+
170
+ def setConsoleMode(self, state=False):
171
+ """sets the highlighter to only apply to console strings
172
+ (lines starting with >>>)
173
+ """
174
+ self._consoleMode = state
175
+
176
+ def setLanguage(self, lang):
177
+ """sets the language of the highlighter by loading the json definition"""
178
+ filename = resourcePath('lang/%s.json' % lang.lower())
179
+ if os.path.exists(filename):
180
+ data = json.load(open(filename))
181
+ self.setObjectName(data.get('name', ''))
182
+ self._keywords = data.get('keywords', [])
183
+ self._comments = data.get('comments', [])
184
+ self._strings = data.get('strings', [])
185
+
186
+ return True
187
+ return False
188
+
189
+ def stringColor(self):
190
+ # pull the color from the parent if possible because this doesn't support
191
+ # stylesheets
192
+ parent = self.parent()
193
+ if parent and hasattr(parent, 'stringColor'):
194
+ return parent.stringColor
195
+ return self._stringColor
196
+
197
+ def setStringColor(self, color):
198
+ # set the color for the parent if possible because this doesn't support
199
+ # stylesheets
200
+ parent = self.parent()
201
+ if parent and hasattr(parent, 'stringColor'):
202
+ parent.stringColor = color
203
+ self._stringColor = color
204
+
205
+ def stringFormat(self):
206
+ """returns the keywords QTextCharFormat for this highligter"""
207
+ format = QTextCharFormat()
208
+ format.setForeground(self.stringColor())
209
+ return format