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
@@ -0,0 +1,1924 @@
1
+ ##
2
+ #
3
+ # \remarks This dialog allows the user to create new python classes and packages
4
+ # based on plugin templates
5
+ #
6
+ # \author beta@blur.com
7
+ # \author Blur Studio
8
+ # \date 08/19/10
9
+ #
10
+ from __future__ import absolute_import
11
+
12
+ import logging
13
+ import os.path
14
+ import re
15
+ import string
16
+ import sys
17
+ from collections import OrderedDict
18
+ from contextlib import contextmanager
19
+ from functools import partial
20
+
21
+ import Qt as Qt_py
22
+ from Qt.QtCore import Property, QPoint, Qt
23
+ from Qt.QtGui import QColor, QFont, QFontMetrics, QIcon, QKeyEvent, QKeySequence
24
+ from Qt.QtWidgets import (
25
+ QAction,
26
+ QApplication,
27
+ QInputDialog,
28
+ QMenu,
29
+ QMessageBox,
30
+ QShortcut,
31
+ )
32
+
33
+ from .. import osystem, resourcePath
34
+ from ..delayable_engine import DelayableEngine
35
+ from ..enum import Enum, EnumGroup
36
+ from ..utils.cute import QtPropertyInit
37
+ from . import QsciScintilla, lang
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ class SearchDirection(EnumGroup):
43
+ First = Enum()
44
+ Forward = Enum()
45
+ Backward = Enum()
46
+
47
+
48
+ class SearchOptions(EnumGroup):
49
+ Backward = Enum()
50
+ CaseSensitive = Enum()
51
+ WholeWords = Enum()
52
+ QRegExp = Enum()
53
+
54
+
55
+ @contextmanager
56
+ def undo_step(editor):
57
+ """Context manager that combines all changes performed inside it as a
58
+ single undo action for the document editor."""
59
+ editor.beginUndoAction()
60
+ try:
61
+ yield
62
+ finally:
63
+ editor.endUndoAction()
64
+
65
+
66
+ class DocumentEditor(QsciScintilla):
67
+ _defaultFont = QFont()
68
+ _defaultFont.fromString('Courier New,9,-1,5,50,0,0,0,1,0')
69
+
70
+ def __init__(self, parent, filename='', lineno=0, delayable_engine='default'):
71
+ super(DocumentEditor, self).__init__(parent)
72
+ self.setObjectName('DocumentEditor')
73
+ # Spell check variables
74
+ self.__speller__ = None
75
+ self.pos = None
76
+ self.anchor = None
77
+
78
+ self.ctrlIsPressed = False
79
+
80
+ # create custom properties
81
+ self._filename = ''
82
+ self._language = ''
83
+ self._defaultLanguage = ""
84
+ self._lastSearch = ''
85
+ self._encoding = 'utf-8'
86
+ self._marginsFont = self._defaultFont
87
+ self._lastSearchDirection = SearchDirection.First
88
+ self._saveTimer = 0.0
89
+ # QSci doesnt provide accessors to these values, so store them internally
90
+ self._foldMarginBackgroundColor = QColor(224, 224, 224)
91
+ self._foldMarginForegroundColor = QColor(Qt.GlobalColor.white)
92
+ self._marginsBackgroundColor = QColor(224, 224, 224)
93
+ self._marginsForegroundColor = QColor()
94
+ self._matchedBraceBackgroundColor = QColor(224, 224, 224)
95
+ self._matchedBraceForegroundColor = QColor()
96
+ self._unmatchedBraceBackgroundColor = QColor(Qt.GlobalColor.white)
97
+ self._unmatchedBraceForegroundColor = QColor(Qt.GlobalColor.blue)
98
+ self._caretForegroundColor = QColor()
99
+ self._caretBackgroundColor = QColor(255, 255, 255, 255)
100
+ self._selectionBackgroundColor = QColor(192, 192, 192)
101
+ self._selectionForegroundColor = QColor(Qt.GlobalColor.black)
102
+ self._indentationGuidesBackgroundColor = QColor(Qt.GlobalColor.white)
103
+ self._indentationGuidesForegroundColor = QColor(Qt.GlobalColor.black)
104
+ self._markerBackgroundColor = QColor(Qt.GlobalColor.white)
105
+ self._markerForegroundColor = QColor(Qt.GlobalColor.black)
106
+
107
+ # Setup the DelayableEngine and add the document to it
108
+ self.delayable_info = OrderedDict()
109
+ self.delayable_engine = DelayableEngine.instance(delayable_engine)
110
+ self.delayable_engine.add_document(self)
111
+ # ------------------------------------------------------------------------------
112
+ # used to store the right click location
113
+ self._clickPos = None
114
+ # dialog shown is used to prevent showing multiple versions of the of the
115
+ # confirmation dialog. this is caused because multiple signals are emitted and
116
+ # processed.
117
+ # used to store perminately highlighted keywords
118
+ self._permaHighlight = []
119
+ self.setSmartHighlightingRegEx()
120
+
121
+ # intialize settings
122
+ self.initSettings(first_time=True)
123
+
124
+ # set one time properties
125
+ self.setFolding(QsciScintilla.FoldStyle.BoxedTreeFoldStyle)
126
+ self.setBraceMatching(QsciScintilla.BraceMatch.SloppyBraceMatch)
127
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
128
+ self.setAcceptDrops(False)
129
+ # Not supported by older builds of QsciScintilla
130
+ if hasattr(self, 'setTabDrawMode'):
131
+ self.setTabDrawMode(QsciScintilla.TabDrawMode.TabStrikeOut)
132
+
133
+ # create connections
134
+ self.customContextMenuRequested.connect(self.showMenu)
135
+ self.selectionChanged.connect(self.updateSelectionInfo)
136
+ window = self.window()
137
+ if hasattr(window, 'styleSheetChanged'):
138
+ window.styleSheetChanged.connect(self.updateColorScheme)
139
+
140
+ # Create shortcuts
141
+ icon = QIcon(resourcePath('img/content-copy.png'))
142
+
143
+ # We have to re-create the copy shortcut so we can use our implementation
144
+ self.uiCopyACT = QAction(icon, 'Copy', self)
145
+ self.uiCopyACT.setShortcut('Ctrl+C')
146
+ self.uiCopyACT.triggered.connect(self.copy)
147
+ self.addAction(self.uiCopyACT)
148
+
149
+ iconlstrip = QIcon(resourcePath('img/content-duplicate.png'))
150
+ self.uiCopyLstripACT = QAction(iconlstrip, 'Copy lstrip', self)
151
+ self.uiCopyLstripACT.setShortcut('Ctrl+Shift+C')
152
+ self.uiCopyLstripACT.triggered.connect(self.copyLstrip)
153
+ self.addAction(self.uiCopyLstripACT)
154
+
155
+ self.uiCopySpaceIndentationACT = QAction(icon, 'Copy Tabs to Spaces', self)
156
+ self.uiCopySpaceIndentationACT.setShortcut('Ctrl+Shift+Space')
157
+ self.uiCopySpaceIndentationACT.triggered.connect(self.copySpaceIndentation)
158
+ self.addAction(self.uiCopySpaceIndentationACT)
159
+
160
+ # Update keyboard shortcuts that come with QsciScintilla
161
+ commands = self.standardCommands()
162
+ # Remove the Ctrl+/ "Move left one word part" shortcut so it can be used to
163
+ # comment
164
+ if Qt_py.IsPyQt6:
165
+ # In Qt6 enums are not longer simple ints. boundTo still requires ints
166
+ def to_int(shortcut):
167
+ return shortcut.toCombined()
168
+
169
+ else:
170
+
171
+ def to_int(shortcut):
172
+ return shortcut
173
+
174
+ command = commands.boundTo(
175
+ to_int(Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_Slash)
176
+ )
177
+ if command is not None:
178
+ command.setKey(0)
179
+
180
+ for command in commands.commands():
181
+ if command.description() == 'Move selected lines up one line':
182
+ command.setKey(
183
+ to_int(
184
+ Qt.KeyboardModifier.ControlModifier
185
+ | Qt.KeyboardModifier.ShiftModifier
186
+ | Qt.Key.Key_Up
187
+ )
188
+ )
189
+ if command.description() == 'Move selected lines down one line':
190
+ command.setKey(
191
+ to_int(
192
+ Qt.KeyboardModifier.ControlModifier
193
+ | Qt.KeyboardModifier.ShiftModifier
194
+ | Qt.Key.Key_Down
195
+ )
196
+ )
197
+ if command.description() == 'Duplicate selection':
198
+ command.setKey(
199
+ to_int(
200
+ Qt.KeyboardModifier.ControlModifier
201
+ | Qt.KeyboardModifier.ShiftModifier
202
+ | Qt.Key.Key_D
203
+ )
204
+ )
205
+ if command.description() == 'Cut current line':
206
+ command.setKey(0)
207
+
208
+ # Add QShortcuts
209
+ self.uiShowAutoCompleteSCT = QShortcut(
210
+ QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Space),
211
+ self,
212
+ context=Qt.ShortcutContext.WidgetShortcut,
213
+ )
214
+ self.uiShowAutoCompleteSCT.activated.connect(lambda: self.showAutoComplete())
215
+
216
+ # load the file
217
+ if not filename:
218
+ self.refreshTitle()
219
+ self.setLanguage('Plain Text')
220
+
221
+ # goto the line
222
+ if lineno:
223
+ self.setCursorPosition(lineno, 0)
224
+
225
+ def caretBackgroundColor(self):
226
+ return self._caretBackgroundColor
227
+
228
+ def caretForegroundColor(self):
229
+ return self._caretForegroundColor
230
+
231
+ def setCaretLineBackgroundColor(self, color):
232
+ self._caretBackgroundColor = color
233
+ super(DocumentEditor, self).setCaretLineBackgroundColor(color)
234
+
235
+ def setCaretForegroundColor(self, color):
236
+ self._caretForegroundColor = color
237
+ super(DocumentEditor, self).setCaretForegroundColor(color)
238
+
239
+ def clear(self):
240
+ super(DocumentEditor, self).clear()
241
+ self.set_filename('')
242
+
243
+ def closeEvent(self, event):
244
+ self.disableTitleUpdate()
245
+ # unsubcribe the file from the open file monitor
246
+ self.setFileMonitoringEnabled(False)
247
+ super(DocumentEditor, self).closeEvent(event)
248
+
249
+ def closeEditor(self):
250
+ parent = self.parent()
251
+ if parent and parent.inherits('QMdiSubWindow'):
252
+ parent.close()
253
+
254
+ def commentCheck(self):
255
+ # collect the language
256
+ language = lang.byName(self._language)
257
+ if not language:
258
+ QMessageBox.critical(
259
+ self,
260
+ 'No Language Defined',
261
+ 'There is no language defined for this editor.',
262
+ )
263
+ return '', False
264
+
265
+ # grab the line comment
266
+ comment = language.lineComment()
267
+ if not comment:
268
+ QMessageBox.critical(
269
+ self,
270
+ 'No Line Comment Defined',
271
+ 'There is no line comment symbol defined for the "%s" language.'
272
+ % self._language,
273
+ )
274
+ return '', False
275
+ return comment, True
276
+
277
+ def commentToggle(self, doWhich=None):
278
+ """Toggle comments, mimicing SublimeText functionality.
279
+
280
+ - Comments will be indented to match the outermost line being commented.
281
+ - Commenting / uncommenting is determined by whether all non-empty lines are
282
+ currently commented or not. If they ALL are, then uncomment, otherwise
283
+ comment.
284
+ """
285
+
286
+ # If called by 'triggered' signal, clear out passed argument.
287
+ if not isinstance(doWhich, str):
288
+ doWhich = None
289
+
290
+ comment, result = self.commentCheck()
291
+ if not result:
292
+ return False
293
+ commentSpace = comment + " "
294
+
295
+ with undo_step(self):
296
+ # lookup the selected text positions
297
+ origSelection = self.expandCursorToLineSelection()
298
+ origStartLine, origStartCol, origEndLine, origEndCol = origSelection
299
+ startLine, startCol, endLine, endCol = self.getSelection()
300
+
301
+ # Collect comments and indents, to determine indentation to use, and whether
302
+ # to comment or uncomment.
303
+ comments = []
304
+ indents = []
305
+ for line in range(startLine, endLine + 1):
306
+ lineText = self.getSelectionCurrentLineText(line)
307
+
308
+ # Skip if line is empty, or line is last line without selection
309
+ if not lineText.strip() or (line == endLine and not endCol):
310
+ continue
311
+
312
+ comments.append(lineText.lstrip()[0] == comment)
313
+
314
+ curIndent = self.determineIndent(lineText, comment)
315
+ indents.append(curIndent)
316
+
317
+ if not indents:
318
+ return
319
+ indent = min(indents)
320
+
321
+ # If all lines are comments, we un-comment. If any aren't
322
+ # comments, we comment.
323
+ sel_adjust = 0
324
+ if doWhich is None:
325
+ if all(comments):
326
+ doWhich = "Uncomment"
327
+ sel_adjust = -1 * len(commentSpace)
328
+ else:
329
+ doWhich = "Comment"
330
+ sel_adjust = len(commentSpace)
331
+
332
+ for line in range(startLine, endLine + 1):
333
+ lineText = self.getSelectionCurrentLineText(line)
334
+ if not lineText.strip():
335
+ continue
336
+
337
+ # Do not toggle comments on the last line if it contains no selection
338
+ if line != endLine or endCol:
339
+ if doWhich == "Comment":
340
+ self.setCursorPosition(line, indent)
341
+ self.insert(commentSpace)
342
+ elif doWhich == "Uncomment":
343
+ for curComment in [commentSpace, comment]:
344
+ foundText = self.getSelectedCommentText(
345
+ line, indent, len(curComment)
346
+ )
347
+ startCol, endCol, _, removed = self.removeComment(
348
+ foundText,
349
+ curComment,
350
+ line,
351
+ indent,
352
+ startLine,
353
+ startCol,
354
+ endLine,
355
+ endCol,
356
+ origEndCol,
357
+ )
358
+ if removed:
359
+ break
360
+
361
+ # Adjust columns so selection moves in or out with comment. If startCol
362
+ # began at 0 keep it there.
363
+ adjustedStartCol = origStartCol + sel_adjust if origStartCol else 0
364
+ adjustedendCol = origEndCol + sel_adjust
365
+
366
+ # restore the currently selected text, or cursor position
367
+ self.setSelection(
368
+ origStartLine, adjustedStartCol, origEndLine, adjustedendCol
369
+ )
370
+
371
+ def removeComment(
372
+ self,
373
+ text,
374
+ comment,
375
+ line,
376
+ indent,
377
+ startLine,
378
+ startCol,
379
+ endLine,
380
+ endCol,
381
+ cursorIndex,
382
+ ):
383
+ removed = False
384
+ if text == comment:
385
+ commentLen = len(comment)
386
+ self.setSelection(line, indent, line, indent + commentLen)
387
+ self.removeSelectedText()
388
+
389
+ # py3 will throw an error if comparing None, so only compare if cursorIndex
390
+ # is not None
391
+ if cursorIndex is not None and cursorIndex > indent:
392
+ adjustment = None
393
+ for checkIndex in range(commentLen - 1):
394
+ newIndex = indent + checkIndex + 1
395
+ if cursorIndex == newIndex:
396
+ adjustment = checkIndex + 1
397
+ break
398
+ if adjustment is None:
399
+ adjustment = commentLen
400
+ cursorIndex -= adjustment
401
+
402
+ if line == startLine:
403
+ startCol -= commentLen
404
+ if line == endLine:
405
+ endCol -= commentLen
406
+
407
+ removed = True
408
+ return startCol, endCol, cursorIndex, removed
409
+
410
+ def determineIndent(self, lineText, comment=None):
411
+ indent = len(lineText) - len(lineText.lstrip())
412
+ return indent
413
+
414
+ def getSelectedCommentText(self, line, indent, commentLen):
415
+ """Because QScintilla.setSelection automatically strips trailing
416
+ whitespace, we grab the whole rest of the line, then reset it
417
+ to just the length of the currentComment
418
+ """
419
+ self.setSelection(line, indent, line, self.lineLength(line))
420
+ text = self.selectedText()
421
+ if len(text) >= commentLen:
422
+ text = text[:commentLen]
423
+ return text
424
+
425
+ def getSelectionCurrentLineText(self, line):
426
+ lineLength = len(self.text(line).rstrip())
427
+ self.setSelection(line, 0, line, lineLength)
428
+ lineText = self.selectedText()
429
+ return lineText
430
+
431
+ def expandCursorToLineSelection(self):
432
+ start_line = None
433
+ start_idx = None
434
+ end_line = None
435
+ end_idx = None
436
+ lineLength = None
437
+
438
+ if self.hasSelectedText():
439
+ start_line, start_idx, end_line, end_idx = self.getSelection()
440
+ else:
441
+ start_line, start_idx = self.getCursorPosition()
442
+ end_line = start_line
443
+ end_idx = start_idx
444
+
445
+ if start_line is not None:
446
+ # Get lineLength this way instead of self.lineLength (QScintilla) because
447
+ # that gets confused by \r\n vs \n , so linked files will behave
448
+ # incorrectly.
449
+ lineLength = len(self.text(end_line).rstrip())
450
+ self.setSelection(start_line, 0, end_line, lineLength)
451
+ return start_line, start_idx, end_line, end_idx
452
+
453
+ def copy(self):
454
+ """Copies the selected text.
455
+
456
+ If copyIndentsAsSpaces and self.indentationsUseTabs() is True it will convert
457
+ any indents to spaces before copying the text.
458
+ """
459
+ if self.copyIndentsAsSpaces and self.indentationsUseTabs():
460
+ self.copySpaceIndentation()
461
+ else:
462
+ super(DocumentEditor, self).copy()
463
+
464
+ def copyFilenameToClipboard(self):
465
+ QApplication.clipboard().setText(self._filename)
466
+
467
+ def copyLineReference(self):
468
+ sel = self.getSelection()
469
+ # Note: getSelection is 0 based like all good code
470
+ if sel[0] == -1 and self._clickPos:
471
+ lines = (self.lineAt(self.mapFromGlobal(self._clickPos)) + 1, -1)
472
+ else:
473
+ end = sel[2]
474
+ if sel[3] == 0:
475
+ # if nothing is selected on the last line, exclude it
476
+ end -= 1
477
+ lines = (sel[0] + 1, end + 1)
478
+ args = {'filename': self.filename(), 'plural': ''}
479
+ if lines[1] == -1 or lines[0] == lines[1]:
480
+ args['line'] = lines[0]
481
+ else:
482
+ args['line'] = '{}-{}'.format(*lines)
483
+ args['plural'] = 's'
484
+ QApplication.clipboard().setText(
485
+ '{filename}: Line{plural} {line}'.format(**args)
486
+ )
487
+
488
+ def copyLstrip(self):
489
+ """Copy's the selected text, but strips off any leading whitespace shared by the
490
+ entire selection.
491
+ """
492
+ start, s, end, e = self.getSelection()
493
+ count = end - start + 1
494
+ self.setSelection(start, 0, end, e)
495
+ txt = self.selectedText()
496
+
497
+ def replacement(match):
498
+ return re.sub('[ \t]', '', match.group(), count=1)
499
+
500
+ # NOTE: Don't use re.M, it does not support mac line endings.
501
+ regex = re.compile('(?:^|\r\n?|\n)[ \t]')
502
+ while len(regex.findall(txt)) == count:
503
+ # We found the same number of leading whitespace as lines of text.
504
+ # This means that it all has leading whitespace that needs removed.
505
+ txt = regex.sub(replacement, txt)
506
+ QApplication.clipboard().setText(txt)
507
+
508
+ def copySpaceIndentation(self):
509
+ """Copy the selected text with any tab indents converted to space indents.
510
+
511
+ If indentationsUseTabs is False it will just copy the text
512
+ """
513
+ txt = self.selectedText()
514
+
515
+ def replacement(match):
516
+ return match.group().replace('\t', ' ' * self.tabWidth())
517
+
518
+ # NOTE: Don't use re.M, it does not support mac line endings.
519
+ ret = re.sub('(?:^|\r\n?|\n)\t+', replacement, txt)
520
+ QApplication.clipboard().setText(ret)
521
+
522
+ def detectEndLine(self, text):
523
+ newlineN = text.find('\n')
524
+ newlineR = text.find('\r')
525
+ if newlineN != -1 and newlineR != -1:
526
+ if newlineN == newlineR + 1:
527
+ # CR LF Windows
528
+ return QsciScintilla.EolMode.EolWindows
529
+ elif newlineR == newlineN + 1:
530
+ # LF CR ACorn and RISC unsupported
531
+ return self.eolMode()
532
+ if newlineN != -1 and newlineR != -1:
533
+ if newlineN < newlineR:
534
+ # First return is a LF
535
+ return QsciScintilla.EolMode.EolUnix
536
+ else:
537
+ # first return is a CR
538
+ return QsciScintilla.EolMode.EolMac
539
+ if newlineN != -1:
540
+ return QsciScintilla.EolMode.EolUnix
541
+ if sys.platform == 'win32':
542
+ return QsciScintilla.EolMode.EolWindows
543
+ return QsciScintilla.EolMode.EolUnix
544
+
545
+ def editPermaHighlight(self):
546
+ text, success = QInputDialog.getText(
547
+ self,
548
+ 'Edit PermaHighlight keywords',
549
+ 'Add keywords separated by a space',
550
+ text=' '.join(self.permaHighlight()),
551
+ )
552
+ if success:
553
+ self.setPermaHighlight(text.split(' '))
554
+
555
+ def disableTitleUpdate(self):
556
+ self.modificationChanged.connect(self.refreshTitle)
557
+
558
+ def enableTitleUpdate(self):
559
+ self.modificationChanged.connect(self.refreshTitle)
560
+
561
+ def exploreDocument(self):
562
+ path = self._filename
563
+ if os.path.isfile(path):
564
+ path = os.path.split(path)[0]
565
+
566
+ if os.path.exists(path):
567
+ osystem.explore(path)
568
+ else:
569
+ QMessageBox.critical(
570
+ self, 'Missing Path', 'Could not find %s' % path.replace('/', '\\')
571
+ )
572
+
573
+ def execStandalone(self):
574
+ if self.save():
575
+ os.startfile(str(self.filename()))
576
+
577
+ def foldMarginColors(self):
578
+ """Returns the fold margin's foreground and background QColor
579
+
580
+ Returns:
581
+ foreground(QColor): The foreground color
582
+ background(QColor): The background color
583
+ """
584
+ return self._foldMarginForegroundColor, self._foldMarginBackgroundColor
585
+
586
+ def setFoldMarginColors(self, foreground, background):
587
+ """Sets the fold margins foreground and background QColor
588
+
589
+ Args:
590
+ foreground(QColor): The forground color of the checkerboard
591
+ background(QColor): The background color of the checkerboard
592
+ """
593
+ self._foldMarginForegroundColor = foreground
594
+ self._foldMarginBackgroundColor = background
595
+ super(DocumentEditor, self).setFoldMarginColors(foreground, background)
596
+
597
+ def goToLine(self, line=None):
598
+ if type(line) != int:
599
+ line, accepted = QInputDialog.getInt(self, 'Line Number', 'Line:')
600
+ else:
601
+ accepted = True
602
+
603
+ if accepted:
604
+ # MH 04/12/11 changed from line + 1 to line - 1 to make the gotoLine dialog
605
+ # go to the correct line.
606
+ self.setCursorPosition(line - 1, 0)
607
+ self.ensureLineVisible(line)
608
+
609
+ def goToDefinition(self, text=None):
610
+ if not text:
611
+ text = self.selectedText()
612
+ if not text:
613
+ text, accepted = QInputDialog.getText(self, 'def Name', 'Name:')
614
+ else:
615
+ accepted = True
616
+ else:
617
+ accepted = True
618
+ if accepted:
619
+ descriptors = lang.byName(self.language()).descriptors()
620
+ docText = self.text()
621
+ for descriptor in descriptors:
622
+ result = descriptor.search(docText)
623
+ while result:
624
+ name = result.group('name')
625
+ if name.startswith(text):
626
+ self.findNext(name, 0)
627
+ return
628
+ result = descriptor.search(docText, result.end())
629
+
630
+ def language(self):
631
+ return self._language
632
+
633
+ def languageChosen(self, action):
634
+ self.setLanguage(action.text())
635
+ self.updateColorScheme()
636
+ window = self.window()
637
+ if hasattr(window, 'uiLanguageDDL'):
638
+ window.uiLanguageDDL.blockSignals(True)
639
+ window.uiLanguageDDL.setCurrentLanguage(action.text())
640
+ window.uiLanguageDDL.blockSignals(False)
641
+
642
+ def lineMarginWidth(self):
643
+ return self.marginWidth(self.SymbolMargin)
644
+
645
+ def filename(self):
646
+ return self._filename
647
+
648
+ def set_filename(self, filename):
649
+ self._filename = filename
650
+
651
+ def findNext(self, text, flags):
652
+ re = (flags & SearchOptions.QRegExp) != 0
653
+ cs = (flags & SearchOptions.CaseSensitive) != 0
654
+ wo = (flags & SearchOptions.WholeWords) != 0
655
+ wrap = True
656
+ forward = True
657
+
658
+ result = self.findFirst(text, re, cs, wo, wrap, forward)
659
+
660
+ if not result:
661
+ self.findTextNotFound(text)
662
+
663
+ return result
664
+
665
+ def findPrev(self, text, flags):
666
+ re = (flags & SearchOptions.QRegExp) != 0
667
+ cs = (flags & SearchOptions.CaseSensitive) != 0
668
+ wo = (flags & SearchOptions.WholeWords) != 0
669
+ wrap = True
670
+ forward = False
671
+
672
+ isSelected = self.hasSelectedText()
673
+ result = self.findFirst(text, re, cs, wo, wrap, forward)
674
+ if result and isSelected:
675
+ # If text is selected when finding previous, it will find the currently
676
+ # selected text so do another find.
677
+ result = QsciScintilla.findNext(self)
678
+
679
+ if not result:
680
+ self.findTextNotFound(text)
681
+
682
+ return result
683
+
684
+ def find_simple(self, find_state):
685
+ """Python implementation of QsciScintilla.simpleFind.
686
+
687
+ Args:
688
+ find_state (preditor.scintilla.FindState): A find state used to
689
+ manage the find.
690
+
691
+ https://github.com/josephwilk/qscintilla/blob/master/Qt4Qt5/qsciscintilla.cpp
692
+ """
693
+ if find_state.start_pos == find_state.end_pos:
694
+ return -1
695
+
696
+ self.SendScintilla(QsciScintilla.SCI_SETTARGETSTART, find_state.start_pos)
697
+ self.SendScintilla(QsciScintilla.SCI_SETTARGETEND, find_state.end_pos)
698
+
699
+ # scintilla can't match unicode strings, even in python 3
700
+ # In python 3 you have to cast it to a bytes object
701
+ expr = bytes(str(find_state.expr).encode("utf-8"))
702
+
703
+ return self.SendScintilla(QsciScintilla.SCI_SEARCHINTARGET, len(expr), expr)
704
+
705
+ def find_text(self, find_state):
706
+ """Finds text in the document without changing the selection.
707
+
708
+ Args:
709
+ find_state (preditor.scintilla.FindState): A find state used to
710
+ manage the find.
711
+
712
+ Based on QsciScintilla.doFind.
713
+ https://github.com/josephwilk/qscintilla/blob/master/Qt4Qt5/qsciscintilla.cpp
714
+ """
715
+ # Set the search flags
716
+ self.SendScintilla(QsciScintilla.SCI_SETSEARCHFLAGS, find_state.flags)
717
+ # If no end was specified, use the end of the document
718
+ if find_state.end_pos is None:
719
+ find_state.end_pos = self.SendScintilla(QsciScintilla.SCI_GETLENGTH)
720
+
721
+ pos = self.find_simple(find_state)
722
+
723
+ # See if it was found. If not and wraparound is wanted, try again.
724
+ if pos == -1 and find_state.wrap:
725
+ if find_state.forward:
726
+ find_state.start_pos = 0
727
+ if find_state.start_pos_original is None:
728
+ find_state.end_pos = self.SendScintilla(QsciScintilla.SCI_GETLENGTH)
729
+ else:
730
+ find_state.end_pos = find_state.start_pos_original
731
+ else:
732
+ if find_state.start_pos_original is None:
733
+ find_state.start_pos = self.SendScintilla(
734
+ QsciScintilla.SCI_GETLENGTH
735
+ )
736
+ else:
737
+ find_state.start_pos = find_state.start_pos_original
738
+ find_state.end_pos = 0
739
+ # Give a indication that we have wrapped
740
+ find_state.wrapped = True
741
+
742
+ pos = self.find_simple(find_state)
743
+
744
+ if pos == -1:
745
+ return -1, 0
746
+
747
+ # It was found.
748
+ target_start = self.SendScintilla(QsciScintilla.SCI_GETTARGETSTART)
749
+ target_end = self.SendScintilla(QsciScintilla.SCI_GETTARGETEND)
750
+
751
+ # Finally adjust the start position so that we don't find the same one again.
752
+ if find_state.forward:
753
+ find_state.start_pos = target_end
754
+ else:
755
+ find_state.start_pos = target_start - 1
756
+ if find_state.start_pos < 0:
757
+ find_state.start_pos = 0
758
+
759
+ return target_start, target_end
760
+
761
+ def find_text_from_cursor(self, find_state):
762
+ """Starting from the current cursor position wrapping around, return all
763
+ matches to the provided find_state.
764
+
765
+ Args:
766
+ find_state (preditor.scintilla.FindState): A find state used to
767
+ manage the find.
768
+ """
769
+ # Start searching from the cursor, wrap past the end and stop where we started
770
+ current_position = self.positionFromLineIndex(*self.getCursorPosition())
771
+ find_state.start_pos = current_position
772
+ find_state.start_pos_original = current_position
773
+
774
+ positions = []
775
+ start, end = self.find_text(find_state)
776
+ while start != -1:
777
+ positions.append((start, end))
778
+ if find_state.wrapped:
779
+ # once we have wrapped, disable wrap
780
+ find_state.wrap = False
781
+ start, end = self.find_text(find_state)
782
+ return positions
783
+
784
+ def findTextNotFound(self, text):
785
+ try:
786
+ # If a number was typed in, ask the user if they wanted to goto that line
787
+ # number.
788
+ line = int(text)
789
+ msg = (
790
+ 'Search string "%s" was not found. \nIt looks like a line number, '
791
+ 'would you like to goto line %i?'
792
+ )
793
+ result = QMessageBox.critical(
794
+ self,
795
+ 'No Text Found',
796
+ msg % (text, line),
797
+ buttons=(
798
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
799
+ ),
800
+ defaultButton=QMessageBox.StandardButton.Yes,
801
+ )
802
+ if result == QMessageBox.StandardButton.Yes:
803
+ self.goToLine(line)
804
+ except ValueError:
805
+ QMessageBox.critical(
806
+ self, 'No Text Found', 'Search string "%s" was not found.' % text
807
+ )
808
+
809
+ def keyPressEvent(self, event):
810
+ key = event.key()
811
+ modifiers = event.modifiers()
812
+
813
+ retPressed = key == Qt.Key.Key_Return
814
+ altPressed = modifiers == Qt.KeyboardModifier.AltModifier
815
+ altReturnPressed = altPressed and retPressed
816
+
817
+ ctrlPressed = modifiers == Qt.KeyboardModifier.ControlModifier
818
+ plusPressed = key == Qt.Key.Key_Plus
819
+ minusPressed = key == Qt.Key.Key_Minus
820
+
821
+ # We will have logger window handle the ctrl++ or ctrl+- shortcuts for
822
+ # font resizing, so we bypass the normal SCintilla functionality. If we
823
+ # don't the SCintilla font will get out-of sync with the console, and also
824
+ # not be accessible by workbox.font() or workbox.__font__().
825
+
826
+ # For some reason, documentEditor doesn't receive a single combination
827
+ # (ie ctrl++) instead receiving two separate keyPressEvents, one for
828
+ # ctrl, and one for plus (+). So, we must store that ctrl is pressed,
829
+ # and unset that when the key is released.
830
+ if ctrlPressed:
831
+ self.ctrlIsPressed = True
832
+ # To determine ctrl combo, check our stored value for the ctrl key.
833
+ ctrlPlusPressed = self.ctrlIsPressed and plusPressed
834
+ ctrlMinusPressed = self.ctrlIsPressed and minusPressed
835
+
836
+ if key == Qt.Key.Key_Backtab:
837
+ self.unindentSelection()
838
+ elif key == Qt.Key.Key_Escape:
839
+ # Using QShortcut for Escape did not seem to work.
840
+ self.showAutoComplete(True)
841
+ elif altReturnPressed:
842
+ # If Alt Return pressed, create new unindented line
843
+
844
+ # Capture initial autoIndent state, then ensure it's disabled
845
+ autoIndent = self.autoIndent()
846
+ self.setAutoIndent(False)
847
+
848
+ # Create and send a new KeyEvent, just Return (Cannot just send original
849
+ # event, it doesn't register.
850
+ new_event = QKeyEvent(
851
+ event.type(),
852
+ Qt.Key.Key_Return,
853
+ Qt.KeyboardModifier(0),
854
+ "",
855
+ event.isAutoRepeat(),
856
+ event.count(),
857
+ )
858
+ QsciScintilla.keyPressEvent(self, new_event)
859
+
860
+ # Reset autoIndent property
861
+ self.setAutoIndent(autoIndent)
862
+ elif ctrlMinusPressed:
863
+ # LoggerWindow will handle this
864
+ self.window().uiDecreaseCodeFontSizeACT.trigger()
865
+ elif ctrlPlusPressed:
866
+ # LoggerWindow will handle this
867
+ self.window().uiIncreaseCodeFontSizeACT.trigger()
868
+
869
+ else:
870
+ return QsciScintilla.keyPressEvent(self, event)
871
+
872
+ def keyReleaseEvent(self, event):
873
+ if event.key() == Qt.Key.Key_Menu:
874
+ # Calculate the screen coordinates of the text cursor.
875
+ position = self.positionFromLineIndex(*self.getCursorPosition())
876
+ x = self.SendScintilla(QsciScintilla.SCI_POINTXFROMPOSITION, 0, position)
877
+ y = self.SendScintilla(QsciScintilla.SCI_POINTYFROMPOSITION, 0, position)
878
+ # When using the menu key, show the right click menu at the text
879
+ # cursor, not the mouse cursor, it is not in the correct place.
880
+ self.showMenu(QPoint(x, y))
881
+
882
+ elif event.key() == Qt.Key.Key_Control:
883
+ self.ctrlIsPressed = False
884
+ else:
885
+ return super(DocumentEditor, self).keyReleaseEvent(event)
886
+
887
+ def initSettings(self, first_time=False):
888
+ """Set/reset settings using the IDE section settings."""
889
+
890
+ # set visibility settings
891
+ self.setAutoIndent(True)
892
+ if first_time:
893
+ self.setIndentationsUseTabs(False)
894
+ self.setTabIndents(True)
895
+ self.setTabWidth(4)
896
+ self.setCaretLineVisible(False)
897
+ self.setShowWhitespaces(False)
898
+ self.setMarginLineNumbers(0, True)
899
+ self.setIndentationGuides(False)
900
+ self.setEolVisibility(False)
901
+ self.setShowSmartHighlighting(True)
902
+ self.setBackspaceUnindents(True)
903
+
904
+ self.setEdgeMode(QsciScintilla.EdgeMode.EdgeNone)
905
+
906
+ # set auto-completion settings
907
+ self.setAutoCompletionSource(QsciScintilla.AutoCompletionSource.AcsAll)
908
+ self.setAutoCompletionThreshold(3)
909
+
910
+ self.setFont(self.documentFont)
911
+ self.setMarginsFont(self.marginsFont())
912
+ metric = QFontMetrics(self.marginsFont())
913
+ if Qt_py.IsPyQt4:
914
+ width = metric.width('0000000')
915
+ else:
916
+ width = metric.horizontalAdvance('0000000')
917
+ self.setMarginWidth(0, width + 5)
918
+
919
+ def markerNext(self):
920
+ line, index = self.getCursorPosition()
921
+ newline = self.markerFindNext(line + 1, self.marginMarkerMask(1))
922
+
923
+ # wrap around the document if necessary
924
+ if newline == -1:
925
+ newline = self.markerFindNext(0, self.marginMarkerMask(1))
926
+
927
+ self.setCursorPosition(newline, index)
928
+
929
+ def markerLoad(self, input):
930
+ r"""
931
+ \remarks Takes a list of line numbers and adds a marker to each of them
932
+ in the file.
933
+ """
934
+ for line in input:
935
+ marker = self.markerDefine(self.Circle)
936
+ self.markerAdd(line, marker)
937
+
938
+ def markerToggle(self):
939
+ line, index = self.getCursorPosition()
940
+ markers = self.markersAtLine(line)
941
+ if not markers:
942
+ marker = self.markerDefine(self.Circle)
943
+ self.markerAdd(line, marker)
944
+ else:
945
+ self.markerDelete(line)
946
+
947
+ def marginsFont(self):
948
+ return self._marginsFont
949
+
950
+ def multipleSelection(self):
951
+ """Returns if multiple selection is enabled."""
952
+ return self.SendScintilla(QsciScintilla.SCI_GETMULTIPLESELECTION)
953
+
954
+ def multipleSelectionAdditionalSelectionTyping(self):
955
+ """Returns if multiple selection allows additional typing."""
956
+ return self.SendScintilla(QsciScintilla.SCI_GETMULTIPLESELECTION)
957
+
958
+ def multipleSelectionMultiPaste(self):
959
+ """Paste into all multiple selections."""
960
+ return self.SendScintilla(QsciScintilla.SCI_GETMULTIPASTE)
961
+
962
+ def paste(self):
963
+ text = QApplication.clipboard().text()
964
+ if text.find('\n') == -1 and text.find('\r') == -1:
965
+ return super(DocumentEditor, self).paste()
966
+
967
+ def repForMode(mode):
968
+ if mode == QsciScintilla.EolMode.EolWindows:
969
+ return '\r\n'
970
+ elif mode == QsciScintilla.EolMode.EolUnix:
971
+ return '\n'
972
+ else:
973
+ return '\r'
974
+
975
+ text = text.replace(
976
+ repForMode(self.detectEndLine(text)), repForMode(self.eolMode())
977
+ )
978
+ QApplication.clipboard().setText(text)
979
+ return super(DocumentEditor, self).paste()
980
+
981
+ def permaHighlight(self):
982
+ return self._permaHighlight
983
+
984
+ def setPermaHighlight(self, value):
985
+ if not isinstance(value, list):
986
+ raise TypeError('PermaHighlight must be a list')
987
+
988
+ def replace(self, text, searchtext=None, all=False):
989
+ # replace the current text with the inputed text
990
+ if not searchtext:
991
+ searchtext = self.selectedText()
992
+
993
+ # make sure something is selected
994
+ if not searchtext:
995
+ return 0
996
+
997
+ with undo_step(self):
998
+ sel = self.getSelection()
999
+
1000
+ # replace all of the instances of the text
1001
+ if all:
1002
+ count = self.text().count(
1003
+ searchtext, Qt.CaseSensitivity.CaseInsensitive
1004
+ )
1005
+ found = 0
1006
+ while self.findFirst(searchtext, False, False, False, True, True):
1007
+ if found == count:
1008
+ # replaced all items, exit so we don't get a infinite loop
1009
+ break
1010
+ found += 1
1011
+ super(DocumentEditor, self).replace(text)
1012
+
1013
+ # replace a single instance of the text
1014
+ else:
1015
+ count = 1
1016
+ super(DocumentEditor, self).replace(text)
1017
+
1018
+ self.setSelection(*sel)
1019
+
1020
+ return count
1021
+
1022
+ def setText(self, text):
1023
+ self.blockSignals(True)
1024
+ super(DocumentEditor, self).setText(text)
1025
+ self.blockSignals(False)
1026
+ self.spellCheck(0, None)
1027
+
1028
+ def refreshTitle(self):
1029
+ try:
1030
+ parent = self.parent()
1031
+ if parent and parent.inherits('QMdiSubWindow'):
1032
+ parent.setWindowTitle(self.windowTitle())
1033
+ except RuntimeError:
1034
+ pass
1035
+
1036
+ def setFileMonitoringEnabled(self, state):
1037
+ window = self.window()
1038
+ if hasattr(window, "setFileMonitoringEnabled"):
1039
+ window.setFileMonitoringEnabled(state)
1040
+
1041
+ def selectProjectItem(self):
1042
+ window = self.window()
1043
+ if window:
1044
+ window.selectProjectItem(self.filename())
1045
+
1046
+ def selectionBackgroundColor(self):
1047
+ return self._selectionBackgroundColor
1048
+
1049
+ def setSelectionBackgroundColor(self, color):
1050
+ self._selectionBackgroundColor = color
1051
+ super(DocumentEditor, self).setSelectionBackgroundColor(color)
1052
+
1053
+ def selectionForegroundColor(self):
1054
+ return self._selectionForegroundColor
1055
+
1056
+ def setSelectionForegroundColor(self, color):
1057
+ self._selectionForegroundColor = color
1058
+ super(DocumentEditor, self).setSelectionForegroundColor(color)
1059
+
1060
+ def selection_is_word(self):
1061
+ """Checks if the current selection is a single word.
1062
+
1063
+ Returns:
1064
+ bool: The selected text is a single word.
1065
+ """
1066
+ sel = self.getSelection()
1067
+ start = self.positionFromLineIndex(*sel[:2])
1068
+ end = self.positionFromLineIndex(*sel[2:])
1069
+ return self.is_word(start, end)
1070
+
1071
+ def is_word(self, start, end):
1072
+ """Checks if the text between start and end position is a word
1073
+
1074
+ Args:
1075
+ start (int): Start of text offset index position.
1076
+ end (int): End of text offset index position.
1077
+
1078
+ Returns:
1079
+ bool: The text between the start and end position is a single word.
1080
+ """
1081
+ if start == end:
1082
+ return False
1083
+ # Get the word at the start of selection, if the selection doesn't match
1084
+ # its not a word.
1085
+ start_pos = self.SendScintilla(QsciScintilla.SCI_WORDSTARTPOSITION, start, True)
1086
+ end_pos = self.SendScintilla(QsciScintilla.SCI_WORDENDPOSITION, start, True)
1087
+
1088
+ return start == start_pos and end == end_pos
1089
+
1090
+ def setLanguage(self, language):
1091
+ if language == 'Plain Text':
1092
+ language = ''
1093
+ # grab the language from the lang module if it is a string
1094
+ if type(language) != lang.Language:
1095
+ language = str(language)
1096
+ language = lang.byName(language)
1097
+
1098
+ # collect the language's lexer
1099
+ if language:
1100
+ lexer = language.createLexer(self)
1101
+ self._language = language.name()
1102
+ else:
1103
+ lexer = None
1104
+ self._language = ''
1105
+
1106
+ # set the lexer & init the settings
1107
+ self.setLexer(lexer)
1108
+ self.initSettings()
1109
+ self.updateColorScheme()
1110
+
1111
+ # Add language keywords to aspell session dictionary
1112
+ if self.spellCheckEnabled():
1113
+ self.delayable_engine.delayables['spell_check'].reset_session(self)
1114
+
1115
+ def setLexer(self, lexer):
1116
+ # Backup values destroyed when we set the lexer
1117
+ marginFont = self.marginsFont()
1118
+ folds = self.contractedFolds()
1119
+ super(DocumentEditor, self).setLexer(lexer)
1120
+ # Restore values destroyed when we set the lexer
1121
+ self.setContractedFolds(folds)
1122
+ self.setMarginsFont(marginFont)
1123
+ self.setMarginsBackgroundColor(self.marginsBackgroundColor())
1124
+ self.setMarginsForegroundColor(self.marginsForegroundColor())
1125
+ self.setFoldMarginColors(*self.foldMarginColors())
1126
+ self.setMatchedBraceBackgroundColor(self.matchedBraceBackgroundColor())
1127
+ self.setMatchedBraceForegroundColor(self.matchedBraceForegroundColor())
1128
+ if lexer:
1129
+ lexer.setColor(
1130
+ self.pyIndentationGuidesForegroundColor, self.STYLE_INDENTGUIDE
1131
+ )
1132
+ lexer.setPaper(
1133
+ self.pyIndentationGuidesBackgroundColor, self.STYLE_INDENTGUIDE
1134
+ )
1135
+ # QSciLexer.wordCharacters is not virtual, or even exposed. This hack allows
1136
+ # custom lexers to define their own wordCharacters
1137
+ if hasattr(lexer, 'wordCharactersOverride'):
1138
+ wordCharacters = lexer.wordCharactersOverride
1139
+ else:
1140
+ # We can't query the lexer for its word characters, but we can query the
1141
+ # document. This ensures the lexer's wordCharacters are used if switching
1142
+ # from a wordCharactersOverride lexer to a lexer that doesn't define custom
1143
+ # wordCharacters.
1144
+ wordCharacters = self.wordCharacters()
1145
+ self.SendScintilla(
1146
+ QsciScintilla.SCI_SETWORDCHARS, wordCharacters.encode('utf8')
1147
+ )
1148
+
1149
+ def setLineMarginWidth(self, width):
1150
+ self.setMarginWidth(self.SymbolMargin, width)
1151
+
1152
+ def setMarginsFont(self, font):
1153
+ super(DocumentEditor, self).setMarginsFont(font)
1154
+ self._marginsFont = font
1155
+
1156
+ def setMultipleSelection(self, state):
1157
+ """Enables or disables multiple selection
1158
+
1159
+ Args:
1160
+ state (bool): Enable or disable multiple selection. When multiple
1161
+ selection is disabled, it is not possible to select multiple
1162
+ ranges by holding down the Ctrl key while dragging with the
1163
+ mouse.
1164
+ """
1165
+ self.SendScintilla(QsciScintilla.SCI_SETMULTIPLESELECTION, state)
1166
+
1167
+ def setMultipleSelectionAdditionalSelectionTyping(self, state):
1168
+ """Enables or disables multiple selection allows additional typing.
1169
+
1170
+ Args:
1171
+ state (bool): Whether typing, new line, cursor left/right/up/down,
1172
+ backspace, delete, home, and end work with multiple selections
1173
+ simultaneously. Also allows selection and word and line
1174
+ deletion commands.
1175
+ """
1176
+ self.SendScintilla(QsciScintilla.SCI_SETADDITIONALSELECTIONTYPING, state)
1177
+
1178
+ def setMultipleSelectionMultiPaste(self, state):
1179
+ """Enables or disables multiple selection allows additional typing.
1180
+
1181
+ Args:
1182
+ state (int): When pasting into multiple selections, the pasted text
1183
+ can go into just the main selection with self.SC_MULTIPASTE_ONCE or
1184
+ into each selection with self.SC_MULTIPASTE_EACH.
1185
+ self.SC_MULTIPASTE_ONCE is the default.
1186
+ """
1187
+ self.SendScintilla(QsciScintilla.SCI_SETMULTIPASTE, state)
1188
+
1189
+ def setSmartHighlightingRegEx(
1190
+ self, exp=r'[ \t\n\r\.,?;:!()\[\]+\-\*\/#@^%$"\\~&{}|=<>\']'
1191
+ ):
1192
+ """Set the regular expression used to control if a selection is considered
1193
+ valid for smart highlighting.
1194
+
1195
+ Args:
1196
+ exp (str):
1197
+ """
1198
+ self._smartHighlightingRegEx = exp
1199
+ self.selectionValidator = re.compile(exp)
1200
+
1201
+ def setShowFolding(self, state):
1202
+ if state:
1203
+ self.setFolding(self.BoxedTreeFoldStyle)
1204
+ else:
1205
+ self.setFolding(self.NoFoldStyle)
1206
+
1207
+ def setShowLineNumbers(self, state):
1208
+ self.setMarginLineNumbers(self.SymbolMargin, state)
1209
+
1210
+ def setShowSmartHighlighting(self, state):
1211
+ self.delayable_engine.set_delayable_enabled('smart_highlight', state)
1212
+
1213
+ def setShowWhitespaces(self, state):
1214
+ if state:
1215
+ self.setWhitespaceVisibility(QsciScintilla.WhitespaceVisibility.WsVisible)
1216
+ else:
1217
+ self.setWhitespaceVisibility(QsciScintilla.WhitespaceVisibility.WsInvisible)
1218
+
1219
+ def spellCheckEnabled(self):
1220
+ """Is spellcheck is enabled for this document."""
1221
+ return self.delayable_engine.delayable_enabled('spell_check')
1222
+
1223
+ def setSpellCheckEnabled(self, state):
1224
+ """Enable/disable spellcheck if spellcheck can be enabled.
1225
+ This changes spellcheck for all documents attached to this
1226
+ documents delayable_engine.
1227
+ """
1228
+ self.delayable_engine.set_delayable_enabled('spell_check', state)
1229
+
1230
+ def addWordToDict(self, word):
1231
+ self.__speller__.addtoPersonal(word)
1232
+ self.__speller__.saveAllwords()
1233
+ self.spellCheck(0, None)
1234
+ self.pos += len(word)
1235
+ self.SendScintilla(QsciScintilla.SCI_GOTOPOS, self.pos)
1236
+
1237
+ def correctSpelling(self, action):
1238
+ self.SendScintilla(QsciScintilla.SCI_GOTOPOS, self.pos)
1239
+ self.SendScintilla(QsciScintilla.SCI_SETANCHOR, self.anchor)
1240
+ with undo_step(self):
1241
+ self.SendScintilla(QsciScintilla.SCI_REPLACESEL, action.text())
1242
+
1243
+ def spellCheck(self, start_pos, end_pos):
1244
+ """Check spelling for some text in the document.
1245
+
1246
+ Args:
1247
+ start_pos (int): The document position to start spell checking.
1248
+ end_pos (int): The document position to stop spell checking.
1249
+
1250
+ Returns:
1251
+ int: Returns 0 if spell check is finished. 1 if additional
1252
+ processing is scheduled. 2 if the spell check was canceled
1253
+ because the widget is not visible.
1254
+ """
1255
+ self.delayable_engine.enqueue(self, 'spell_check', start_pos, end_pos)
1256
+
1257
+ def onTextModified(
1258
+ self,
1259
+ pos,
1260
+ mtype,
1261
+ text,
1262
+ length,
1263
+ linesAdded,
1264
+ line,
1265
+ foldNow,
1266
+ foldPrev,
1267
+ token,
1268
+ annotationLinesAdded,
1269
+ ):
1270
+ if self.spellCheckEnabled() and (
1271
+ (mtype & self.SC_MOD_INSERTTEXT) == self.SC_MOD_INSERTTEXT
1272
+ or (mtype & self.SC_MOD_DELETETEXT) == self.SC_MOD_DELETETEXT
1273
+ ):
1274
+ # Only spell-check if text was inserted/deleted
1275
+ line = self.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION, pos)
1276
+ # More than one line could have been inserted.
1277
+ # If this number is negative it will cause Qt to crash.
1278
+ lines_to_check = line + max(0, linesAdded)
1279
+ self.spellCheck(
1280
+ self.SendScintilla(QsciScintilla.SCI_POSITIONFROMLINE, line),
1281
+ self.SendScintilla(
1282
+ QsciScintilla.SCI_GETLINEENDPOSITION, lines_to_check
1283
+ ),
1284
+ )
1285
+
1286
+ def showAutoComplete(self, toggle=False):
1287
+ # if using autoComplete toggle the autoComplete list
1288
+ if self.autoCompletionSource() == QsciScintilla.AutoCompletionSource.AcsAll:
1289
+ if self.isListActive(): # is the autoComplete list visible
1290
+ if toggle:
1291
+ self.cancelList() # Close the autoComplete list
1292
+ else:
1293
+ self.autoCompleteFromAll() # Show the autoComplete list
1294
+
1295
+ def showMenu(self, pos, popup=True):
1296
+ menu = QMenu(self)
1297
+ menu.setFont(self.window().font())
1298
+
1299
+ pos = self.mapToGlobal(pos)
1300
+ self._clickPos = pos
1301
+
1302
+ if self.spellCheckEnabled():
1303
+ # Get the word under the mouse and split the word if camelCase
1304
+ point = self.mapFromGlobal(self._clickPos)
1305
+ x = point.x()
1306
+ y = point.y()
1307
+ wordUnderMouse = self.wordAtPoint(point)
1308
+ positionMouse = self.SendScintilla(
1309
+ QsciScintilla.SCI_POSITIONFROMPOINT, x, y
1310
+ )
1311
+ wordStartPosition = self.SendScintilla(
1312
+ QsciScintilla.SCI_WORDSTARTPOSITION, positionMouse, True
1313
+ )
1314
+ spell_check = self.delayable_engine.delayables['spell_check']
1315
+ results = spell_check.chunk_re.findall(
1316
+ self.text(wordStartPosition, wordStartPosition + len(wordUnderMouse))
1317
+ )
1318
+
1319
+ for space, wordChunk in results:
1320
+ camel_case_words = spell_check.camel_case_split(wordChunk)
1321
+ lengthSpace = len(space)
1322
+ for word in camel_case_words:
1323
+ lengthWord = len(word)
1324
+ # Calcualate the actual word start position accounting for any
1325
+ # non-alpha chars word_new_start_position = wordStartPosition +
1326
+ # lengthSpace
1327
+ if (
1328
+ wordStartPosition + lengthSpace <= positionMouse
1329
+ and wordStartPosition + lengthSpace + lengthWord > positionMouse
1330
+ and not any(letter in string.digits for letter in word)
1331
+ and not self.__speller__.check(word)
1332
+ ):
1333
+ # For camelCase words, get the exact word under the mouse
1334
+ self.pos = wordStartPosition + lengthSpace
1335
+ self.anchor = wordStartPosition + lengthSpace + lengthWord
1336
+ # Add spelling suggestions to menu
1337
+ submenu = menu.addMenu(word)
1338
+ submenu.setObjectName('uiSpellCheckMENU')
1339
+ wordSuggestionList = self.__speller__.suggest(word)
1340
+ for wordSuggestion in wordSuggestionList:
1341
+ act = submenu.addAction(wordSuggestion)
1342
+ submenu.triggered.connect(self.correctSpelling)
1343
+ addmenu = menu.addAction('Add %s to dictionary' % word)
1344
+ addmenu.triggered.connect(partial(self.addWordToDict, word))
1345
+ addmenu.setObjectName('uiSpellCheckAddWordACT')
1346
+ menu.addSeparator()
1347
+ break
1348
+ else:
1349
+ wordStartPosition += lengthWord
1350
+ wordStartPosition += lengthSpace
1351
+
1352
+ act = menu.addAction('Goto')
1353
+ # act.setShortcut('Ctrl+G')
1354
+ act.triggered.connect(self.goToLine)
1355
+ act.setIcon(QIcon(resourcePath('img/skip-next-outline.png')))
1356
+ act = menu.addAction('Go to Definition')
1357
+ # act.setShortcut('Ctrl+Shift+G')
1358
+ act.triggered.connect(self.goToDefinition)
1359
+ act.setIcon(QIcon(resourcePath('img/skip-forward-outline.png')))
1360
+ if self.showSmartHighlighting():
1361
+ act = menu.addAction('Edit PermaHighlight')
1362
+ act.setIcon(QIcon(resourcePath('img/marker.png')))
1363
+ act.triggered.connect(self.editPermaHighlight)
1364
+
1365
+ menu.addSeparator()
1366
+
1367
+ act = menu.addAction('Collapse/Expand All')
1368
+ act.triggered.connect(self.toggleFolding)
1369
+ act.setIcon(QIcon(resourcePath('img/plus-minus-variant.png')))
1370
+
1371
+ menu.addSeparator()
1372
+
1373
+ act = menu.addAction('Cut')
1374
+ act.triggered.connect(self.cut)
1375
+ act.setShortcut('Ctrl+X')
1376
+ act.setIcon(QIcon(resourcePath('img/content-cut.png')))
1377
+
1378
+ act = menu.addAction('Copy')
1379
+ act.triggered.connect(self.copy)
1380
+ act.setShortcut('Ctrl+C')
1381
+ act.setIcon(QIcon(resourcePath('img/content-copy.png')))
1382
+
1383
+ copyMenu = menu.addMenu('Advanced Copy')
1384
+
1385
+ # Note: I cant use the actions defined above because they end up getting garbage
1386
+ # collected
1387
+ iconlstrip = QIcon(resourcePath('img/content-duplicate.png'))
1388
+ act = QAction(iconlstrip, 'Copy lstrip', copyMenu)
1389
+ act.setShortcut('Ctrl+Shift+C')
1390
+ act.triggered.connect(self.copyLstrip)
1391
+ copyMenu.addAction(act)
1392
+
1393
+ icon = QIcon(resourcePath('img/content-copy.png'))
1394
+ act = QAction(icon, 'Copy Tabs to Spaces', copyMenu)
1395
+ act.setShortcut('Ctrl+Shift+Space')
1396
+ act.triggered.connect(self.copySpaceIndentation)
1397
+ copyMenu.addAction(act)
1398
+
1399
+ act = menu.addAction('Paste')
1400
+ act.triggered.connect(self.paste)
1401
+ act.setShortcut('Ctrl+V')
1402
+ act.setIcon(QIcon(resourcePath('img/content-paste.png')))
1403
+
1404
+ menu.addSeparator()
1405
+
1406
+ act = menu.addAction('Copy Line Reference')
1407
+ act.triggered.connect(self.copyLineReference)
1408
+ act.setIcon(QIcon(resourcePath('img/content-copy.png')))
1409
+
1410
+ menu.addSeparator()
1411
+
1412
+ act = menu.addAction('Comment Toggle')
1413
+ act.triggered.connect(self.commentToggle)
1414
+ act.setShortcut("Ctrl+/")
1415
+ act.setIcon(QIcon(resourcePath('img/comment-edit.png')))
1416
+
1417
+ menu.addSeparator()
1418
+
1419
+ act = menu.addAction('To Lowercase')
1420
+ act.triggered.connect(self.toLower)
1421
+ # act.setShortcut('Ctrl+L')
1422
+ act.setIcon(QIcon(resourcePath('img/format-letter-case-lower.png')))
1423
+ act = menu.addAction('To Uppercase')
1424
+ act.triggered.connect(self.toUpper)
1425
+ # act.setShortcut('Ctrl+U')
1426
+ act.setIcon(QIcon(resourcePath('img/format-letter-case-upper.png')))
1427
+
1428
+ menu.addSeparator()
1429
+
1430
+ submenu = menu.addMenu('View as...')
1431
+ submenu.setIcon(QIcon(resourcePath('img/eye-check.png')))
1432
+ lg = self.language()
1433
+ act = submenu.addAction('Plain Text')
1434
+ if lg == "":
1435
+ act.setIcon(QIcon(resourcePath('img/check-bold.png')))
1436
+ submenu.addSeparator()
1437
+
1438
+ for language in lang.languages():
1439
+ act = submenu.addAction(language)
1440
+ if language == lg:
1441
+ act.setIcon(QIcon(resourcePath('img/check-bold.png')))
1442
+
1443
+ submenu.triggered.connect(self.languageChosen)
1444
+
1445
+ menu.addSeparator()
1446
+
1447
+ act = menu.addAction('Indent using tabs')
1448
+ act.triggered.connect(self.setIndentationsUseTabs)
1449
+ act.setCheckable(True)
1450
+ act.setChecked(self.indentationsUseTabs())
1451
+
1452
+ if popup:
1453
+ menu.popup(self._clickPos)
1454
+ return menu
1455
+
1456
+ def showEvent(self, event):
1457
+ super(DocumentEditor, self).showEvent(event)
1458
+ # Update the colorScheme after the stylesheet has been fully loaded.
1459
+ self.updateColorScheme()
1460
+
1461
+ def showFolding(self):
1462
+ return self.folding() != self.NoFoldStyle
1463
+
1464
+ def showLineNumbers(self):
1465
+ return self.marginLineNumbers(self.SymbolMargin)
1466
+
1467
+ def showSmartHighlighting(self):
1468
+ return self.delayable_engine.delayable_enabled('smart_highlight')
1469
+
1470
+ def showWhitespaces(self):
1471
+ return (
1472
+ self.whitespaceVisibility() == QsciScintilla.WhitespaceVisibility.WsVisible
1473
+ )
1474
+
1475
+ def smartHighlightingRegEx(self):
1476
+ return self._smartHighlightingRegEx
1477
+
1478
+ def toLower(self):
1479
+ with undo_step(self):
1480
+ lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
1481
+ text = self.selectedText().lower()
1482
+ self.removeSelectedText()
1483
+ self.insert(text)
1484
+ self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
1485
+
1486
+ def toggleFolding(self):
1487
+ self.foldAll(
1488
+ QApplication.instance().keyboardModifiers()
1489
+ == Qt.KeyboardModifier.ShiftModifier
1490
+ )
1491
+
1492
+ def toUpper(self):
1493
+ with undo_step(self):
1494
+ lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
1495
+ text = self.selectedText().upper()
1496
+ self.removeSelectedText()
1497
+ self.insert(text)
1498
+ self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
1499
+
1500
+ def updateColorScheme(self):
1501
+ """Sets the DocumentEditor's lexer colors, see colorScheme for a compatible
1502
+ dict
1503
+ """
1504
+ # lookup the language
1505
+ language = lang.byName(self.language())
1506
+ lex = self.lexer()
1507
+ if not lex:
1508
+ self.setPaper(self.paperDefault)
1509
+ self.setColor(self.colorDefault)
1510
+ return
1511
+ # Backup the lexer font. The calls to setPaper/setColor cause it to be reset.
1512
+ font = lex.font(0)
1513
+ # Set Default lexer colors
1514
+ for i in range(128):
1515
+ lex.setPaper(self.paperDefault, i)
1516
+ lex.setColor(self.colorDefault, i)
1517
+ lex.setDefaultPaper(self.paperDefault)
1518
+ lex.setDefaultColor(self.colorDefault)
1519
+ # Override lexer color/paper values
1520
+ if language:
1521
+ _lexerColorNames = set(
1522
+ [
1523
+ x.replace('color', '')
1524
+ for x in dir(self)
1525
+ if x.startswith('color') and x.replace('color', '')
1526
+ ]
1527
+ )
1528
+ for colorName, keys in language.lexerColorTypes().items():
1529
+ color = None
1530
+ paper = None
1531
+ if colorName == 'misc':
1532
+ color = self.colorDefault
1533
+ paper = self.paperDefault
1534
+ else:
1535
+ for name in _lexerColorNames:
1536
+ if name.lower() == colorName:
1537
+ color = getattr(self, 'color{}'.format(name))
1538
+ paper = getattr(self, 'paper{}'.format(name))
1539
+ break
1540
+ for key in keys:
1541
+ if paper:
1542
+ lex.setPaper(paper, key)
1543
+ if color:
1544
+ lex.setColor(color, key)
1545
+ lex.setColor(self.braceBadForeground, self.STYLE_BRACEBAD)
1546
+ lex.setPaper(self.braceBadBackground, self.STYLE_BRACEBAD)
1547
+ lex.setColor(self.pyIndentationGuidesForegroundColor, self.STYLE_INDENTGUIDE)
1548
+ lex.setPaper(self.pyIndentationGuidesBackgroundColor, self.STYLE_INDENTGUIDE)
1549
+ # Update other values stored in the lexer
1550
+ self.setFoldMarginColors(
1551
+ self.foldMarginsForegroundColor, self.foldMarginsBackgroundColor
1552
+ )
1553
+ self.setMarginsBackgroundColor(self.marginsBackgroundColor())
1554
+ self.setMarginsForegroundColor(self.marginsForegroundColor())
1555
+ self.setFoldMarginColors(*self.foldMarginColors())
1556
+ self.setMatchedBraceBackgroundColor(self.matchedBraceBackgroundColor())
1557
+ self.setMatchedBraceForegroundColor(self.matchedBraceForegroundColor())
1558
+ # Restore the existing font
1559
+ lex.setFont(font, 0)
1560
+
1561
+ def updateFilename(self, filename):
1562
+ filename = str(filename)
1563
+ extension = os.path.splitext(filename)[1]
1564
+
1565
+ if self._filename and (
1566
+ filename and extension != os.path.splitext(self._filename)[1]
1567
+ ):
1568
+ language = lang.byExtension(extension)
1569
+ else:
1570
+ language = self._defaultLanguage
1571
+
1572
+ if language != self.language():
1573
+ self.setLanguage(language)
1574
+
1575
+ # update the filename information
1576
+ filename = os.path.abspath(filename) if filename else ""
1577
+ self.set_filename(filename)
1578
+ self.setModified(False)
1579
+
1580
+ try:
1581
+ self.window().emitDocumentTitleChanged()
1582
+ except Exception:
1583
+ pass
1584
+
1585
+ self.refreshTitle()
1586
+
1587
+ def updateHighlighter(self):
1588
+ # Get selection
1589
+ selectedText = self.selectedText()
1590
+ # if text is selected make sure it is a word
1591
+ lexer = self.lexer()
1592
+ if selectedText != lexer.highlightedKeywords:
1593
+ if selectedText:
1594
+ validator = self.selectionValidator
1595
+ if hasattr(lexer, 'selectionValidator'):
1596
+ # If a lexer has defined its own selectionValidator use that instead
1597
+ validator = lexer.selectionValidator
1598
+ # Does the text contain a non allowed word?
1599
+ if not validator.findall(selectedText) == []:
1600
+ return
1601
+ else:
1602
+ selection = self.getSelection()
1603
+ # the character before and after the selection must not be a word.
1604
+ text = self.text(selection[2]) # Character after
1605
+ if selection[3] < len(text):
1606
+ if validator.findall(text[selection[3]]) == []:
1607
+ return
1608
+ text = self.text(selection[0]) # Character Before
1609
+ if selection[1] and selection[1] != -1:
1610
+ if validator.findall(text[selection[1] - 1]) == []:
1611
+ return
1612
+
1613
+ def updateSelectionInfo(self):
1614
+ window = self.window()
1615
+ if window and hasattr(window, 'uiCursorInfoLBL'):
1616
+ sline, spos, eline, epos = self.getSelection()
1617
+ # Add 1 to line numbers because document line numbers are 1 based
1618
+ text = ''
1619
+ if sline == -1:
1620
+ line, pos = self.getCursorPosition()
1621
+ line += 1
1622
+ text = 'Line: {} Pos: {}'.format(line, pos)
1623
+ else:
1624
+ sline += 1
1625
+ eline += 1
1626
+ text = (
1627
+ 'Line: {sline} Pos: {spos} To Line: {eline} '
1628
+ 'Pos: {epos} Line Count: {lineCount}'
1629
+ )
1630
+ text = text.format(
1631
+ sline=sline,
1632
+ spos=spos,
1633
+ eline=eline,
1634
+ epos=epos,
1635
+ lineCount=eline - sline + 1,
1636
+ )
1637
+ if self._encoding:
1638
+ text = 'Encoding: {enc} {text}'.format(enc=self._encoding, text=text)
1639
+ window.uiCursorInfoLBL.setText(text)
1640
+
1641
+ def indentSelection(self, all=False):
1642
+ if all:
1643
+ lineFrom = 0
1644
+ lineTo = self.lines()
1645
+ else:
1646
+ lineFrom, indexFrom, lineTo, indextTo = self.getSelection()
1647
+ with undo_step(self):
1648
+ for line in range(lineFrom, lineTo + 1):
1649
+ self.indent(line)
1650
+
1651
+ def unindentSelection(self, all=False):
1652
+ if all:
1653
+ lineFrom = 0
1654
+ lineTo = self.lines()
1655
+ else:
1656
+ lineFrom, indexFrom, lineTo, indextTo = self.getSelection()
1657
+ with undo_step(self):
1658
+ for line in range(lineFrom, lineTo + 1):
1659
+ self.unindent(line)
1660
+
1661
+ def windowTitle(self):
1662
+ if self._filename:
1663
+ title = os.path.basename(self._filename)
1664
+ else:
1665
+ title = 'New Document'
1666
+
1667
+ if self.isModified():
1668
+ title += '*'
1669
+
1670
+ return title
1671
+
1672
+ def wheelEvent(self, event):
1673
+ """Scroll-wheel based font resizing is handled by LoggerWindow, so prevent
1674
+ the built-in QScintilla functionality. Only proceed if ctrl is not part
1675
+ of the event modifiers.
1676
+ """
1677
+ if not (
1678
+ event.modifiers() == self.window().gui_font_mod
1679
+ or event.modifiers() == Qt.KeyboardModifier.ControlModifier
1680
+ ):
1681
+ super(DocumentEditor, self).wheelEvent(event)
1682
+
1683
+ # expose properties for the designer
1684
+ pyLanguage = Property("QString", language, setLanguage)
1685
+ pyLineMarginWidth = Property("int", lineMarginWidth, setLineMarginWidth)
1686
+ pyShowLineNumbers = Property("bool", showLineNumbers, setShowLineNumbers)
1687
+ pyShowFolding = Property("bool", showFolding, setShowFolding)
1688
+ pyShowSmartHighlighting = Property(
1689
+ "bool", showSmartHighlighting, setShowSmartHighlighting
1690
+ )
1691
+ pySmartHighlightingRegEx = Property(
1692
+ "QString", smartHighlightingRegEx, setSmartHighlightingRegEx
1693
+ )
1694
+
1695
+ pyAutoCompletionCaseSensitivity = Property(
1696
+ "bool",
1697
+ QsciScintilla.autoCompletionCaseSensitivity,
1698
+ QsciScintilla.setAutoCompletionCaseSensitivity,
1699
+ )
1700
+ pyAutoCompletionReplaceWord = Property(
1701
+ "bool",
1702
+ QsciScintilla.autoCompletionReplaceWord,
1703
+ QsciScintilla.setAutoCompletionReplaceWord,
1704
+ )
1705
+ pyAutoCompletionShowSingle = Property(
1706
+ "bool",
1707
+ QsciScintilla.autoCompletionShowSingle,
1708
+ QsciScintilla.setAutoCompletionShowSingle,
1709
+ )
1710
+ pyAutoCompletionThreshold = Property(
1711
+ "int",
1712
+ QsciScintilla.autoCompletionThreshold,
1713
+ QsciScintilla.setAutoCompletionThreshold,
1714
+ )
1715
+ pyAutoIndent = Property(
1716
+ "bool", QsciScintilla.autoIndent, QsciScintilla.setAutoIndent
1717
+ )
1718
+ pyBackspaceUnindents = Property(
1719
+ "bool", QsciScintilla.backspaceUnindents, QsciScintilla.setBackspaceUnindents
1720
+ )
1721
+ pyIndentationGuides = Property(
1722
+ "bool", QsciScintilla.indentationGuides, QsciScintilla.setIndentationGuides
1723
+ )
1724
+ pyIndentationsUseTabs = Property(
1725
+ "bool", QsciScintilla.indentationsUseTabs, QsciScintilla.setIndentationsUseTabs
1726
+ )
1727
+ pyTabIndents = Property(
1728
+ "bool", QsciScintilla.tabIndents, QsciScintilla.setTabIndents
1729
+ )
1730
+ pyUtf8 = Property("bool", QsciScintilla.isUtf8, QsciScintilla.setUtf8)
1731
+ pyWhitespaceVisibility = Property(
1732
+ "bool",
1733
+ QsciScintilla.whitespaceVisibility,
1734
+ QsciScintilla.setWhitespaceVisibility,
1735
+ )
1736
+
1737
+ # Color Setters required because QSci doesn't expose getters.
1738
+ # --------------------------------------------------------------------------------
1739
+ def edgeColor(self):
1740
+ """This is subclassed so we can create a Property of it"""
1741
+ return super(DocumentEditor, self).edgeColor()
1742
+
1743
+ def setEdgeColor(self, color):
1744
+ """This is subclassed so we can create a Property of it"""
1745
+ super(DocumentEditor, self).setEdgeColor(color)
1746
+
1747
+ # Because foreground and background must be set together, this cant use
1748
+ # QtPropertyInit
1749
+ @Property(QColor)
1750
+ def foldMarginsBackgroundColor(self):
1751
+ return self._foldMarginBackgroundColor
1752
+
1753
+ @foldMarginsBackgroundColor.setter
1754
+ def foldMarginsBackgroundColor(self, color):
1755
+ self._foldMarginBackgroundColor = color
1756
+ self.setFoldMarginColors(self._foldMarginForegroundColor, color)
1757
+
1758
+ @Property(QColor)
1759
+ def foldMarginsForegroundColor(self):
1760
+ return self._foldMarginForegroundColor
1761
+
1762
+ @foldMarginsForegroundColor.setter
1763
+ def foldMarginsForegroundColor(self, color):
1764
+ self._foldMarginForegroundColor = color
1765
+ self.setFoldMarginColors(color, self._foldMarginBackgroundColor)
1766
+
1767
+ def indentationGuidesBackgroundColor(self):
1768
+ return self._indentationGuidesBackgroundColor
1769
+
1770
+ def setIndentationGuidesBackgroundColor(self, color):
1771
+ self._indentationGuidesBackgroundColor = color
1772
+ super(DocumentEditor, self).setIndentationGuidesBackgroundColor(color)
1773
+
1774
+ def indentationGuidesForegroundColor(self):
1775
+ return self._indentationGuidesForegroundColor
1776
+
1777
+ def setIndentationGuidesForegroundColor(self, color):
1778
+ self._indentationGuidesForegroundColor = color
1779
+ super(DocumentEditor, self).setIndentationGuidesForegroundColor(color)
1780
+
1781
+ def marginsBackgroundColor(self):
1782
+ return self._marginsBackgroundColor
1783
+
1784
+ def setMarginsBackgroundColor(self, color):
1785
+ self._marginsBackgroundColor = color
1786
+ super(DocumentEditor, self).setMarginsBackgroundColor(color)
1787
+
1788
+ def marginsForegroundColor(self):
1789
+ return self._marginsForegroundColor
1790
+
1791
+ def setMarginsForegroundColor(self, color):
1792
+ self._marginsForegroundColor = color
1793
+ super(DocumentEditor, self).setMarginsForegroundColor(color)
1794
+
1795
+ def matchedBraceBackgroundColor(self):
1796
+ return self._matchedBraceBackgroundColor
1797
+
1798
+ def matchedBraceForegroundColor(self):
1799
+ return self._matchedBraceForegroundColor
1800
+
1801
+ def setMatchedBraceBackgroundColor(self, color):
1802
+ self._matchedBraceBackgroundColor = color
1803
+ super(DocumentEditor, self).setMatchedBraceBackgroundColor(color)
1804
+
1805
+ def setMatchedBraceForegroundColor(self, color):
1806
+ self._matchedBraceForegroundColor = color
1807
+ super(DocumentEditor, self).setMatchedBraceForegroundColor(color)
1808
+
1809
+ def markerBackgroundColor(self):
1810
+ return self._markerBackgroundColor
1811
+
1812
+ def setMarkerBackgroundColor(self, color):
1813
+ self._markerBackgroundColor = color
1814
+ super(DocumentEditor, self).setMarkerBackgroundColor(color)
1815
+
1816
+ def markerForegroundColor(self):
1817
+ return self._markerForegroundColor
1818
+
1819
+ def setMarkerForegroundColor(self, color):
1820
+ self._markerForegroundColor = color
1821
+ super(DocumentEditor, self).setMarkerForegroundColor(color)
1822
+
1823
+ def unmatchedBraceBackgroundColor(self):
1824
+ return self._unmatchedBraceBackgroundColor
1825
+
1826
+ def setUnmatchedBraceBackgroundColor(self, color):
1827
+ self._unmatchedBraceBackgroundColor = color
1828
+ super(DocumentEditor, self).setUnmatchedBraceBackgroundColor(color)
1829
+
1830
+ def unmatchedBraceForegroundColor(self):
1831
+ return self._unmatchedBraceForegroundColor
1832
+
1833
+ def setUnmatchedBraceForegroundColor(self, color):
1834
+ self._unmatchedBraceForegroundColor = color
1835
+ super(DocumentEditor, self).setUnmatchedBraceForegroundColor(color)
1836
+
1837
+ # Handle Stylesheet colors for properties that are built into QsciScintilla but dont
1838
+ # have getters.
1839
+ pyMarginsBackgroundColor = Property(
1840
+ QColor, marginsBackgroundColor, setMarginsBackgroundColor
1841
+ )
1842
+ pyMarginsForegroundColor = Property(
1843
+ QColor, marginsForegroundColor, setMarginsForegroundColor
1844
+ )
1845
+ pyMatchedBraceBackgroundColor = Property(
1846
+ QColor, matchedBraceBackgroundColor, setMatchedBraceBackgroundColor
1847
+ )
1848
+ pyMatchedBraceForegroundColor = Property(
1849
+ QColor, matchedBraceForegroundColor, setMatchedBraceForegroundColor
1850
+ )
1851
+ pyCaretBackgroundColor = Property(
1852
+ QColor, caretBackgroundColor, setCaretLineBackgroundColor
1853
+ )
1854
+ pyCaretForegroundColor = Property(
1855
+ QColor, caretForegroundColor, setCaretForegroundColor
1856
+ )
1857
+ pySelectionBackgroundColor = Property(
1858
+ QColor, selectionBackgroundColor, setSelectionBackgroundColor
1859
+ )
1860
+ pySelectionForegroundColor = Property(
1861
+ QColor, selectionForegroundColor, setSelectionForegroundColor
1862
+ )
1863
+ pyIndentationGuidesBackgroundColor = Property(
1864
+ QColor, indentationGuidesBackgroundColor, setIndentationGuidesBackgroundColor
1865
+ )
1866
+ pyIndentationGuidesForegroundColor = Property(
1867
+ QColor, indentationGuidesForegroundColor, setIndentationGuidesForegroundColor
1868
+ )
1869
+ pyMarkerBackgroundColor = Property(
1870
+ QColor, markerBackgroundColor, setMarkerBackgroundColor
1871
+ )
1872
+ pyMarkerForegroundColor = Property(
1873
+ QColor, markerForegroundColor, setMarkerForegroundColor
1874
+ )
1875
+ pyUnmatchedBraceBackgroundColor = Property(
1876
+ QColor, unmatchedBraceBackgroundColor, setUnmatchedBraceBackgroundColor
1877
+ )
1878
+ pyUnmatchedBraceForegroundColor = Property(
1879
+ QColor, unmatchedBraceForegroundColor, setUnmatchedBraceForegroundColor
1880
+ )
1881
+ pyEdgeColor = Property(QColor, edgeColor, setEdgeColor)
1882
+ documentFont = QtPropertyInit('_documentFont', _defaultFont)
1883
+ pyMarginsFont = Property(QFont, marginsFont, setMarginsFont)
1884
+
1885
+ copyIndentsAsSpaces = QtPropertyInit('_copyIndentsAsSpaces', False)
1886
+
1887
+ # These colors are purely defined in DocumentEditor so we can use QtPropertyInit
1888
+ braceBadForeground = QtPropertyInit('_braceBadForeground', QColor(255, 255, 255))
1889
+ braceBadBackground = QtPropertyInit('_braceBadBackground', QColor(100, 60, 60))
1890
+
1891
+ colorDefault = QtPropertyInit('_colorDefault', QColor())
1892
+ colorComment = QtPropertyInit('_colorComment', QColor(0, 127, 0))
1893
+ colorNumber = QtPropertyInit('_colorNumber', QColor(0, 127, 127))
1894
+ colorString = QtPropertyInit('_colorString', QColor(127, 0, 127))
1895
+ colorKeyword = QtPropertyInit('_colorKeyword', QColor(0, 0, 127))
1896
+ colorTripleQuotedString = QtPropertyInit(
1897
+ '_colorTripleQuotedString', QColor(127, 0, 0)
1898
+ )
1899
+ colorMethod = QtPropertyInit('_colorMethod', QColor(0, 0, 255))
1900
+ colorFunction = QtPropertyInit('_colorFunction', QColor(0, 127, 127))
1901
+ colorOperator = QtPropertyInit('_colorOperator', QColor(0, 0, 0))
1902
+ colorIdentifier = QtPropertyInit('_colorIdentifier', QColor(0, 0, 0))
1903
+ colorCommentBlock = QtPropertyInit('_colorCommentBlock', QColor(127, 127, 127))
1904
+ colorUnclosedString = QtPropertyInit('_colorUnclosedString', QColor(0, 0, 0))
1905
+ colorSmartHighlight = QtPropertyInit('_colorSmartHighlight', QColor(64, 112, 144))
1906
+ colorDecorator = QtPropertyInit('_colorDecorator', QColor(128, 80, 0))
1907
+
1908
+ _defaultPaper = QColor(255, 255, 255)
1909
+ paperDefault = QtPropertyInit('_paperDefault', _defaultPaper)
1910
+ paperComment = QtPropertyInit('_paperComment', _defaultPaper)
1911
+ paperNumber = QtPropertyInit('_paperNumber', _defaultPaper)
1912
+ paperString = QtPropertyInit('_paperString', _defaultPaper)
1913
+ paperKeyword = QtPropertyInit('_paperKeyword', _defaultPaper)
1914
+ paperTripleQuotedString = QtPropertyInit('_paperTripleQuotedString', _defaultPaper)
1915
+ paperMethod = QtPropertyInit('_paperMethod', _defaultPaper)
1916
+ paperFunction = QtPropertyInit('_paperFunction', _defaultPaper)
1917
+ paperOperator = QtPropertyInit('_paperOperator', _defaultPaper)
1918
+ paperIdentifier = QtPropertyInit('_paperIdentifier', _defaultPaper)
1919
+ paperCommentBlock = QtPropertyInit('_paperCommentBlock', _defaultPaper)
1920
+ paperUnclosedString = QtPropertyInit('_paperUnclosedString', QColor(224, 192, 224))
1921
+ paperSmartHighlight = QtPropertyInit(
1922
+ '_paperSmartHighlight', QColor(155, 255, 155, 75)
1923
+ )
1924
+ paperDecorator = QtPropertyInit('_paperDecorator', _defaultPaper)