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