|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2002 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the editor component of the eric6 IDE. |
|
8 """ |
|
9 from __future__ import unicode_literals |
|
10 try: |
|
11 str = unicode |
|
12 chr = unichr |
|
13 except NameError: |
|
14 pass |
|
15 |
|
16 import os |
|
17 import re |
|
18 import difflib |
|
19 |
|
20 from PyQt5.QtCore import QDir, QTimer, QModelIndex, QFileInfo, pyqtSignal, \ |
|
21 pyqtSlot, QCryptographicHash, QEvent, QDateTime, QRegExp, Qt, QPoint |
|
22 from PyQt5.QtGui import QCursor, QPalette, QFont, QPixmap, QPainter |
|
23 from PyQt5.QtWidgets import QLineEdit, QActionGroup, QDialog, QInputDialog, \ |
|
24 QApplication, QMenu |
|
25 from PyQt5.QtPrintSupport import QPrinter, QPrintDialog, QAbstractPrintDialog |
|
26 from PyQt5.Qsci import QsciScintilla, QsciMacro, QsciStyledText |
|
27 |
|
28 from E5Gui.E5Application import e5App |
|
29 from E5Gui import E5FileDialog, E5MessageBox |
|
30 from E5Utilities.E5Cache import E5Cache |
|
31 |
|
32 from .QsciScintillaCompat import QsciScintillaCompat, QSCINTILLA_VERSION |
|
33 from .EditorMarkerMap import EditorMarkerMap |
|
34 |
|
35 import Preferences |
|
36 import Utilities |
|
37 from Utilities import MouseUtilities |
|
38 from Globals import qVersionTuple |
|
39 |
|
40 import UI.PixmapCache |
|
41 |
|
42 from ThirdParty.EditorConfig import editorconfig |
|
43 |
|
44 EditorAutoCompletionListID = 1 |
|
45 TemplateCompletionListID = 2 |
|
46 |
|
47 |
|
48 class Editor(QsciScintillaCompat): |
|
49 """ |
|
50 Class implementing the editor component of the eric6 IDE. |
|
51 |
|
52 @signal modificationStatusChanged(bool, QsciScintillaCompat) emitted when |
|
53 the modification status has changed |
|
54 @signal undoAvailable(bool) emitted to signal the undo availability |
|
55 @signal redoAvailable(bool) emitted to signal the redo availability |
|
56 @signal cursorChanged(str, int, int) emitted when the cursor position |
|
57 was changed |
|
58 @signal cursorLineChanged(int) emitted when the cursor line was changed |
|
59 @signal editorAboutToBeSaved(str) emitted before the editor is saved |
|
60 @signal editorSaved(str) emitted after the editor has been saved |
|
61 @signal editorRenamed(str) emitted after the editor got a new name |
|
62 (i.e. after a 'Save As') |
|
63 @signal captionChanged(str, QsciScintillaCompat) emitted when the caption |
|
64 is updated. Typically due to a readOnly attribute change. |
|
65 @signal breakpointToggled(QsciScintillaCompat) emitted when a breakpoint |
|
66 is toggled |
|
67 @signal bookmarkToggled(QsciScintillaCompat) emitted when a bookmark is |
|
68 toggled |
|
69 @signal syntaxerrorToggled(QsciScintillaCompat) emitted when a syntax error |
|
70 was discovered |
|
71 @signal autoCompletionAPIsAvailable(bool) emitted after the autocompletion |
|
72 function has been configured |
|
73 @signal coverageMarkersShown(bool) emitted after the coverage markers have |
|
74 been shown or cleared |
|
75 @signal taskMarkersUpdated(QsciScintillaCompat) emitted when the task |
|
76 markers were updated |
|
77 @signal changeMarkersUpdated(QsciScintillaCompat) emitted when the change |
|
78 markers were updated |
|
79 @signal showMenu(str, QMenu, QsciScintillaCompat) emitted when a menu is |
|
80 about to be shown. The name of the menu, a reference to the menu and |
|
81 a reference to the editor are given. |
|
82 @signal languageChanged(str) emitted when the editors language was set. The |
|
83 language is passed as a parameter. |
|
84 @signal eolChanged(str) emitted when the editors eol type was set. The eol |
|
85 string is passed as a parameter. |
|
86 @signal encodingChanged(str) emitted when the editors encoding was set. The |
|
87 encoding name is passed as a parameter. |
|
88 @signal lastEditPositionAvailable() emitted when a last edit position is |
|
89 available |
|
90 @signal refreshed() emitted to signal a refresh of the editor contents |
|
91 @signal settingsRead() emitted to signal, that the settings have been read |
|
92 and set |
|
93 @signal mouseDoubleClick(position, buttons) emitted to signal a mouse |
|
94 double click somewhere in the editor area |
|
95 """ |
|
96 modificationStatusChanged = pyqtSignal(bool, QsciScintillaCompat) |
|
97 undoAvailable = pyqtSignal(bool) |
|
98 redoAvailable = pyqtSignal(bool) |
|
99 cursorChanged = pyqtSignal(str, int, int) |
|
100 cursorLineChanged = pyqtSignal(int) |
|
101 editorAboutToBeSaved = pyqtSignal(str) |
|
102 editorSaved = pyqtSignal(str) |
|
103 editorRenamed = pyqtSignal(str) |
|
104 captionChanged = pyqtSignal(str, QsciScintillaCompat) |
|
105 breakpointToggled = pyqtSignal(QsciScintillaCompat) |
|
106 bookmarkToggled = pyqtSignal(QsciScintillaCompat) |
|
107 syntaxerrorToggled = pyqtSignal(QsciScintillaCompat) |
|
108 autoCompletionAPIsAvailable = pyqtSignal(bool) |
|
109 coverageMarkersShown = pyqtSignal(bool) |
|
110 taskMarkersUpdated = pyqtSignal(QsciScintillaCompat) |
|
111 changeMarkersUpdated = pyqtSignal(QsciScintillaCompat) |
|
112 showMenu = pyqtSignal(str, QMenu, QsciScintillaCompat) |
|
113 languageChanged = pyqtSignal(str) |
|
114 eolChanged = pyqtSignal(str) |
|
115 encodingChanged = pyqtSignal(str) |
|
116 lastEditPositionAvailable = pyqtSignal() |
|
117 refreshed = pyqtSignal() |
|
118 settingsRead = pyqtSignal() |
|
119 mouseDoubleClick = pyqtSignal(QPoint, int) |
|
120 |
|
121 WarningCode = 1 |
|
122 WarningStyle = 2 |
|
123 |
|
124 # Autocompletion icon definitions |
|
125 ClassID = 1 |
|
126 ClassProtectedID = 2 |
|
127 ClassPrivateID = 3 |
|
128 MethodID = 4 |
|
129 MethodProtectedID = 5 |
|
130 MethodPrivateID = 6 |
|
131 AttributeID = 7 |
|
132 AttributeProtectedID = 8 |
|
133 AttributePrivateID = 9 |
|
134 EnumID = 10 |
|
135 KeywordsID = 11 |
|
136 ModuleID = 12 |
|
137 |
|
138 FromDocumentID = 99 |
|
139 |
|
140 TemplateImageID = 100 |
|
141 |
|
142 # Cooperation related definitions |
|
143 Separator = "@@@" |
|
144 |
|
145 StartEditToken = "START_EDIT" |
|
146 EndEditToken = "END_EDIT" |
|
147 CancelEditToken = "CANCEL_EDIT" |
|
148 RequestSyncToken = "REQUEST_SYNC" |
|
149 SyncToken = "SYNC" |
|
150 |
|
151 VcsConflictMarkerLineRegExpList = ( |
|
152 r"""^<<<<<<< .*?$""", |
|
153 r"""^\|\|\|\|\|\|\| .*?$""", |
|
154 r"""^=======.*?$""", |
|
155 r"""^>>>>>>> .*?$""", |
|
156 ) |
|
157 |
|
158 def __init__(self, dbs, fn="", vm=None, |
|
159 filetype="", editor=None, tv=None): |
|
160 """ |
|
161 Constructor |
|
162 |
|
163 @param dbs reference to the debug server object |
|
164 @param fn name of the file to be opened (string). If it is None, |
|
165 a new (empty) editor is opened |
|
166 @param vm reference to the view manager object |
|
167 (ViewManager.ViewManager) |
|
168 @param filetype type of the source file (string) |
|
169 @param editor reference to an Editor object, if this is a cloned view |
|
170 @param tv reference to the task viewer object |
|
171 @exception IOError raised to indicate an issue accessing the file |
|
172 """ |
|
173 super(Editor, self).__init__() |
|
174 self.setAttribute(Qt.WA_KeyCompression) |
|
175 self.setUtf8(True) |
|
176 |
|
177 self.dbs = dbs |
|
178 self.taskViewer = tv |
|
179 self.fileName = fn |
|
180 self.vm = vm |
|
181 self.filetype = filetype |
|
182 self.filetypeByFlag = False |
|
183 self.noName = "" |
|
184 self.project = e5App().getObject("Project") |
|
185 |
|
186 # clear some variables |
|
187 self.lastHighlight = None # remember the last highlighted line |
|
188 self.lastErrorMarker = None # remember the last error line |
|
189 self.lastCurrMarker = None # remember the last current line |
|
190 |
|
191 self.breaks = {} |
|
192 # key: marker handle, |
|
193 # value: (lineno, condition, temporary, |
|
194 # enabled, ignorecount) |
|
195 self.bookmarks = [] |
|
196 # bookmarks are just a list of handles to the |
|
197 # bookmark markers |
|
198 self.syntaxerrors = {} |
|
199 # key: marker handle |
|
200 # value: list of (error message, error index) |
|
201 self.warnings = {} |
|
202 # key: marker handle |
|
203 # value: list of (warning message, warning type) |
|
204 self.notcoveredMarkers = [] # just a list of marker handles |
|
205 self.showingNotcoveredMarkers = False |
|
206 |
|
207 self.lexer_ = None |
|
208 |
|
209 self.__loadEditorConfig() |
|
210 |
|
211 self.condHistory = [] |
|
212 self.__lexerReset = False |
|
213 self.completer = None |
|
214 self.encoding = self.__getEditorConfig("DefaultEncoding") |
|
215 self.apiLanguage = '' |
|
216 self.lastModified = 0 |
|
217 self.line = -1 |
|
218 self.inReopenPrompt = False |
|
219 # true if the prompt to reload a changed source is present |
|
220 self.inFileRenamed = False |
|
221 # true if we are propagating a rename action |
|
222 self.inLanguageChanged = False |
|
223 # true if we are propagating a language change |
|
224 self.inEolChanged = False |
|
225 # true if we are propagating an eol change |
|
226 self.inEncodingChanged = False |
|
227 # true if we are propagating an encoding change |
|
228 self.inDragDrop = False |
|
229 # true if we are in drop mode |
|
230 self.inLinesChanged = False |
|
231 # true if we are propagating a lines changed event |
|
232 self.__hasTaskMarkers = False |
|
233 # no task markers present |
|
234 |
|
235 self.macros = {} # list of defined macros |
|
236 self.curMacro = None |
|
237 self.recording = False |
|
238 |
|
239 self.acAPI = False |
|
240 |
|
241 self.__lastEditPosition = None |
|
242 self.__annotationLines = 0 |
|
243 |
|
244 # list of clones |
|
245 self.__clones = [] |
|
246 |
|
247 # clear QScintilla defined keyboard commands |
|
248 # we do our own handling through the view manager |
|
249 self.clearAlternateKeys() |
|
250 self.clearKeys() |
|
251 |
|
252 # initialize the mark occurrences timer |
|
253 self.__markOccurrencesTimer = QTimer(self) |
|
254 self.__markOccurrencesTimer.setSingleShot(True) |
|
255 self.__markOccurrencesTimer.setInterval( |
|
256 Preferences.getEditor("MarkOccurrencesTimeout")) |
|
257 self.__markOccurrencesTimer.timeout.connect(self.__markOccurrences) |
|
258 self.__markedText = "" |
|
259 self.__searchIndicatorLines = [] |
|
260 |
|
261 # initialize some spellchecking stuff |
|
262 self.spell = None |
|
263 self.lastLine = 0 |
|
264 self.lastIndex = 0 |
|
265 |
|
266 # initialize some cooperation stuff |
|
267 self.__isSyncing = False |
|
268 self.__receivedWhileSyncing = [] |
|
269 self.__savedText = "" |
|
270 self.__inSharedEdit = False |
|
271 self.__isShared = False |
|
272 self.__inRemoteSharedEdit = False |
|
273 |
|
274 # connect signals before loading the text |
|
275 self.modificationChanged.connect(self.__modificationChanged) |
|
276 self.cursorPositionChanged.connect(self.__cursorPositionChanged) |
|
277 self.modificationAttempted.connect(self.__modificationReadOnly) |
|
278 |
|
279 # define the margins markers |
|
280 self.__changeMarkerSaved = self.markerDefine( |
|
281 self.__createChangeMarkerPixmap( |
|
282 "OnlineChangeTraceMarkerSaved")) |
|
283 self.__changeMarkerUnsaved = self.markerDefine( |
|
284 self.__createChangeMarkerPixmap( |
|
285 "OnlineChangeTraceMarkerUnsaved")) |
|
286 self.breakpoint = self.markerDefine( |
|
287 UI.PixmapCache.getPixmap("break.png")) |
|
288 self.cbreakpoint = self.markerDefine( |
|
289 UI.PixmapCache.getPixmap("cBreak.png")) |
|
290 self.tbreakpoint = self.markerDefine( |
|
291 UI.PixmapCache.getPixmap("tBreak.png")) |
|
292 self.tcbreakpoint = self.markerDefine( |
|
293 UI.PixmapCache.getPixmap("tCBreak.png")) |
|
294 self.dbreakpoint = self.markerDefine( |
|
295 UI.PixmapCache.getPixmap("breakDisabled.png")) |
|
296 self.bookmark = self.markerDefine( |
|
297 UI.PixmapCache.getPixmap("bookmark16.png")) |
|
298 self.syntaxerror = self.markerDefine( |
|
299 UI.PixmapCache.getPixmap("syntaxError.png")) |
|
300 self.notcovered = self.markerDefine( |
|
301 UI.PixmapCache.getPixmap("notcovered.png")) |
|
302 self.taskmarker = self.markerDefine( |
|
303 UI.PixmapCache.getPixmap("task.png")) |
|
304 self.warning = self.markerDefine( |
|
305 UI.PixmapCache.getPixmap("warning.png")) |
|
306 |
|
307 # define the line markers |
|
308 if Preferences.getEditor("LineMarkersBackground"): |
|
309 self.currentline = self.markerDefine(QsciScintilla.Background) |
|
310 self.errorline = self.markerDefine(QsciScintilla.Background) |
|
311 self.__setLineMarkerColours() |
|
312 else: |
|
313 self.currentline = self.markerDefine( |
|
314 UI.PixmapCache.getPixmap("currentLineMarker.png")) |
|
315 self.errorline = self.markerDefine( |
|
316 UI.PixmapCache.getPixmap("errorLineMarker.png")) |
|
317 |
|
318 self.breakpointMask = (1 << self.breakpoint) | \ |
|
319 (1 << self.cbreakpoint) | \ |
|
320 (1 << self.tbreakpoint) | \ |
|
321 (1 << self.tcbreakpoint) | \ |
|
322 (1 << self.dbreakpoint) |
|
323 |
|
324 self.changeMarkersMask = (1 << self.__changeMarkerSaved) | \ |
|
325 (1 << self.__changeMarkerUnsaved) |
|
326 |
|
327 self.__markerMap = EditorMarkerMap(self) |
|
328 |
|
329 # configure the margins |
|
330 self.__setMarginsDisplay() |
|
331 self.linesChanged.connect(self.__resizeLinenoMargin) |
|
332 |
|
333 self.marginClicked.connect(self.__marginClicked) |
|
334 |
|
335 # set the eol mode |
|
336 self.__setEolMode() |
|
337 |
|
338 # set the text display |
|
339 self.__setTextDisplay() |
|
340 |
|
341 # initialize the online syntax check timer |
|
342 try: |
|
343 self.syntaxCheckService = e5App().getObject('SyntaxCheckService') |
|
344 self.syntaxCheckService.syntaxChecked.connect( |
|
345 self.__processSyntaxCheckResult) |
|
346 self.syntaxCheckService.error.connect( |
|
347 self.__processSyntaxCheckError) |
|
348 self.__initOnlineSyntaxCheck() |
|
349 except KeyError: |
|
350 self.syntaxCheckService = None |
|
351 |
|
352 self.isResourcesFile = False |
|
353 if editor is None: |
|
354 if self.fileName: |
|
355 if (QFileInfo(self.fileName).size() // 1024) > \ |
|
356 Preferences.getEditor("WarnFilesize"): |
|
357 res = E5MessageBox.yesNo( |
|
358 self, |
|
359 self.tr("Open File"), |
|
360 self.tr("""<p>The size of the file <b>{0}</b>""" |
|
361 """ is <b>{1} KB</b>.""" |
|
362 """ Do you really want to load it?</p>""") |
|
363 .format(self.fileName, |
|
364 QFileInfo(self.fileName).size() // 1024), |
|
365 icon=E5MessageBox.Warning) |
|
366 if not res: |
|
367 raise IOError() |
|
368 self.readFile(self.fileName, True) |
|
369 self.__bindLexer(self.fileName) |
|
370 self.__bindCompleter(self.fileName) |
|
371 self.checkSyntax() |
|
372 self.isResourcesFile = self.fileName.endswith(".qrc") |
|
373 |
|
374 self.recolor() |
|
375 else: |
|
376 # clone the given editor |
|
377 self.setDocument(editor.document()) |
|
378 self.breaks = editor.breaks |
|
379 self.bookmarks = editor.bookmarks |
|
380 self.syntaxerrors = editor.syntaxerrors |
|
381 self.notcoveredMarkers = editor.notcoveredMarkers |
|
382 self.showingNotcoveredMarkers = editor.showingNotcoveredMarkers |
|
383 self.isResourcesFile = editor.isResourcesFile |
|
384 self.lastModified = editor.lastModified |
|
385 |
|
386 self.addClone(editor) |
|
387 editor.addClone(self) |
|
388 |
|
389 self.gotoLine(1) |
|
390 |
|
391 # set the text display again |
|
392 self.__setTextDisplay() |
|
393 |
|
394 # set the auto-completion function |
|
395 self.__acContext = True |
|
396 self.__acText = "" |
|
397 self.__acCompletions = set() |
|
398 self.__acCompletionsFinished = 0 |
|
399 self.__acCache = E5Cache( |
|
400 size=Preferences.getEditor("AutoCompletionCacheSize")) |
|
401 self.__acCache.setMaximumCacheTime( |
|
402 Preferences.getEditor("AutoCompletionCacheTime")) |
|
403 self.__acCacheEnabled = Preferences.getEditor( |
|
404 "AutoCompletionCacheEnabled") |
|
405 self.__acTimer = QTimer(self) |
|
406 self.__acTimer.setSingleShot(True) |
|
407 self.__acTimer.setInterval( |
|
408 Preferences.getEditor("AutoCompletionTimeout")) |
|
409 self.__acTimer.timeout.connect(self.__autoComplete) |
|
410 |
|
411 self.__acWatchdog = QTimer(self) |
|
412 self.__acWatchdog.setSingleShot(True) |
|
413 self.__acWatchdog.setInterval( |
|
414 Preferences.getEditor("AutoCompletionWatchdogTime")) |
|
415 self.__acWatchdog.timeout.connect(self.autoCompleteQScintilla) |
|
416 |
|
417 self.userListActivated.connect(self.__completionListSelected) |
|
418 self.SCN_CHARADDED.connect(self.__charAdded) |
|
419 self.SCN_AUTOCCANCELLED.connect(self.__autocompletionCancelled) |
|
420 |
|
421 self.__completionListHookFunctions = {} |
|
422 self.__completionListAsyncHookFunctions = {} |
|
423 self.__setAutoCompletion() |
|
424 |
|
425 # set the call-tips function |
|
426 self.__ctHookFunctions = {} |
|
427 self.__setCallTips() |
|
428 |
|
429 # set the mouse click handlers (fired on mouse release) |
|
430 self.__mouseClickHandlers = {} |
|
431 # dictionary with tuple of keyboard modifier and mouse button as key |
|
432 # and tuple of plug-in name and function as value |
|
433 |
|
434 sh = self.sizeHint() |
|
435 if sh.height() < 300: |
|
436 sh.setHeight(300) |
|
437 self.resize(sh) |
|
438 |
|
439 # Make sure tabbing through a QWorkspace works. |
|
440 self.setFocusPolicy(Qt.StrongFocus) |
|
441 |
|
442 self.__updateReadOnly(True) |
|
443 |
|
444 self.setWhatsThis(self.tr( |
|
445 """<b>A Source Editor Window</b>""" |
|
446 """<p>This window is used to display and edit a source file.""" |
|
447 """ You can open as many of these as you like. The name of the""" |
|
448 """ file is displayed in the window's titlebar.</p>""" |
|
449 """<p>In order to set breakpoints just click in the space""" |
|
450 """ between the line numbers and the fold markers. Via the""" |
|
451 """ context menu of the margins they may be edited.</p>""" |
|
452 """<p>In order to set bookmarks just Shift click in the space""" |
|
453 """ between the line numbers and the fold markers.</p>""" |
|
454 """<p>These actions can be reversed via the context menu.</p>""" |
|
455 """<p>Ctrl clicking on a syntax error marker shows some info""" |
|
456 """ about this error.</p>""" |
|
457 )) |
|
458 |
|
459 # Set the editors size, if it is too big for the view manager. |
|
460 if self.vm is not None: |
|
461 req = self.size() |
|
462 bnd = req.boundedTo(self.vm.size()) |
|
463 |
|
464 if bnd.width() < req.width() or bnd.height() < req.height(): |
|
465 self.resize(bnd) |
|
466 |
|
467 # set the autosave flag |
|
468 self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0 |
|
469 self.autosaveManuallyDisabled = False |
|
470 |
|
471 self.__initContextMenu() |
|
472 self.__initContextMenuMargins() |
|
473 |
|
474 self.__checkEol() |
|
475 if editor is None: |
|
476 self.__checkLanguage() |
|
477 self.__checkEncoding() |
|
478 else: |
|
479 # it's a clone |
|
480 self.__languageChanged(editor.apiLanguage, propagate=False) |
|
481 self.__encodingChanged(editor.encoding, propagate=False) |
|
482 |
|
483 self.setAcceptDrops(True) |
|
484 |
|
485 # breakpoint handling |
|
486 self.breakpointModel = self.dbs.getBreakPointModel() |
|
487 self.__restoreBreakpoints() |
|
488 self.breakpointModel.rowsAboutToBeRemoved.connect( |
|
489 self.__deleteBreakPoints) |
|
490 self.breakpointModel.dataAboutToBeChanged.connect( |
|
491 self.__breakPointDataAboutToBeChanged) |
|
492 self.breakpointModel.dataChanged.connect( |
|
493 self.__changeBreakPoints) |
|
494 self.breakpointModel.rowsInserted.connect( |
|
495 self.__addBreakPoints) |
|
496 self.SCN_MODIFIED.connect(self.__modified) |
|
497 |
|
498 # establish connection to some ViewManager action groups |
|
499 self.addActions(self.vm.editorActGrp.actions()) |
|
500 self.addActions(self.vm.editActGrp.actions()) |
|
501 self.addActions(self.vm.copyActGrp.actions()) |
|
502 self.addActions(self.vm.viewActGrp.actions()) |
|
503 |
|
504 # register images to be shown in autocompletion lists |
|
505 self.__registerImages() |
|
506 |
|
507 # connect signals after loading the text |
|
508 self.textChanged.connect(self.__textChanged) |
|
509 |
|
510 # initialize the online change trace timer |
|
511 self.__initOnlineChangeTrace() |
|
512 |
|
513 if self.fileName and \ |
|
514 self.project.isOpen() and \ |
|
515 self.project.isProjectSource(self.fileName): |
|
516 self.project.projectPropertiesChanged.connect( |
|
517 self.__projectPropertiesChanged) |
|
518 |
|
519 self.grabGesture(Qt.PinchGesture) |
|
520 |
|
521 self.SCN_ZOOM.connect(self.__markerMap.update) |
|
522 self.__markerMap.update() |
|
523 |
|
524 def __registerImages(self): |
|
525 """ |
|
526 Private method to register images for autocompletion lists. |
|
527 """ |
|
528 self.registerImage(self.ClassID, |
|
529 UI.PixmapCache.getPixmap("class.png")) |
|
530 self.registerImage(self.ClassProtectedID, |
|
531 UI.PixmapCache.getPixmap("class_protected.png")) |
|
532 self.registerImage(self.ClassPrivateID, |
|
533 UI.PixmapCache.getPixmap("class_private.png")) |
|
534 self.registerImage(self.MethodID, |
|
535 UI.PixmapCache.getPixmap("method.png")) |
|
536 self.registerImage(self.MethodProtectedID, |
|
537 UI.PixmapCache.getPixmap("method_protected.png")) |
|
538 self.registerImage(self.MethodPrivateID, |
|
539 UI.PixmapCache.getPixmap("method_private.png")) |
|
540 self.registerImage(self.AttributeID, |
|
541 UI.PixmapCache.getPixmap("attribute.png")) |
|
542 self.registerImage(self.AttributeProtectedID, |
|
543 UI.PixmapCache.getPixmap("attribute_protected.png")) |
|
544 self.registerImage(self.AttributePrivateID, |
|
545 UI.PixmapCache.getPixmap("attribute_private.png")) |
|
546 self.registerImage(self.EnumID, |
|
547 UI.PixmapCache.getPixmap("enum.png")) |
|
548 self.registerImage(self.KeywordsID, |
|
549 UI.PixmapCache.getPixmap("keywords.png")) |
|
550 self.registerImage(self.ModuleID, |
|
551 UI.PixmapCache.getPixmap("module.png")) |
|
552 |
|
553 self.registerImage(self.FromDocumentID, |
|
554 UI.PixmapCache.getPixmap("editor.png")) |
|
555 |
|
556 self.registerImage(self.TemplateImageID, |
|
557 UI.PixmapCache.getPixmap("templateViewer.png")) |
|
558 |
|
559 def addClone(self, editor): |
|
560 """ |
|
561 Public method to add a clone to our list. |
|
562 |
|
563 @param editor reference to the cloned editor |
|
564 @type Editor |
|
565 """ |
|
566 self.__clones.append(editor) |
|
567 |
|
568 editor.editorRenamed.connect(self.fileRenamed) |
|
569 editor.languageChanged.connect(self.languageChanged) |
|
570 editor.eolChanged.connect(self.__eolChanged) |
|
571 editor.encodingChanged.connect(self.__encodingChanged) |
|
572 |
|
573 def removeClone(self, editor): |
|
574 """ |
|
575 Public method to remove a clone from our list. |
|
576 |
|
577 @param editor reference to the cloned editor |
|
578 @type Editor |
|
579 """ |
|
580 if editor in self.__clones: |
|
581 editor.editorRenamed.disconnect(self.fileRenamed) |
|
582 editor.languageChanged.disconnect(self.languageChanged) |
|
583 editor.eolChanged.disconnect(self.__eolChanged) |
|
584 editor.encodingChanged.disconnect(self.__encodingChanged) |
|
585 self.__clones.remove(editor) |
|
586 |
|
587 def isClone(self, editor): |
|
588 """ |
|
589 Public method to test, if the given editor is a clone. |
|
590 |
|
591 @param editor reference to the cloned editor |
|
592 @type Editor |
|
593 @return flag indicating a clone |
|
594 @rtype bool |
|
595 """ |
|
596 return editor in self.__clones |
|
597 |
|
598 def __bindName(self, line0): |
|
599 """ |
|
600 Private method to generate a dummy filename for binding a lexer. |
|
601 |
|
602 @param line0 first line of text to use in the generation process |
|
603 (string) |
|
604 @return dummy file name to be used for binding a lexer (string) |
|
605 """ |
|
606 bindName = "" |
|
607 line0 = line0.lower() |
|
608 |
|
609 # check first line if it does not start with #! |
|
610 if line0.startswith(("<html", "<!doctype html", "<?php")): |
|
611 bindName = "dummy.html" |
|
612 elif line0.startswith(("<?xml", "<!doctype")): |
|
613 bindName = "dummy.xml" |
|
614 elif line0.startswith("index: "): |
|
615 bindName = "dummy.diff" |
|
616 elif line0.startswith("\\documentclass"): |
|
617 bindName = "dummy.tex" |
|
618 |
|
619 if not bindName and self.filetype: |
|
620 # check filetype |
|
621 from . import Lexers |
|
622 supportedLanguages = Lexers.getSupportedLanguages() |
|
623 if self.filetype in supportedLanguages: |
|
624 bindName = supportedLanguages[self.filetype][1] |
|
625 elif self.filetype in ["Python", "Python2", "Python3"]: |
|
626 bindName = "dummy.py" |
|
627 |
|
628 if not bindName and line0.startswith("#!"): |
|
629 # #! marker detection |
|
630 if "python3" in line0: |
|
631 bindName = "dummy.py" |
|
632 self.filetype = "Python3" |
|
633 elif "python2" in line0: |
|
634 bindName = "dummy.py" |
|
635 self.filetype = "Python2" |
|
636 elif "python" in line0: |
|
637 bindName = "dummy.py" |
|
638 self.filetype = "Python2" |
|
639 elif "pypy3" in line0: |
|
640 bindName = "dummy.py" |
|
641 self.filetype = "Python3" |
|
642 elif "pypy2" in line0: |
|
643 bindName = "dummy.py" |
|
644 self.filetype = "Python2" |
|
645 elif "pypy" in line0: |
|
646 bindName = "dummy.py" |
|
647 self.filetype = "Python2" |
|
648 elif ("/bash" in line0 or "/sh" in line0): |
|
649 bindName = "dummy.sh" |
|
650 elif "ruby" in line0: |
|
651 bindName = "dummy.rb" |
|
652 self.filetype = "Ruby" |
|
653 elif "perl" in line0: |
|
654 bindName = "dummy.pl" |
|
655 elif "lua" in line0: |
|
656 bindName = "dummy.lua" |
|
657 elif "dmd" in line0: |
|
658 bindName = "dummy.d" |
|
659 self.filetype = "D" |
|
660 |
|
661 if not bindName: |
|
662 # mode line detection: -*- mode: python -*- |
|
663 match = re.search(r"mode[:=]\s*([-\w_.]+)", line0) |
|
664 if match: |
|
665 mode = match.group(1).lower() |
|
666 if mode in ["python3", "pypy3"]: |
|
667 bindName = "dummy.py" |
|
668 self.filetype = "Python3" |
|
669 elif mode in ["python2", "pypy2", "python", "pypy"]: |
|
670 bindName = "dummy.py" |
|
671 self.filetype = "Python2" |
|
672 elif mode == "ruby": |
|
673 bindName = "dummy.rb" |
|
674 self.filetype = "Ruby" |
|
675 elif mode == "perl": |
|
676 bindName = "dummy.pl" |
|
677 elif mode == "lua": |
|
678 bindName = "dummy.lua" |
|
679 elif mode in ["dmd", "d"]: |
|
680 bindName = "dummy.d" |
|
681 self.filetype = "D" |
|
682 |
|
683 if not bindName: |
|
684 bindName = self.fileName |
|
685 |
|
686 return bindName |
|
687 |
|
688 def getMenu(self, menuName): |
|
689 """ |
|
690 Public method to get a reference to the main context menu or a submenu. |
|
691 |
|
692 @param menuName name of the menu (string) |
|
693 @return reference to the requested menu (QMenu) or None |
|
694 """ |
|
695 try: |
|
696 return self.__menus[menuName] |
|
697 except KeyError: |
|
698 return None |
|
699 |
|
700 def hasMiniMenu(self): |
|
701 """ |
|
702 Public method to check the miniMenu flag. |
|
703 |
|
704 @return flag indicating a minimized context menu (boolean) |
|
705 """ |
|
706 return self.miniMenu |
|
707 |
|
708 def __initContextMenu(self): |
|
709 """ |
|
710 Private method used to setup the context menu. |
|
711 """ |
|
712 self.miniMenu = Preferences.getEditor("MiniContextMenu") |
|
713 |
|
714 self.menuActs = {} |
|
715 self.menu = QMenu() |
|
716 self.__menus = { |
|
717 "Main": self.menu, |
|
718 } |
|
719 |
|
720 self.languagesMenu = self.__initContextMenuLanguages() |
|
721 self.__menus["Languages"] = self.languagesMenu |
|
722 if self.isResourcesFile: |
|
723 self.resourcesMenu = self.__initContextMenuResources() |
|
724 self.__menus["Resources"] = self.resourcesMenu |
|
725 else: |
|
726 self.checksMenu = self.__initContextMenuChecks() |
|
727 self.menuShow = self.__initContextMenuShow() |
|
728 self.graphicsMenu = self.__initContextMenuGraphics() |
|
729 self.autocompletionMenu = self.__initContextMenuAutocompletion() |
|
730 self.__menus["Checks"] = self.checksMenu |
|
731 self.__menus["Show"] = self.menuShow |
|
732 self.__menus["Graphics"] = self.graphicsMenu |
|
733 self.__menus["Autocompletion"] = self.autocompletionMenu |
|
734 self.toolsMenu = self.__initContextMenuTools() |
|
735 self.__menus["Tools"] = self.toolsMenu |
|
736 self.exportersMenu = self.__initContextMenuExporters() |
|
737 self.__menus["Exporters"] = self.exportersMenu |
|
738 self.eolMenu = self.__initContextMenuEol() |
|
739 self.__menus["Eol"] = self.eolMenu |
|
740 self.encodingsMenu = self.__initContextMenuEncodings() |
|
741 self.__menus["Encodings"] = self.encodingsMenu |
|
742 |
|
743 self.menuActs["Undo"] = self.menu.addAction( |
|
744 UI.PixmapCache.getIcon("editUndo.png"), |
|
745 self.tr('Undo'), self.undo) |
|
746 self.menuActs["Redo"] = self.menu.addAction( |
|
747 UI.PixmapCache.getIcon("editRedo.png"), |
|
748 self.tr('Redo'), self.redo) |
|
749 self.menuActs["Revert"] = self.menu.addAction( |
|
750 self.tr("Revert to last saved state"), |
|
751 self.revertToUnmodified) |
|
752 self.menu.addSeparator() |
|
753 self.menuActs["Cut"] = self.menu.addAction( |
|
754 UI.PixmapCache.getIcon("editCut.png"), |
|
755 self.tr('Cut'), self.cut) |
|
756 self.menuActs["Copy"] = self.menu.addAction( |
|
757 UI.PixmapCache.getIcon("editCopy.png"), |
|
758 self.tr('Copy'), self.copy) |
|
759 self.menuActs["Paste"] = self.menu.addAction( |
|
760 UI.PixmapCache.getIcon("editPaste.png"), |
|
761 self.tr('Paste'), self.paste) |
|
762 if not self.miniMenu: |
|
763 self.menuActs["ExecuteSelection"] = self.menu.addAction( |
|
764 self.tr("Execute Selection In Console"), |
|
765 self.__executeSelection) |
|
766 self.menu.addSeparator() |
|
767 self.menu.addAction( |
|
768 UI.PixmapCache.getIcon("editIndent.png"), |
|
769 self.tr('Indent'), self.indentLineOrSelection) |
|
770 self.menu.addAction( |
|
771 UI.PixmapCache.getIcon("editUnindent.png"), |
|
772 self.tr('Unindent'), self.unindentLineOrSelection) |
|
773 self.menuActs["Comment"] = self.menu.addAction( |
|
774 UI.PixmapCache.getIcon("editComment.png"), |
|
775 self.tr('Comment'), self.commentLineOrSelection) |
|
776 self.menuActs["Uncomment"] = self.menu.addAction( |
|
777 UI.PixmapCache.getIcon("editUncomment.png"), |
|
778 self.tr('Uncomment'), self.uncommentLineOrSelection) |
|
779 self.menuActs["StreamComment"] = self.menu.addAction( |
|
780 self.tr('Stream Comment'), |
|
781 self.streamCommentLineOrSelection) |
|
782 self.menuActs["BoxComment"] = self.menu.addAction( |
|
783 self.tr('Box Comment'), |
|
784 self.boxCommentLineOrSelection) |
|
785 self.menu.addSeparator() |
|
786 self.menu.addAction( |
|
787 self.tr('Select to brace'), self.selectToMatchingBrace) |
|
788 self.menu.addAction(self.tr('Select all'), self.__selectAll) |
|
789 self.menu.addAction( |
|
790 self.tr('Deselect all'), self.__deselectAll) |
|
791 else: |
|
792 self.menuActs["ExecuteSelection"] = None |
|
793 self.menu.addSeparator() |
|
794 self.menuActs["SpellCheck"] = self.menu.addAction( |
|
795 UI.PixmapCache.getIcon("spellchecking.png"), |
|
796 self.tr('Check spelling...'), self.checkSpelling) |
|
797 self.menuActs["SpellCheckSelection"] = self.menu.addAction( |
|
798 UI.PixmapCache.getIcon("spellchecking.png"), |
|
799 self.tr('Check spelling of selection...'), |
|
800 self.__checkSpellingSelection) |
|
801 self.menuActs["SpellCheckRemove"] = self.menu.addAction( |
|
802 self.tr("Remove from dictionary"), |
|
803 self.__removeFromSpellingDictionary) |
|
804 self.menu.addSeparator() |
|
805 self.menu.addAction( |
|
806 self.tr('Shorten empty lines'), self.shortenEmptyLines) |
|
807 self.menu.addSeparator() |
|
808 self.menuActs["Languages"] = self.menu.addMenu(self.languagesMenu) |
|
809 self.menuActs["Encodings"] = self.menu.addMenu(self.encodingsMenu) |
|
810 self.menuActs["Eol"] = self.menu.addMenu(self.eolMenu) |
|
811 self.menu.addSeparator() |
|
812 self.menuActs["MonospacedFont"] = self.menu.addAction( |
|
813 self.tr("Use Monospaced Font"), |
|
814 self.handleMonospacedEnable) |
|
815 self.menuActs["MonospacedFont"].setCheckable(True) |
|
816 self.menuActs["MonospacedFont"].setChecked(self.useMonospaced) |
|
817 self.menuActs["AutosaveEnable"] = self.menu.addAction( |
|
818 self.tr("Autosave enabled"), self.__autosaveEnable) |
|
819 self.menuActs["AutosaveEnable"].setCheckable(True) |
|
820 self.menuActs["AutosaveEnable"].setChecked(self.autosaveEnabled) |
|
821 self.menuActs["TypingAidsEnabled"] = self.menu.addAction( |
|
822 self.tr("Typing aids enabled"), self.__toggleTypingAids) |
|
823 self.menuActs["TypingAidsEnabled"].setCheckable(True) |
|
824 self.menuActs["TypingAidsEnabled"].setEnabled( |
|
825 self.completer is not None) |
|
826 self.menuActs["TypingAidsEnabled"].setChecked( |
|
827 self.completer is not None and self.completer.isEnabled()) |
|
828 self.menuActs["AutoCompletionEnable"] = self.menu.addAction( |
|
829 self.tr("Automatic Completion enabled"), |
|
830 self.__toggleAutoCompletionEnable) |
|
831 self.menuActs["AutoCompletionEnable"].setCheckable(True) |
|
832 self.menuActs["AutoCompletionEnable"].setChecked( |
|
833 self.autoCompletionThreshold() != -1) |
|
834 if not self.isResourcesFile: |
|
835 self.menu.addMenu(self.autocompletionMenu) |
|
836 self.menuActs["calltip"] = self.menu.addAction( |
|
837 self.tr('Calltip'), self.callTip) |
|
838 self.menuActs["codeInfo"] = self.menu.addAction( |
|
839 self.tr('Code Info'), self.__showCodeInfo) |
|
840 self.menu.addSeparator() |
|
841 if self.isResourcesFile: |
|
842 self.menu.addMenu(self.resourcesMenu) |
|
843 else: |
|
844 self.menuActs["Check"] = self.menu.addMenu(self.checksMenu) |
|
845 self.menu.addSeparator() |
|
846 self.menuActs["Show"] = self.menu.addMenu(self.menuShow) |
|
847 self.menu.addSeparator() |
|
848 self.menuActs["Diagrams"] = self.menu.addMenu(self.graphicsMenu) |
|
849 self.menu.addSeparator() |
|
850 self.menuActs["Tools"] = self.menu.addMenu(self.toolsMenu) |
|
851 self.menu.addSeparator() |
|
852 self.menu.addAction( |
|
853 UI.PixmapCache.getIcon("documentNewView.png"), |
|
854 self.tr('New Document View'), self.__newView) |
|
855 self.menuActs["NewSplit"] = self.menu.addAction( |
|
856 UI.PixmapCache.getIcon("splitVertical.png"), |
|
857 self.tr('New Document View (with new split)'), |
|
858 self.__newViewNewSplit) |
|
859 self.menuActs["NewSplit"].setEnabled(self.vm.canSplit()) |
|
860 self.menu.addAction( |
|
861 UI.PixmapCache.getIcon("close.png"), |
|
862 self.tr('Close'), self.__contextClose) |
|
863 self.menu.addSeparator() |
|
864 self.reopenEncodingMenu = self.__initContextMenuReopenWithEncoding() |
|
865 self.menuActs["Reopen"] = self.menu.addMenu(self.reopenEncodingMenu) |
|
866 self.menuActs["Save"] = self.menu.addAction( |
|
867 UI.PixmapCache.getIcon("fileSave.png"), |
|
868 self.tr('Save'), self.__contextSave) |
|
869 self.menu.addAction( |
|
870 UI.PixmapCache.getIcon("fileSaveAs.png"), |
|
871 self.tr('Save As...'), self.__contextSaveAs) |
|
872 self.menu.addAction( |
|
873 UI.PixmapCache.getIcon("fileSaveCopy.png"), |
|
874 self.tr('Save Copy...'), self.__contextSaveCopy) |
|
875 if not self.miniMenu: |
|
876 self.menu.addMenu(self.exportersMenu) |
|
877 self.menu.addSeparator() |
|
878 self.menuActs["OpenRejections"] = self.menu.addAction( |
|
879 self.tr("Open 'rejection' file"), |
|
880 self.__contextOpenRejections) |
|
881 self.menu.addSeparator() |
|
882 self.menu.addAction( |
|
883 UI.PixmapCache.getIcon("printPreview.png"), |
|
884 self.tr("Print Preview"), self.printPreviewFile) |
|
885 self.menu.addAction( |
|
886 UI.PixmapCache.getIcon("print.png"), |
|
887 self.tr('Print'), self.printFile) |
|
888 else: |
|
889 self.menuActs["OpenRejections"] = None |
|
890 |
|
891 self.menu.aboutToShow.connect(self.__showContextMenu) |
|
892 |
|
893 self.spellingMenu = QMenu() |
|
894 self.__menus["Spelling"] = self.spellingMenu |
|
895 |
|
896 self.spellingMenu.aboutToShow.connect(self.__showContextMenuSpelling) |
|
897 self.spellingMenu.triggered.connect( |
|
898 self.__contextMenuSpellingTriggered) |
|
899 |
|
900 def __initContextMenuAutocompletion(self): |
|
901 """ |
|
902 Private method used to setup the Checks context sub menu. |
|
903 |
|
904 @return reference to the generated menu (QMenu) |
|
905 """ |
|
906 menu = QMenu(self.tr('Complete')) |
|
907 |
|
908 self.menuActs["acDynamic"] = menu.addAction( |
|
909 self.tr('Complete'), self.autoComplete) |
|
910 menu.addSeparator() |
|
911 self.menuActs["acClearCache"] = menu.addAction( |
|
912 self.tr("Clear Completions Cache"), self.__clearCompletionsCache) |
|
913 menu.addSeparator() |
|
914 menu.addAction( |
|
915 self.tr('Complete from Document'), self.autoCompleteFromDocument) |
|
916 self.menuActs["acAPI"] = menu.addAction( |
|
917 self.tr('Complete from APIs'), self.autoCompleteFromAPIs) |
|
918 self.menuActs["acAPIDocument"] = menu.addAction( |
|
919 self.tr('Complete from Document and APIs'), |
|
920 self.autoCompleteFromAll) |
|
921 |
|
922 menu.aboutToShow.connect(self.__showContextMenuAutocompletion) |
|
923 |
|
924 return menu |
|
925 |
|
926 def __initContextMenuChecks(self): |
|
927 """ |
|
928 Private method used to setup the Checks context sub menu. |
|
929 |
|
930 @return reference to the generated menu (QMenu) |
|
931 """ |
|
932 menu = QMenu(self.tr('Check')) |
|
933 menu.aboutToShow.connect(self.__showContextMenuChecks) |
|
934 return menu |
|
935 |
|
936 def __initContextMenuTools(self): |
|
937 """ |
|
938 Private method used to setup the Tools context sub menu. |
|
939 |
|
940 @return reference to the generated menu (QMenu) |
|
941 """ |
|
942 menu = QMenu(self.tr('Tools')) |
|
943 menu.aboutToShow.connect(self.__showContextMenuTools) |
|
944 return menu |
|
945 |
|
946 def __initContextMenuShow(self): |
|
947 """ |
|
948 Private method used to setup the Show context sub menu. |
|
949 |
|
950 @return reference to the generated menu (QMenu) |
|
951 """ |
|
952 menu = QMenu(self.tr('Show')) |
|
953 |
|
954 menu.addAction(self.tr('Code metrics...'), self.__showCodeMetrics) |
|
955 self.coverageMenuAct = menu.addAction( |
|
956 self.tr('Code coverage...'), self.__showCodeCoverage) |
|
957 self.coverageShowAnnotationMenuAct = menu.addAction( |
|
958 self.tr('Show code coverage annotations'), |
|
959 self.codeCoverageShowAnnotations) |
|
960 self.coverageHideAnnotationMenuAct = menu.addAction( |
|
961 self.tr('Hide code coverage annotations'), |
|
962 self.__codeCoverageHideAnnotations) |
|
963 self.profileMenuAct = menu.addAction( |
|
964 self.tr('Profile data...'), self.__showProfileData) |
|
965 |
|
966 menu.aboutToShow.connect(self.__showContextMenuShow) |
|
967 |
|
968 return menu |
|
969 |
|
970 def __initContextMenuGraphics(self): |
|
971 """ |
|
972 Private method used to setup the diagrams context sub menu. |
|
973 |
|
974 @return reference to the generated menu (QMenu) |
|
975 """ |
|
976 menu = QMenu(self.tr('Diagrams')) |
|
977 |
|
978 menu.addAction( |
|
979 self.tr('Class Diagram...'), self.__showClassDiagram) |
|
980 menu.addAction( |
|
981 self.tr('Package Diagram...'), self.__showPackageDiagram) |
|
982 menu.addAction( |
|
983 self.tr('Imports Diagram...'), self.__showImportsDiagram) |
|
984 self.applicationDiagramMenuAct = menu.addAction( |
|
985 self.tr('Application Diagram...'), |
|
986 self.__showApplicationDiagram) |
|
987 menu.addSeparator() |
|
988 menu.addAction( |
|
989 UI.PixmapCache.getIcon("open.png"), |
|
990 self.tr("Load Diagram..."), self.__loadDiagram) |
|
991 |
|
992 menu.aboutToShow.connect(self.__showContextMenuGraphics) |
|
993 |
|
994 return menu |
|
995 |
|
996 def __initContextMenuLanguages(self): |
|
997 """ |
|
998 Private method used to setup the Languages context sub menu. |
|
999 |
|
1000 @return reference to the generated menu (QMenu) |
|
1001 """ |
|
1002 menu = QMenu(self.tr("Languages")) |
|
1003 |
|
1004 self.languagesActGrp = QActionGroup(self) |
|
1005 self.noLanguageAct = menu.addAction( |
|
1006 UI.PixmapCache.getIcon("fileText.png"), |
|
1007 self.tr("No Language")) |
|
1008 self.noLanguageAct.setCheckable(True) |
|
1009 self.noLanguageAct.setData("None") |
|
1010 self.languagesActGrp.addAction(self.noLanguageAct) |
|
1011 menu.addSeparator() |
|
1012 |
|
1013 from . import Lexers |
|
1014 self.supportedLanguages = {} |
|
1015 supportedLanguages = Lexers.getSupportedLanguages() |
|
1016 languages = sorted(list(supportedLanguages.keys())) |
|
1017 for language in languages: |
|
1018 if language != "Guessed": |
|
1019 self.supportedLanguages[language] = \ |
|
1020 supportedLanguages[language][:2] |
|
1021 act = menu.addAction( |
|
1022 UI.PixmapCache.getIcon(supportedLanguages[language][2]), |
|
1023 self.supportedLanguages[language][0]) |
|
1024 act.setCheckable(True) |
|
1025 act.setData(language) |
|
1026 self.supportedLanguages[language].append(act) |
|
1027 self.languagesActGrp.addAction(act) |
|
1028 |
|
1029 menu.addSeparator() |
|
1030 self.pygmentsAct = menu.addAction(self.tr("Guessed")) |
|
1031 self.pygmentsAct.setCheckable(True) |
|
1032 self.pygmentsAct.setData("Guessed") |
|
1033 self.languagesActGrp.addAction(self.pygmentsAct) |
|
1034 self.pygmentsSelAct = menu.addAction(self.tr("Alternatives")) |
|
1035 self.pygmentsSelAct.setData("Alternatives") |
|
1036 |
|
1037 menu.triggered.connect(self.__languageMenuTriggered) |
|
1038 menu.aboutToShow.connect(self.__showContextMenuLanguages) |
|
1039 |
|
1040 return menu |
|
1041 |
|
1042 def __initContextMenuEncodings(self): |
|
1043 """ |
|
1044 Private method used to setup the Encodings context sub menu. |
|
1045 |
|
1046 @return reference to the generated menu (QMenu) |
|
1047 """ |
|
1048 self.supportedEncodings = {} |
|
1049 |
|
1050 menu = QMenu(self.tr("Encodings")) |
|
1051 |
|
1052 self.encodingsActGrp = QActionGroup(self) |
|
1053 |
|
1054 for encoding in sorted(Utilities.supportedCodecs): |
|
1055 act = menu.addAction(encoding) |
|
1056 act.setCheckable(True) |
|
1057 act.setData(encoding) |
|
1058 self.supportedEncodings[encoding] = act |
|
1059 self.encodingsActGrp.addAction(act) |
|
1060 |
|
1061 menu.triggered.connect(self.__encodingsMenuTriggered) |
|
1062 menu.aboutToShow.connect(self.__showContextMenuEncodings) |
|
1063 |
|
1064 return menu |
|
1065 |
|
1066 def __initContextMenuReopenWithEncoding(self): |
|
1067 """ |
|
1068 Private method used to setup the Reopen With Encoding context sub menu. |
|
1069 |
|
1070 @return reference to the generated menu (QMenu) |
|
1071 """ |
|
1072 menu = QMenu(self.tr("Re-Open With Encoding")) |
|
1073 menu.setIcon(UI.PixmapCache.getIcon("open.png")) |
|
1074 |
|
1075 for encoding in sorted(Utilities.supportedCodecs): |
|
1076 act = menu.addAction(encoding) |
|
1077 act.setData(encoding) |
|
1078 |
|
1079 menu.triggered.connect(self.__reopenWithEncodingMenuTriggered) |
|
1080 |
|
1081 return menu |
|
1082 |
|
1083 def __initContextMenuEol(self): |
|
1084 """ |
|
1085 Private method to setup the eol context sub menu. |
|
1086 |
|
1087 @return reference to the generated menu (QMenu) |
|
1088 """ |
|
1089 self.supportedEols = {} |
|
1090 |
|
1091 menu = QMenu(self.tr("End-of-Line Type")) |
|
1092 |
|
1093 self.eolActGrp = QActionGroup(self) |
|
1094 |
|
1095 act = menu.addAction(UI.PixmapCache.getIcon("eolLinux.png"), |
|
1096 self.tr("Unix")) |
|
1097 act.setCheckable(True) |
|
1098 act.setData('\n') |
|
1099 self.supportedEols['\n'] = act |
|
1100 self.eolActGrp.addAction(act) |
|
1101 |
|
1102 act = menu.addAction(UI.PixmapCache.getIcon("eolWindows.png"), |
|
1103 self.tr("Windows")) |
|
1104 act.setCheckable(True) |
|
1105 act.setData('\r\n') |
|
1106 self.supportedEols['\r\n'] = act |
|
1107 self.eolActGrp.addAction(act) |
|
1108 |
|
1109 act = menu.addAction(UI.PixmapCache.getIcon("eolMac.png"), |
|
1110 self.tr("Macintosh")) |
|
1111 act.setCheckable(True) |
|
1112 act.setData('\r') |
|
1113 self.supportedEols['\r'] = act |
|
1114 self.eolActGrp.addAction(act) |
|
1115 |
|
1116 menu.triggered.connect(self.__eolMenuTriggered) |
|
1117 menu.aboutToShow.connect(self.__showContextMenuEol) |
|
1118 |
|
1119 return menu |
|
1120 |
|
1121 def __initContextMenuExporters(self): |
|
1122 """ |
|
1123 Private method used to setup the Exporters context sub menu. |
|
1124 |
|
1125 @return reference to the generated menu (QMenu) |
|
1126 """ |
|
1127 menu = QMenu(self.tr("Export as")) |
|
1128 |
|
1129 from . import Exporters |
|
1130 supportedExporters = Exporters.getSupportedFormats() |
|
1131 exporters = sorted(list(supportedExporters.keys())) |
|
1132 for exporter in exporters: |
|
1133 act = menu.addAction(supportedExporters[exporter]) |
|
1134 act.setData(exporter) |
|
1135 |
|
1136 menu.triggered.connect(self.__exportMenuTriggered) |
|
1137 |
|
1138 return menu |
|
1139 |
|
1140 def __initContextMenuMargins(self): |
|
1141 """ |
|
1142 Private method used to setup the context menu for the margins. |
|
1143 """ |
|
1144 self.marginMenuActs = {} |
|
1145 |
|
1146 # bookmark margin |
|
1147 self.bmMarginMenu = QMenu() |
|
1148 |
|
1149 self.bmMarginMenu.addAction( |
|
1150 self.tr('Toggle bookmark'), self.menuToggleBookmark) |
|
1151 self.marginMenuActs["NextBookmark"] = self.bmMarginMenu.addAction( |
|
1152 self.tr('Next bookmark'), self.nextBookmark) |
|
1153 self.marginMenuActs["PreviousBookmark"] = self.bmMarginMenu.addAction( |
|
1154 self.tr('Previous bookmark'), self.previousBookmark) |
|
1155 self.marginMenuActs["ClearBookmark"] = self.bmMarginMenu.addAction( |
|
1156 self.tr('Clear all bookmarks'), self.clearBookmarks) |
|
1157 |
|
1158 self.bmMarginMenu.aboutToShow.connect( |
|
1159 lambda: self.__showContextMenuMargin(self.bmMarginMenu)) |
|
1160 |
|
1161 # breakpoint margin |
|
1162 self.bpMarginMenu = QMenu() |
|
1163 |
|
1164 self.marginMenuActs["Breakpoint"] = self.bpMarginMenu.addAction( |
|
1165 self.tr('Toggle breakpoint'), self.menuToggleBreakpoint) |
|
1166 self.marginMenuActs["TempBreakpoint"] = self.bpMarginMenu.addAction( |
|
1167 self.tr('Toggle temporary breakpoint'), |
|
1168 self.__menuToggleTemporaryBreakpoint) |
|
1169 self.marginMenuActs["EditBreakpoint"] = self.bpMarginMenu.addAction( |
|
1170 self.tr('Edit breakpoint...'), self.menuEditBreakpoint) |
|
1171 self.marginMenuActs["EnableBreakpoint"] = self.bpMarginMenu.addAction( |
|
1172 self.tr('Enable breakpoint'), |
|
1173 self.__menuToggleBreakpointEnabled) |
|
1174 self.marginMenuActs["NextBreakpoint"] = self.bpMarginMenu.addAction( |
|
1175 self.tr('Next breakpoint'), self.menuNextBreakpoint) |
|
1176 self.marginMenuActs["PreviousBreakpoint"] = \ |
|
1177 self.bpMarginMenu.addAction( |
|
1178 self.tr('Previous breakpoint'), |
|
1179 self.menuPreviousBreakpoint) |
|
1180 self.marginMenuActs["ClearBreakpoint"] = self.bpMarginMenu.addAction( |
|
1181 self.tr('Clear all breakpoints'), self.__menuClearBreakpoints) |
|
1182 |
|
1183 self.bpMarginMenu.aboutToShow.connect( |
|
1184 lambda: self.__showContextMenuMargin(self.bpMarginMenu)) |
|
1185 |
|
1186 # fold margin |
|
1187 self.foldMarginMenu = QMenu() |
|
1188 |
|
1189 self.marginMenuActs["ToggleAllFolds"] = \ |
|
1190 self.foldMarginMenu.addAction( |
|
1191 self.tr("Toggle all folds"), |
|
1192 self.foldAll) |
|
1193 self.marginMenuActs["ToggleAllFoldsAndChildren"] = \ |
|
1194 self.foldMarginMenu.addAction( |
|
1195 self.tr("Toggle all folds (including children)"), |
|
1196 lambda: self.foldAll(True)) |
|
1197 self.marginMenuActs["ToggleCurrentFold"] = \ |
|
1198 self.foldMarginMenu.addAction( |
|
1199 self.tr("Toggle current fold"), |
|
1200 self.toggleCurrentFold) |
|
1201 self.foldMarginMenu.addSeparator() |
|
1202 self.marginMenuActs["ExpandChildren"] = \ |
|
1203 self.foldMarginMenu.addAction( |
|
1204 self.tr("Expand (including children)"), |
|
1205 self.__contextMenuExpandFoldWithChildren) |
|
1206 self.marginMenuActs["CollapseChildren"] = \ |
|
1207 self.foldMarginMenu.addAction( |
|
1208 self.tr("Collapse (including children)"), |
|
1209 self.__contextMenuCollapseFoldWithChildren) |
|
1210 self.foldMarginMenu.addSeparator() |
|
1211 self.marginMenuActs["ClearAllFolds"] = \ |
|
1212 self.foldMarginMenu.addAction( |
|
1213 self.tr("Clear all folds"), |
|
1214 self.clearFolds) |
|
1215 |
|
1216 self.foldMarginMenu.aboutToShow.connect( |
|
1217 lambda: self.__showContextMenuMargin(self.foldMarginMenu)) |
|
1218 |
|
1219 # indicator margin |
|
1220 self.indicMarginMenu = QMenu() |
|
1221 |
|
1222 self.marginMenuActs["GotoSyntaxError"] = \ |
|
1223 self.indicMarginMenu.addAction( |
|
1224 self.tr('Goto syntax error'), self.gotoSyntaxError) |
|
1225 self.marginMenuActs["ShowSyntaxError"] = \ |
|
1226 self.indicMarginMenu.addAction( |
|
1227 self.tr('Show syntax error message'), |
|
1228 self.__showSyntaxError) |
|
1229 self.marginMenuActs["ClearSyntaxError"] = \ |
|
1230 self.indicMarginMenu.addAction( |
|
1231 self.tr('Clear syntax error'), self.clearSyntaxError) |
|
1232 self.indicMarginMenu.addSeparator() |
|
1233 self.marginMenuActs["NextWarningMarker"] = \ |
|
1234 self.indicMarginMenu.addAction( |
|
1235 self.tr("Next warning"), self.nextWarning) |
|
1236 self.marginMenuActs["PreviousWarningMarker"] = \ |
|
1237 self.indicMarginMenu.addAction( |
|
1238 self.tr("Previous warning"), self.previousWarning) |
|
1239 self.marginMenuActs["ShowWarning"] = \ |
|
1240 self.indicMarginMenu.addAction( |
|
1241 self.tr('Show warning message'), self.__showWarning) |
|
1242 self.marginMenuActs["ClearWarnings"] = \ |
|
1243 self.indicMarginMenu.addAction( |
|
1244 self.tr('Clear warnings'), self.clearWarnings) |
|
1245 self.indicMarginMenu.addSeparator() |
|
1246 self.marginMenuActs["NextCoverageMarker"] = \ |
|
1247 self.indicMarginMenu.addAction( |
|
1248 self.tr('Next uncovered line'), self.nextUncovered) |
|
1249 self.marginMenuActs["PreviousCoverageMarker"] = \ |
|
1250 self.indicMarginMenu.addAction( |
|
1251 self.tr('Previous uncovered line'), self.previousUncovered) |
|
1252 self.indicMarginMenu.addSeparator() |
|
1253 self.marginMenuActs["NextTaskMarker"] = \ |
|
1254 self.indicMarginMenu.addAction( |
|
1255 self.tr('Next task'), self.nextTask) |
|
1256 self.marginMenuActs["PreviousTaskMarker"] = \ |
|
1257 self.indicMarginMenu.addAction( |
|
1258 self.tr('Previous task'), self.previousTask) |
|
1259 self.indicMarginMenu.addSeparator() |
|
1260 self.marginMenuActs["NextChangeMarker"] = \ |
|
1261 self.indicMarginMenu.addAction( |
|
1262 self.tr('Next change'), self.nextChange) |
|
1263 self.marginMenuActs["PreviousChangeMarker"] = \ |
|
1264 self.indicMarginMenu.addAction( |
|
1265 self.tr('Previous change'), self.previousChange) |
|
1266 self.marginMenuActs["ClearChangeMarkers"] = \ |
|
1267 self.indicMarginMenu.addAction( |
|
1268 self.tr('Clear changes'), self.__reinitOnlineChangeTrace) |
|
1269 |
|
1270 self.indicMarginMenu.aboutToShow.connect( |
|
1271 lambda: self.__showContextMenuMargin(self.indicMarginMenu)) |
|
1272 |
|
1273 def __exportMenuTriggered(self, act): |
|
1274 """ |
|
1275 Private method to handle the selection of an export format. |
|
1276 |
|
1277 @param act reference to the action that was triggered (QAction) |
|
1278 """ |
|
1279 exporterFormat = act.data() |
|
1280 self.exportFile(exporterFormat) |
|
1281 |
|
1282 def exportFile(self, exporterFormat): |
|
1283 """ |
|
1284 Public method to export the file. |
|
1285 |
|
1286 @param exporterFormat format the file should be exported into (string) |
|
1287 """ |
|
1288 if exporterFormat: |
|
1289 from . import Exporters |
|
1290 exporter = Exporters.getExporter(exporterFormat, self) |
|
1291 if exporter: |
|
1292 exporter.exportSource() |
|
1293 else: |
|
1294 E5MessageBox.critical( |
|
1295 self, |
|
1296 self.tr("Export source"), |
|
1297 self.tr( |
|
1298 """<p>No exporter available for the """ |
|
1299 """export format <b>{0}</b>. Aborting...</p>""") |
|
1300 .format(exporterFormat)) |
|
1301 else: |
|
1302 E5MessageBox.critical( |
|
1303 self, |
|
1304 self.tr("Export source"), |
|
1305 self.tr("""No export format given. Aborting...""")) |
|
1306 |
|
1307 def __showContextMenuLanguages(self): |
|
1308 """ |
|
1309 Private slot handling the aboutToShow signal of the languages context |
|
1310 menu. |
|
1311 """ |
|
1312 if self.apiLanguage.startswith("Pygments|"): |
|
1313 self.pygmentsSelAct.setText( |
|
1314 self.tr("Alternatives ({0})").format( |
|
1315 self.getLanguage(normalized=False))) |
|
1316 else: |
|
1317 self.pygmentsSelAct.setText(self.tr("Alternatives")) |
|
1318 self.showMenu.emit("Languages", self.languagesMenu, self) |
|
1319 |
|
1320 def __selectPygmentsLexer(self): |
|
1321 """ |
|
1322 Private method to select a specific pygments lexer. |
|
1323 |
|
1324 @return name of the selected pygments lexer (string) |
|
1325 """ |
|
1326 from pygments.lexers import get_all_lexers |
|
1327 lexerList = sorted(l[0] for l in get_all_lexers()) |
|
1328 try: |
|
1329 lexerSel = lexerList.index( |
|
1330 self.getLanguage(normalized=False, forPygments=True)) |
|
1331 except ValueError: |
|
1332 lexerSel = 0 |
|
1333 lexerName, ok = QInputDialog.getItem( |
|
1334 self, |
|
1335 self.tr("Pygments Lexer"), |
|
1336 self.tr("Select the Pygments lexer to apply."), |
|
1337 lexerList, |
|
1338 lexerSel, |
|
1339 False) |
|
1340 if ok and lexerName: |
|
1341 return lexerName |
|
1342 else: |
|
1343 return "" |
|
1344 |
|
1345 def __languageMenuTriggered(self, act): |
|
1346 """ |
|
1347 Private method to handle the selection of a lexer language. |
|
1348 |
|
1349 @param act reference to the action that was triggered (QAction) |
|
1350 """ |
|
1351 if act == self.noLanguageAct: |
|
1352 self.__resetLanguage() |
|
1353 elif act == self.pygmentsAct: |
|
1354 self.setLanguage("dummy.pygments") |
|
1355 elif act == self.pygmentsSelAct: |
|
1356 language = self.__selectPygmentsLexer() |
|
1357 if language: |
|
1358 self.setLanguage("dummy.pygments", pyname=language) |
|
1359 else: |
|
1360 language = act.data() |
|
1361 if language: |
|
1362 self.filetype = language |
|
1363 self.setLanguage(self.supportedLanguages[language][1]) |
|
1364 self.checkSyntax() |
|
1365 |
|
1366 def __languageChanged(self, language, propagate=True): |
|
1367 """ |
|
1368 Private slot handling a change of a connected editor's language. |
|
1369 |
|
1370 @param language language to be set (string) |
|
1371 @keyparam propagate flag indicating to propagate the change (boolean) |
|
1372 """ |
|
1373 if language == '': |
|
1374 self.__resetLanguage(propagate=propagate) |
|
1375 elif language == "Guessed": |
|
1376 self.setLanguage("dummy.pygments", |
|
1377 propagate=propagate) |
|
1378 elif language.startswith("Pygments|"): |
|
1379 pyname = language.split("|", 1)[1] |
|
1380 self.setLanguage("dummy.pygments", pyname=pyname, |
|
1381 propagate=propagate) |
|
1382 else: |
|
1383 self.filetype = language |
|
1384 self.setLanguage(self.supportedLanguages[language][1], |
|
1385 propagate=propagate) |
|
1386 self.checkSyntax() |
|
1387 |
|
1388 def __resetLanguage(self, propagate=True): |
|
1389 """ |
|
1390 Private method used to reset the language selection. |
|
1391 |
|
1392 @keyparam propagate flag indicating to propagate the change (boolean) |
|
1393 """ |
|
1394 if self.lexer_ is not None and \ |
|
1395 (self.lexer_.lexer() == "container" or self.lexer_.lexer() is None): |
|
1396 self.SCN_STYLENEEDED.disconnect(self.__styleNeeded) |
|
1397 |
|
1398 self.apiLanguage = "" |
|
1399 self.lexer_ = None |
|
1400 self.__lexerReset = True |
|
1401 self.setLexer() |
|
1402 if self.completer is not None: |
|
1403 self.completer.setEnabled(False) |
|
1404 self.completer = None |
|
1405 useMonospaced = self.useMonospaced |
|
1406 self.__setTextDisplay() |
|
1407 self.setMonospaced(useMonospaced) |
|
1408 self.menuActs["MonospacedFont"].setChecked(self.useMonospaced) |
|
1409 |
|
1410 if not self.inLanguageChanged and propagate: |
|
1411 self.inLanguageChanged = True |
|
1412 self.languageChanged.emit(self.apiLanguage) |
|
1413 self.inLanguageChanged = False |
|
1414 |
|
1415 def setLanguage(self, filename, initTextDisplay=True, propagate=True, |
|
1416 pyname=""): |
|
1417 """ |
|
1418 Public method to set a lexer language. |
|
1419 |
|
1420 @param filename filename used to determine the associated lexer |
|
1421 language (string) |
|
1422 @param initTextDisplay flag indicating an initialization of the text |
|
1423 display is required as well (boolean) |
|
1424 @keyparam propagate flag indicating to propagate the change (boolean) |
|
1425 @keyparam pyname name of the pygments lexer to use (string) |
|
1426 """ |
|
1427 # clear all warning and syntax error markers |
|
1428 self.clearSyntaxError() |
|
1429 self.clearWarnings() |
|
1430 |
|
1431 self.menuActs["MonospacedFont"].setChecked(False) |
|
1432 |
|
1433 self.__lexerReset = False |
|
1434 self.__bindLexer(filename, pyname=pyname) |
|
1435 self.__bindCompleter(filename) |
|
1436 self.recolor() |
|
1437 self.__checkLanguage() |
|
1438 |
|
1439 # set the text display |
|
1440 if initTextDisplay: |
|
1441 self.__setTextDisplay() |
|
1442 |
|
1443 # set the auto-completion and call-tips function |
|
1444 self.__setAutoCompletion() |
|
1445 self.__setCallTips() |
|
1446 |
|
1447 if not self.inLanguageChanged and propagate: |
|
1448 self.inLanguageChanged = True |
|
1449 self.languageChanged.emit(self.apiLanguage) |
|
1450 self.inLanguageChanged = False |
|
1451 |
|
1452 def __checkLanguage(self): |
|
1453 """ |
|
1454 Private method to check the selected language of the language submenu. |
|
1455 """ |
|
1456 if self.apiLanguage == "": |
|
1457 self.noLanguageAct.setChecked(True) |
|
1458 elif self.apiLanguage == "Guessed": |
|
1459 self.pygmentsAct.setChecked(True) |
|
1460 elif self.apiLanguage.startswith("Pygments|"): |
|
1461 act = self.languagesActGrp.checkedAction() |
|
1462 if act: |
|
1463 act.setChecked(False) |
|
1464 else: |
|
1465 self.supportedLanguages[self.apiLanguage][2].setChecked(True) |
|
1466 |
|
1467 def projectLexerAssociationsChanged(self): |
|
1468 """ |
|
1469 Public slot to handle changes of the project lexer associations. |
|
1470 """ |
|
1471 self.setLanguage(self.fileName) |
|
1472 |
|
1473 def __showContextMenuEncodings(self): |
|
1474 """ |
|
1475 Private slot handling the aboutToShow signal of the encodings context |
|
1476 menu. |
|
1477 """ |
|
1478 self.showMenu.emit("Encodings", self.encodingsMenu, self) |
|
1479 |
|
1480 def __encodingsMenuTriggered(self, act): |
|
1481 """ |
|
1482 Private method to handle the selection of an encoding. |
|
1483 |
|
1484 @param act reference to the action that was triggered (QAction) |
|
1485 """ |
|
1486 encoding = act.data() |
|
1487 self.setModified(True) |
|
1488 self.__encodingChanged("{0}-selected".format(encoding)) |
|
1489 |
|
1490 def __checkEncoding(self): |
|
1491 """ |
|
1492 Private method to check the selected encoding of the encodings submenu. |
|
1493 """ |
|
1494 try: |
|
1495 self.supportedEncodings[self.__normalizedEncoding()]\ |
|
1496 .setChecked(True) |
|
1497 except (AttributeError, KeyError): |
|
1498 pass |
|
1499 |
|
1500 def __encodingChanged(self, encoding, propagate=True): |
|
1501 """ |
|
1502 Private slot to handle a change of the encoding. |
|
1503 |
|
1504 @param encoding changed encoding (string) |
|
1505 @keyparam propagate flag indicating to propagate the change (boolean) |
|
1506 """ |
|
1507 self.encoding = encoding |
|
1508 self.__checkEncoding() |
|
1509 |
|
1510 if not self.inEncodingChanged and propagate: |
|
1511 self.inEncodingChanged = True |
|
1512 self.encodingChanged.emit(self.encoding) |
|
1513 self.inEncodingChanged = False |
|
1514 |
|
1515 def __normalizedEncoding(self, encoding=""): |
|
1516 """ |
|
1517 Private method to calculate the normalized encoding string. |
|
1518 |
|
1519 @param encoding encoding to be normalized (string) |
|
1520 @return normalized encoding (string) |
|
1521 """ |
|
1522 if not encoding: |
|
1523 encoding = self.encoding |
|
1524 return encoding.replace("-default", "")\ |
|
1525 .replace("-guessed", "")\ |
|
1526 .replace("-selected", "") |
|
1527 |
|
1528 def __showContextMenuEol(self): |
|
1529 """ |
|
1530 Private slot handling the aboutToShow signal of the eol context menu. |
|
1531 """ |
|
1532 self.showMenu.emit("Eol", self.eolMenu, self) |
|
1533 |
|
1534 def __eolMenuTriggered(self, act): |
|
1535 """ |
|
1536 Private method to handle the selection of an eol type. |
|
1537 |
|
1538 @param act reference to the action that was triggered (QAction) |
|
1539 """ |
|
1540 eol = act.data() |
|
1541 self.setEolModeByEolString(eol) |
|
1542 self.convertEols(self.eolMode()) |
|
1543 |
|
1544 def __checkEol(self): |
|
1545 """ |
|
1546 Private method to check the selected eol type of the eol submenu. |
|
1547 """ |
|
1548 try: |
|
1549 self.supportedEols[self.getLineSeparator()].setChecked(True) |
|
1550 except (AttributeError, TypeError): |
|
1551 pass |
|
1552 |
|
1553 def __eolChanged(self): |
|
1554 """ |
|
1555 Private slot to handle a change of the eol mode. |
|
1556 """ |
|
1557 self.__checkEol() |
|
1558 |
|
1559 if not self.inEolChanged: |
|
1560 self.inEolChanged = True |
|
1561 eol = self.getLineSeparator() |
|
1562 self.eolChanged.emit(eol) |
|
1563 self.inEolChanged = False |
|
1564 |
|
1565 def __bindLexer(self, filename, pyname=""): |
|
1566 """ |
|
1567 Private slot to set the correct lexer depending on language. |
|
1568 |
|
1569 @param filename filename used to determine the associated lexer |
|
1570 language (string) |
|
1571 @keyparam pyname name of the pygments lexer to use (string) |
|
1572 """ |
|
1573 if self.lexer_ is not None and \ |
|
1574 (self.lexer_.lexer() == "container" or self.lexer_.lexer() is None): |
|
1575 self.SCN_STYLENEEDED.disconnect(self.__styleNeeded) |
|
1576 |
|
1577 language = "" |
|
1578 if not self.filetype: |
|
1579 if filename: |
|
1580 basename = os.path.basename(filename) |
|
1581 if self.project.isOpen() and \ |
|
1582 self.project.isProjectFile(filename): |
|
1583 language = self.project.getEditorLexerAssoc(basename) |
|
1584 if not language: |
|
1585 language = Preferences.getEditorLexerAssoc(basename) |
|
1586 if not language: |
|
1587 bindName = self.__bindName(self.text(0)) |
|
1588 if bindName: |
|
1589 language = Preferences.getEditorLexerAssoc(bindName) |
|
1590 if language == "Python": |
|
1591 # correction for Python |
|
1592 pyVer = Utilities.determinePythonVersion( |
|
1593 filename, self.text(0), self) |
|
1594 language = "Python{0}".format(pyVer) |
|
1595 if language in ['Python2', 'Python3', 'Ruby', 'JavaScript']: |
|
1596 self.filetype = language |
|
1597 else: |
|
1598 self.filetype = "" |
|
1599 else: |
|
1600 language = self.filetype |
|
1601 |
|
1602 if language.startswith("Pygments|"): |
|
1603 pyname = language.split("|", 1)[1] |
|
1604 language = "" |
|
1605 |
|
1606 from . import Lexers |
|
1607 self.lexer_ = Lexers.getLexer(language, self, pyname=pyname) |
|
1608 if self.lexer_ is None: |
|
1609 self.setLexer() |
|
1610 self.apiLanguage = "" |
|
1611 return |
|
1612 |
|
1613 if pyname: |
|
1614 self.apiLanguage = "Pygments|{0}".format(pyname) |
|
1615 else: |
|
1616 if language == "Protocol": |
|
1617 self.apiLanguage = language |
|
1618 else: |
|
1619 # Change API language for lexer where QScintilla reports |
|
1620 # an abbreviated name. |
|
1621 self.apiLanguage = self.lexer_.language() |
|
1622 if self.apiLanguage == "POV": |
|
1623 self.apiLanguage = "Povray" |
|
1624 elif self.apiLanguage == "PO": |
|
1625 self.apiLanguage = "Gettext" |
|
1626 self.setLexer(self.lexer_) |
|
1627 self.__setMarginsDisplay() |
|
1628 if self.lexer_.lexer() == "container" or self.lexer_.lexer() is None: |
|
1629 self.SCN_STYLENEEDED.connect(self.__styleNeeded) |
|
1630 |
|
1631 # get the font for style 0 and set it as the default font |
|
1632 key = 'Scintilla/{0}/style0/font'.format(self.lexer_.language()) |
|
1633 fdesc = Preferences.Prefs.settings.value(key) |
|
1634 if fdesc is not None: |
|
1635 font = QFont(fdesc[0], int(fdesc[1])) |
|
1636 self.lexer_.setDefaultFont(font) |
|
1637 self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla") |
|
1638 if self.lexer_.hasSubstyles(): |
|
1639 self.lexer_.readSubstyles(self) |
|
1640 |
|
1641 # now set the lexer properties |
|
1642 self.lexer_.initProperties() |
|
1643 |
|
1644 # initialize the lexer APIs settings |
|
1645 if self.project.isOpen() and self.project.isProjectFile(filename): |
|
1646 projectType = self.project.getProjectType() |
|
1647 else: |
|
1648 projectType = "" |
|
1649 api = self.vm.getAPIsManager().getAPIs(self.apiLanguage, |
|
1650 projectType=projectType) |
|
1651 if api is not None and not api.isEmpty(): |
|
1652 self.lexer_.setAPIs(api.getQsciAPIs()) |
|
1653 self.acAPI = True |
|
1654 else: |
|
1655 self.acAPI = False |
|
1656 self.autoCompletionAPIsAvailable.emit(self.acAPI) |
|
1657 |
|
1658 self.__setAnnotationStyles() |
|
1659 |
|
1660 self.lexer_.setDefaultColor(self.lexer_.color(0)) |
|
1661 self.lexer_.setDefaultPaper(self.lexer_.paper(0)) |
|
1662 |
|
1663 def __styleNeeded(self, position): |
|
1664 """ |
|
1665 Private slot to handle the need for more styling. |
|
1666 |
|
1667 @param position end position, that needs styling (integer) |
|
1668 """ |
|
1669 self.lexer_.styleText(self.getEndStyled(), position) |
|
1670 |
|
1671 def getLexer(self): |
|
1672 """ |
|
1673 Public method to retrieve a reference to the lexer object. |
|
1674 |
|
1675 @return the lexer object (Lexer) |
|
1676 """ |
|
1677 return self.lexer_ |
|
1678 |
|
1679 def getLanguage(self, normalized=True, forPygments=False): |
|
1680 """ |
|
1681 Public method to retrieve the language of the editor. |
|
1682 |
|
1683 @keyparam normalized flag indicating to normalize some Pygments |
|
1684 lexer names (boolean) |
|
1685 @keyparam forPygments flag indicating to normalize some lexer |
|
1686 names for Pygments (boolean) |
|
1687 @return language of the editor (string) |
|
1688 """ |
|
1689 if self.apiLanguage == "Guessed" or \ |
|
1690 self.apiLanguage.startswith("Pygments|"): |
|
1691 lang = self.lexer_.name() |
|
1692 if normalized: |
|
1693 # adjust some Pygments lexer names |
|
1694 if lang == "Python": |
|
1695 lang = "Python2" |
|
1696 elif lang == "Python 3": |
|
1697 lang = "Python3" |
|
1698 else: |
|
1699 lang = self.apiLanguage |
|
1700 if forPygments: |
|
1701 # adjust some names to Pygments lexer names |
|
1702 if lang == "Python2": |
|
1703 lang = "Python" |
|
1704 elif lang == "Python3": |
|
1705 lang = "Python 3" |
|
1706 return lang |
|
1707 |
|
1708 def getApiLanguage(self): |
|
1709 """ |
|
1710 Public method to get the API language of the editor. |
|
1711 |
|
1712 @return API language |
|
1713 @rtype str |
|
1714 """ |
|
1715 return self.apiLanguage |
|
1716 |
|
1717 def __bindCompleter(self, filename): |
|
1718 """ |
|
1719 Private slot to set the correct typing completer depending on language. |
|
1720 |
|
1721 @param filename filename used to determine the associated typing |
|
1722 completer language (string) |
|
1723 """ |
|
1724 if self.completer is not None: |
|
1725 self.completer.setEnabled(False) |
|
1726 self.completer = None |
|
1727 |
|
1728 filename = os.path.basename(filename) |
|
1729 apiLanguage = Preferences.getEditorLexerAssoc(filename) |
|
1730 if apiLanguage == "": |
|
1731 pyVer = self.__getPyVersion() |
|
1732 if pyVer: |
|
1733 apiLanguage = "Python{0}".format(pyVer) |
|
1734 elif self.isRubyFile(): |
|
1735 apiLanguage = "Ruby" |
|
1736 |
|
1737 from . import TypingCompleters |
|
1738 self.completer = TypingCompleters.getCompleter(apiLanguage, self) |
|
1739 |
|
1740 def getCompleter(self): |
|
1741 """ |
|
1742 Public method to retrieve a reference to the completer object. |
|
1743 |
|
1744 @return the completer object (CompleterBase) |
|
1745 """ |
|
1746 return self.completer |
|
1747 |
|
1748 def __modificationChanged(self, m): |
|
1749 """ |
|
1750 Private slot to handle the modificationChanged signal. |
|
1751 |
|
1752 It emits the signal modificationStatusChanged with parameters |
|
1753 m and self. |
|
1754 |
|
1755 @param m modification status |
|
1756 """ |
|
1757 if not m and bool(self.fileName): |
|
1758 self.lastModified = QFileInfo(self.fileName).lastModified() |
|
1759 self.modificationStatusChanged.emit(m, self) |
|
1760 self.undoAvailable.emit(self.isUndoAvailable()) |
|
1761 self.redoAvailable.emit(self.isRedoAvailable()) |
|
1762 |
|
1763 def __cursorPositionChanged(self, line, index): |
|
1764 """ |
|
1765 Private slot to handle the cursorPositionChanged signal. |
|
1766 |
|
1767 It emits the signal cursorChanged with parameters fileName, |
|
1768 line and pos. |
|
1769 |
|
1770 @param line line number of the cursor |
|
1771 @param index position in line of the cursor |
|
1772 """ |
|
1773 self.cursorChanged.emit(self.fileName, line + 1, index) |
|
1774 |
|
1775 if Preferences.getEditor("MarkOccurrencesEnabled"): |
|
1776 self.__markOccurrencesTimer.stop() |
|
1777 self.__markOccurrencesTimer.start() |
|
1778 |
|
1779 if self.lastLine != line: |
|
1780 self.cursorLineChanged.emit(line) |
|
1781 |
|
1782 if self.spell is not None: |
|
1783 # do spell checking |
|
1784 doSpelling = True |
|
1785 if self.lastLine == line: |
|
1786 start, end = self.getWordBoundaries( |
|
1787 line, index, useWordChars=False) |
|
1788 if start <= self.lastIndex and self.lastIndex <= end: |
|
1789 doSpelling = False |
|
1790 if doSpelling: |
|
1791 pos = self.positionFromLineIndex(self.lastLine, self.lastIndex) |
|
1792 self.spell.checkWord(pos) |
|
1793 |
|
1794 if self.lastLine != line: |
|
1795 self.__markerMap.update() |
|
1796 |
|
1797 self.lastLine = line |
|
1798 self.lastIndex = index |
|
1799 |
|
1800 def __modificationReadOnly(self): |
|
1801 """ |
|
1802 Private slot to handle the modificationAttempted signal. |
|
1803 """ |
|
1804 E5MessageBox.warning( |
|
1805 self, |
|
1806 self.tr("Modification of Read Only file"), |
|
1807 self.tr("""You are attempting to change a read only file. """ |
|
1808 """Please save to a different file first.""")) |
|
1809 |
|
1810 def setNoName(self, noName): |
|
1811 """ |
|
1812 Public method to set the display string for an unnamed editor. |
|
1813 |
|
1814 @param noName display string for this unnamed editor (string) |
|
1815 """ |
|
1816 self.noName = noName |
|
1817 |
|
1818 def getNoName(self): |
|
1819 """ |
|
1820 Public method to get the display string for an unnamed editor. |
|
1821 |
|
1822 @return display string for this unnamed editor (string) |
|
1823 """ |
|
1824 return self.noName |
|
1825 |
|
1826 def getFileName(self): |
|
1827 """ |
|
1828 Public method to return the name of the file being displayed. |
|
1829 |
|
1830 @return filename of the displayed file (string) |
|
1831 """ |
|
1832 return self.fileName |
|
1833 |
|
1834 def getFileType(self): |
|
1835 """ |
|
1836 Public method to return the type of the file being displayed. |
|
1837 |
|
1838 @return type of the displayed file (string) |
|
1839 """ |
|
1840 return self.filetype |
|
1841 |
|
1842 def getFileTypeByFlag(self): |
|
1843 """ |
|
1844 Public method to return the type of the file, if it was set by an |
|
1845 eflag: marker. |
|
1846 |
|
1847 @return type of the displayed file, if set by an eflag: marker or an |
|
1848 empty string (string) |
|
1849 """ |
|
1850 if self.filetypeByFlag: |
|
1851 return self.filetype |
|
1852 else: |
|
1853 return "" |
|
1854 |
|
1855 def determineFileType(self): |
|
1856 """ |
|
1857 Public method to determine the file type using various tests. |
|
1858 |
|
1859 @return type of the displayed file or an empty string (string) |
|
1860 """ |
|
1861 ftype = self.filetype |
|
1862 if not ftype: |
|
1863 pyVer = self.__getPyVersion() |
|
1864 if pyVer: |
|
1865 ftype = "Python{0}".format(pyVer) |
|
1866 elif self.isRubyFile(): |
|
1867 ftype = "Ruby" |
|
1868 else: |
|
1869 ftype = "" |
|
1870 |
|
1871 return ftype |
|
1872 |
|
1873 def getEncoding(self): |
|
1874 """ |
|
1875 Public method to return the current encoding. |
|
1876 |
|
1877 @return current encoding (string) |
|
1878 """ |
|
1879 return self.encoding |
|
1880 |
|
1881 def __getPyVersion(self): |
|
1882 """ |
|
1883 Private method to return the Python main version (2 or 3) or 0 if it's |
|
1884 not a Python file at all. |
|
1885 |
|
1886 @return Python version (2 or 3) or 0 if it's not a Python file (int) |
|
1887 """ |
|
1888 return Utilities.determinePythonVersion( |
|
1889 self.fileName, self.text(0), self) |
|
1890 |
|
1891 def isPyFile(self): |
|
1892 """ |
|
1893 Public method to return a flag indicating a Python (2 or 3) file. |
|
1894 |
|
1895 @return flag indicating a Python (2 or 3) file (boolean) |
|
1896 """ |
|
1897 return self.__getPyVersion() in [2, 3] |
|
1898 |
|
1899 def isPy2File(self): |
|
1900 """ |
|
1901 Public method to return a flag indicating a Python2 file. |
|
1902 |
|
1903 @return flag indicating a Python2 file (boolean) |
|
1904 """ |
|
1905 return self.__getPyVersion() == 2 |
|
1906 |
|
1907 def isPy3File(self): |
|
1908 """ |
|
1909 Public method to return a flag indicating a Python3 file. |
|
1910 |
|
1911 @return flag indicating a Python3 file (boolean) |
|
1912 """ |
|
1913 return self.__getPyVersion() == 3 |
|
1914 |
|
1915 def isRubyFile(self): |
|
1916 """ |
|
1917 Public method to return a flag indicating a Ruby file. |
|
1918 |
|
1919 @return flag indicating a Ruby file (boolean) |
|
1920 """ |
|
1921 if self.filetype == "Ruby": |
|
1922 return True |
|
1923 |
|
1924 if self.filetype == "": |
|
1925 line0 = self.text(0) |
|
1926 if line0.startswith("#!") and \ |
|
1927 "ruby" in line0: |
|
1928 self.filetype = "Ruby" |
|
1929 return True |
|
1930 |
|
1931 if bool(self.fileName) and \ |
|
1932 os.path.splitext(self.fileName)[1] in \ |
|
1933 self.dbs.getExtensions('Ruby'): |
|
1934 self.filetype = "Ruby" |
|
1935 return True |
|
1936 |
|
1937 return False |
|
1938 |
|
1939 def isJavascriptFile(self): |
|
1940 """ |
|
1941 Public method to return a flag indicating a Javascript file. |
|
1942 |
|
1943 @return flag indicating a Javascript file (boolean) |
|
1944 """ |
|
1945 if self.filetype == "JavaScript": |
|
1946 return True |
|
1947 |
|
1948 if self.filetype == "": |
|
1949 if self.fileName and \ |
|
1950 os.path.splitext(self.fileName)[1] == ".js": |
|
1951 self.filetype = "JavaScript" |
|
1952 return True |
|
1953 |
|
1954 return False |
|
1955 |
|
1956 def highlightVisible(self): |
|
1957 """ |
|
1958 Public method to make sure that the highlight is visible. |
|
1959 """ |
|
1960 if self.lastHighlight is not None: |
|
1961 lineno = self.markerLine(self.lastHighlight) |
|
1962 self.ensureVisible(lineno + 1) |
|
1963 |
|
1964 def highlight(self, line=None, error=False, syntaxError=False): |
|
1965 """ |
|
1966 Public method to highlight [or de-highlight] a particular line. |
|
1967 |
|
1968 @param line line number to highlight (integer) |
|
1969 @param error flag indicating whether the error highlight should be |
|
1970 used (boolean) |
|
1971 @param syntaxError flag indicating a syntax error (boolean) |
|
1972 """ |
|
1973 if line is None: |
|
1974 self.lastHighlight = None |
|
1975 if self.lastErrorMarker is not None: |
|
1976 self.markerDeleteHandle(self.lastErrorMarker) |
|
1977 self.lastErrorMarker = None |
|
1978 if self.lastCurrMarker is not None: |
|
1979 self.markerDeleteHandle(self.lastCurrMarker) |
|
1980 self.lastCurrMarker = None |
|
1981 else: |
|
1982 if error: |
|
1983 if self.lastErrorMarker is not None: |
|
1984 self.markerDeleteHandle(self.lastErrorMarker) |
|
1985 self.lastErrorMarker = self.markerAdd(line - 1, self.errorline) |
|
1986 self.lastHighlight = self.lastErrorMarker |
|
1987 else: |
|
1988 if self.lastCurrMarker is not None: |
|
1989 self.markerDeleteHandle(self.lastCurrMarker) |
|
1990 self.lastCurrMarker = self.markerAdd(line - 1, |
|
1991 self.currentline) |
|
1992 self.lastHighlight = self.lastCurrMarker |
|
1993 self.setCursorPosition(line - 1, 0) |
|
1994 |
|
1995 def getHighlightPosition(self): |
|
1996 """ |
|
1997 Public method to return the position of the highlight bar. |
|
1998 |
|
1999 @return line number of the highlight bar (integer) |
|
2000 """ |
|
2001 if self.lastHighlight is not None: |
|
2002 return self.markerLine(self.lastHighlight) |
|
2003 else: |
|
2004 return 1 |
|
2005 |
|
2006 ########################################################################### |
|
2007 ## Breakpoint handling methods below |
|
2008 ########################################################################### |
|
2009 |
|
2010 def __modified(self, pos, mtype, text, length, linesAdded, line, foldNow, |
|
2011 foldPrev, token, annotationLinesAdded): |
|
2012 """ |
|
2013 Private method to handle changes of the number of lines. |
|
2014 |
|
2015 @param pos start position of change (integer) |
|
2016 @param mtype flags identifying the change (integer) |
|
2017 @param text text that is given to the Undo system (string) |
|
2018 @param length length of the change (integer) |
|
2019 @param linesAdded number of added/deleted lines (integer) |
|
2020 @param line line number of a fold level or marker change (integer) |
|
2021 @param foldNow new fold level (integer) |
|
2022 @param foldPrev previous fold level (integer) |
|
2023 @param token ??? |
|
2024 @param annotationLinesAdded number of added/deleted annotation lines |
|
2025 (integer) |
|
2026 """ |
|
2027 if mtype & (self.SC_MOD_INSERTTEXT | self.SC_MOD_DELETETEXT) and \ |
|
2028 linesAdded != 0: |
|
2029 if self.breaks: |
|
2030 bps = [] # list of breakpoints |
|
2031 for handle, (ln, cond, temp, enabled, ignorecount) in \ |
|
2032 self.breaks.items(): |
|
2033 line = self.markerLine(handle) + 1 |
|
2034 if ln != line: |
|
2035 bps.append((ln, line)) |
|
2036 self.breaks[handle] = (line, cond, temp, enabled, |
|
2037 ignorecount) |
|
2038 self.inLinesChanged = True |
|
2039 for ln, line in sorted(bps, reverse=linesAdded > 0): |
|
2040 index1 = self.breakpointModel.getBreakPointIndex( |
|
2041 self.fileName, ln) |
|
2042 index2 = self.breakpointModel.index(index1.row(), 1) |
|
2043 self.breakpointModel.setData(index2, line) |
|
2044 self.inLinesChanged = False |
|
2045 |
|
2046 def __restoreBreakpoints(self): |
|
2047 """ |
|
2048 Private method to restore the breakpoints. |
|
2049 """ |
|
2050 for handle in list(self.breaks.keys()): |
|
2051 self.markerDeleteHandle(handle) |
|
2052 self.__addBreakPoints( |
|
2053 QModelIndex(), 0, self.breakpointModel.rowCount() - 1) |
|
2054 self.__markerMap.update() |
|
2055 |
|
2056 def __deleteBreakPoints(self, parentIndex, start, end): |
|
2057 """ |
|
2058 Private slot to delete breakpoints. |
|
2059 |
|
2060 @param parentIndex index of parent item (QModelIndex) |
|
2061 @param start start row (integer) |
|
2062 @param end end row (integer) |
|
2063 """ |
|
2064 for row in range(start, end + 1): |
|
2065 index = self.breakpointModel.index(row, 0, parentIndex) |
|
2066 fn, lineno = self.breakpointModel.getBreakPointByIndex(index)[0:2] |
|
2067 if fn == self.fileName: |
|
2068 self.clearBreakpoint(lineno) |
|
2069 |
|
2070 def __changeBreakPoints(self, startIndex, endIndex): |
|
2071 """ |
|
2072 Private slot to set changed breakpoints. |
|
2073 |
|
2074 @param startIndex start index of the breakpoints being changed |
|
2075 (QModelIndex) |
|
2076 @param endIndex end index of the breakpoints being changed |
|
2077 (QModelIndex) |
|
2078 """ |
|
2079 if not self.inLinesChanged: |
|
2080 self.__addBreakPoints(QModelIndex(), startIndex.row(), |
|
2081 endIndex.row()) |
|
2082 |
|
2083 def __breakPointDataAboutToBeChanged(self, startIndex, endIndex): |
|
2084 """ |
|
2085 Private slot to handle the dataAboutToBeChanged signal of the |
|
2086 breakpoint model. |
|
2087 |
|
2088 @param startIndex start index of the rows to be changed (QModelIndex) |
|
2089 @param endIndex end index of the rows to be changed (QModelIndex) |
|
2090 """ |
|
2091 self.__deleteBreakPoints(QModelIndex(), startIndex.row(), |
|
2092 endIndex.row()) |
|
2093 |
|
2094 def __addBreakPoints(self, parentIndex, start, end): |
|
2095 """ |
|
2096 Private slot to add breakpoints. |
|
2097 |
|
2098 @param parentIndex index of parent item (QModelIndex) |
|
2099 @param start start row (integer) |
|
2100 @param end end row (integer) |
|
2101 """ |
|
2102 for row in range(start, end + 1): |
|
2103 index = self.breakpointModel.index(row, 0, parentIndex) |
|
2104 fn, line, cond, temp, enabled, ignorecount = \ |
|
2105 self.breakpointModel.getBreakPointByIndex(index)[:6] |
|
2106 if fn == self.fileName: |
|
2107 self.newBreakpointWithProperties( |
|
2108 line, (cond, temp, enabled, ignorecount)) |
|
2109 |
|
2110 def clearBreakpoint(self, line): |
|
2111 """ |
|
2112 Public method to clear a breakpoint. |
|
2113 |
|
2114 Note: This doesn't clear the breakpoint in the debugger, |
|
2115 it just deletes it from the editor internal list of breakpoints. |
|
2116 |
|
2117 @param line line number of the breakpoint (integer) |
|
2118 """ |
|
2119 if self.inLinesChanged: |
|
2120 return |
|
2121 |
|
2122 for handle in self.breaks: |
|
2123 if self.markerLine(handle) == line - 1: |
|
2124 break |
|
2125 else: |
|
2126 # not found, simply ignore it |
|
2127 return |
|
2128 |
|
2129 del self.breaks[handle] |
|
2130 self.markerDeleteHandle(handle) |
|
2131 self.__markerMap.update() |
|
2132 |
|
2133 def newBreakpointWithProperties(self, line, properties): |
|
2134 """ |
|
2135 Public method to set a new breakpoint and its properties. |
|
2136 |
|
2137 @param line line number of the breakpoint (integer) |
|
2138 @param properties properties for the breakpoint (tuple) |
|
2139 (condition, temporary flag, enabled flag, ignore count) |
|
2140 """ |
|
2141 if not properties[2]: |
|
2142 marker = self.dbreakpoint |
|
2143 elif properties[0]: |
|
2144 marker = properties[1] and self.tcbreakpoint or self.cbreakpoint |
|
2145 else: |
|
2146 marker = properties[1] and self.tbreakpoint or self.breakpoint |
|
2147 |
|
2148 if self.markersAtLine(line - 1) & self.breakpointMask == 0: |
|
2149 handle = self.markerAdd(line - 1, marker) |
|
2150 self.breaks[handle] = (line,) + properties |
|
2151 self.breakpointToggled.emit(self) |
|
2152 self.__markerMap.update() |
|
2153 |
|
2154 def __toggleBreakpoint(self, line, temporary=False): |
|
2155 """ |
|
2156 Private method to toggle a breakpoint. |
|
2157 |
|
2158 @param line line number of the breakpoint (integer) |
|
2159 @param temporary flag indicating a temporary breakpoint (boolean) |
|
2160 """ |
|
2161 for handle in self.breaks: |
|
2162 if self.markerLine(handle) == line - 1: |
|
2163 # delete breakpoint or toggle it to the next state |
|
2164 index = self.breakpointModel.getBreakPointIndex( |
|
2165 self.fileName, line) |
|
2166 if Preferences.getDebugger("ThreeStateBreakPoints") and \ |
|
2167 not self.breakpointModel.isBreakPointTemporaryByIndex( |
|
2168 index): |
|
2169 self.breakpointModel.deleteBreakPointByIndex(index) |
|
2170 self.__addBreakPoint(line, True) |
|
2171 else: |
|
2172 self.breakpointModel.deleteBreakPointByIndex(index) |
|
2173 self.breakpointToggled.emit(self) |
|
2174 break |
|
2175 else: |
|
2176 self.__addBreakPoint(line, temporary) |
|
2177 |
|
2178 def __addBreakPoint(self, line, temporary): |
|
2179 """ |
|
2180 Private method to add a new breakpoint. |
|
2181 |
|
2182 @param line line number of the breakpoint (integer) |
|
2183 @param temporary flag indicating a temporary breakpoint (boolean) |
|
2184 """ |
|
2185 if self.fileName and self.isPyFile(): |
|
2186 self.breakpointModel.addBreakPoint( |
|
2187 self.fileName, line, ('', temporary, True, 0)) |
|
2188 self.breakpointToggled.emit(self) |
|
2189 |
|
2190 def __toggleBreakpointEnabled(self, line): |
|
2191 """ |
|
2192 Private method to toggle a breakpoints enabled status. |
|
2193 |
|
2194 @param line line number of the breakpoint (integer) |
|
2195 """ |
|
2196 for handle in self.breaks: |
|
2197 if self.markerLine(handle) == line - 1: |
|
2198 index = self.breakpointModel.getBreakPointIndex( |
|
2199 self.fileName, line) |
|
2200 self.breakpointModel.setBreakPointEnabledByIndex( |
|
2201 index, not self.breaks[handle][3]) |
|
2202 break |
|
2203 |
|
2204 def curLineHasBreakpoint(self): |
|
2205 """ |
|
2206 Public method to check for the presence of a breakpoint at the current |
|
2207 line. |
|
2208 |
|
2209 @return flag indicating the presence of a breakpoint (boolean) |
|
2210 """ |
|
2211 line, _ = self.getCursorPosition() |
|
2212 return self.markersAtLine(line) & self.breakpointMask != 0 |
|
2213 |
|
2214 def getBreakpointLines(self): |
|
2215 """ |
|
2216 Public method to get the lines containing a breakpoint. |
|
2217 |
|
2218 @return list of lines containing a breakpoint (list of integer) |
|
2219 """ |
|
2220 lines = [] |
|
2221 line = -1 |
|
2222 while True: |
|
2223 line = self.markerFindNext(line + 1, self.breakpointMask) |
|
2224 if line < 0: |
|
2225 break |
|
2226 else: |
|
2227 lines.append(line) |
|
2228 return lines |
|
2229 |
|
2230 def hasBreakpoints(self): |
|
2231 """ |
|
2232 Public method to check for the presence of breakpoints. |
|
2233 |
|
2234 @return flag indicating the presence of breakpoints (boolean) |
|
2235 """ |
|
2236 return len(self.breaks) > 0 |
|
2237 |
|
2238 def __menuToggleTemporaryBreakpoint(self): |
|
2239 """ |
|
2240 Private slot to handle the 'Toggle temporary breakpoint' context menu |
|
2241 action. |
|
2242 """ |
|
2243 if self.line < 0: |
|
2244 self.line, index = self.getCursorPosition() |
|
2245 self.line += 1 |
|
2246 self.__toggleBreakpoint(self.line, 1) |
|
2247 self.line = -1 |
|
2248 |
|
2249 def menuToggleBreakpoint(self): |
|
2250 """ |
|
2251 Public slot to handle the 'Toggle breakpoint' context menu action. |
|
2252 """ |
|
2253 if self.line < 0: |
|
2254 self.line, index = self.getCursorPosition() |
|
2255 self.line += 1 |
|
2256 self.__toggleBreakpoint(self.line) |
|
2257 self.line = -1 |
|
2258 |
|
2259 def __menuToggleBreakpointEnabled(self): |
|
2260 """ |
|
2261 Private slot to handle the 'Enable/Disable breakpoint' context menu |
|
2262 action. |
|
2263 """ |
|
2264 if self.line < 0: |
|
2265 self.line, index = self.getCursorPosition() |
|
2266 self.line += 1 |
|
2267 self.__toggleBreakpointEnabled(self.line) |
|
2268 self.line = -1 |
|
2269 |
|
2270 def menuEditBreakpoint(self, line=None): |
|
2271 """ |
|
2272 Public slot to handle the 'Edit breakpoint' context menu action. |
|
2273 |
|
2274 @param line linenumber of the breakpoint to edit |
|
2275 """ |
|
2276 if line is not None: |
|
2277 self.line = line - 1 |
|
2278 if self.line < 0: |
|
2279 self.line, index = self.getCursorPosition() |
|
2280 |
|
2281 for handle in self.breaks: |
|
2282 if self.markerLine(handle) == self.line: |
|
2283 ln, cond, temp, enabled, ignorecount = self.breaks[handle] |
|
2284 index = self.breakpointModel.getBreakPointIndex(self.fileName, |
|
2285 ln) |
|
2286 if not index.isValid(): |
|
2287 return |
|
2288 |
|
2289 from Debugger.EditBreakpointDialog import EditBreakpointDialog |
|
2290 dlg = EditBreakpointDialog( |
|
2291 (self.fileName, ln), |
|
2292 (cond, temp, enabled, ignorecount), |
|
2293 self.condHistory, self, modal=True) |
|
2294 if dlg.exec_() == QDialog.Accepted: |
|
2295 cond, temp, enabled, ignorecount = dlg.getData() |
|
2296 self.breakpointModel.setBreakPointByIndex( |
|
2297 index, self.fileName, ln, |
|
2298 (cond, temp, enabled, ignorecount)) |
|
2299 break |
|
2300 |
|
2301 self.line = -1 |
|
2302 |
|
2303 def menuNextBreakpoint(self): |
|
2304 """ |
|
2305 Public slot to handle the 'Next breakpoint' context menu action. |
|
2306 """ |
|
2307 line, index = self.getCursorPosition() |
|
2308 if line == self.lines() - 1: |
|
2309 line = 0 |
|
2310 else: |
|
2311 line += 1 |
|
2312 bpline = self.markerFindNext(line, self.breakpointMask) |
|
2313 if bpline < 0: |
|
2314 # wrap around |
|
2315 bpline = self.markerFindNext(0, self.breakpointMask) |
|
2316 if bpline >= 0: |
|
2317 self.setCursorPosition(bpline, 0) |
|
2318 self.ensureLineVisible(bpline) |
|
2319 |
|
2320 def menuPreviousBreakpoint(self): |
|
2321 """ |
|
2322 Public slot to handle the 'Previous breakpoint' context menu action. |
|
2323 """ |
|
2324 line, index = self.getCursorPosition() |
|
2325 if line == 0: |
|
2326 line = self.lines() - 1 |
|
2327 else: |
|
2328 line -= 1 |
|
2329 bpline = self.markerFindPrevious(line, self.breakpointMask) |
|
2330 if bpline < 0: |
|
2331 # wrap around |
|
2332 bpline = self.markerFindPrevious( |
|
2333 self.lines() - 1, self.breakpointMask) |
|
2334 if bpline >= 0: |
|
2335 self.setCursorPosition(bpline, 0) |
|
2336 self.ensureLineVisible(bpline) |
|
2337 |
|
2338 def __menuClearBreakpoints(self): |
|
2339 """ |
|
2340 Private slot to handle the 'Clear all breakpoints' context menu action. |
|
2341 """ |
|
2342 self.__clearBreakpoints(self.fileName) |
|
2343 |
|
2344 def __clearBreakpoints(self, fileName): |
|
2345 """ |
|
2346 Private slot to clear all breakpoints. |
|
2347 |
|
2348 @param fileName name of the file (string) |
|
2349 """ |
|
2350 idxList = [] |
|
2351 for (ln, _, _, _, _) in self.breaks.values(): |
|
2352 index = self.breakpointModel.getBreakPointIndex(fileName, ln) |
|
2353 if index.isValid(): |
|
2354 idxList.append(index) |
|
2355 if idxList: |
|
2356 self.breakpointModel.deleteBreakPoints(idxList) |
|
2357 |
|
2358 ########################################################################### |
|
2359 ## Bookmark handling methods below |
|
2360 ########################################################################### |
|
2361 |
|
2362 def toggleBookmark(self, line): |
|
2363 """ |
|
2364 Public method to toggle a bookmark. |
|
2365 |
|
2366 @param line line number of the bookmark (integer) |
|
2367 """ |
|
2368 for handle in self.bookmarks: |
|
2369 if self.markerLine(handle) == line - 1: |
|
2370 self.bookmarks.remove(handle) |
|
2371 self.markerDeleteHandle(handle) |
|
2372 break |
|
2373 else: |
|
2374 # set a new bookmark |
|
2375 handle = self.markerAdd(line - 1, self.bookmark) |
|
2376 self.bookmarks.append(handle) |
|
2377 self.bookmarkToggled.emit(self) |
|
2378 self.__markerMap.update() |
|
2379 |
|
2380 def getBookmarks(self): |
|
2381 """ |
|
2382 Public method to retrieve the bookmarks. |
|
2383 |
|
2384 @return sorted list of all lines containing a bookmark |
|
2385 (list of integer) |
|
2386 """ |
|
2387 bmlist = [] |
|
2388 for handle in self.bookmarks: |
|
2389 bmlist.append(self.markerLine(handle) + 1) |
|
2390 |
|
2391 bmlist.sort() |
|
2392 return bmlist |
|
2393 |
|
2394 def getBookmarkLines(self): |
|
2395 """ |
|
2396 Public method to get the lines containing a bookmark. |
|
2397 |
|
2398 @return list of lines containing a bookmark (list of integer) |
|
2399 """ |
|
2400 lines = [] |
|
2401 line = -1 |
|
2402 while True: |
|
2403 line = self.markerFindNext(line + 1, 1 << self.bookmark) |
|
2404 if line < 0: |
|
2405 break |
|
2406 else: |
|
2407 lines.append(line) |
|
2408 return lines |
|
2409 |
|
2410 def hasBookmarks(self): |
|
2411 """ |
|
2412 Public method to check for the presence of bookmarks. |
|
2413 |
|
2414 @return flag indicating the presence of bookmarks (boolean) |
|
2415 """ |
|
2416 return len(self.bookmarks) > 0 |
|
2417 |
|
2418 def menuToggleBookmark(self): |
|
2419 """ |
|
2420 Public slot to handle the 'Toggle bookmark' context menu action. |
|
2421 """ |
|
2422 if self.line < 0: |
|
2423 self.line, index = self.getCursorPosition() |
|
2424 self.line += 1 |
|
2425 self.toggleBookmark(self.line) |
|
2426 self.line = -1 |
|
2427 |
|
2428 def nextBookmark(self): |
|
2429 """ |
|
2430 Public slot to handle the 'Next bookmark' context menu action. |
|
2431 """ |
|
2432 line, index = self.getCursorPosition() |
|
2433 if line == self.lines() - 1: |
|
2434 line = 0 |
|
2435 else: |
|
2436 line += 1 |
|
2437 bmline = self.markerFindNext(line, 1 << self.bookmark) |
|
2438 if bmline < 0: |
|
2439 # wrap around |
|
2440 bmline = self.markerFindNext(0, 1 << self.bookmark) |
|
2441 if bmline >= 0: |
|
2442 self.setCursorPosition(bmline, 0) |
|
2443 self.ensureLineVisible(bmline) |
|
2444 |
|
2445 def previousBookmark(self): |
|
2446 """ |
|
2447 Public slot to handle the 'Previous bookmark' context menu action. |
|
2448 """ |
|
2449 line, index = self.getCursorPosition() |
|
2450 if line == 0: |
|
2451 line = self.lines() - 1 |
|
2452 else: |
|
2453 line -= 1 |
|
2454 bmline = self.markerFindPrevious(line, 1 << self.bookmark) |
|
2455 if bmline < 0: |
|
2456 # wrap around |
|
2457 bmline = self.markerFindPrevious( |
|
2458 self.lines() - 1, 1 << self.bookmark) |
|
2459 if bmline >= 0: |
|
2460 self.setCursorPosition(bmline, 0) |
|
2461 self.ensureLineVisible(bmline) |
|
2462 |
|
2463 def clearBookmarks(self): |
|
2464 """ |
|
2465 Public slot to handle the 'Clear all bookmarks' context menu action. |
|
2466 """ |
|
2467 for handle in self.bookmarks: |
|
2468 self.markerDeleteHandle(handle) |
|
2469 self.bookmarks = [] |
|
2470 self.bookmarkToggled.emit(self) |
|
2471 self.__markerMap.update() |
|
2472 |
|
2473 ########################################################################### |
|
2474 ## Printing methods below |
|
2475 ########################################################################### |
|
2476 |
|
2477 def printFile(self): |
|
2478 """ |
|
2479 Public slot to print the text. |
|
2480 """ |
|
2481 from .Printer import Printer |
|
2482 printer = Printer(mode=QPrinter.HighResolution) |
|
2483 sb = e5App().getObject("UserInterface").statusBar() |
|
2484 printDialog = QPrintDialog(printer, self) |
|
2485 if self.hasSelectedText(): |
|
2486 printDialog.setOption(QAbstractPrintDialog.PrintSelection, True) |
|
2487 if printDialog.exec_() == QDialog.Accepted: |
|
2488 sb.showMessage(self.tr('Printing...')) |
|
2489 QApplication.processEvents() |
|
2490 fn = self.getFileName() |
|
2491 if fn is not None: |
|
2492 printer.setDocName(os.path.basename(fn)) |
|
2493 else: |
|
2494 printer.setDocName(self.noName) |
|
2495 if printDialog.printRange() == QAbstractPrintDialog.Selection: |
|
2496 # get the selection |
|
2497 fromLine, fromIndex, toLine, toIndex = self.getSelection() |
|
2498 if toIndex == 0: |
|
2499 toLine -= 1 |
|
2500 # Qscintilla seems to print one line more than told |
|
2501 res = printer.printRange(self, fromLine, toLine - 1) |
|
2502 else: |
|
2503 res = printer.printRange(self) |
|
2504 if res: |
|
2505 sb.showMessage(self.tr('Printing completed'), 2000) |
|
2506 else: |
|
2507 sb.showMessage(self.tr('Error while printing'), 2000) |
|
2508 QApplication.processEvents() |
|
2509 else: |
|
2510 sb.showMessage(self.tr('Printing aborted'), 2000) |
|
2511 QApplication.processEvents() |
|
2512 |
|
2513 def printPreviewFile(self): |
|
2514 """ |
|
2515 Public slot to show a print preview of the text. |
|
2516 """ |
|
2517 from PyQt5.QtPrintSupport import QPrintPreviewDialog |
|
2518 from .Printer import Printer |
|
2519 |
|
2520 printer = Printer(mode=QPrinter.HighResolution) |
|
2521 fn = self.getFileName() |
|
2522 if fn is not None: |
|
2523 printer.setDocName(os.path.basename(fn)) |
|
2524 else: |
|
2525 printer.setDocName(self.noName) |
|
2526 preview = QPrintPreviewDialog(printer, self) |
|
2527 preview.paintRequested.connect(self.__printPreview) |
|
2528 preview.exec_() |
|
2529 |
|
2530 def __printPreview(self, printer): |
|
2531 """ |
|
2532 Private slot to generate a print preview. |
|
2533 |
|
2534 @param printer reference to the printer object |
|
2535 (QScintilla.Printer.Printer) |
|
2536 """ |
|
2537 printer.printRange(self) |
|
2538 |
|
2539 ########################################################################### |
|
2540 ## Task handling methods below |
|
2541 ########################################################################### |
|
2542 |
|
2543 def getTaskLines(self): |
|
2544 """ |
|
2545 Public method to get the lines containing a task. |
|
2546 |
|
2547 @return list of lines containing a task (list of integer) |
|
2548 """ |
|
2549 lines = [] |
|
2550 line = -1 |
|
2551 while True: |
|
2552 line = self.markerFindNext(line + 1, 1 << self.taskmarker) |
|
2553 if line < 0: |
|
2554 break |
|
2555 else: |
|
2556 lines.append(line) |
|
2557 return lines |
|
2558 |
|
2559 def hasTaskMarkers(self): |
|
2560 """ |
|
2561 Public method to determine, if this editor contains any task markers. |
|
2562 |
|
2563 @return flag indicating the presence of task markers (boolean) |
|
2564 """ |
|
2565 return self.__hasTaskMarkers |
|
2566 |
|
2567 def nextTask(self): |
|
2568 """ |
|
2569 Public slot to handle the 'Next task' context menu action. |
|
2570 """ |
|
2571 line, index = self.getCursorPosition() |
|
2572 if line == self.lines() - 1: |
|
2573 line = 0 |
|
2574 else: |
|
2575 line += 1 |
|
2576 taskline = self.markerFindNext(line, 1 << self.taskmarker) |
|
2577 if taskline < 0: |
|
2578 # wrap around |
|
2579 taskline = self.markerFindNext(0, 1 << self.taskmarker) |
|
2580 if taskline >= 0: |
|
2581 self.setCursorPosition(taskline, 0) |
|
2582 self.ensureLineVisible(taskline) |
|
2583 |
|
2584 def previousTask(self): |
|
2585 """ |
|
2586 Public slot to handle the 'Previous task' context menu action. |
|
2587 """ |
|
2588 line, index = self.getCursorPosition() |
|
2589 if line == 0: |
|
2590 line = self.lines() - 1 |
|
2591 else: |
|
2592 line -= 1 |
|
2593 taskline = self.markerFindPrevious(line, 1 << self.taskmarker) |
|
2594 if taskline < 0: |
|
2595 # wrap around |
|
2596 taskline = self.markerFindPrevious( |
|
2597 self.lines() - 1, 1 << self.taskmarker) |
|
2598 if taskline >= 0: |
|
2599 self.setCursorPosition(taskline, 0) |
|
2600 self.ensureLineVisible(taskline) |
|
2601 |
|
2602 def extractTasks(self): |
|
2603 """ |
|
2604 Public slot to extract all tasks. |
|
2605 """ |
|
2606 from Tasks.Task import Task |
|
2607 markers = { |
|
2608 Task.TypeWarning: |
|
2609 Preferences.getTasks("TasksWarningMarkers").split(), |
|
2610 Task.TypeNote: |
|
2611 Preferences.getTasks("TasksNoteMarkers").split(), |
|
2612 Task.TypeTodo: |
|
2613 Preferences.getTasks("TasksTodoMarkers").split(), |
|
2614 Task.TypeFixme: |
|
2615 Preferences.getTasks("TasksFixmeMarkers").split(), |
|
2616 } |
|
2617 txtList = self.text().split(self.getLineSeparator()) |
|
2618 |
|
2619 # clear all task markers and tasks |
|
2620 self.markerDeleteAll(self.taskmarker) |
|
2621 self.taskViewer.clearFileTasks(self.fileName) |
|
2622 self.__hasTaskMarkers = False |
|
2623 |
|
2624 # now search tasks and record them |
|
2625 lineIndex = -1 |
|
2626 for line in txtList: |
|
2627 lineIndex += 1 |
|
2628 shouldBreak = False |
|
2629 |
|
2630 if line.endswith("__NO-TASK__"): |
|
2631 # ignore potential task marker |
|
2632 continue |
|
2633 |
|
2634 for taskType, taskMarkers in markers.items(): |
|
2635 for taskMarker in taskMarkers: |
|
2636 index = line.find(taskMarker) |
|
2637 if index > -1: |
|
2638 task = line[index:] |
|
2639 self.markerAdd(lineIndex, self.taskmarker) |
|
2640 self.taskViewer.addFileTask( |
|
2641 task, self.fileName, lineIndex + 1, taskType) |
|
2642 self.__hasTaskMarkers = True |
|
2643 shouldBreak = True |
|
2644 break |
|
2645 if shouldBreak: |
|
2646 break |
|
2647 self.taskMarkersUpdated.emit(self) |
|
2648 self.__markerMap.update() |
|
2649 |
|
2650 ########################################################################### |
|
2651 ## Change tracing methods below |
|
2652 ########################################################################### |
|
2653 |
|
2654 def __createChangeMarkerPixmap(self, key, size=16, width=4): |
|
2655 """ |
|
2656 Private method to create a pixmap for the change markers. |
|
2657 |
|
2658 @param key key of the color to use (string) |
|
2659 @param size size of the pixmap (integer) |
|
2660 @param width width of the marker line (integer) |
|
2661 @return create pixmap (QPixmap) |
|
2662 """ |
|
2663 pixmap = QPixmap(size, size) |
|
2664 pixmap.fill(Qt.transparent) |
|
2665 painter = QPainter(pixmap) |
|
2666 painter.fillRect(size - 4, 0, 4, size, |
|
2667 Preferences.getEditorColour(key)) |
|
2668 painter.end() |
|
2669 return pixmap |
|
2670 |
|
2671 def __initOnlineChangeTrace(self): |
|
2672 """ |
|
2673 Private slot to initialize the online change trace. |
|
2674 """ |
|
2675 self.__hasChangeMarkers = False |
|
2676 self.__oldText = self.text() |
|
2677 self.__lastSavedText = self.text() |
|
2678 self.__onlineChangeTraceTimer = QTimer(self) |
|
2679 self.__onlineChangeTraceTimer.setSingleShot(True) |
|
2680 self.__onlineChangeTraceTimer.setInterval( |
|
2681 Preferences.getEditor("OnlineChangeTraceInterval")) |
|
2682 self.__onlineChangeTraceTimer.timeout.connect( |
|
2683 self.__onlineChangeTraceTimerTimeout) |
|
2684 self.textChanged.connect(self.__resetOnlineChangeTraceTimer) |
|
2685 |
|
2686 def __reinitOnlineChangeTrace(self): |
|
2687 """ |
|
2688 Private slot to re-initialize the online change trace. |
|
2689 """ |
|
2690 self.__oldText = self.text() |
|
2691 self.__lastSavedText = self.text() |
|
2692 self.__deleteAllChangeMarkers() |
|
2693 |
|
2694 def __resetOnlineChangeTraceTimer(self): |
|
2695 """ |
|
2696 Private method to reset the online syntax check timer. |
|
2697 """ |
|
2698 if Preferences.getEditor("OnlineChangeTrace"): |
|
2699 self.__onlineChangeTraceTimer.stop() |
|
2700 self.__onlineChangeTraceTimer.start() |
|
2701 |
|
2702 def __onlineChangeTraceTimerTimeout(self): |
|
2703 """ |
|
2704 Private slot to mark added and changed lines. |
|
2705 """ |
|
2706 self.__deleteAllChangeMarkers() |
|
2707 |
|
2708 # step 1: mark saved changes |
|
2709 oldL = self.__oldText.splitlines() |
|
2710 newL = self.__lastSavedText.splitlines() |
|
2711 matcher = difflib.SequenceMatcher(None, oldL, newL) |
|
2712 |
|
2713 for token, _, _, j1, j2 in matcher.get_opcodes(): |
|
2714 if token in ["insert", "replace"]: |
|
2715 for lineNo in range(j1, j2): |
|
2716 self.markerAdd(lineNo, self.__changeMarkerSaved) |
|
2717 self.__hasChangeMarkers = True |
|
2718 |
|
2719 # step 2: mark unsaved changes |
|
2720 oldL = self.__lastSavedText.splitlines() |
|
2721 newL = self.text().splitlines() |
|
2722 matcher = difflib.SequenceMatcher(None, oldL, newL) |
|
2723 |
|
2724 for token, _, _, j1, j2 in matcher.get_opcodes(): |
|
2725 if token in ["insert", "replace"]: |
|
2726 for lineNo in range(j1, j2): |
|
2727 self.markerAdd(lineNo, self.__changeMarkerUnsaved) |
|
2728 self.__hasChangeMarkers = True |
|
2729 |
|
2730 if self.__hasChangeMarkers: |
|
2731 self.changeMarkersUpdated.emit(self) |
|
2732 self.__markerMap.update() |
|
2733 |
|
2734 def __resetOnlineChangeTraceInfo(self): |
|
2735 """ |
|
2736 Private slot to reset the online change trace info. |
|
2737 """ |
|
2738 self.__lastSavedText = self.text() |
|
2739 self.__deleteAllChangeMarkers() |
|
2740 |
|
2741 # mark saved changes |
|
2742 oldL = self.__oldText.splitlines() |
|
2743 newL = self.__lastSavedText.splitlines() |
|
2744 matcher = difflib.SequenceMatcher(None, oldL, newL) |
|
2745 |
|
2746 for token, _, _, j1, j2 in matcher.get_opcodes(): |
|
2747 if token in ["insert", "replace"]: |
|
2748 for lineNo in range(j1, j2): |
|
2749 self.markerAdd(lineNo, self.__changeMarkerSaved) |
|
2750 self.__hasChangeMarkers = True |
|
2751 |
|
2752 if self.__hasChangeMarkers: |
|
2753 self.changeMarkersUpdated.emit(self) |
|
2754 self.__markerMap.update() |
|
2755 |
|
2756 def __deleteAllChangeMarkers(self): |
|
2757 """ |
|
2758 Private slot to delete all change markers. |
|
2759 """ |
|
2760 self.markerDeleteAll(self.__changeMarkerUnsaved) |
|
2761 self.markerDeleteAll(self.__changeMarkerSaved) |
|
2762 self.__hasChangeMarkers = False |
|
2763 self.changeMarkersUpdated.emit(self) |
|
2764 self.__markerMap.update() |
|
2765 |
|
2766 def getChangeLines(self): |
|
2767 """ |
|
2768 Public method to get the lines containing a change. |
|
2769 |
|
2770 @return list of lines containing a change (list of integer) |
|
2771 """ |
|
2772 lines = [] |
|
2773 line = -1 |
|
2774 while True: |
|
2775 line = self.markerFindNext(line + 1, self.changeMarkersMask) |
|
2776 if line < 0: |
|
2777 break |
|
2778 else: |
|
2779 lines.append(line) |
|
2780 return lines |
|
2781 |
|
2782 def hasChangeMarkers(self): |
|
2783 """ |
|
2784 Public method to determine, if this editor contains any change markers. |
|
2785 |
|
2786 @return flag indicating the presence of change markers (boolean) |
|
2787 """ |
|
2788 return self.__hasChangeMarkers |
|
2789 |
|
2790 def nextChange(self): |
|
2791 """ |
|
2792 Public slot to handle the 'Next change' context menu action. |
|
2793 """ |
|
2794 line, index = self.getCursorPosition() |
|
2795 if line == self.lines() - 1: |
|
2796 line = 0 |
|
2797 else: |
|
2798 line += 1 |
|
2799 changeline = self.markerFindNext(line, self.changeMarkersMask) |
|
2800 if changeline < 0: |
|
2801 # wrap around |
|
2802 changeline = self.markerFindNext(0, self.changeMarkersMask) |
|
2803 if changeline >= 0: |
|
2804 self.setCursorPosition(changeline, 0) |
|
2805 self.ensureLineVisible(changeline) |
|
2806 |
|
2807 def previousChange(self): |
|
2808 """ |
|
2809 Public slot to handle the 'Previous change' context menu action. |
|
2810 """ |
|
2811 line, index = self.getCursorPosition() |
|
2812 if line == 0: |
|
2813 line = self.lines() - 1 |
|
2814 else: |
|
2815 line -= 1 |
|
2816 changeline = self.markerFindPrevious(line, self.changeMarkersMask) |
|
2817 if changeline < 0: |
|
2818 # wrap around |
|
2819 changeline = self.markerFindPrevious( |
|
2820 self.lines() - 1, self.changeMarkersMask) |
|
2821 if changeline >= 0: |
|
2822 self.setCursorPosition(changeline, 0) |
|
2823 self.ensureLineVisible(changeline) |
|
2824 |
|
2825 ########################################################################### |
|
2826 ## Flags handling methods below |
|
2827 ########################################################################### |
|
2828 |
|
2829 def __processFlags(self): |
|
2830 """ |
|
2831 Private method to extract flags and process them. |
|
2832 |
|
2833 @return list of change flags (list of string) |
|
2834 """ |
|
2835 txt = self.text() |
|
2836 flags = Utilities.extractFlags(txt) |
|
2837 |
|
2838 changedFlags = [] |
|
2839 |
|
2840 # Flag 1: FileType |
|
2841 if "FileType" in flags: |
|
2842 oldFiletype = self.filetype |
|
2843 if isinstance(flags["FileType"], str): |
|
2844 self.filetype = flags["FileType"] |
|
2845 self.filetypeByFlag = True |
|
2846 if oldFiletype != self.filetype: |
|
2847 changedFlags.append("FileType") |
|
2848 else: |
|
2849 if self.filetype != "" and self.filetypeByFlag: |
|
2850 self.filetype = "" |
|
2851 self.filetypeByFlag = False |
|
2852 self.__bindName(txt.splitlines()[0]) |
|
2853 changedFlags.append("FileType") |
|
2854 |
|
2855 return changedFlags |
|
2856 |
|
2857 ########################################################################### |
|
2858 ## File handling methods below |
|
2859 ########################################################################### |
|
2860 |
|
2861 def checkDirty(self): |
|
2862 """ |
|
2863 Public method to check dirty status and open a message window. |
|
2864 |
|
2865 @return flag indicating successful reset of the dirty flag (boolean) |
|
2866 """ |
|
2867 if self.isModified(): |
|
2868 fn = self.fileName |
|
2869 if fn is None: |
|
2870 fn = self.noName |
|
2871 res = E5MessageBox.okToClearData( |
|
2872 self, |
|
2873 self.tr("File Modified"), |
|
2874 self.tr("<p>The file <b>{0}</b> has unsaved changes.</p>") |
|
2875 .format(fn), |
|
2876 self.saveFile) |
|
2877 if res: |
|
2878 self.vm.setEditorName(self, self.fileName) |
|
2879 return res |
|
2880 |
|
2881 return True |
|
2882 |
|
2883 def revertToUnmodified(self): |
|
2884 """ |
|
2885 Public method to revert back to the last saved state. |
|
2886 """ |
|
2887 undo_ = True |
|
2888 while self.isModified(): |
|
2889 if undo_: |
|
2890 # try undo first |
|
2891 if self.isUndoAvailable(): |
|
2892 self.undo() |
|
2893 else: |
|
2894 undo_ = False |
|
2895 else: |
|
2896 # try redo next |
|
2897 if self.isRedoAvailable(): |
|
2898 self.redo() |
|
2899 else: |
|
2900 break |
|
2901 # Couldn't find the unmodified state |
|
2902 |
|
2903 def readFile(self, fn, createIt=False, encoding=""): |
|
2904 """ |
|
2905 Public slot to read the text from a file. |
|
2906 |
|
2907 @param fn filename to read from (string) |
|
2908 @keyparam createIt flag indicating the creation of a new file, if the |
|
2909 given one doesn't exist (boolean) |
|
2910 @keyparam encoding encoding to be used to read the file (string) |
|
2911 (Note: this parameter overrides encoding detection) |
|
2912 """ |
|
2913 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) |
|
2914 |
|
2915 self.__loadEditorConfig(fileName=fn) |
|
2916 |
|
2917 try: |
|
2918 if createIt and not os.path.exists(fn): |
|
2919 f = open(fn, "w") |
|
2920 f.close() |
|
2921 if encoding == "": |
|
2922 encoding = self.__getEditorConfig("DefaultEncoding", |
|
2923 nodefault=True) |
|
2924 if encoding: |
|
2925 txt, self.encoding = Utilities.readEncodedFileWithEncoding( |
|
2926 fn, encoding) |
|
2927 else: |
|
2928 txt, self.encoding = Utilities.readEncodedFile(fn) |
|
2929 except (UnicodeDecodeError, IOError) as why: |
|
2930 QApplication.restoreOverrideCursor() |
|
2931 E5MessageBox.critical( |
|
2932 self.vm, |
|
2933 self.tr('Open File'), |
|
2934 self.tr('<p>The file <b>{0}</b> could not be opened.</p>' |
|
2935 '<p>Reason: {1}</p>') |
|
2936 .format(fn, str(why))) |
|
2937 QApplication.restoreOverrideCursor() |
|
2938 raise |
|
2939 |
|
2940 modified = False |
|
2941 |
|
2942 if (not self.__getEditorConfig("TabForIndentation")) and \ |
|
2943 Preferences.getEditor("ConvertTabsOnLoad") and \ |
|
2944 not (self.lexer_ and |
|
2945 self.lexer_.alwaysKeepTabs()): |
|
2946 txtExpanded = txt.expandtabs(self.__getEditorConfig("TabWidth")) |
|
2947 if txtExpanded != txt: |
|
2948 modified = True |
|
2949 txt = txtExpanded |
|
2950 |
|
2951 self.setText(txt) |
|
2952 |
|
2953 # get eric specific flags |
|
2954 self.__processFlags() |
|
2955 |
|
2956 # perform automatic EOL conversion |
|
2957 if self.__getEditorConfig("EOLMode", nodefault=True) or \ |
|
2958 Preferences.getEditor("AutomaticEOLConversion"): |
|
2959 self.convertEols(self.eolMode()) |
|
2960 else: |
|
2961 fileEol = self.detectEolString(txt) |
|
2962 self.setEolModeByEolString(fileEol) |
|
2963 |
|
2964 self.extractTasks() |
|
2965 |
|
2966 QApplication.restoreOverrideCursor() |
|
2967 |
|
2968 self.setModified(modified) |
|
2969 self.lastModified = QFileInfo(self.fileName).lastModified() |
|
2970 |
|
2971 def __removeTrailingWhitespace(self): |
|
2972 """ |
|
2973 Private method to remove trailing whitespace. |
|
2974 """ |
|
2975 searchRE = r"[ \t]+$" # whitespace at the end of a line |
|
2976 |
|
2977 ok = self.findFirstTarget(searchRE, True, False, False, 0, 0) |
|
2978 self.beginUndoAction() |
|
2979 while ok: |
|
2980 self.replaceTarget("") |
|
2981 ok = self.findNextTarget() |
|
2982 self.endUndoAction() |
|
2983 |
|
2984 def writeFile(self, fn, backup=True): |
|
2985 """ |
|
2986 Public slot to write the text to a file. |
|
2987 |
|
2988 @param fn filename to write to (string) |
|
2989 @param backup flag indicating to save a backup (boolean) |
|
2990 @return flag indicating success (boolean) |
|
2991 """ |
|
2992 config = self.__loadEditorConfigObject(fn) |
|
2993 |
|
2994 eol = self.__getEditorConfig("EOLMode", nodefault=True, config=config) |
|
2995 if eol is not None: |
|
2996 self.convertEols(eol) |
|
2997 |
|
2998 if self.__getEditorConfig("StripTrailingWhitespace", config=config): |
|
2999 self.__removeTrailingWhitespace() |
|
3000 |
|
3001 txt = self.text() |
|
3002 |
|
3003 if self.__getEditorConfig("InsertFinalNewline", config=config): |
|
3004 eol = self.getLineSeparator() |
|
3005 if eol: |
|
3006 if len(txt) >= len(eol): |
|
3007 if txt[-len(eol):] != eol: |
|
3008 txt += eol |
|
3009 else: |
|
3010 txt += eol |
|
3011 |
|
3012 # create a backup file, if the option is set |
|
3013 createBackup = backup and Preferences.getEditor("CreateBackupFile") |
|
3014 if createBackup: |
|
3015 if os.path.islink(fn): |
|
3016 fn = os.path.realpath(fn) |
|
3017 bfn = '{0}~'.format(fn) |
|
3018 try: |
|
3019 permissions = os.stat(fn).st_mode |
|
3020 perms_valid = True |
|
3021 except EnvironmentError: |
|
3022 # if there was an error, ignore it |
|
3023 perms_valid = False |
|
3024 try: |
|
3025 os.remove(bfn) |
|
3026 except EnvironmentError: |
|
3027 # if there was an error, ignore it |
|
3028 pass |
|
3029 try: |
|
3030 os.rename(fn, bfn) |
|
3031 except EnvironmentError: |
|
3032 # if there was an error, ignore it |
|
3033 pass |
|
3034 |
|
3035 # now write text to the file fn |
|
3036 try: |
|
3037 editorConfigEncoding = self.__getEditorConfig( |
|
3038 "DefaultEncoding", nodefault=True, config=config) |
|
3039 self.encoding = Utilities.writeEncodedFile( |
|
3040 fn, txt, self.encoding, forcedEncoding=editorConfigEncoding) |
|
3041 if createBackup and perms_valid: |
|
3042 os.chmod(fn, permissions) |
|
3043 return True |
|
3044 except (IOError, Utilities.CodingError, UnicodeError) as why: |
|
3045 E5MessageBox.critical( |
|
3046 self, |
|
3047 self.tr('Save File'), |
|
3048 self.tr('<p>The file <b>{0}</b> could not be saved.<br/>' |
|
3049 'Reason: {1}</p>') |
|
3050 .format(fn, str(why))) |
|
3051 return False |
|
3052 |
|
3053 def __getSaveFileName(self, path=None): |
|
3054 """ |
|
3055 Private method to get the name of the file to be saved. |
|
3056 |
|
3057 @param path directory to save the file in (string) |
|
3058 @return file name (string) |
|
3059 """ |
|
3060 # save to project, if a project is loaded |
|
3061 if self.project.isOpen(): |
|
3062 if self.fileName and \ |
|
3063 self.project.startswithProjectPath(self.fileName): |
|
3064 path = os.path.dirname(self.fileName) |
|
3065 else: |
|
3066 path = self.project.getProjectPath() |
|
3067 |
|
3068 if not path and self.fileName: |
|
3069 path = os.path.dirname(self.fileName) |
|
3070 if not path: |
|
3071 path = Preferences.getMultiProject("Workspace") or \ |
|
3072 Utilities.getHomeDir() |
|
3073 |
|
3074 from . import Lexers |
|
3075 if self.fileName: |
|
3076 filterPattern = "(*{0})".format( |
|
3077 os.path.splitext(self.fileName)[1]) |
|
3078 for fileFilter in Lexers.getSaveFileFiltersList(True): |
|
3079 if filterPattern in fileFilter: |
|
3080 defaultFilter = fileFilter |
|
3081 break |
|
3082 else: |
|
3083 defaultFilter = Preferences.getEditor("DefaultSaveFilter") |
|
3084 else: |
|
3085 defaultFilter = Preferences.getEditor("DefaultSaveFilter") |
|
3086 fn, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
|
3087 self, |
|
3088 self.tr("Save File"), |
|
3089 path, |
|
3090 Lexers.getSaveFileFiltersList(True, True), |
|
3091 defaultFilter, |
|
3092 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
|
3093 |
|
3094 if fn: |
|
3095 if fn.endswith("."): |
|
3096 fn = fn[:-1] |
|
3097 |
|
3098 ext = QFileInfo(fn).suffix() |
|
3099 if not ext: |
|
3100 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
3101 if ex: |
|
3102 fn += ex |
|
3103 if QFileInfo(fn).exists(): |
|
3104 res = E5MessageBox.yesNo( |
|
3105 self, |
|
3106 self.tr("Save File"), |
|
3107 self.tr("<p>The file <b>{0}</b> already exists." |
|
3108 " Overwrite it?</p>").format(fn), |
|
3109 icon=E5MessageBox.Warning) |
|
3110 if not res: |
|
3111 return "" |
|
3112 fn = Utilities.toNativeSeparators(fn) |
|
3113 |
|
3114 return fn |
|
3115 |
|
3116 def saveFileCopy(self, path=None): |
|
3117 """ |
|
3118 Public method to save a copy of the file. |
|
3119 |
|
3120 @param path directory to save the file in (string) |
|
3121 @return flag indicating success (boolean) |
|
3122 """ |
|
3123 fn = self.__getSaveFileName(path) |
|
3124 if not fn: |
|
3125 return False |
|
3126 |
|
3127 res = self.writeFile(fn) |
|
3128 if res: |
|
3129 # save to project, if a project is loaded |
|
3130 if self.project.isOpen() and \ |
|
3131 self.project.startswithProjectPath(fn): |
|
3132 self.project.appendFile(fn) |
|
3133 |
|
3134 return res |
|
3135 |
|
3136 def saveFile(self, saveas=False, path=None): |
|
3137 """ |
|
3138 Public method to save the text to a file. |
|
3139 |
|
3140 @param saveas flag indicating a 'save as' action (boolean) |
|
3141 @param path directory to save the file in (string) |
|
3142 @return flag indicating success (boolean) |
|
3143 """ |
|
3144 if not saveas and not self.isModified(): |
|
3145 return False # do nothing if text wasn't changed |
|
3146 |
|
3147 newName = None |
|
3148 if saveas or self.fileName == "": |
|
3149 saveas = True |
|
3150 |
|
3151 fn = self.__getSaveFileName(path) |
|
3152 if not fn: |
|
3153 return False |
|
3154 |
|
3155 newName = fn |
|
3156 |
|
3157 # save to project, if a project is loaded |
|
3158 if self.project.isOpen() and \ |
|
3159 self.project.startswithProjectPath(fn): |
|
3160 editorConfigEol = self.__getEditorConfig( |
|
3161 "EOLMode", nodefault=True, |
|
3162 config=self.__loadEditorConfigObject(fn)) |
|
3163 if editorConfigEol is not None: |
|
3164 self.setEolMode(editorConfigEol) |
|
3165 else: |
|
3166 self.setEolModeByEolString(self.project.getEolString()) |
|
3167 self.convertEols(self.eolMode()) |
|
3168 else: |
|
3169 fn = self.fileName |
|
3170 |
|
3171 self.__loadEditorConfig(fn) |
|
3172 self.editorAboutToBeSaved.emit(self.fileName) |
|
3173 if self.writeFile(fn): |
|
3174 if saveas: |
|
3175 self.__clearBreakpoints(self.fileName) |
|
3176 self.fileName = fn |
|
3177 self.setModified(False) |
|
3178 self.setReadOnly(False) |
|
3179 self.setWindowTitle(self.fileName) |
|
3180 # get eric specific flags |
|
3181 changedFlags = self.__processFlags() |
|
3182 if not self.__lexerReset and "FileType" in changedFlags: |
|
3183 self.setLanguage(self.fileName) |
|
3184 |
|
3185 if saveas: |
|
3186 self.isResourcesFile = self.fileName.endswith(".qrc") |
|
3187 self.__initContextMenu() |
|
3188 self.editorRenamed.emit(self.fileName) |
|
3189 |
|
3190 # save to project, if a project is loaded |
|
3191 if self.project.isOpen() and \ |
|
3192 self.project.startswithProjectPath(fn): |
|
3193 self.project.appendFile(fn) |
|
3194 self.addedToProject() |
|
3195 |
|
3196 self.setLanguage(self.fileName) |
|
3197 |
|
3198 self.lastModified = QFileInfo(self.fileName).lastModified() |
|
3199 if newName is not None: |
|
3200 self.vm.addToRecentList(newName) |
|
3201 self.editorSaved.emit(self.fileName) |
|
3202 self.checkSyntax() |
|
3203 self.extractTasks() |
|
3204 self.__resetOnlineChangeTraceInfo() |
|
3205 self.__checkEncoding() |
|
3206 return True |
|
3207 else: |
|
3208 self.lastModified = QFileInfo(fn).lastModified() |
|
3209 return False |
|
3210 |
|
3211 def saveFileAs(self, path=None, toProject=False): |
|
3212 """ |
|
3213 Public slot to save a file with a new name. |
|
3214 |
|
3215 @param path directory to save the file in (string) |
|
3216 @keyparam toProject flag indicating a save to project operation |
|
3217 (boolean) |
|
3218 @return tuple of two values (boolean, string) giving a success |
|
3219 indicator and the name of the saved file |
|
3220 """ |
|
3221 return self.saveFile(True, path) |
|
3222 |
|
3223 def handleRenamed(self, fn): |
|
3224 """ |
|
3225 Public slot to handle the editorRenamed signal. |
|
3226 |
|
3227 @param fn filename to be set for the editor (string). |
|
3228 """ |
|
3229 self.__clearBreakpoints(fn) |
|
3230 |
|
3231 self.fileName = fn |
|
3232 self.setWindowTitle(self.fileName) |
|
3233 |
|
3234 self.__loadEditorConfig() |
|
3235 |
|
3236 if self.lexer_ is None: |
|
3237 self.setLanguage(self.fileName) |
|
3238 |
|
3239 self.lastModified = QFileInfo(self.fileName).lastModified() |
|
3240 self.vm.setEditorName(self, self.fileName) |
|
3241 self.__updateReadOnly(True) |
|
3242 |
|
3243 def fileRenamed(self, fn): |
|
3244 """ |
|
3245 Public slot to handle the editorRenamed signal. |
|
3246 |
|
3247 @param fn filename to be set for the editor (string). |
|
3248 """ |
|
3249 self.handleRenamed(fn) |
|
3250 if not self.inFileRenamed: |
|
3251 self.inFileRenamed = True |
|
3252 self.editorRenamed.emit(self.fileName) |
|
3253 self.inFileRenamed = False |
|
3254 |
|
3255 ########################################################################### |
|
3256 ## Utility methods below |
|
3257 ########################################################################### |
|
3258 |
|
3259 def ensureVisible(self, line, expand=False): |
|
3260 """ |
|
3261 Public slot to ensure, that the specified line is visible. |
|
3262 |
|
3263 @param line line number to make visible |
|
3264 @type int |
|
3265 @keyparam expand flag indicating to expand all folds |
|
3266 @type bool |
|
3267 """ |
|
3268 self.ensureLineVisible(line - 1) |
|
3269 if expand: |
|
3270 self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line - 1, |
|
3271 QsciScintilla.SC_FOLDACTION_EXPAND) |
|
3272 |
|
3273 def ensureVisibleTop(self, line, expand=False): |
|
3274 """ |
|
3275 Public slot to ensure, that the specified line is visible at the top |
|
3276 of the editor. |
|
3277 |
|
3278 @param line line number to make visible |
|
3279 @type int |
|
3280 @keyparam expand flag indicating to expand all folds |
|
3281 @type bool |
|
3282 """ |
|
3283 self.ensureVisible(line) |
|
3284 self.setFirstVisibleLine(line - 1) |
|
3285 self.ensureCursorVisible() |
|
3286 if expand: |
|
3287 self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line - 1, |
|
3288 QsciScintilla.SC_FOLDACTION_EXPAND) |
|
3289 |
|
3290 def __marginClicked(self, margin, line, modifiers): |
|
3291 """ |
|
3292 Private slot to handle the marginClicked signal. |
|
3293 |
|
3294 @param margin id of the clicked margin (integer) |
|
3295 @param line line number of the click (integer) |
|
3296 @param modifiers keyboard modifiers (Qt.KeyboardModifiers) |
|
3297 """ |
|
3298 if margin == self.__bmMargin: |
|
3299 self.toggleBookmark(line + 1) |
|
3300 elif margin == self.__bpMargin: |
|
3301 self.__toggleBreakpoint(line + 1) |
|
3302 elif margin == self.__indicMargin: |
|
3303 if self.markersAtLine(line) & (1 << self.syntaxerror): |
|
3304 self.__showSyntaxError(line) |
|
3305 elif self.markersAtLine(line) & (1 << self.warning): |
|
3306 self.__showWarning(line) |
|
3307 |
|
3308 def handleMonospacedEnable(self): |
|
3309 """ |
|
3310 Public slot to handle the Use Monospaced Font context menu entry. |
|
3311 """ |
|
3312 if self.menuActs["MonospacedFont"].isChecked(): |
|
3313 if not self.lexer_: |
|
3314 self.setMonospaced(True) |
|
3315 else: |
|
3316 if self.lexer_: |
|
3317 self.lexer_.readSettings( |
|
3318 Preferences.Prefs.settings, "Scintilla") |
|
3319 if self.lexer_.hasSubstyles(): |
|
3320 self.lexer_.readSubstyles(self) |
|
3321 self.lexer_.initProperties() |
|
3322 self.setMonospaced(False) |
|
3323 self.__setMarginsDisplay() |
|
3324 |
|
3325 def getWordBoundaries(self, line, index, useWordChars=True): |
|
3326 """ |
|
3327 Public method to get the word boundaries at a position. |
|
3328 |
|
3329 @param line number of line to look at (int) |
|
3330 @param index position to look at (int) |
|
3331 @keyparam useWordChars flag indicating to use the wordCharacters |
|
3332 method (boolean) |
|
3333 @return tuple with start and end indexes of the word at the position |
|
3334 (integer, integer) |
|
3335 """ |
|
3336 text = self.text(line) |
|
3337 if self.caseSensitive(): |
|
3338 cs = Qt.CaseSensitive |
|
3339 else: |
|
3340 cs = Qt.CaseInsensitive |
|
3341 wc = self.wordCharacters() |
|
3342 if wc is None or not useWordChars: |
|
3343 regExp = QRegExp(r'[^\w_]', cs) |
|
3344 else: |
|
3345 wc = re.sub(r'\w', "", wc) |
|
3346 regExp = QRegExp(r'[^\w{0}]'.format(re.escape(wc)), cs) |
|
3347 start = regExp.lastIndexIn(text, index) + 1 |
|
3348 end = regExp.indexIn(text, index) |
|
3349 if start == end + 1 and index > 0: |
|
3350 # we are on a word boundary, try again |
|
3351 start = regExp.lastIndexIn(text, index - 1) + 1 |
|
3352 if start == -1: |
|
3353 start = 0 |
|
3354 if end == -1: |
|
3355 end = len(text) |
|
3356 |
|
3357 return (start, end) |
|
3358 |
|
3359 def getWord(self, line, index, direction=0, useWordChars=True): |
|
3360 """ |
|
3361 Public method to get the word at a position. |
|
3362 |
|
3363 @param line number of line to look at (int) |
|
3364 @param index position to look at (int) |
|
3365 @param direction direction to look in (0 = whole word, 1 = left, |
|
3366 2 = right) |
|
3367 @keyparam useWordChars flag indicating to use the wordCharacters |
|
3368 method (boolean) |
|
3369 @return the word at that position (string) |
|
3370 """ |
|
3371 start, end = self.getWordBoundaries(line, index, useWordChars) |
|
3372 if direction == 1: |
|
3373 end = index |
|
3374 elif direction == 2: |
|
3375 start = index |
|
3376 if end > start: |
|
3377 text = self.text(line) |
|
3378 word = text[start:end] |
|
3379 else: |
|
3380 word = '' |
|
3381 return word |
|
3382 |
|
3383 def getWordLeft(self, line, index): |
|
3384 """ |
|
3385 Public method to get the word to the left of a position. |
|
3386 |
|
3387 @param line number of line to look at (int) |
|
3388 @param index position to look at (int) |
|
3389 @return the word to the left of that position (string) |
|
3390 """ |
|
3391 return self.getWord(line, index, 1) |
|
3392 |
|
3393 def getWordRight(self, line, index): |
|
3394 """ |
|
3395 Public method to get the word to the right of a position. |
|
3396 |
|
3397 @param line number of line to look at (int) |
|
3398 @param index position to look at (int) |
|
3399 @return the word to the right of that position (string) |
|
3400 """ |
|
3401 return self.getWord(line, index, 2) |
|
3402 |
|
3403 def getCurrentWord(self): |
|
3404 """ |
|
3405 Public method to get the word at the current position. |
|
3406 |
|
3407 @return the word at that current position (string) |
|
3408 """ |
|
3409 line, index = self.getCursorPosition() |
|
3410 return self.getWord(line, index) |
|
3411 |
|
3412 def getCurrentWordBoundaries(self): |
|
3413 """ |
|
3414 Public method to get the word boundaries at the current position. |
|
3415 |
|
3416 @return tuple with start and end indexes of the current word |
|
3417 (integer, integer) |
|
3418 """ |
|
3419 line, index = self.getCursorPosition() |
|
3420 return self.getWordBoundaries(line, index) |
|
3421 |
|
3422 def selectWord(self, line, index): |
|
3423 """ |
|
3424 Public method to select the word at a position. |
|
3425 |
|
3426 @param line number of line to look at (int) |
|
3427 @param index position to look at (int) |
|
3428 """ |
|
3429 start, end = self.getWordBoundaries(line, index) |
|
3430 self.setSelection(line, start, line, end) |
|
3431 |
|
3432 def selectCurrentWord(self): |
|
3433 """ |
|
3434 Public method to select the current word. |
|
3435 """ |
|
3436 line, index = self.getCursorPosition() |
|
3437 self.selectWord(line, index) |
|
3438 |
|
3439 def __getCharacter(self, pos): |
|
3440 """ |
|
3441 Private method to get the character to the left of the current position |
|
3442 in the current line. |
|
3443 |
|
3444 @param pos position to get character at (integer) |
|
3445 @return requested character or "", if there are no more (string) and |
|
3446 the next position (i.e. pos - 1) |
|
3447 """ |
|
3448 if pos <= 0: |
|
3449 return "", pos |
|
3450 |
|
3451 pos = self.positionBefore(pos) |
|
3452 ch = self.charAt(pos) |
|
3453 |
|
3454 # Don't go past the end of the previous line |
|
3455 if ch == '\n' or ch == '\r': |
|
3456 return "", pos |
|
3457 |
|
3458 return ch, pos |
|
3459 |
|
3460 def getSearchText(self, selectionOnly=False): |
|
3461 """ |
|
3462 Public method to determine the selection or the current word for the |
|
3463 next search operation. |
|
3464 |
|
3465 @param selectionOnly flag indicating that only selected text should be |
|
3466 returned (boolean) |
|
3467 @return selection or current word (string) |
|
3468 """ |
|
3469 if self.hasSelectedText(): |
|
3470 text = self.selectedText() |
|
3471 if '\r' in text or '\n' in text: |
|
3472 # the selection contains at least a newline, it is |
|
3473 # unlikely to be the expression to search for |
|
3474 return '' |
|
3475 |
|
3476 return text |
|
3477 |
|
3478 if not selectionOnly: |
|
3479 # no selected text, determine the word at the current position |
|
3480 return self.getCurrentWord() |
|
3481 |
|
3482 return '' |
|
3483 |
|
3484 def setSearchIndicator(self, startPos, indicLength): |
|
3485 """ |
|
3486 Public method to set a search indicator for the given range. |
|
3487 |
|
3488 @param startPos start position of the indicator (integer) |
|
3489 @param indicLength length of the indicator (integer) |
|
3490 """ |
|
3491 self.setIndicatorRange(self.searchIndicator, startPos, indicLength) |
|
3492 line = self.lineIndexFromPosition(startPos)[0] |
|
3493 if line not in self.__searchIndicatorLines: |
|
3494 self.__searchIndicatorLines.append(line) |
|
3495 |
|
3496 def clearSearchIndicators(self): |
|
3497 """ |
|
3498 Public method to clear all search indicators. |
|
3499 """ |
|
3500 self.clearAllIndicators(self.searchIndicator) |
|
3501 self.__markedText = "" |
|
3502 self.__searchIndicatorLines = [] |
|
3503 self.__markerMap.update() |
|
3504 |
|
3505 def __markOccurrences(self): |
|
3506 """ |
|
3507 Private method to mark all occurrences of the current word. |
|
3508 """ |
|
3509 word = self.getCurrentWord() |
|
3510 if not word: |
|
3511 self.clearSearchIndicators() |
|
3512 return |
|
3513 |
|
3514 if self.__markedText == word: |
|
3515 return |
|
3516 |
|
3517 self.clearSearchIndicators() |
|
3518 ok = self.findFirstTarget(word, False, self.caseSensitive(), True, |
|
3519 0, 0) |
|
3520 while ok: |
|
3521 tgtPos, tgtLen = self.getFoundTarget() |
|
3522 self.setSearchIndicator(tgtPos, tgtLen) |
|
3523 ok = self.findNextTarget() |
|
3524 self.__markedText = word |
|
3525 self.__markerMap.update() |
|
3526 |
|
3527 def getSearchIndicatorLines(self): |
|
3528 """ |
|
3529 Public method to get the lines containing a search indicator. |
|
3530 |
|
3531 @return list of lines containing a search indicator (list of integer) |
|
3532 """ |
|
3533 return self.__searchIndicatorLines[:] |
|
3534 |
|
3535 def updateMarkerMap(self): |
|
3536 """ |
|
3537 Public method to initiate an update of the marker map. |
|
3538 """ |
|
3539 self.__markerMap.update() |
|
3540 |
|
3541 ########################################################################### |
|
3542 ## Highlightinh marker handling methods below |
|
3543 ########################################################################### |
|
3544 |
|
3545 def setHighlight(self, startLine, startIndex, endLine, endIndex): |
|
3546 """ |
|
3547 Public method to set a text highlight. |
|
3548 |
|
3549 @param startLine line of the highlight start |
|
3550 @type int |
|
3551 @param startIndex index of the highlight start |
|
3552 @type int |
|
3553 @param endLine line of the highlight end |
|
3554 @type int |
|
3555 @param endIndex index of the highlight end |
|
3556 @type int |
|
3557 """ |
|
3558 self.setIndicator(self.highlightIndicator, startLine, startIndex, |
|
3559 endLine, endIndex) |
|
3560 |
|
3561 def clearAllHighlights(self): |
|
3562 """ |
|
3563 Public method to clear all highlights. |
|
3564 """ |
|
3565 self.clearAllIndicators(self.highlightIndicator) |
|
3566 |
|
3567 def clearHighlight(self, startLine, startIndex, endLine, endIndex): |
|
3568 """ |
|
3569 Public method to clear a text highlight. |
|
3570 |
|
3571 @param startLine line of the highlight start |
|
3572 @type int |
|
3573 @param startIndex index of the highlight start |
|
3574 @type int |
|
3575 @param endLine line of the highlight end |
|
3576 @type int |
|
3577 @param endIndex index of the highlight end |
|
3578 @type int |
|
3579 """ |
|
3580 self.clearIndicator(self.highlightIndicator, startLine, startIndex, |
|
3581 endLine, endIndex) |
|
3582 |
|
3583 ########################################################################### |
|
3584 ## Comment handling methods below |
|
3585 ########################################################################### |
|
3586 |
|
3587 def __isCommentedLine(self, line, commentStr): |
|
3588 """ |
|
3589 Private method to check, if the given line is a comment line as |
|
3590 produced by the configured comment rules. |
|
3591 |
|
3592 @param line text of the line to check (string) |
|
3593 @param commentStr comment string to check against (string) |
|
3594 @return flag indicating a commented line (boolean) |
|
3595 """ |
|
3596 if Preferences.getEditor("CommentColumn0"): |
|
3597 return line.startswith(commentStr) |
|
3598 else: |
|
3599 return line.strip().startswith(commentStr) |
|
3600 |
|
3601 def toggleCommentBlock(self): |
|
3602 """ |
|
3603 Public slot to toggle the comment of a block. |
|
3604 |
|
3605 If the line of the cursor or the selection is not commented, it will |
|
3606 be commented. If it is commented, the comment block will be removed. |
|
3607 The later works independent of the current selection. |
|
3608 """ |
|
3609 if self.lexer_ is None or not self.lexer_.canBlockComment(): |
|
3610 return |
|
3611 |
|
3612 commentStr = self.lexer_.commentStr() |
|
3613 line, index = self.getCursorPosition() |
|
3614 |
|
3615 # check if line starts with our comment string (i.e. was commented |
|
3616 # by our comment...() slots |
|
3617 if self.hasSelectedText() and \ |
|
3618 self.__isCommentedLine(self.text(self.getSelection()[0]), |
|
3619 commentStr): |
|
3620 self.uncommentLineOrSelection() |
|
3621 elif not self.__isCommentedLine(self.text(line), commentStr): |
|
3622 # it doesn't, so comment the line or selection |
|
3623 self.commentLineOrSelection() |
|
3624 else: |
|
3625 # determine the start of the comment block |
|
3626 begline = line |
|
3627 while begline > 0 and \ |
|
3628 self.__isCommentedLine(self.text(begline - 1), commentStr): |
|
3629 begline -= 1 |
|
3630 # determine the end of the comment block |
|
3631 endline = line |
|
3632 lines = self.lines() |
|
3633 while endline < lines and \ |
|
3634 self.__isCommentedLine(self.text(endline + 1), commentStr): |
|
3635 endline += 1 |
|
3636 |
|
3637 self.setSelection(begline, 0, endline, self.lineLength(endline)) |
|
3638 self.uncommentLineOrSelection() |
|
3639 |
|
3640 # reset the cursor |
|
3641 self.setCursorPosition(line, index - len(commentStr)) |
|
3642 |
|
3643 def commentLine(self): |
|
3644 """ |
|
3645 Public slot to comment the current line. |
|
3646 """ |
|
3647 if self.lexer_ is None or not self.lexer_.canBlockComment(): |
|
3648 return |
|
3649 |
|
3650 line, index = self.getCursorPosition() |
|
3651 self.beginUndoAction() |
|
3652 if Preferences.getEditor("CommentColumn0"): |
|
3653 self.insertAt(self.lexer_.commentStr(), line, 0) |
|
3654 else: |
|
3655 self.insertAt(self.lexer_.commentStr(), line, |
|
3656 self.indentation(line)) |
|
3657 self.endUndoAction() |
|
3658 |
|
3659 def uncommentLine(self): |
|
3660 """ |
|
3661 Public slot to uncomment the current line. |
|
3662 """ |
|
3663 if self.lexer_ is None or not self.lexer_.canBlockComment(): |
|
3664 return |
|
3665 |
|
3666 commentStr = self.lexer_.commentStr() |
|
3667 line, index = self.getCursorPosition() |
|
3668 |
|
3669 # check if line starts with our comment string (i.e. was commented |
|
3670 # by our comment...() slots |
|
3671 if not self.__isCommentedLine(self.text(line), commentStr): |
|
3672 return |
|
3673 |
|
3674 # now remove the comment string |
|
3675 self.beginUndoAction() |
|
3676 if Preferences.getEditor("CommentColumn0"): |
|
3677 self.setSelection(line, 0, line, len(commentStr)) |
|
3678 else: |
|
3679 self.setSelection(line, self.indentation(line), |
|
3680 line, self.indentation(line) + len(commentStr)) |
|
3681 self.removeSelectedText() |
|
3682 self.endUndoAction() |
|
3683 |
|
3684 def commentSelection(self): |
|
3685 """ |
|
3686 Public slot to comment the current selection. |
|
3687 """ |
|
3688 if self.lexer_ is None or not self.lexer_.canBlockComment(): |
|
3689 return |
|
3690 |
|
3691 if not self.hasSelectedText(): |
|
3692 return |
|
3693 |
|
3694 commentStr = self.lexer_.commentStr() |
|
3695 |
|
3696 # get the selection boundaries |
|
3697 lineFrom, indexFrom, lineTo, indexTo = self.getSelection() |
|
3698 if indexTo == 0: |
|
3699 endLine = lineTo - 1 |
|
3700 else: |
|
3701 endLine = lineTo |
|
3702 |
|
3703 self.beginUndoAction() |
|
3704 # iterate over the lines |
|
3705 for line in range(lineFrom, endLine + 1): |
|
3706 if Preferences.getEditor("CommentColumn0"): |
|
3707 self.insertAt(commentStr, line, 0) |
|
3708 else: |
|
3709 self.insertAt(commentStr, line, self.indentation(line)) |
|
3710 |
|
3711 # change the selection accordingly |
|
3712 self.setSelection(lineFrom, 0, endLine + 1, 0) |
|
3713 self.endUndoAction() |
|
3714 |
|
3715 def uncommentSelection(self): |
|
3716 """ |
|
3717 Public slot to uncomment the current selection. |
|
3718 """ |
|
3719 if self.lexer_ is None or not self.lexer_.canBlockComment(): |
|
3720 return |
|
3721 |
|
3722 if not self.hasSelectedText(): |
|
3723 return |
|
3724 |
|
3725 commentStr = self.lexer_.commentStr() |
|
3726 |
|
3727 # get the selection boundaries |
|
3728 lineFrom, indexFrom, lineTo, indexTo = self.getSelection() |
|
3729 if indexTo == 0: |
|
3730 endLine = lineTo - 1 |
|
3731 else: |
|
3732 endLine = lineTo |
|
3733 |
|
3734 self.beginUndoAction() |
|
3735 # iterate over the lines |
|
3736 for line in range(lineFrom, endLine + 1): |
|
3737 # check if line starts with our comment string (i.e. was commented |
|
3738 # by our comment...() slots |
|
3739 if not self.__isCommentedLine(self.text(line), commentStr): |
|
3740 continue |
|
3741 |
|
3742 if Preferences.getEditor("CommentColumn0"): |
|
3743 self.setSelection(line, 0, line, len(commentStr)) |
|
3744 else: |
|
3745 self.setSelection(line, |
|
3746 self.indentation(line), |
|
3747 line, |
|
3748 self.indentation(line) + len(commentStr)) |
|
3749 self.removeSelectedText() |
|
3750 |
|
3751 # adjust selection start |
|
3752 if line == lineFrom: |
|
3753 indexFrom -= len(commentStr) |
|
3754 if indexFrom < 0: |
|
3755 indexFrom = 0 |
|
3756 |
|
3757 # adjust selection end |
|
3758 if line == lineTo: |
|
3759 indexTo -= len(commentStr) |
|
3760 if indexTo < 0: |
|
3761 indexTo = 0 |
|
3762 |
|
3763 # change the selection accordingly |
|
3764 self.setSelection(lineFrom, indexFrom, lineTo, indexTo) |
|
3765 self.endUndoAction() |
|
3766 |
|
3767 def commentLineOrSelection(self): |
|
3768 """ |
|
3769 Public slot to comment the current line or current selection. |
|
3770 """ |
|
3771 if self.hasSelectedText(): |
|
3772 self.commentSelection() |
|
3773 else: |
|
3774 self.commentLine() |
|
3775 |
|
3776 def uncommentLineOrSelection(self): |
|
3777 """ |
|
3778 Public slot to uncomment the current line or current selection. |
|
3779 """ |
|
3780 if self.hasSelectedText(): |
|
3781 self.uncommentSelection() |
|
3782 else: |
|
3783 self.uncommentLine() |
|
3784 |
|
3785 def streamCommentLine(self): |
|
3786 """ |
|
3787 Public slot to stream comment the current line. |
|
3788 """ |
|
3789 if self.lexer_ is None or not self.lexer_.canStreamComment(): |
|
3790 return |
|
3791 |
|
3792 commentStr = self.lexer_.streamCommentStr() |
|
3793 line, index = self.getCursorPosition() |
|
3794 |
|
3795 self.beginUndoAction() |
|
3796 self.insertAt(commentStr['end'], line, self.lineLength(line)) |
|
3797 self.insertAt(commentStr['start'], line, 0) |
|
3798 self.endUndoAction() |
|
3799 |
|
3800 def streamCommentSelection(self): |
|
3801 """ |
|
3802 Public slot to comment the current selection. |
|
3803 """ |
|
3804 if self.lexer_ is None or not self.lexer_.canStreamComment(): |
|
3805 return |
|
3806 |
|
3807 if not self.hasSelectedText(): |
|
3808 return |
|
3809 |
|
3810 commentStr = self.lexer_.streamCommentStr() |
|
3811 |
|
3812 # get the selection boundaries |
|
3813 lineFrom, indexFrom, lineTo, indexTo = self.getSelection() |
|
3814 if indexTo == 0: |
|
3815 endLine = lineTo - 1 |
|
3816 endIndex = self.lineLength(endLine) |
|
3817 else: |
|
3818 endLine = lineTo |
|
3819 endIndex = indexTo |
|
3820 |
|
3821 self.beginUndoAction() |
|
3822 self.insertAt(commentStr['end'], endLine, endIndex) |
|
3823 self.insertAt(commentStr['start'], lineFrom, indexFrom) |
|
3824 |
|
3825 # change the selection accordingly |
|
3826 if indexTo > 0: |
|
3827 indexTo += len(commentStr['end']) |
|
3828 if lineFrom == endLine: |
|
3829 indexTo += len(commentStr['start']) |
|
3830 self.setSelection(lineFrom, indexFrom, lineTo, indexTo) |
|
3831 self.endUndoAction() |
|
3832 |
|
3833 def streamCommentLineOrSelection(self): |
|
3834 """ |
|
3835 Public slot to stream comment the current line or current selection. |
|
3836 """ |
|
3837 if self.hasSelectedText(): |
|
3838 self.streamCommentSelection() |
|
3839 else: |
|
3840 self.streamCommentLine() |
|
3841 |
|
3842 def boxCommentLine(self): |
|
3843 """ |
|
3844 Public slot to box comment the current line. |
|
3845 """ |
|
3846 if self.lexer_ is None or not self.lexer_.canBoxComment(): |
|
3847 return |
|
3848 |
|
3849 commentStr = self.lexer_.boxCommentStr() |
|
3850 line, index = self.getCursorPosition() |
|
3851 |
|
3852 eol = self.getLineSeparator() |
|
3853 self.beginUndoAction() |
|
3854 self.insertAt(eol, line, self.lineLength(line)) |
|
3855 self.insertAt(commentStr['end'], line + 1, 0) |
|
3856 self.insertAt(commentStr['middle'], line, 0) |
|
3857 self.insertAt(eol, line, 0) |
|
3858 self.insertAt(commentStr['start'], line, 0) |
|
3859 self.endUndoAction() |
|
3860 |
|
3861 def boxCommentSelection(self): |
|
3862 """ |
|
3863 Public slot to box comment the current selection. |
|
3864 """ |
|
3865 if self.lexer_ is None or not self.lexer_.canBoxComment(): |
|
3866 return |
|
3867 |
|
3868 if not self.hasSelectedText(): |
|
3869 return |
|
3870 |
|
3871 commentStr = self.lexer_.boxCommentStr() |
|
3872 |
|
3873 # get the selection boundaries |
|
3874 lineFrom, indexFrom, lineTo, indexTo = self.getSelection() |
|
3875 if indexTo == 0: |
|
3876 endLine = lineTo - 1 |
|
3877 else: |
|
3878 endLine = lineTo |
|
3879 |
|
3880 self.beginUndoAction() |
|
3881 # iterate over the lines |
|
3882 for line in range(lineFrom, endLine + 1): |
|
3883 self.insertAt(commentStr['middle'], line, 0) |
|
3884 |
|
3885 # now do the comments before and after the selection |
|
3886 eol = self.getLineSeparator() |
|
3887 self.insertAt(eol, endLine, self.lineLength(endLine)) |
|
3888 self.insertAt(commentStr['end'], endLine + 1, 0) |
|
3889 self.insertAt(eol, lineFrom, 0) |
|
3890 self.insertAt(commentStr['start'], lineFrom, 0) |
|
3891 |
|
3892 # change the selection accordingly |
|
3893 self.setSelection(lineFrom, 0, endLine + 3, 0) |
|
3894 self.endUndoAction() |
|
3895 |
|
3896 def boxCommentLineOrSelection(self): |
|
3897 """ |
|
3898 Public slot to box comment the current line or current selection. |
|
3899 """ |
|
3900 if self.hasSelectedText(): |
|
3901 self.boxCommentSelection() |
|
3902 else: |
|
3903 self.boxCommentLine() |
|
3904 |
|
3905 ########################################################################### |
|
3906 ## Indentation handling methods below |
|
3907 ########################################################################### |
|
3908 |
|
3909 def __indentLine(self, indent=True): |
|
3910 """ |
|
3911 Private method to indent or unindent the current line. |
|
3912 |
|
3913 @param indent flag indicating an indent operation (boolean) |
|
3914 <br />If the flag is true, an indent operation is performed. |
|
3915 Otherwise the current line is unindented. |
|
3916 """ |
|
3917 line, index = self.getCursorPosition() |
|
3918 self.beginUndoAction() |
|
3919 if indent: |
|
3920 self.indent(line) |
|
3921 else: |
|
3922 self.unindent(line) |
|
3923 self.endUndoAction() |
|
3924 if indent: |
|
3925 self.setCursorPosition(line, index + self.indentationWidth()) |
|
3926 else: |
|
3927 self.setCursorPosition(line, index - self.indentationWidth()) |
|
3928 |
|
3929 def __indentSelection(self, indent=True): |
|
3930 """ |
|
3931 Private method to indent or unindent the current selection. |
|
3932 |
|
3933 @param indent flag indicating an indent operation (boolean) |
|
3934 <br />If the flag is true, an indent operation is performed. |
|
3935 Otherwise the current line is unindented. |
|
3936 """ |
|
3937 if not self.hasSelectedText(): |
|
3938 return |
|
3939 |
|
3940 # get the selection |
|
3941 lineFrom, indexFrom, lineTo, indexTo = self.getSelection() |
|
3942 |
|
3943 if indexTo == 0: |
|
3944 endLine = lineTo - 1 |
|
3945 else: |
|
3946 endLine = lineTo |
|
3947 |
|
3948 self.beginUndoAction() |
|
3949 # iterate over the lines |
|
3950 for line in range(lineFrom, endLine + 1): |
|
3951 if indent: |
|
3952 self.indent(line) |
|
3953 else: |
|
3954 self.unindent(line) |
|
3955 self.endUndoAction() |
|
3956 if indent: |
|
3957 if indexTo == 0: |
|
3958 self.setSelection( |
|
3959 lineFrom, indexFrom + self.indentationWidth(), |
|
3960 lineTo, 0) |
|
3961 else: |
|
3962 self.setSelection( |
|
3963 lineFrom, indexFrom + self.indentationWidth(), |
|
3964 lineTo, indexTo + self.indentationWidth()) |
|
3965 else: |
|
3966 indexStart = indexFrom - self.indentationWidth() |
|
3967 if indexStart < 0: |
|
3968 indexStart = 0 |
|
3969 indexEnd = indexTo - self.indentationWidth() |
|
3970 if indexEnd < 0: |
|
3971 indexEnd = 0 |
|
3972 self.setSelection(lineFrom, indexStart, lineTo, indexEnd) |
|
3973 |
|
3974 def indentLineOrSelection(self): |
|
3975 """ |
|
3976 Public slot to indent the current line or current selection. |
|
3977 """ |
|
3978 if self.hasSelectedText(): |
|
3979 self.__indentSelection(True) |
|
3980 else: |
|
3981 self.__indentLine(True) |
|
3982 |
|
3983 def unindentLineOrSelection(self): |
|
3984 """ |
|
3985 Public slot to unindent the current line or current selection. |
|
3986 """ |
|
3987 if self.hasSelectedText(): |
|
3988 self.__indentSelection(False) |
|
3989 else: |
|
3990 self.__indentLine(False) |
|
3991 |
|
3992 def smartIndentLineOrSelection(self): |
|
3993 """ |
|
3994 Public slot to indent current line smartly. |
|
3995 """ |
|
3996 if self.hasSelectedText(): |
|
3997 if self.lexer_ and self.lexer_.hasSmartIndent(): |
|
3998 self.lexer_.smartIndentSelection(self) |
|
3999 else: |
|
4000 self.__indentSelection(True) |
|
4001 else: |
|
4002 if self.lexer_ and self.lexer_.hasSmartIndent(): |
|
4003 self.lexer_.smartIndentLine(self) |
|
4004 else: |
|
4005 self.__indentLine(True) |
|
4006 |
|
4007 def gotoLine(self, line, pos=1, firstVisible=False, expand=False): |
|
4008 """ |
|
4009 Public slot to jump to the beginning of a line. |
|
4010 |
|
4011 @param line line number to go to |
|
4012 @type int |
|
4013 @keyparam pos position in line to go to |
|
4014 @type int |
|
4015 @keyparam firstVisible flag indicating to make the line the first |
|
4016 visible line |
|
4017 @type bool |
|
4018 @keyparam expand flag indicating to expand all folds |
|
4019 @type bool |
|
4020 """ |
|
4021 self.setCursorPosition(line - 1, pos - 1) |
|
4022 if firstVisible: |
|
4023 self.ensureVisibleTop(line, expand) |
|
4024 else: |
|
4025 self.ensureVisible(line, expand) |
|
4026 |
|
4027 def __textChanged(self): |
|
4028 """ |
|
4029 Private slot to handle a change of the editor text. |
|
4030 |
|
4031 This slot defers the handling to the next time the event loop |
|
4032 is run in order to ensure, that cursor position has been updated |
|
4033 by the underlying Scintilla editor. |
|
4034 """ |
|
4035 QTimer.singleShot(0, self.__saveLastEditPosition) |
|
4036 |
|
4037 def __saveLastEditPosition(self): |
|
4038 """ |
|
4039 Private slot to record the last edit position. |
|
4040 """ |
|
4041 self.__lastEditPosition = self.getCursorPosition() |
|
4042 self.lastEditPositionAvailable.emit() |
|
4043 |
|
4044 def isLastEditPositionAvailable(self): |
|
4045 """ |
|
4046 Public method to check, if a last edit position is available. |
|
4047 |
|
4048 @return flag indicating availability (boolean) |
|
4049 """ |
|
4050 return self.__lastEditPosition is not None |
|
4051 |
|
4052 def gotoLastEditPosition(self): |
|
4053 """ |
|
4054 Public method to move the cursor to the last edit position. |
|
4055 """ |
|
4056 self.setCursorPosition(*self.__lastEditPosition) |
|
4057 self.ensureVisible(self.__lastEditPosition[0]) |
|
4058 |
|
4059 def gotoMethodClass(self, goUp=False): |
|
4060 """ |
|
4061 Public method to go to the next Python method or class definition. |
|
4062 |
|
4063 @param goUp flag indicating the move direction (boolean) |
|
4064 """ |
|
4065 if self.isPyFile() or self.isRubyFile(): |
|
4066 lineNo = self.getCursorPosition()[0] |
|
4067 line = self.text(lineNo) |
|
4068 if line.strip().startswith(("class ", "def ", "module ")): |
|
4069 if goUp: |
|
4070 lineNo -= 1 |
|
4071 else: |
|
4072 lineNo += 1 |
|
4073 while True: |
|
4074 if goUp and lineNo < 0: |
|
4075 self.setCursorPosition(0, 0) |
|
4076 self.ensureVisible(0) |
|
4077 return |
|
4078 elif not goUp and lineNo == self.lines(): |
|
4079 lineNo = self.lines() - 1 |
|
4080 self.setCursorPosition(lineNo, self.lineLength(lineNo)) |
|
4081 self.ensureVisible(lineNo) |
|
4082 return |
|
4083 |
|
4084 line = self.text(lineNo) |
|
4085 if line.strip().startswith(("class ", "def ", "module ")): |
|
4086 # try 'def ' first because it occurs more often |
|
4087 first = line.find("def ") |
|
4088 if first > -1: |
|
4089 first += 4 |
|
4090 else: |
|
4091 first = line.find("class ") |
|
4092 if first > -1: |
|
4093 first += 6 |
|
4094 else: |
|
4095 first = line.find("module ") + 7 |
|
4096 match = re.search("[:(]", line) |
|
4097 if match: |
|
4098 end = match.start() |
|
4099 else: |
|
4100 end = self.lineLength(lineNo) - 1 |
|
4101 self.setSelection(lineNo, first, lineNo, end) |
|
4102 self.ensureVisible(lineNo) |
|
4103 return |
|
4104 |
|
4105 if goUp: |
|
4106 lineNo -= 1 |
|
4107 else: |
|
4108 lineNo += 1 |
|
4109 |
|
4110 ########################################################################### |
|
4111 ## Setup methods below |
|
4112 ########################################################################### |
|
4113 |
|
4114 def readSettings(self): |
|
4115 """ |
|
4116 Public slot to read the settings into our lexer. |
|
4117 """ |
|
4118 # read the lexer settings and reinit the properties |
|
4119 if self.lexer_ is not None: |
|
4120 self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla") |
|
4121 if self.lexer_.hasSubstyles(): |
|
4122 self.lexer_.readSubstyles(self) |
|
4123 self.lexer_.initProperties() |
|
4124 |
|
4125 self.lexer_.setDefaultColor(self.lexer_.color(0)) |
|
4126 self.lexer_.setDefaultPaper(self.lexer_.paper(0)) |
|
4127 |
|
4128 self.__bindLexer(self.fileName) |
|
4129 self.recolor() |
|
4130 |
|
4131 # read the typing completer settings |
|
4132 if self.completer is not None: |
|
4133 self.completer.readSettings() |
|
4134 |
|
4135 # set the line marker colours or pixmap |
|
4136 if Preferences.getEditor("LineMarkersBackground"): |
|
4137 self.markerDefine(QsciScintilla.Background, self.currentline) |
|
4138 self.markerDefine(QsciScintilla.Background, self.errorline) |
|
4139 self.__setLineMarkerColours() |
|
4140 else: |
|
4141 self.markerDefine( |
|
4142 UI.PixmapCache.getPixmap("currentLineMarker.png"), |
|
4143 self.currentline) |
|
4144 self.markerDefine( |
|
4145 UI.PixmapCache.getPixmap("errorLineMarker.png"), |
|
4146 self.errorline) |
|
4147 |
|
4148 # set the text display |
|
4149 self.__setTextDisplay() |
|
4150 |
|
4151 # set margin 0 and 2 configuration |
|
4152 self.__setMarginsDisplay() |
|
4153 |
|
4154 # set the auto-completion function |
|
4155 self.__acCache.setSize( |
|
4156 Preferences.getEditor("AutoCompletionCacheSize")) |
|
4157 self.__acCache.setMaximumCacheTime( |
|
4158 Preferences.getEditor("AutoCompletionCacheTime")) |
|
4159 self.__acCacheEnabled = Preferences.getEditor( |
|
4160 "AutoCompletionCacheEnabled") |
|
4161 acTimeout = Preferences.getEditor("AutoCompletionTimeout") |
|
4162 if acTimeout != self.__acTimer.interval: |
|
4163 self.__acTimer.setInterval(acTimeout) |
|
4164 self.__setAutoCompletion() |
|
4165 |
|
4166 # set the calltips function |
|
4167 self.__setCallTips() |
|
4168 |
|
4169 # set the autosave flags |
|
4170 self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0 |
|
4171 |
|
4172 if Preferences.getEditor("MiniContextMenu") != self.miniMenu: |
|
4173 # regenerate context menu |
|
4174 self.__initContextMenu() |
|
4175 else: |
|
4176 # set checked context menu items |
|
4177 self.menuActs["AutoCompletionEnable"].setChecked( |
|
4178 self.autoCompletionThreshold() != -1) |
|
4179 self.menuActs["MonospacedFont"].setChecked( |
|
4180 self.useMonospaced) |
|
4181 self.menuActs["AutosaveEnable"].setChecked( |
|
4182 self.autosaveEnabled and not self.autosaveManuallyDisabled) |
|
4183 |
|
4184 # regenerate the margins context menu(s) |
|
4185 self.__initContextMenuMargins() |
|
4186 |
|
4187 if Preferences.getEditor("MarkOccurrencesEnabled"): |
|
4188 self.__markOccurrencesTimer.setInterval( |
|
4189 Preferences.getEditor("MarkOccurrencesTimeout")) |
|
4190 else: |
|
4191 self.__markOccurrencesTimer.stop() |
|
4192 self.clearSearchIndicators() |
|
4193 |
|
4194 if Preferences.getEditor("OnlineSyntaxCheck"): |
|
4195 self.__onlineSyntaxCheckTimer.setInterval( |
|
4196 Preferences.getEditor("OnlineSyntaxCheckInterval") * 1000) |
|
4197 else: |
|
4198 self.__onlineSyntaxCheckTimer.stop() |
|
4199 |
|
4200 if Preferences.getEditor("OnlineChangeTrace"): |
|
4201 self.__onlineChangeTraceTimer.setInterval( |
|
4202 Preferences.getEditor("OnlineChangeTraceInterval")) |
|
4203 else: |
|
4204 self.__onlineChangeTraceTimer.stop() |
|
4205 self.__deleteAllChangeMarkers() |
|
4206 self.markerDefine(self.__createChangeMarkerPixmap( |
|
4207 "OnlineChangeTraceMarkerUnsaved"), self.__changeMarkerUnsaved) |
|
4208 self.markerDefine(self.__createChangeMarkerPixmap( |
|
4209 "OnlineChangeTraceMarkerSaved"), self.__changeMarkerSaved) |
|
4210 |
|
4211 # refresh the annotations display |
|
4212 self.__refreshAnnotations() |
|
4213 |
|
4214 self.__markerMap.setMapPosition( |
|
4215 Preferences.getEditor("ShowMarkerMapOnRight")) |
|
4216 self.__markerMap.initColors() |
|
4217 |
|
4218 self.settingsRead.emit() |
|
4219 |
|
4220 def __setLineMarkerColours(self): |
|
4221 """ |
|
4222 Private method to set the line marker colours. |
|
4223 """ |
|
4224 self.setMarkerForegroundColor( |
|
4225 Preferences.getEditorColour("CurrentMarker"), self.currentline) |
|
4226 self.setMarkerBackgroundColor( |
|
4227 Preferences.getEditorColour("CurrentMarker"), self.currentline) |
|
4228 self.setMarkerForegroundColor( |
|
4229 Preferences.getEditorColour("ErrorMarker"), self.errorline) |
|
4230 self.setMarkerBackgroundColor( |
|
4231 Preferences.getEditorColour("ErrorMarker"), self.errorline) |
|
4232 |
|
4233 def __setMarginsDisplay(self): |
|
4234 """ |
|
4235 Private method to configure margins 0 and 2. |
|
4236 """ |
|
4237 # set the settings for all margins |
|
4238 self.setMarginsFont(Preferences.getEditorOtherFonts("MarginsFont")) |
|
4239 self.setMarginsForegroundColor( |
|
4240 Preferences.getEditorColour("MarginsForeground")) |
|
4241 self.setMarginsBackgroundColor( |
|
4242 Preferences.getEditorColour("MarginsBackground")) |
|
4243 |
|
4244 # reset standard margins settings |
|
4245 for margin in range(5): |
|
4246 self.setMarginLineNumbers(margin, False) |
|
4247 self.setMarginMarkerMask(margin, 0) |
|
4248 self.setMarginWidth(margin, 0) |
|
4249 self.setMarginSensitivity(margin, False) |
|
4250 |
|
4251 # set marker margin(s) settings |
|
4252 self.__bmMargin = 0 |
|
4253 self.__linenoMargin = 1 |
|
4254 self.__bpMargin = 2 |
|
4255 self.__foldMargin = 3 |
|
4256 self.__indicMargin = 4 |
|
4257 |
|
4258 marginBmMask = (1 << self.bookmark) |
|
4259 self.setMarginWidth(self.__bmMargin, 16) |
|
4260 self.setMarginSensitivity(self.__bmMargin, True) |
|
4261 self.setMarginMarkerMask(self.__bmMargin, marginBmMask) |
|
4262 |
|
4263 marginBpMask = (1 << self.breakpoint) | \ |
|
4264 (1 << self.cbreakpoint) | \ |
|
4265 (1 << self.tbreakpoint) | \ |
|
4266 (1 << self.tcbreakpoint) | \ |
|
4267 (1 << self.dbreakpoint) |
|
4268 self.setMarginWidth(self.__bpMargin, 16) |
|
4269 self.setMarginSensitivity(self.__bpMargin, True) |
|
4270 self.setMarginMarkerMask(self.__bpMargin, marginBpMask) |
|
4271 |
|
4272 marginIndicMask = (1 << self.syntaxerror) | \ |
|
4273 (1 << self.notcovered) | \ |
|
4274 (1 << self.taskmarker) | \ |
|
4275 (1 << self.warning) | \ |
|
4276 (1 << self.__changeMarkerUnsaved) | \ |
|
4277 (1 << self.__changeMarkerSaved) | \ |
|
4278 (1 << self.currentline) | \ |
|
4279 (1 << self.errorline) |
|
4280 self.setMarginWidth(self.__indicMargin, 16) |
|
4281 self.setMarginSensitivity(self.__indicMargin, True) |
|
4282 self.setMarginMarkerMask(self.__indicMargin, marginIndicMask) |
|
4283 |
|
4284 # set linenumber margin settings |
|
4285 linenoMargin = Preferences.getEditor("LinenoMargin") |
|
4286 self.setMarginLineNumbers(self.__linenoMargin, linenoMargin) |
|
4287 if linenoMargin: |
|
4288 self.__resizeLinenoMargin() |
|
4289 else: |
|
4290 self.setMarginWidth(self.__linenoMargin, 0) |
|
4291 |
|
4292 # set folding margin settings |
|
4293 if Preferences.getEditor("FoldingMargin"): |
|
4294 self.setMarginWidth(self.__foldMargin, 16) |
|
4295 folding = Preferences.getEditor("FoldingStyle") |
|
4296 try: |
|
4297 folding = QsciScintilla.FoldStyle(folding) |
|
4298 except AttributeError: |
|
4299 pass |
|
4300 self.setFolding(folding, self.__foldMargin) |
|
4301 self.setFoldMarginColors( |
|
4302 Preferences.getEditorColour("FoldmarginBackground"), |
|
4303 Preferences.getEditorColour("FoldmarginBackground")) |
|
4304 self.setFoldMarkersColors( |
|
4305 Preferences.getEditorColour("FoldMarkersForeground"), |
|
4306 Preferences.getEditorColour("FoldMarkersBackground")) |
|
4307 else: |
|
4308 self.setMarginWidth(self.__foldMargin, 0) |
|
4309 self.setFolding(QsciScintilla.NoFoldStyle, self.__foldMargin) |
|
4310 |
|
4311 def __resizeLinenoMargin(self): |
|
4312 """ |
|
4313 Private slot to resize the line numbers margin. |
|
4314 """ |
|
4315 linenoMargin = Preferences.getEditor("LinenoMargin") |
|
4316 if linenoMargin: |
|
4317 self.setMarginWidth( |
|
4318 self.__linenoMargin, '8' * (len(str(self.lines())) + 1)) |
|
4319 |
|
4320 def __setTabAndIndent(self): |
|
4321 """ |
|
4322 Private method to set indentation size and style and tab width. |
|
4323 """ |
|
4324 self.setTabWidth(self.__getEditorConfig("TabWidth")) |
|
4325 self.setIndentationWidth(self.__getEditorConfig("IndentWidth")) |
|
4326 if self.lexer_ and self.lexer_.alwaysKeepTabs(): |
|
4327 self.setIndentationsUseTabs(True) |
|
4328 else: |
|
4329 self.setIndentationsUseTabs( |
|
4330 self.__getEditorConfig("TabForIndentation")) |
|
4331 |
|
4332 def __setTextDisplay(self): |
|
4333 """ |
|
4334 Private method to configure the text display. |
|
4335 """ |
|
4336 self.__setTabAndIndent() |
|
4337 |
|
4338 self.setTabIndents(Preferences.getEditor("TabIndents")) |
|
4339 self.setBackspaceUnindents(Preferences.getEditor("TabIndents")) |
|
4340 self.setIndentationGuides(Preferences.getEditor("IndentationGuides")) |
|
4341 self.setIndentationGuidesBackgroundColor( |
|
4342 Preferences.getEditorColour("IndentationGuidesBackground")) |
|
4343 self.setIndentationGuidesForegroundColor( |
|
4344 Preferences.getEditorColour("IndentationGuidesForeground")) |
|
4345 if Preferences.getEditor("ShowWhitespace"): |
|
4346 self.setWhitespaceVisibility(QsciScintilla.WsVisible) |
|
4347 try: |
|
4348 self.setWhitespaceForegroundColor( |
|
4349 Preferences.getEditorColour("WhitespaceForeground")) |
|
4350 self.setWhitespaceBackgroundColor( |
|
4351 Preferences.getEditorColour("WhitespaceBackground")) |
|
4352 self.setWhitespaceSize( |
|
4353 Preferences.getEditor("WhitespaceSize")) |
|
4354 except AttributeError: |
|
4355 # QScintilla before 2.5 doesn't support this |
|
4356 pass |
|
4357 else: |
|
4358 self.setWhitespaceVisibility(QsciScintilla.WsInvisible) |
|
4359 self.setEolVisibility(Preferences.getEditor("ShowEOL")) |
|
4360 self.setAutoIndent(Preferences.getEditor("AutoIndentation")) |
|
4361 if Preferences.getEditor("BraceHighlighting"): |
|
4362 self.setBraceMatching(QsciScintilla.SloppyBraceMatch) |
|
4363 else: |
|
4364 self.setBraceMatching(QsciScintilla.NoBraceMatch) |
|
4365 self.setMatchedBraceForegroundColor( |
|
4366 Preferences.getEditorColour("MatchingBrace")) |
|
4367 self.setMatchedBraceBackgroundColor( |
|
4368 Preferences.getEditorColour("MatchingBraceBack")) |
|
4369 self.setUnmatchedBraceForegroundColor( |
|
4370 Preferences.getEditorColour("NonmatchingBrace")) |
|
4371 self.setUnmatchedBraceBackgroundColor( |
|
4372 Preferences.getEditorColour("NonmatchingBraceBack")) |
|
4373 if Preferences.getEditor("CustomSelectionColours"): |
|
4374 self.setSelectionBackgroundColor( |
|
4375 Preferences.getEditorColour("SelectionBackground")) |
|
4376 else: |
|
4377 self.setSelectionBackgroundColor( |
|
4378 QApplication.palette().color(QPalette.Highlight)) |
|
4379 if Preferences.getEditor("ColourizeSelText"): |
|
4380 self.resetSelectionForegroundColor() |
|
4381 elif Preferences.getEditor("CustomSelectionColours"): |
|
4382 self.setSelectionForegroundColor( |
|
4383 Preferences.getEditorColour("SelectionForeground")) |
|
4384 else: |
|
4385 self.setSelectionForegroundColor( |
|
4386 QApplication.palette().color(QPalette.HighlightedText)) |
|
4387 self.setSelectionToEol(Preferences.getEditor("ExtendSelectionToEol")) |
|
4388 self.setCaretForegroundColor( |
|
4389 Preferences.getEditorColour("CaretForeground")) |
|
4390 self.setCaretLineBackgroundColor( |
|
4391 Preferences.getEditorColour("CaretLineBackground")) |
|
4392 self.setCaretLineVisible(Preferences.getEditor("CaretLineVisible")) |
|
4393 self.setCaretLineAlwaysVisible( |
|
4394 Preferences.getEditor("CaretLineAlwaysVisible")) |
|
4395 self.caretWidth = Preferences.getEditor("CaretWidth") |
|
4396 self.setCaretWidth(self.caretWidth) |
|
4397 self.caretLineFrameWidth = Preferences.getEditor("CaretLineFrameWidth") |
|
4398 if QSCINTILLA_VERSION() >= 0x020B00: |
|
4399 self.setCaretLineFrameWidth(self.caretLineFrameWidth) |
|
4400 self.useMonospaced = Preferences.getEditor("UseMonospacedFont") |
|
4401 self.setMonospaced(self.useMonospaced) |
|
4402 edgeMode = Preferences.getEditor("EdgeMode") |
|
4403 edge = QsciScintilla.EdgeMode(edgeMode) |
|
4404 self.setEdgeMode(edge) |
|
4405 if edgeMode: |
|
4406 self.setEdgeColumn(Preferences.getEditor("EdgeColumn")) |
|
4407 self.setEdgeColor(Preferences.getEditorColour("Edge")) |
|
4408 |
|
4409 wrapVisualFlag = Preferences.getEditor("WrapVisualFlag") |
|
4410 self.setWrapMode(Preferences.getEditor("WrapLongLinesMode")) |
|
4411 self.setWrapVisualFlags(wrapVisualFlag, wrapVisualFlag) |
|
4412 self.setWrapIndentMode(Preferences.getEditor("WrapIndentMode")) |
|
4413 self.setWrapStartIndent(Preferences.getEditor("WrapStartIndent")) |
|
4414 |
|
4415 self.zoomTo(Preferences.getEditor("ZoomFactor")) |
|
4416 |
|
4417 self.searchIndicator = QsciScintilla.INDIC_CONTAINER |
|
4418 self.indicatorDefine( |
|
4419 self.searchIndicator, QsciScintilla.INDIC_BOX, |
|
4420 Preferences.getEditorColour("SearchMarkers")) |
|
4421 if not Preferences.getEditor("SearchMarkersEnabled") and \ |
|
4422 not Preferences.getEditor("QuickSearchMarkersEnabled") and \ |
|
4423 not Preferences.getEditor("MarkOccurrencesEnabled"): |
|
4424 self.clearAllIndicators(self.searchIndicator) |
|
4425 |
|
4426 self.spellingIndicator = QsciScintilla.INDIC_CONTAINER + 1 |
|
4427 self.indicatorDefine( |
|
4428 self.spellingIndicator, QsciScintilla.INDIC_SQUIGGLE, |
|
4429 Preferences.getEditorColour("SpellingMarkers")) |
|
4430 self.__setSpelling() |
|
4431 |
|
4432 self.highlightIndicator = QsciScintilla.INDIC_CONTAINER + 2 |
|
4433 self.indicatorDefine( |
|
4434 self.highlightIndicator, QsciScintilla.INDIC_FULLBOX, |
|
4435 Preferences.getEditorColour("HighlightMarker")) |
|
4436 |
|
4437 self.setCursorFlashTime(QApplication.cursorFlashTime()) |
|
4438 |
|
4439 try: |
|
4440 if Preferences.getEditor("AnnotationsEnabled"): |
|
4441 self.setAnnotationDisplay(QsciScintilla.AnnotationBoxed) |
|
4442 else: |
|
4443 self.setAnnotationDisplay(QsciScintilla.AnnotationHidden) |
|
4444 except AttributeError: |
|
4445 pass |
|
4446 self.__setAnnotationStyles() |
|
4447 |
|
4448 if Preferences.getEditor("OverrideEditAreaColours"): |
|
4449 self.setColor(Preferences.getEditorColour("EditAreaForeground")) |
|
4450 self.setPaper(Preferences.getEditorColour("EditAreaBackground")) |
|
4451 |
|
4452 self.setVirtualSpaceOptions( |
|
4453 Preferences.getEditor("VirtualSpaceOptions")) |
|
4454 |
|
4455 self.__markerMap.setEnabled(True) |
|
4456 |
|
4457 def __setEolMode(self): |
|
4458 """ |
|
4459 Private method to configure the eol mode of the editor. |
|
4460 """ |
|
4461 if self.fileName and \ |
|
4462 self.project.isOpen() and \ |
|
4463 self.project.isProjectFile(self.fileName): |
|
4464 eolMode = self.__getEditorConfig("EOLMode", nodefault=True) |
|
4465 if eolMode is None: |
|
4466 eolStr = self.project.getEolString() |
|
4467 self.setEolModeByEolString(eolStr) |
|
4468 else: |
|
4469 self.setEolMode(eolMode) |
|
4470 else: |
|
4471 eolMode = self.__getEditorConfig("EOLMode") |
|
4472 eolMode = QsciScintilla.EolMode(eolMode) |
|
4473 self.setEolMode(eolMode) |
|
4474 self.__eolChanged() |
|
4475 |
|
4476 def __setAutoCompletion(self): |
|
4477 """ |
|
4478 Private method to configure the autocompletion function. |
|
4479 """ |
|
4480 if self.lexer_: |
|
4481 self.setAutoCompletionFillupsEnabled( |
|
4482 Preferences.getEditor("AutoCompletionFillups")) |
|
4483 self.setAutoCompletionCaseSensitivity( |
|
4484 Preferences.getEditor("AutoCompletionCaseSensitivity")) |
|
4485 self.setAutoCompletionReplaceWord( |
|
4486 Preferences.getEditor("AutoCompletionReplaceWord")) |
|
4487 self.setAutoCompletionThreshold(0) |
|
4488 try: |
|
4489 self.setAutoCompletionUseSingle( |
|
4490 Preferences.getEditor("AutoCompletionShowSingle")) |
|
4491 except AttributeError: |
|
4492 self.setAutoCompletionShowSingle( |
|
4493 Preferences.getEditor("AutoCompletionShowSingle")) |
|
4494 autoCompletionSource = Preferences.getEditor("AutoCompletionSource") |
|
4495 if autoCompletionSource == QsciScintilla.AcsDocument: |
|
4496 self.setAutoCompletionSource(QsciScintilla.AcsDocument) |
|
4497 elif autoCompletionSource == QsciScintilla.AcsAPIs: |
|
4498 self.setAutoCompletionSource(QsciScintilla.AcsAPIs) |
|
4499 else: |
|
4500 self.setAutoCompletionSource(QsciScintilla.AcsAll) |
|
4501 |
|
4502 self.maxLines = Preferences.getEditor("AutoCompletionMaxLines") |
|
4503 self.maxChars = Preferences.getEditor("AutoCompletionMaxChars") |
|
4504 |
|
4505 def __setCallTips(self): |
|
4506 """ |
|
4507 Private method to configure the calltips function. |
|
4508 """ |
|
4509 self.setCallTipsBackgroundColor( |
|
4510 Preferences.getEditorColour("CallTipsBackground")) |
|
4511 self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible")) |
|
4512 calltipsStyle = Preferences.getEditor("CallTipsStyle") |
|
4513 try: |
|
4514 self.setCallTipsPosition( |
|
4515 Preferences.getEditor("CallTipsPosition")) |
|
4516 except AttributeError: |
|
4517 pass |
|
4518 |
|
4519 if Preferences.getEditor("CallTipsEnabled"): |
|
4520 if calltipsStyle == QsciScintilla.CallTipsNoContext: |
|
4521 self.setCallTipsStyle(QsciScintilla.CallTipsNoContext) |
|
4522 elif calltipsStyle == \ |
|
4523 QsciScintilla.CallTipsNoAutoCompletionContext: |
|
4524 self.setCallTipsStyle( |
|
4525 QsciScintilla.CallTipsNoAutoCompletionContext) |
|
4526 else: |
|
4527 self.setCallTipsStyle(QsciScintilla.CallTipsContext) |
|
4528 else: |
|
4529 self.setCallTipsStyle(QsciScintilla.CallTipsNone) |
|
4530 |
|
4531 ########################################################################### |
|
4532 ## Autocompletion handling methods below |
|
4533 ########################################################################### |
|
4534 |
|
4535 def canAutoCompleteFromAPIs(self): |
|
4536 """ |
|
4537 Public method to check for API availablity. |
|
4538 |
|
4539 @return flag indicating autocompletion from APIs is available (boolean) |
|
4540 """ |
|
4541 return self.acAPI |
|
4542 |
|
4543 def autoCompleteQScintilla(self): |
|
4544 """ |
|
4545 Public method to perform an autocompletion using QScintilla methods. |
|
4546 """ |
|
4547 self.__acText = ' ' # Prevent long running ACs to add results |
|
4548 self.__acWatchdog.stop() |
|
4549 if self.__acCompletions: |
|
4550 return |
|
4551 |
|
4552 acs = Preferences.getEditor("AutoCompletionSource") |
|
4553 if acs == QsciScintilla.AcsDocument: |
|
4554 self.autoCompleteFromDocument() |
|
4555 elif acs == QsciScintilla.AcsAPIs: |
|
4556 self.autoCompleteFromAPIs() |
|
4557 elif acs == QsciScintilla.AcsAll: |
|
4558 self.autoCompleteFromAll() |
|
4559 else: |
|
4560 E5MessageBox.information( |
|
4561 self, |
|
4562 self.tr("Autocompletion"), |
|
4563 self.tr( |
|
4564 """Autocompletion is not available because""" |
|
4565 """ there is no autocompletion source set.""")) |
|
4566 |
|
4567 def setAutoCompletionEnabled(self, enable): |
|
4568 """ |
|
4569 Public method to enable/disable autocompletion. |
|
4570 |
|
4571 @param enable flag indicating the desired autocompletion status |
|
4572 (boolean) |
|
4573 """ |
|
4574 if enable: |
|
4575 autoCompletionSource = \ |
|
4576 Preferences.getEditor("AutoCompletionSource") |
|
4577 if autoCompletionSource == QsciScintilla.AcsDocument: |
|
4578 self.setAutoCompletionSource(QsciScintilla.AcsDocument) |
|
4579 elif autoCompletionSource == QsciScintilla.AcsAPIs: |
|
4580 self.setAutoCompletionSource(QsciScintilla.AcsAPIs) |
|
4581 else: |
|
4582 self.setAutoCompletionSource(QsciScintilla.AcsAll) |
|
4583 |
|
4584 def __toggleAutoCompletionEnable(self): |
|
4585 """ |
|
4586 Private slot to handle the Enable Autocompletion context menu entry. |
|
4587 """ |
|
4588 if self.menuActs["AutoCompletionEnable"].isChecked(): |
|
4589 self.setAutoCompletionEnabled(True) |
|
4590 else: |
|
4591 self.setAutoCompletionEnabled(False) |
|
4592 |
|
4593 ################################################################# |
|
4594 ## Support for autocompletion hook methods |
|
4595 ################################################################# |
|
4596 |
|
4597 def __charAdded(self, charNumber): |
|
4598 """ |
|
4599 Private slot called to handle the user entering a character. |
|
4600 |
|
4601 @param charNumber value of the character entered (integer) |
|
4602 """ |
|
4603 char = chr(charNumber) |
|
4604 # update code documentation viewer |
|
4605 if char == "(" and \ |
|
4606 Preferences.getDocuViewer("ShowInfoOnOpenParenthesis"): |
|
4607 self.vm.showEditorInfo(self) |
|
4608 |
|
4609 if self.isListActive(): |
|
4610 if self.__isStartChar(char): |
|
4611 self.cancelList() |
|
4612 self.autoComplete(auto=True, context=True) |
|
4613 return |
|
4614 elif char == '(': |
|
4615 self.cancelList() |
|
4616 else: |
|
4617 self.__acTimer.stop() |
|
4618 |
|
4619 if self.callTipsStyle() != QsciScintilla.CallTipsNone and \ |
|
4620 self.lexer_ is not None and chr(charNumber) in '()': |
|
4621 self.callTip() |
|
4622 |
|
4623 if not self.isCallTipActive(): |
|
4624 char = chr(charNumber) |
|
4625 if self.__isStartChar(char): |
|
4626 self.autoComplete(auto=True, context=True) |
|
4627 return |
|
4628 |
|
4629 line, col = self.getCursorPosition() |
|
4630 txt = self.getWordLeft(line, col) |
|
4631 if len(txt) >= Preferences.getEditor("AutoCompletionThreshold"): |
|
4632 self.autoComplete(auto=True, context=False) |
|
4633 return |
|
4634 |
|
4635 def __isStartChar(self, ch): |
|
4636 """ |
|
4637 Private method to check, if a character is an autocompletion start |
|
4638 character. |
|
4639 |
|
4640 @param ch character to be checked (one character string) |
|
4641 @return flag indicating the result (boolean) |
|
4642 """ |
|
4643 if self.lexer_ is None: |
|
4644 return False |
|
4645 |
|
4646 wseps = self.lexer_.autoCompletionWordSeparators() |
|
4647 for wsep in wseps: |
|
4648 if wsep.endswith(ch): |
|
4649 return True |
|
4650 |
|
4651 return False |
|
4652 |
|
4653 def __autocompletionCancelled(self): |
|
4654 """ |
|
4655 Private slot to handle the cancellation of an auto-completion list. |
|
4656 """ |
|
4657 self.__acWatchdog.stop() |
|
4658 |
|
4659 self.__acText = "" |
|
4660 |
|
4661 ################################################################# |
|
4662 ## auto-completion hook interfaces |
|
4663 ################################################################# |
|
4664 |
|
4665 def addCompletionListHook(self, key, func, asynchroneous=False): |
|
4666 """ |
|
4667 Public method to set an auto-completion list provider. |
|
4668 |
|
4669 @param key name of the provider |
|
4670 @type str |
|
4671 @param func function providing completion list. func |
|
4672 should be a function taking a reference to the editor and |
|
4673 a boolean indicating to complete a context. It should return |
|
4674 the possible completions as a list of strings. |
|
4675 @type function(editor, bool) -> list of str in case async is False |
|
4676 and function(editor, bool, str) returning nothing in case async |
|
4677 is True |
|
4678 @param asynchroneous flag indicating an asynchroneous function |
|
4679 @type bool |
|
4680 """ |
|
4681 if key in self.__completionListHookFunctions or \ |
|
4682 key in self.__completionListAsyncHookFunctions: |
|
4683 # it was already registered |
|
4684 E5MessageBox.warning( |
|
4685 self, |
|
4686 self.tr("Auto-Completion Provider"), |
|
4687 self.tr("""The completion list provider '{0}' was already""" |
|
4688 """ registered. Ignoring duplicate request.""") |
|
4689 .format(key)) |
|
4690 return |
|
4691 |
|
4692 if asynchroneous: |
|
4693 self.__completionListAsyncHookFunctions[key] = func |
|
4694 else: |
|
4695 self.__completionListHookFunctions[key] = func |
|
4696 |
|
4697 def removeCompletionListHook(self, key): |
|
4698 """ |
|
4699 Public method to remove a previously registered completion list |
|
4700 provider. |
|
4701 |
|
4702 @param key name of the provider |
|
4703 @type str |
|
4704 """ |
|
4705 if key in self.__completionListHookFunctions: |
|
4706 del self.__completionListHookFunctions[key] |
|
4707 elif key in self.__completionListAsyncHookFunctions: |
|
4708 del self.__completionListAsyncHookFunctions[key] |
|
4709 |
|
4710 def getCompletionListHook(self, key): |
|
4711 """ |
|
4712 Public method to get the registered completion list provider. |
|
4713 |
|
4714 @param key name of the provider |
|
4715 @type str |
|
4716 @return function providing completion list |
|
4717 @rtype function or None |
|
4718 """ |
|
4719 return (self.__completionListHookFunctions.get(key) or |
|
4720 self.__completionListAsyncHookFunctions.get(key)) |
|
4721 |
|
4722 def autoComplete(self, auto=False, context=True): |
|
4723 """ |
|
4724 Public method to start auto-completion. |
|
4725 |
|
4726 @keyparam auto flag indicating a call from the __charAdded method |
|
4727 (boolean) |
|
4728 @keyparam context flag indicating to complete a context (boolean) |
|
4729 """ |
|
4730 if auto and not Preferences.getEditor("AutoCompletionEnabled"): |
|
4731 # auto-completion is disabled |
|
4732 return |
|
4733 |
|
4734 if self.isListActive(): |
|
4735 self.cancelList() |
|
4736 |
|
4737 if self.__completionListHookFunctions or \ |
|
4738 self.__completionListAsyncHookFunctions: |
|
4739 # Avoid delayed auto-completion after cursor repositioning |
|
4740 self.__acText = self.__getAcText() |
|
4741 if auto and Preferences.getEditor("AutoCompletionTimeout"): |
|
4742 self.__acTimer.stop() |
|
4743 self.__acContext = context |
|
4744 self.__acTimer.start() |
|
4745 else: |
|
4746 self.__autoComplete(auto, context) |
|
4747 elif not auto: |
|
4748 self.autoCompleteQScintilla() |
|
4749 elif self.autoCompletionSource() != QsciScintilla.AcsNone: |
|
4750 self.autoCompleteQScintilla() |
|
4751 |
|
4752 def __getAcText(self): |
|
4753 """ |
|
4754 Private method to get the text from cursor position for autocompleting. |
|
4755 |
|
4756 @return text left of cursor position |
|
4757 @rtype str |
|
4758 """ |
|
4759 line, col = self.getCursorPosition() |
|
4760 text = self.text(line) |
|
4761 try: |
|
4762 if self.__isStartChar(text[col - 1]): |
|
4763 acText = self.getWordLeft(line, col - 1) + text[col - 1] |
|
4764 else: |
|
4765 acText = self.getWordLeft(line, col) |
|
4766 except IndexError: |
|
4767 acText = "" |
|
4768 |
|
4769 return acText |
|
4770 |
|
4771 def __autoComplete(self, auto=True, context=None): |
|
4772 """ |
|
4773 Private method to start auto-completion via plug-ins. |
|
4774 |
|
4775 @keyparam auto flag indicating a call from the __charAdded method |
|
4776 (boolean) |
|
4777 @keyparam context flag indicating to complete a context |
|
4778 @type bool or None |
|
4779 """ |
|
4780 self.__acCompletions.clear() |
|
4781 self.__acCompletionsFinished = 0 |
|
4782 |
|
4783 # Suppress empty completions |
|
4784 if auto and self.__acText == '': |
|
4785 return |
|
4786 |
|
4787 if self.__acCacheEnabled: |
|
4788 completions = self.__acCache.get(self.__acText) |
|
4789 else: |
|
4790 completions = None |
|
4791 if completions is not None: |
|
4792 # show list with cached entries |
|
4793 if self.isListActive(): |
|
4794 self.cancelList() |
|
4795 |
|
4796 self.__showCompletionsList(completions) |
|
4797 else: |
|
4798 if context is None: |
|
4799 context = self.__acContext |
|
4800 |
|
4801 for key in self.__completionListAsyncHookFunctions: |
|
4802 self.__completionListAsyncHookFunctions[key]( |
|
4803 self, context, self.__acText) |
|
4804 |
|
4805 for key in self.__completionListHookFunctions: |
|
4806 completions = self.__completionListHookFunctions[key]( |
|
4807 self, context) |
|
4808 self.completionsListReady(completions, self.__acText) |
|
4809 |
|
4810 if Preferences.getEditor("AutoCompletionScintillaOnFail"): |
|
4811 self.__acWatchdog.start() |
|
4812 |
|
4813 def completionsListReady(self, completions, acText): |
|
4814 """ |
|
4815 Public method to show the completions determined by a completions |
|
4816 provider. |
|
4817 |
|
4818 @param completions list of possible completions |
|
4819 @type list of str or set of str |
|
4820 @param acText text to be completed |
|
4821 @type str |
|
4822 """ |
|
4823 currentWord = self.__getAcText() or ' ' |
|
4824 # process the list only, if not already obsolete ... |
|
4825 if acText != self.__acText or not self.__acText.endswith(currentWord): |
|
4826 # Suppress auto-completion done by QScintilla as fallback |
|
4827 self.__acWatchdog.stop() |
|
4828 return |
|
4829 |
|
4830 self.__acCompletions.update(set(completions)) |
|
4831 |
|
4832 self.__acCompletionsFinished += 1 |
|
4833 # Got all results from auto completer? |
|
4834 if self.__acCompletionsFinished >= ( |
|
4835 len(self.__completionListAsyncHookFunctions) + |
|
4836 len(self.__completionListHookFunctions) |
|
4837 ): |
|
4838 self.__acWatchdog.stop() |
|
4839 |
|
4840 # Autocomplete with QScintilla if no results present |
|
4841 if Preferences.getEditor("AutoCompletionScintillaOnFail") and \ |
|
4842 not self.__acCompletions: |
|
4843 self.autoCompleteQScintilla() |
|
4844 return |
|
4845 |
|
4846 # ... or completions are not empty |
|
4847 if not bool(completions): |
|
4848 return |
|
4849 |
|
4850 if self.isListActive(): |
|
4851 self.cancelList() |
|
4852 |
|
4853 if self.__acCompletions: |
|
4854 if self.__acCacheEnabled: |
|
4855 self.__acCache.add(acText, set(self.__acCompletions)) |
|
4856 self.__showCompletionsList(self.__acCompletions) |
|
4857 |
|
4858 def __showCompletionsList(self, completions): |
|
4859 """ |
|
4860 Private method to show the completions list. |
|
4861 |
|
4862 @param completions completions to be shown |
|
4863 @type list of str or set of str |
|
4864 """ |
|
4865 if Preferences.getEditor("AutoCompletionReversedList"): |
|
4866 acCompletions = sorted( |
|
4867 list(completions), |
|
4868 key=self.__replaceLeadingUnderscores) |
|
4869 else: |
|
4870 acCompletions = sorted(list(completions)) |
|
4871 self.showUserList(EditorAutoCompletionListID, acCompletions) |
|
4872 |
|
4873 def __replaceLeadingUnderscores(self, txt): |
|
4874 """ |
|
4875 Private method to replace the first two underlines for invers sorting. |
|
4876 |
|
4877 @param txt completion text |
|
4878 @type str |
|
4879 @return modified completion text |
|
4880 @rtype str |
|
4881 """ |
|
4882 if txt.startswith('_'): |
|
4883 return txt[:2].replace('_', '~') + txt[2:] |
|
4884 else: |
|
4885 return txt |
|
4886 |
|
4887 def __clearCompletionsCache(self): |
|
4888 """ |
|
4889 Private method to clear the auto-completions cache. |
|
4890 """ |
|
4891 self.__acCache.clear() |
|
4892 |
|
4893 def __completionListSelected(self, listId, txt): |
|
4894 """ |
|
4895 Private slot to handle the selection from the completion list. |
|
4896 |
|
4897 @param listId the ID of the user list (should be 1 or 2) (integer) |
|
4898 @param txt the selected text (string) |
|
4899 """ |
|
4900 if listId == EditorAutoCompletionListID: |
|
4901 lst = txt.split() |
|
4902 if len(lst) > 1: |
|
4903 txt = lst[0] |
|
4904 |
|
4905 self.beginUndoAction() |
|
4906 if Preferences.getEditor("AutoCompletionReplaceWord"): |
|
4907 self.selectCurrentWord() |
|
4908 self.removeSelectedText() |
|
4909 line, col = self.getCursorPosition() |
|
4910 else: |
|
4911 line, col = self.getCursorPosition() |
|
4912 wLeft = self.getWordLeft(line, col) |
|
4913 if not txt.startswith(wLeft): |
|
4914 self.selectCurrentWord() |
|
4915 self.removeSelectedText() |
|
4916 line, col = self.getCursorPosition() |
|
4917 elif wLeft: |
|
4918 txt = txt[len(wLeft):] |
|
4919 self.insert(txt) |
|
4920 self.endUndoAction() |
|
4921 self.setCursorPosition(line, col + len(txt)) |
|
4922 elif listId == TemplateCompletionListID: |
|
4923 self.__applyTemplate(txt, self.getLanguage()) |
|
4924 |
|
4925 def canProvideDynamicAutoCompletion(self): |
|
4926 """ |
|
4927 Public method to test the dynamic auto-completion availability. |
|
4928 |
|
4929 @return flag indicating the availability of dynamic auto-completion |
|
4930 (boolean) |
|
4931 """ |
|
4932 return (self.acAPI or |
|
4933 bool(self.__completionListHookFunctions) or |
|
4934 bool(self.__completionListAsyncHookFunctions)) |
|
4935 |
|
4936 ################################################################# |
|
4937 ## call-tip hook interfaces |
|
4938 ################################################################# |
|
4939 |
|
4940 def addCallTipHook(self, key, func): |
|
4941 """ |
|
4942 Public method to set a calltip provider. |
|
4943 |
|
4944 @param key name of the provider |
|
4945 @type str |
|
4946 @param func function providing calltips. func |
|
4947 should be a function taking a reference to the editor, |
|
4948 a position into the text and the amount of commas to the |
|
4949 left of the cursor. It should return the possible |
|
4950 calltips as a list of strings. |
|
4951 @type function(editor, int, int) -> list of str |
|
4952 """ |
|
4953 if key in self.__ctHookFunctions: |
|
4954 # it was already registered |
|
4955 E5MessageBox.warning( |
|
4956 self, |
|
4957 self.tr("Call-Tips Provider"), |
|
4958 self.tr("""The call-tips provider '{0}' was already""" |
|
4959 """ registered. Ignoring duplicate request.""") |
|
4960 .format(key)) |
|
4961 return |
|
4962 |
|
4963 self.__ctHookFunctions[key] = func |
|
4964 |
|
4965 def removeCallTipHook(self, key): |
|
4966 """ |
|
4967 Public method to remove a previously registered calltip provider. |
|
4968 |
|
4969 @param key name of the provider |
|
4970 @type str |
|
4971 """ |
|
4972 if key in self.__ctHookFunctions: |
|
4973 del self.__ctHookFunctions[key] |
|
4974 |
|
4975 def getCallTipHook(self, key): |
|
4976 """ |
|
4977 Public method to get the registered calltip provider. |
|
4978 |
|
4979 @param key name of the provider |
|
4980 @type str |
|
4981 @return function providing calltips |
|
4982 @rtype function or None |
|
4983 """ |
|
4984 if key in self.__ctHookFunctions: |
|
4985 return self.__ctHookFunctions[key] |
|
4986 else: |
|
4987 return None |
|
4988 |
|
4989 def canProvideCallTipps(self): |
|
4990 """ |
|
4991 Public method to test the calltips availability. |
|
4992 |
|
4993 @return flag indicating the availability of calltips (boolean) |
|
4994 """ |
|
4995 return (self.acAPI or |
|
4996 bool(self.__ctHookFunctions)) |
|
4997 |
|
4998 def callTip(self): |
|
4999 """ |
|
5000 Public method to show calltips. |
|
5001 """ |
|
5002 if bool(self.__ctHookFunctions): |
|
5003 self.__callTip() |
|
5004 else: |
|
5005 super(Editor, self).callTip() |
|
5006 |
|
5007 def __callTip(self): |
|
5008 """ |
|
5009 Private method to show call tips provided by a plugin. |
|
5010 """ |
|
5011 pos = self.currentPosition() |
|
5012 |
|
5013 # move backward to the start of the current calltip working out |
|
5014 # which argument to highlight |
|
5015 commas = 0 |
|
5016 found = False |
|
5017 ch, pos = self.__getCharacter(pos) |
|
5018 while ch: |
|
5019 if ch == ',': |
|
5020 commas += 1 |
|
5021 elif ch == ')': |
|
5022 depth = 1 |
|
5023 |
|
5024 # ignore everything back to the start of the corresponding |
|
5025 # parenthesis |
|
5026 ch, pos = self.__getCharacter(pos) |
|
5027 while ch: |
|
5028 if ch == ')': |
|
5029 depth += 1 |
|
5030 elif ch == '(': |
|
5031 depth -= 1 |
|
5032 if depth == 0: |
|
5033 break |
|
5034 ch, pos = self.__getCharacter(pos) |
|
5035 elif ch == '(': |
|
5036 found = True |
|
5037 break |
|
5038 |
|
5039 ch, pos = self.__getCharacter(pos) |
|
5040 |
|
5041 self.SendScintilla(QsciScintilla.SCI_CALLTIPCANCEL) |
|
5042 |
|
5043 if not found: |
|
5044 return |
|
5045 |
|
5046 if self.__ctHookFunctions: |
|
5047 callTips = [] |
|
5048 for key in self.__ctHookFunctions: |
|
5049 callTips.extend(self.__ctHookFunctions[key](self, pos, commas)) |
|
5050 callTips = list(set(callTips)) |
|
5051 callTips.sort() |
|
5052 if len(callTips) == 0: |
|
5053 if Preferences.getEditor("CallTipsScintillaOnFail"): |
|
5054 # try QScintilla calltips |
|
5055 super(Editor, self).callTip() |
|
5056 return |
|
5057 |
|
5058 ctshift = 0 |
|
5059 for ct in callTips: |
|
5060 shift = ct.index("(") |
|
5061 if ctshift < shift: |
|
5062 ctshift = shift |
|
5063 |
|
5064 cv = self.callTipsVisible() |
|
5065 if cv > 0: |
|
5066 # this is just a safe guard |
|
5067 ct = self._encodeString("\n".join(callTips[:cv])) |
|
5068 else: |
|
5069 # until here and unindent below |
|
5070 ct = self._encodeString("\n".join(callTips)) |
|
5071 |
|
5072 self.SendScintilla(QsciScintilla.SCI_CALLTIPSHOW, |
|
5073 self.__adjustedCallTipPosition(ctshift, pos), ct) |
|
5074 if b'\n' in ct: |
|
5075 return |
|
5076 |
|
5077 # Highlight the current argument |
|
5078 if commas == 0: |
|
5079 astart = ct.find(b'(') |
|
5080 else: |
|
5081 astart = ct.find(b',') |
|
5082 commas -= 1 |
|
5083 while astart != -1 and commas > 0: |
|
5084 astart = ct.find(b',', astart + 1) |
|
5085 commas -= 1 |
|
5086 |
|
5087 if astart == -1: |
|
5088 return |
|
5089 |
|
5090 depth = 0 |
|
5091 for aend in range(astart + 1, len(ct)): |
|
5092 ch = ct[aend:aend + 1] |
|
5093 |
|
5094 if ch == b',' and depth == 0: |
|
5095 break |
|
5096 elif ch == b'(': |
|
5097 depth += 1 |
|
5098 elif ch == b')': |
|
5099 if depth == 0: |
|
5100 break |
|
5101 |
|
5102 depth -= 1 |
|
5103 |
|
5104 if astart != aend: |
|
5105 self.SendScintilla(QsciScintilla.SCI_CALLTIPSETHLT, |
|
5106 astart + 1, aend) |
|
5107 |
|
5108 def __adjustedCallTipPosition(self, ctshift, pos): |
|
5109 """ |
|
5110 Private method to calculate an adjusted position for showing calltips. |
|
5111 |
|
5112 @param ctshift amount the calltip shall be shifted (integer) |
|
5113 @param pos position into the text (integer) |
|
5114 @return new position for the calltip (integer) |
|
5115 """ |
|
5116 ct = pos |
|
5117 if ctshift: |
|
5118 ctmin = self.SendScintilla( |
|
5119 QsciScintilla.SCI_POSITIONFROMLINE, |
|
5120 self.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION, ct)) |
|
5121 if ct - ctshift < ctmin: |
|
5122 ct = ctmin |
|
5123 else: |
|
5124 ct = ct - ctshift |
|
5125 return ct |
|
5126 |
|
5127 ################################################################# |
|
5128 ## Methods needed by the code documentation viewer |
|
5129 ################################################################# |
|
5130 |
|
5131 def __showCodeInfo(self): |
|
5132 """ |
|
5133 Private slot to handle the context menu action to show code info. |
|
5134 """ |
|
5135 self.vm.showEditorInfo(self) |
|
5136 |
|
5137 ################################################################# |
|
5138 ## Methods needed by the context menu |
|
5139 ################################################################# |
|
5140 |
|
5141 def __marginNumber(self, xPos): |
|
5142 """ |
|
5143 Private method to calculate the margin number based on a x position. |
|
5144 |
|
5145 @param xPos x position (integer) |
|
5146 @return margin number (integer, -1 for no margin) |
|
5147 """ |
|
5148 width = 0 |
|
5149 for margin in range(5): |
|
5150 width += self.marginWidth(margin) |
|
5151 if xPos <= width: |
|
5152 return margin |
|
5153 return -1 |
|
5154 |
|
5155 def contextMenuEvent(self, evt): |
|
5156 """ |
|
5157 Protected method implementing the context menu event. |
|
5158 |
|
5159 @param evt the context menu event (QContextMenuEvent) |
|
5160 """ |
|
5161 evt.accept() |
|
5162 if self.__marginNumber(evt.x()) == -1: |
|
5163 self.spellingMenuPos = self.positionFromPoint(evt.pos()) |
|
5164 if self.spellingMenuPos >= 0 and \ |
|
5165 self.spell is not None and \ |
|
5166 self.hasIndicator(self.spellingIndicator, self.spellingMenuPos): |
|
5167 self.spellingMenu.popup(evt.globalPos()) |
|
5168 else: |
|
5169 self.menu.popup(evt.globalPos()) |
|
5170 else: |
|
5171 self.line = self.lineAt(evt.pos()) |
|
5172 if self.__marginNumber(evt.x()) in [self.__bmMargin, |
|
5173 self.__linenoMargin]: |
|
5174 self.bmMarginMenu.popup(evt.globalPos()) |
|
5175 elif self.__marginNumber(evt.x()) == self.__bpMargin: |
|
5176 self.bpMarginMenu.popup(evt.globalPos()) |
|
5177 elif self.__marginNumber(evt.x()) == self.__indicMargin: |
|
5178 self.indicMarginMenu.popup(evt.globalPos()) |
|
5179 elif self.__marginNumber(evt.x()) == self.__foldMargin: |
|
5180 self.foldMarginMenu.popup(evt.globalPos()) |
|
5181 |
|
5182 def __showContextMenu(self): |
|
5183 """ |
|
5184 Private slot handling the aboutToShow signal of the context menu. |
|
5185 """ |
|
5186 self.menuActs["Reopen"].setEnabled( |
|
5187 not self.isModified() and bool(self.fileName)) |
|
5188 self.menuActs["Save"].setEnabled(self.isModified()) |
|
5189 self.menuActs["Undo"].setEnabled(self.isUndoAvailable()) |
|
5190 self.menuActs["Redo"].setEnabled(self.isRedoAvailable()) |
|
5191 self.menuActs["Revert"].setEnabled(self.isModified()) |
|
5192 self.menuActs["Cut"].setEnabled(self.hasSelectedText()) |
|
5193 self.menuActs["Copy"].setEnabled(self.hasSelectedText()) |
|
5194 if self.menuActs["ExecuteSelection"] is not None: |
|
5195 self.menuActs["ExecuteSelection"].setEnabled( |
|
5196 self.hasSelectedText()) |
|
5197 self.menuActs["Paste"].setEnabled(self.canPaste()) |
|
5198 if not self.isResourcesFile: |
|
5199 if self.fileName and self.isPyFile(): |
|
5200 self.menuActs["Show"].setEnabled(True) |
|
5201 else: |
|
5202 self.menuActs["Show"].setEnabled(False) |
|
5203 if self.fileName and \ |
|
5204 (self.isPyFile() or self.isRubyFile()): |
|
5205 self.menuActs["Diagrams"].setEnabled(True) |
|
5206 else: |
|
5207 self.menuActs["Diagrams"].setEnabled(False) |
|
5208 if not self.miniMenu: |
|
5209 if self.lexer_ is not None: |
|
5210 self.menuActs["Comment"].setEnabled( |
|
5211 self.lexer_.canBlockComment()) |
|
5212 self.menuActs["Uncomment"].setEnabled( |
|
5213 self.lexer_.canBlockComment()) |
|
5214 self.menuActs["StreamComment"].setEnabled( |
|
5215 self.lexer_.canStreamComment()) |
|
5216 self.menuActs["BoxComment"].setEnabled( |
|
5217 self.lexer_.canBoxComment()) |
|
5218 else: |
|
5219 self.menuActs["Comment"].setEnabled(False) |
|
5220 self.menuActs["Uncomment"].setEnabled(False) |
|
5221 self.menuActs["StreamComment"].setEnabled(False) |
|
5222 self.menuActs["BoxComment"].setEnabled(False) |
|
5223 |
|
5224 self.menuActs["TypingAidsEnabled"].setEnabled( |
|
5225 self.completer is not None) |
|
5226 self.menuActs["TypingAidsEnabled"].setChecked( |
|
5227 self.completer is not None and self.completer.isEnabled()) |
|
5228 |
|
5229 if not self.isResourcesFile: |
|
5230 self.menuActs["calltip"].setEnabled(self.canProvideCallTipps()) |
|
5231 self.menuActs["codeInfo"].setEnabled( |
|
5232 self.vm.isEditorInfoSupported(self.getLanguage())) |
|
5233 |
|
5234 from .SpellChecker import SpellChecker |
|
5235 spellingAvailable = SpellChecker.isAvailable() |
|
5236 self.menuActs["SpellCheck"].setEnabled(spellingAvailable) |
|
5237 self.menuActs["SpellCheckSelection"].setEnabled( |
|
5238 spellingAvailable and self.hasSelectedText()) |
|
5239 self.menuActs["SpellCheckRemove"].setEnabled( |
|
5240 spellingAvailable and self.spellingMenuPos >= 0) |
|
5241 |
|
5242 if self.menuActs["OpenRejections"]: |
|
5243 if self.fileName: |
|
5244 rej = "{0}.rej".format(self.fileName) |
|
5245 self.menuActs["OpenRejections"].setEnabled(os.path.exists(rej)) |
|
5246 else: |
|
5247 self.menuActs["OpenRejections"].setEnabled(False) |
|
5248 |
|
5249 self.menuActs["MonospacedFont"].setEnabled(self.lexer_ is None) |
|
5250 |
|
5251 splitOrientation = self.vm.getSplitOrientation() |
|
5252 if splitOrientation == Qt.Horizontal: |
|
5253 self.menuActs["NewSplit"].setIcon( |
|
5254 UI.PixmapCache.getIcon("splitHorizontal.png")) |
|
5255 else: |
|
5256 self.menuActs["NewSplit"].setIcon( |
|
5257 UI.PixmapCache.getIcon("splitVertical.png")) |
|
5258 |
|
5259 self.menuActs["Tools"].setEnabled(not self.toolsMenu.isEmpty()) |
|
5260 |
|
5261 self.showMenu.emit("Main", self.menu, self) |
|
5262 |
|
5263 def __showContextMenuAutocompletion(self): |
|
5264 """ |
|
5265 Private slot called before the autocompletion menu is shown. |
|
5266 """ |
|
5267 self.menuActs["acDynamic"].setEnabled( |
|
5268 self.canProvideDynamicAutoCompletion()) |
|
5269 self.menuActs["acClearCache"].setEnabled( |
|
5270 self.canProvideDynamicAutoCompletion()) |
|
5271 self.menuActs["acAPI"].setEnabled(self.acAPI) |
|
5272 self.menuActs["acAPIDocument"].setEnabled(self.acAPI) |
|
5273 |
|
5274 self.showMenu.emit("Autocompletion", self.autocompletionMenu, self) |
|
5275 |
|
5276 def __showContextMenuShow(self): |
|
5277 """ |
|
5278 Private slot called before the show menu is shown. |
|
5279 """ |
|
5280 prEnable = False |
|
5281 coEnable = False |
|
5282 |
|
5283 # first check if the file belongs to a project |
|
5284 if self.project.isOpen() and \ |
|
5285 self.project.isProjectSource(self.fileName): |
|
5286 fn = self.project.getMainScript(True) |
|
5287 if fn is not None: |
|
5288 tfn = Utilities.getTestFileName(fn) |
|
5289 basename = os.path.splitext(fn)[0] |
|
5290 tbasename = os.path.splitext(tfn)[0] |
|
5291 prEnable = prEnable or \ |
|
5292 os.path.isfile("{0}.profile".format(basename)) or \ |
|
5293 os.path.isfile("{0}.profile".format(tbasename)) |
|
5294 coEnable = ( |
|
5295 coEnable or |
|
5296 os.path.isfile("{0}.coverage".format(basename)) or |
|
5297 os.path.isfile("{0}.coverage".format(tbasename))) and \ |
|
5298 (self.project.isPy3Project() or |
|
5299 self.project.isPy2Project()) |
|
5300 |
|
5301 # now check ourselves |
|
5302 fn = self.getFileName() |
|
5303 if fn is not None: |
|
5304 tfn = Utilities.getTestFileName(fn) |
|
5305 basename = os.path.splitext(fn)[0] |
|
5306 tbasename = os.path.splitext(tfn)[0] |
|
5307 prEnable = prEnable or \ |
|
5308 os.path.isfile("{0}.profile".format(basename)) or \ |
|
5309 os.path.isfile("{0}.profile".format(tbasename)) |
|
5310 coEnable = ( |
|
5311 coEnable or |
|
5312 os.path.isfile("{0}.coverage".format(basename)) or |
|
5313 os.path.isfile("{0}.coverage".format(tbasename))) and \ |
|
5314 self.isPyFile() |
|
5315 |
|
5316 # now check for syntax errors |
|
5317 if self.hasSyntaxErrors(): |
|
5318 coEnable = False |
|
5319 |
|
5320 self.profileMenuAct.setEnabled(prEnable) |
|
5321 self.coverageMenuAct.setEnabled(coEnable) |
|
5322 self.coverageShowAnnotationMenuAct.setEnabled( |
|
5323 coEnable and len(self.notcoveredMarkers) == 0) |
|
5324 self.coverageHideAnnotationMenuAct.setEnabled( |
|
5325 len(self.notcoveredMarkers) > 0) |
|
5326 |
|
5327 self.showMenu.emit("Show", self.menuShow, self) |
|
5328 |
|
5329 def __showContextMenuGraphics(self): |
|
5330 """ |
|
5331 Private slot handling the aboutToShow signal of the diagrams context |
|
5332 menu. |
|
5333 """ |
|
5334 if self.project.isOpen() and \ |
|
5335 self.project.isProjectSource(self.fileName): |
|
5336 self.applicationDiagramMenuAct.setEnabled(True) |
|
5337 else: |
|
5338 self.applicationDiagramMenuAct.setEnabled(False) |
|
5339 |
|
5340 self.showMenu.emit("Graphics", self.graphicsMenu, self) |
|
5341 |
|
5342 def __showContextMenuMargin(self, menu): |
|
5343 """ |
|
5344 Private slot handling the aboutToShow signal of the margins context |
|
5345 menu. |
|
5346 |
|
5347 @param menu reference to the menu to be shown |
|
5348 @type QMenu |
|
5349 """ |
|
5350 if menu is self.bpMarginMenu: |
|
5351 supportsDebugger = bool(self.fileName and self.isPyFile()) |
|
5352 hasBreakpoints = bool(self.breaks) |
|
5353 hasBreakpoint = bool( |
|
5354 self.markersAtLine(self.line) & self.breakpointMask) |
|
5355 |
|
5356 self.marginMenuActs["Breakpoint"].setEnabled(supportsDebugger) |
|
5357 self.marginMenuActs["TempBreakpoint"].setEnabled(supportsDebugger) |
|
5358 self.marginMenuActs["NextBreakpoint"].setEnabled( |
|
5359 supportsDebugger and hasBreakpoints) |
|
5360 self.marginMenuActs["PreviousBreakpoint"].setEnabled( |
|
5361 supportsDebugger and hasBreakpoints) |
|
5362 self.marginMenuActs["ClearBreakpoint"].setEnabled( |
|
5363 supportsDebugger and hasBreakpoints) |
|
5364 self.marginMenuActs["EditBreakpoint"].setEnabled( |
|
5365 supportsDebugger and hasBreakpoint) |
|
5366 self.marginMenuActs["EnableBreakpoint"].setEnabled( |
|
5367 supportsDebugger and hasBreakpoint) |
|
5368 if supportsDebugger: |
|
5369 if self.markersAtLine(self.line) & (1 << self.dbreakpoint): |
|
5370 self.marginMenuActs["EnableBreakpoint"].setText( |
|
5371 self.tr('Enable breakpoint')) |
|
5372 else: |
|
5373 self.marginMenuActs["EnableBreakpoint"].setText( |
|
5374 self.tr('Disable breakpoint')) |
|
5375 |
|
5376 if menu is self.bmMarginMenu: |
|
5377 hasBookmarks = bool(self.bookmarks) |
|
5378 |
|
5379 self.marginMenuActs["NextBookmark"].setEnabled(hasBookmarks) |
|
5380 self.marginMenuActs["PreviousBookmark"].setEnabled(hasBookmarks) |
|
5381 self.marginMenuActs["ClearBookmark"].setEnabled(hasBookmarks) |
|
5382 |
|
5383 if menu is self.foldMarginMenu: |
|
5384 isFoldHeader = bool(self.SendScintilla( |
|
5385 QsciScintilla.SCI_GETFOLDLEVEL, self.line) & |
|
5386 QsciScintilla.SC_FOLDLEVELHEADERFLAG) |
|
5387 |
|
5388 self.marginMenuActs["ExpandChildren"].setEnabled(isFoldHeader) |
|
5389 self.marginMenuActs["CollapseChildren"].setEnabled(isFoldHeader) |
|
5390 |
|
5391 if menu is self.indicMarginMenu: |
|
5392 hasSyntaxErrors = bool(self.syntaxerrors) |
|
5393 hasWarnings = bool(self.warnings) |
|
5394 hasNotCoveredMarkers = bool(self.notcoveredMarkers) |
|
5395 |
|
5396 self.marginMenuActs["GotoSyntaxError"].setEnabled(hasSyntaxErrors) |
|
5397 self.marginMenuActs["ClearSyntaxError"].setEnabled(hasSyntaxErrors) |
|
5398 if hasSyntaxErrors and \ |
|
5399 self.markersAtLine(self.line) & (1 << self.syntaxerror): |
|
5400 self.marginMenuActs["ShowSyntaxError"].setEnabled(True) |
|
5401 else: |
|
5402 self.marginMenuActs["ShowSyntaxError"].setEnabled(False) |
|
5403 |
|
5404 self.marginMenuActs["NextWarningMarker"].setEnabled(hasWarnings) |
|
5405 self.marginMenuActs["PreviousWarningMarker"].setEnabled( |
|
5406 hasWarnings) |
|
5407 self.marginMenuActs["ClearWarnings"].setEnabled(hasWarnings) |
|
5408 if hasWarnings and \ |
|
5409 self.markersAtLine(self.line) & (1 << self.warning): |
|
5410 self.marginMenuActs["ShowWarning"].setEnabled(True) |
|
5411 else: |
|
5412 self.marginMenuActs["ShowWarning"].setEnabled(False) |
|
5413 |
|
5414 self.marginMenuActs["NextCoverageMarker"].setEnabled( |
|
5415 hasNotCoveredMarkers) |
|
5416 self.marginMenuActs["PreviousCoverageMarker"].setEnabled( |
|
5417 hasNotCoveredMarkers) |
|
5418 |
|
5419 self.marginMenuActs["PreviousTaskMarker"].setEnabled( |
|
5420 self.__hasTaskMarkers) |
|
5421 self.marginMenuActs["NextTaskMarker"].setEnabled( |
|
5422 self.__hasTaskMarkers) |
|
5423 |
|
5424 self.marginMenuActs["PreviousChangeMarker"].setEnabled( |
|
5425 self.__hasChangeMarkers) |
|
5426 self.marginMenuActs["NextChangeMarker"].setEnabled( |
|
5427 self.__hasChangeMarkers) |
|
5428 self.marginMenuActs["ClearChangeMarkers"].setEnabled( |
|
5429 self.__hasChangeMarkers) |
|
5430 |
|
5431 self.showMenu.emit("Margin", menu, self) |
|
5432 |
|
5433 def __showContextMenuChecks(self): |
|
5434 """ |
|
5435 Private slot handling the aboutToShow signal of the checks context |
|
5436 menu. |
|
5437 """ |
|
5438 self.showMenu.emit("Checks", self.checksMenu, self) |
|
5439 |
|
5440 def __showContextMenuTools(self): |
|
5441 """ |
|
5442 Private slot handling the aboutToShow signal of the tools context |
|
5443 menu. |
|
5444 """ |
|
5445 self.showMenu.emit("Tools", self.toolsMenu, self) |
|
5446 |
|
5447 def __reopenWithEncodingMenuTriggered(self, act): |
|
5448 """ |
|
5449 Private method to handle the rereading of the file with a selected |
|
5450 encoding. |
|
5451 |
|
5452 @param act reference to the action that was triggered (QAction) |
|
5453 """ |
|
5454 encoding = act.data() |
|
5455 self.readFile(self.fileName, encoding=encoding) |
|
5456 self.__checkEncoding() |
|
5457 |
|
5458 def __contextSave(self): |
|
5459 """ |
|
5460 Private slot handling the save context menu entry. |
|
5461 """ |
|
5462 ok = self.saveFile() |
|
5463 if ok: |
|
5464 self.vm.setEditorName(self, self.fileName) |
|
5465 |
|
5466 def __contextSaveAs(self): |
|
5467 """ |
|
5468 Private slot handling the save as context menu entry. |
|
5469 """ |
|
5470 ok = self.saveFileAs() |
|
5471 if ok: |
|
5472 self.vm.setEditorName(self, self.fileName) |
|
5473 |
|
5474 def __contextSaveCopy(self): |
|
5475 """ |
|
5476 Private slot handling the save copy context menu entry. |
|
5477 """ |
|
5478 self.saveFileCopy() |
|
5479 |
|
5480 def __contextClose(self): |
|
5481 """ |
|
5482 Private slot handling the close context menu entry. |
|
5483 """ |
|
5484 self.vm.closeEditor(self) |
|
5485 |
|
5486 def __contextOpenRejections(self): |
|
5487 """ |
|
5488 Private slot handling the open rejections file context menu entry. |
|
5489 """ |
|
5490 if self.fileName: |
|
5491 rej = "{0}.rej".format(self.fileName) |
|
5492 if os.path.exists(rej): |
|
5493 self.vm.openSourceFile(rej) |
|
5494 |
|
5495 def __newView(self): |
|
5496 """ |
|
5497 Private slot to create a new view to an open document. |
|
5498 """ |
|
5499 self.vm.newEditorView(self.fileName, self, self.filetype) |
|
5500 |
|
5501 def __newViewNewSplit(self): |
|
5502 """ |
|
5503 Private slot to create a new view to an open document. |
|
5504 """ |
|
5505 self.vm.addSplit() |
|
5506 self.vm.newEditorView(self.fileName, self, self.filetype) |
|
5507 |
|
5508 def __selectAll(self): |
|
5509 """ |
|
5510 Private slot handling the select all context menu action. |
|
5511 """ |
|
5512 self.selectAll(True) |
|
5513 |
|
5514 def __deselectAll(self): |
|
5515 """ |
|
5516 Private slot handling the deselect all context menu action. |
|
5517 """ |
|
5518 self.selectAll(False) |
|
5519 |
|
5520 def joinLines(self): |
|
5521 """ |
|
5522 Public slot to join the current line with the next one. |
|
5523 """ |
|
5524 curLine = self.getCursorPosition()[0] |
|
5525 if curLine == self.lines() - 1: |
|
5526 return |
|
5527 |
|
5528 line0Text = self.text(curLine) |
|
5529 line1Text = self.text(curLine + 1) |
|
5530 if line1Text in ["", "\r", "\n", "\r\n"]: |
|
5531 return |
|
5532 |
|
5533 if line0Text.rstrip("\r\n\\ \t").endswith(("'", '"')) and \ |
|
5534 line1Text.lstrip().startswith(("'", '"')): |
|
5535 # merging multi line strings |
|
5536 startChars = "\r\n\\ \t'\"" |
|
5537 endChars = " \t'\"" |
|
5538 else: |
|
5539 startChars = "\r\n\\ \t" |
|
5540 endChars = " \t" |
|
5541 |
|
5542 # determine start index |
|
5543 startIndex = len(line0Text) |
|
5544 while startIndex > 0 and line0Text[startIndex - 1] in startChars: |
|
5545 startIndex -= 1 |
|
5546 if startIndex == 0: |
|
5547 return |
|
5548 |
|
5549 # determine end index |
|
5550 endIndex = 0 |
|
5551 while line1Text[endIndex] in endChars: |
|
5552 endIndex += 1 |
|
5553 |
|
5554 self.setSelection(curLine, startIndex, curLine + 1, endIndex) |
|
5555 self.beginUndoAction() |
|
5556 self.removeSelectedText() |
|
5557 self.insertAt(" ", curLine, startIndex) |
|
5558 self.endUndoAction() |
|
5559 |
|
5560 def shortenEmptyLines(self): |
|
5561 """ |
|
5562 Public slot to compress lines consisting solely of whitespace |
|
5563 characters. |
|
5564 """ |
|
5565 searchRE = r"^[ \t]+$" |
|
5566 |
|
5567 ok = self.findFirstTarget(searchRE, True, False, False, 0, 0) |
|
5568 self.beginUndoAction() |
|
5569 while ok: |
|
5570 self.replaceTarget("") |
|
5571 ok = self.findNextTarget() |
|
5572 self.endUndoAction() |
|
5573 |
|
5574 def __autosaveEnable(self): |
|
5575 """ |
|
5576 Private slot handling the autosave enable context menu action. |
|
5577 """ |
|
5578 if self.menuActs["AutosaveEnable"].isChecked(): |
|
5579 self.autosaveManuallyDisabled = False |
|
5580 else: |
|
5581 self.autosaveManuallyDisabled = True |
|
5582 |
|
5583 def shouldAutosave(self): |
|
5584 """ |
|
5585 Public slot to check the autosave flags. |
|
5586 |
|
5587 @return flag indicating this editor should be saved (boolean) |
|
5588 """ |
|
5589 return bool(self.fileName) and \ |
|
5590 not self.autosaveManuallyDisabled and \ |
|
5591 not self.isReadOnly() |
|
5592 |
|
5593 def checkSyntax(self): |
|
5594 """ |
|
5595 Public method to perform an automatic syntax check of the file. |
|
5596 """ |
|
5597 if self.syntaxCheckService is None or \ |
|
5598 self.filetype not in self.syntaxCheckService.getLanguages(): |
|
5599 return |
|
5600 |
|
5601 if Preferences.getEditor("AutoCheckSyntax"): |
|
5602 if Preferences.getEditor("OnlineSyntaxCheck"): |
|
5603 self.__onlineSyntaxCheckTimer.stop() |
|
5604 |
|
5605 self.syntaxCheckService.syntaxCheck( |
|
5606 self.filetype, self.fileName or "(Unnamed)", self.text()) |
|
5607 |
|
5608 def __processSyntaxCheckError(self, fn, msg): |
|
5609 """ |
|
5610 Private slot to report an error message of a syntax check. |
|
5611 |
|
5612 @param fn filename of the file |
|
5613 @type str |
|
5614 @param msg error message |
|
5615 @type str |
|
5616 """ |
|
5617 if fn != self.fileName and ( |
|
5618 bool(self.fileName) or fn != "(Unnamed)"): |
|
5619 return |
|
5620 |
|
5621 self.clearSyntaxError() |
|
5622 self.clearFlakesWarnings() |
|
5623 |
|
5624 self.toggleWarning(0, 0, True, msg) |
|
5625 |
|
5626 self.updateVerticalScrollBar() |
|
5627 |
|
5628 def __processSyntaxCheckResult(self, fn, problems): |
|
5629 """ |
|
5630 Private slot to report the resulting messages of a syntax check. |
|
5631 |
|
5632 @param fn filename of the checked file (str) |
|
5633 @param problems dictionary with the keys 'error' and 'warnings' which |
|
5634 hold a list containing details about the error/ warnings |
|
5635 (file name, line number, column, codestring (only at syntax |
|
5636 errors), the message) (dict) |
|
5637 """ |
|
5638 # Check if it's the requested file, otherwise ignore signal |
|
5639 if fn != self.fileName and ( |
|
5640 bool(self.fileName) or fn != "(Unnamed)"): |
|
5641 return |
|
5642 |
|
5643 self.clearSyntaxError() |
|
5644 self.clearFlakesWarnings() |
|
5645 |
|
5646 error = problems.get('error') |
|
5647 if error: |
|
5648 _fn, lineno, col, code, msg = error |
|
5649 self.toggleSyntaxError(lineno, col, True, msg) |
|
5650 |
|
5651 warnings = problems.get('warnings', []) |
|
5652 for _fn, lineno, col, _code, msg in warnings: |
|
5653 self.toggleWarning(lineno, col, True, msg) |
|
5654 |
|
5655 self.updateVerticalScrollBar() |
|
5656 |
|
5657 def __initOnlineSyntaxCheck(self): |
|
5658 """ |
|
5659 Private slot to initialize the online syntax check. |
|
5660 """ |
|
5661 self.__onlineSyntaxCheckTimer = QTimer(self) |
|
5662 self.__onlineSyntaxCheckTimer.setSingleShot(True) |
|
5663 self.__onlineSyntaxCheckTimer.setInterval( |
|
5664 Preferences.getEditor("OnlineSyntaxCheckInterval") * 1000) |
|
5665 self.__onlineSyntaxCheckTimer.timeout.connect(self.checkSyntax) |
|
5666 self.textChanged.connect(self.__resetOnlineSyntaxCheckTimer) |
|
5667 |
|
5668 def __resetOnlineSyntaxCheckTimer(self): |
|
5669 """ |
|
5670 Private method to reset the online syntax check timer. |
|
5671 """ |
|
5672 if Preferences.getEditor("OnlineSyntaxCheck"): |
|
5673 self.__onlineSyntaxCheckTimer.stop() |
|
5674 self.__onlineSyntaxCheckTimer.start() |
|
5675 |
|
5676 def __showCodeMetrics(self): |
|
5677 """ |
|
5678 Private method to handle the code metrics context menu action. |
|
5679 """ |
|
5680 if not self.checkDirty(): |
|
5681 return |
|
5682 |
|
5683 from DataViews.CodeMetricsDialog import CodeMetricsDialog |
|
5684 self.codemetrics = CodeMetricsDialog() |
|
5685 self.codemetrics.show() |
|
5686 self.codemetrics.start(self.fileName) |
|
5687 |
|
5688 def __getCodeCoverageFile(self): |
|
5689 """ |
|
5690 Private method to get the file name of the file containing coverage |
|
5691 info. |
|
5692 |
|
5693 @return file name of the coverage file (string) |
|
5694 """ |
|
5695 files = [] |
|
5696 |
|
5697 # first check if the file belongs to a project and there is |
|
5698 # a project coverage file |
|
5699 if self.project.isOpen() and \ |
|
5700 self.project.isProjectSource(self.fileName): |
|
5701 fn = self.project.getMainScript(True) |
|
5702 if fn is not None: |
|
5703 tfn = Utilities.getTestFileName(fn) |
|
5704 basename = os.path.splitext(fn)[0] |
|
5705 tbasename = os.path.splitext(tfn)[0] |
|
5706 |
|
5707 f = "{0}.coverage".format(basename) |
|
5708 tf = "{0}.coverage".format(tbasename) |
|
5709 if os.path.isfile(f): |
|
5710 files.append(f) |
|
5711 if os.path.isfile(tf): |
|
5712 files.append(tf) |
|
5713 |
|
5714 # now check, if there are coverage files belonging to ourselves |
|
5715 fn = self.getFileName() |
|
5716 if fn is not None: |
|
5717 tfn = Utilities.getTestFileName(fn) |
|
5718 basename = os.path.splitext(fn)[0] |
|
5719 tbasename = os.path.splitext(tfn)[0] |
|
5720 |
|
5721 f = "{0}.coverage".format(basename) |
|
5722 tf = "{0}.coverage".format(tbasename) |
|
5723 if os.path.isfile(f) and f not in files: |
|
5724 files.append(f) |
|
5725 if os.path.isfile(tf) and tf not in files: |
|
5726 files.append(tf) |
|
5727 |
|
5728 if files: |
|
5729 if len(files) > 1: |
|
5730 fn, ok = QInputDialog.getItem( |
|
5731 self, |
|
5732 self.tr("Code Coverage"), |
|
5733 self.tr("Please select a coverage file"), |
|
5734 files, |
|
5735 0, False) |
|
5736 if not ok: |
|
5737 return "" |
|
5738 else: |
|
5739 fn = files[0] |
|
5740 else: |
|
5741 fn = None |
|
5742 |
|
5743 return fn |
|
5744 |
|
5745 def __showCodeCoverage(self): |
|
5746 """ |
|
5747 Private method to handle the code coverage context menu action. |
|
5748 """ |
|
5749 fn = self.__getCodeCoverageFile() |
|
5750 if fn: |
|
5751 from DataViews.PyCoverageDialog import PyCoverageDialog |
|
5752 self.codecoverage = PyCoverageDialog() |
|
5753 self.codecoverage.show() |
|
5754 self.codecoverage.start(fn, self.fileName) |
|
5755 |
|
5756 def refreshCoverageAnnotations(self): |
|
5757 """ |
|
5758 Public method to refresh the code coverage annotations. |
|
5759 """ |
|
5760 if self.showingNotcoveredMarkers: |
|
5761 self.codeCoverageShowAnnotations(silent=True) |
|
5762 |
|
5763 def codeCoverageShowAnnotations(self, silent=False): |
|
5764 """ |
|
5765 Public method to handle the show code coverage annotations context |
|
5766 menu action. |
|
5767 |
|
5768 @param silent flag indicating to not show any dialog (boolean) |
|
5769 """ |
|
5770 self.__codeCoverageHideAnnotations() |
|
5771 |
|
5772 fn = self.__getCodeCoverageFile() |
|
5773 if fn: |
|
5774 from coverage import Coverage |
|
5775 cover = Coverage(data_file=fn) |
|
5776 cover.load() |
|
5777 missing = cover.analysis2(self.fileName)[3] |
|
5778 if missing: |
|
5779 for line in missing: |
|
5780 handle = self.markerAdd(line - 1, self.notcovered) |
|
5781 self.notcoveredMarkers.append(handle) |
|
5782 self.coverageMarkersShown.emit(True) |
|
5783 self.__markerMap.update() |
|
5784 else: |
|
5785 if not silent: |
|
5786 E5MessageBox.information( |
|
5787 self, |
|
5788 self.tr("Show Code Coverage Annotations"), |
|
5789 self.tr("""All lines have been covered.""")) |
|
5790 self.showingNotcoveredMarkers = True |
|
5791 else: |
|
5792 if not silent: |
|
5793 E5MessageBox.warning( |
|
5794 self, |
|
5795 self.tr("Show Code Coverage Annotations"), |
|
5796 self.tr("""There is no coverage file available.""")) |
|
5797 |
|
5798 def __codeCoverageHideAnnotations(self): |
|
5799 """ |
|
5800 Private method to handle the hide code coverage annotations context |
|
5801 menu action. |
|
5802 """ |
|
5803 for handle in self.notcoveredMarkers: |
|
5804 self.markerDeleteHandle(handle) |
|
5805 self.notcoveredMarkers = [] |
|
5806 self.coverageMarkersShown.emit(False) |
|
5807 self.showingNotcoveredMarkers = False |
|
5808 self.__markerMap.update() |
|
5809 |
|
5810 def getCoverageLines(self): |
|
5811 """ |
|
5812 Public method to get the lines containing a coverage marker. |
|
5813 |
|
5814 @return list of lines containing a coverage marker (list of integer) |
|
5815 """ |
|
5816 lines = [] |
|
5817 line = -1 |
|
5818 while True: |
|
5819 line = self.markerFindNext(line + 1, 1 << self.notcovered) |
|
5820 if line < 0: |
|
5821 break |
|
5822 else: |
|
5823 lines.append(line) |
|
5824 return lines |
|
5825 |
|
5826 def hasCoverageMarkers(self): |
|
5827 """ |
|
5828 Public method to test, if there are coverage markers. |
|
5829 |
|
5830 @return flag indicating the presence of coverage markers (boolean) |
|
5831 """ |
|
5832 return len(self.notcoveredMarkers) > 0 |
|
5833 |
|
5834 def nextUncovered(self): |
|
5835 """ |
|
5836 Public slot to handle the 'Next uncovered' context menu action. |
|
5837 """ |
|
5838 line, index = self.getCursorPosition() |
|
5839 if line == self.lines() - 1: |
|
5840 line = 0 |
|
5841 else: |
|
5842 line += 1 |
|
5843 ucline = self.markerFindNext(line, 1 << self.notcovered) |
|
5844 if ucline < 0: |
|
5845 # wrap around |
|
5846 ucline = self.markerFindNext(0, 1 << self.notcovered) |
|
5847 if ucline >= 0: |
|
5848 self.setCursorPosition(ucline, 0) |
|
5849 self.ensureLineVisible(ucline) |
|
5850 |
|
5851 def previousUncovered(self): |
|
5852 """ |
|
5853 Public slot to handle the 'Previous uncovered' context menu action. |
|
5854 """ |
|
5855 line, index = self.getCursorPosition() |
|
5856 if line == 0: |
|
5857 line = self.lines() - 1 |
|
5858 else: |
|
5859 line -= 1 |
|
5860 ucline = self.markerFindPrevious(line, 1 << self.notcovered) |
|
5861 if ucline < 0: |
|
5862 # wrap around |
|
5863 ucline = self.markerFindPrevious( |
|
5864 self.lines() - 1, 1 << self.notcovered) |
|
5865 if ucline >= 0: |
|
5866 self.setCursorPosition(ucline, 0) |
|
5867 self.ensureLineVisible(ucline) |
|
5868 |
|
5869 def __showProfileData(self): |
|
5870 """ |
|
5871 Private method to handle the show profile data context menu action. |
|
5872 """ |
|
5873 files = [] |
|
5874 |
|
5875 # first check if the file belongs to a project and there is |
|
5876 # a project profile file |
|
5877 if self.project.isOpen() and \ |
|
5878 self.project.isProjectSource(self.fileName): |
|
5879 fn = self.project.getMainScript(True) |
|
5880 if fn is not None: |
|
5881 tfn = Utilities.getTestFileName(fn) |
|
5882 basename = os.path.splitext(fn)[0] |
|
5883 tbasename = os.path.splitext(tfn)[0] |
|
5884 |
|
5885 f = "{0}.profile".format(basename) |
|
5886 tf = "{0}.profile".format(tbasename) |
|
5887 if os.path.isfile(f): |
|
5888 files.append(f) |
|
5889 if os.path.isfile(tf): |
|
5890 files.append(tf) |
|
5891 |
|
5892 # now check, if there are profile files belonging to ourselves |
|
5893 fn = self.getFileName() |
|
5894 if fn is not None: |
|
5895 tfn = Utilities.getTestFileName(fn) |
|
5896 basename = os.path.splitext(fn)[0] |
|
5897 tbasename = os.path.splitext(tfn)[0] |
|
5898 |
|
5899 f = "{0}.profile".format(basename) |
|
5900 tf = "{0}.profile".format(tbasename) |
|
5901 if os.path.isfile(f) and f not in files: |
|
5902 files.append(f) |
|
5903 if os.path.isfile(tf) and tf not in files: |
|
5904 files.append(tf) |
|
5905 |
|
5906 if files: |
|
5907 if len(files) > 1: |
|
5908 fn, ok = QInputDialog.getItem( |
|
5909 self, |
|
5910 self.tr("Profile Data"), |
|
5911 self.tr("Please select a profile file"), |
|
5912 files, |
|
5913 0, False) |
|
5914 if not ok: |
|
5915 return |
|
5916 else: |
|
5917 fn = files[0] |
|
5918 else: |
|
5919 return |
|
5920 |
|
5921 from DataViews.PyProfileDialog import PyProfileDialog |
|
5922 self.profiledata = PyProfileDialog() |
|
5923 self.profiledata.show() |
|
5924 self.profiledata.start(fn, self.fileName) |
|
5925 |
|
5926 def __lmBbookmarks(self): |
|
5927 """ |
|
5928 Private method to handle the 'LMB toggles bookmark' context menu |
|
5929 action. |
|
5930 """ |
|
5931 self.marginMenuActs["LMBbookmarks"].setChecked(True) |
|
5932 self.marginMenuActs["LMBbreakpoints"].setChecked(False) |
|
5933 |
|
5934 def __lmBbreakpoints(self): |
|
5935 """ |
|
5936 Private method to handle the 'LMB toggles breakpoint' context menu |
|
5937 action. |
|
5938 """ |
|
5939 self.marginMenuActs["LMBbookmarks"].setChecked(True) |
|
5940 self.marginMenuActs["LMBbreakpoints"].setChecked(False) |
|
5941 |
|
5942 ########################################################################### |
|
5943 ## Syntax error handling methods below |
|
5944 ########################################################################### |
|
5945 |
|
5946 def toggleSyntaxError(self, line, index, error, msg="", show=False): |
|
5947 """ |
|
5948 Public method to toggle a syntax error indicator. |
|
5949 |
|
5950 @param line line number of the syntax error (integer) |
|
5951 @param index index number of the syntax error (integer) |
|
5952 @param error flag indicating if the error marker should be |
|
5953 set or deleted (boolean) |
|
5954 @param msg error message (string) |
|
5955 @keyparam show flag indicating to set the cursor to the error position |
|
5956 (boolean) |
|
5957 """ |
|
5958 if line == 0: |
|
5959 line = 1 |
|
5960 # hack to show a syntax error marker, if line is reported to be 0 |
|
5961 if error: |
|
5962 # set a new syntax error marker |
|
5963 markers = self.markersAtLine(line - 1) |
|
5964 index += self.indentation(line - 1) |
|
5965 if not (markers & (1 << self.syntaxerror)): |
|
5966 handle = self.markerAdd(line - 1, self.syntaxerror) |
|
5967 self.syntaxerrors[handle] = [(msg, index)] |
|
5968 self.syntaxerrorToggled.emit(self) |
|
5969 else: |
|
5970 for handle in list(self.syntaxerrors.keys()): |
|
5971 if self.markerLine(handle) == line - 1 and \ |
|
5972 (msg, index) not in self.syntaxerrors[handle]: |
|
5973 self.syntaxerrors[handle].append((msg, index)) |
|
5974 if show: |
|
5975 self.setCursorPosition(line - 1, index) |
|
5976 self.ensureLineVisible(line - 1) |
|
5977 else: |
|
5978 for handle in list(self.syntaxerrors.keys()): |
|
5979 if self.markerLine(handle) == line - 1: |
|
5980 del self.syntaxerrors[handle] |
|
5981 self.markerDeleteHandle(handle) |
|
5982 self.syntaxerrorToggled.emit(self) |
|
5983 |
|
5984 self.__setAnnotation(line - 1) |
|
5985 self.__markerMap.update() |
|
5986 |
|
5987 def getSyntaxErrors(self): |
|
5988 """ |
|
5989 Public method to retrieve the syntax error markers. |
|
5990 |
|
5991 @return sorted list of all lines containing a syntax error |
|
5992 (list of integer) |
|
5993 """ |
|
5994 selist = [] |
|
5995 for handle in list(self.syntaxerrors.keys()): |
|
5996 selist.append(self.markerLine(handle) + 1) |
|
5997 |
|
5998 selist.sort() |
|
5999 return selist |
|
6000 |
|
6001 def getSyntaxErrorLines(self): |
|
6002 """ |
|
6003 Public method to get the lines containing a syntax error. |
|
6004 |
|
6005 @return list of lines containing a syntax error (list of integer) |
|
6006 """ |
|
6007 lines = [] |
|
6008 line = -1 |
|
6009 while True: |
|
6010 line = self.markerFindNext(line + 1, 1 << self.syntaxerror) |
|
6011 if line < 0: |
|
6012 break |
|
6013 else: |
|
6014 lines.append(line) |
|
6015 return lines |
|
6016 |
|
6017 def hasSyntaxErrors(self): |
|
6018 """ |
|
6019 Public method to check for the presence of syntax errors. |
|
6020 |
|
6021 @return flag indicating the presence of syntax errors (boolean) |
|
6022 """ |
|
6023 return len(self.syntaxerrors) > 0 |
|
6024 |
|
6025 def gotoSyntaxError(self): |
|
6026 """ |
|
6027 Public slot to handle the 'Goto syntax error' context menu action. |
|
6028 """ |
|
6029 seline = self.markerFindNext(0, 1 << self.syntaxerror) |
|
6030 if seline >= 0: |
|
6031 index = 0 |
|
6032 for handle in self.syntaxerrors.keys(): |
|
6033 if self.markerLine(handle) == seline: |
|
6034 index = self.syntaxerrors[handle][0][1] |
|
6035 self.setCursorPosition(seline, index) |
|
6036 self.ensureLineVisible(seline) |
|
6037 |
|
6038 def clearSyntaxError(self): |
|
6039 """ |
|
6040 Public slot to handle the 'Clear all syntax error' context menu action. |
|
6041 """ |
|
6042 for handle in list(self.syntaxerrors.keys()): |
|
6043 line = self.markerLine(handle) + 1 |
|
6044 self.toggleSyntaxError(line, 0, False) |
|
6045 |
|
6046 self.syntaxerrors = {} |
|
6047 self.syntaxerrorToggled.emit(self) |
|
6048 |
|
6049 def __showSyntaxError(self, line=-1): |
|
6050 """ |
|
6051 Private slot to handle the 'Show syntax error message' |
|
6052 context menu action. |
|
6053 |
|
6054 @param line line number to show the syntax error for (integer) |
|
6055 """ |
|
6056 if line == -1: |
|
6057 line = self.line |
|
6058 |
|
6059 for handle in list(self.syntaxerrors.keys()): |
|
6060 if self.markerLine(handle) == line: |
|
6061 errors = [e[0] for e in self.syntaxerrors[handle]] |
|
6062 E5MessageBox.critical( |
|
6063 self, |
|
6064 self.tr("Syntax Error"), |
|
6065 "\n".join(errors)) |
|
6066 break |
|
6067 else: |
|
6068 E5MessageBox.critical( |
|
6069 self, |
|
6070 self.tr("Syntax Error"), |
|
6071 self.tr("No syntax error message available.")) |
|
6072 |
|
6073 ########################################################################### |
|
6074 ## VCS conflict marker handling methods below |
|
6075 ########################################################################### |
|
6076 |
|
6077 def getVcsConflictMarkerLines(self): |
|
6078 """ |
|
6079 Public method to determine the lines containing a VCS conflict marker. |
|
6080 |
|
6081 @return list of line numbers containg a VCS conflict marker |
|
6082 @rtype list of int |
|
6083 """ |
|
6084 conflictMarkerLines = [] |
|
6085 |
|
6086 regExp = re.compile("|".join(Editor.VcsConflictMarkerLineRegExpList), |
|
6087 re.MULTILINE) |
|
6088 matches = [m for m in regExp.finditer(self.text())] |
|
6089 for match in matches: |
|
6090 line, _ = self.lineIndexFromPosition(match.start()) |
|
6091 conflictMarkerLines.append(line) |
|
6092 |
|
6093 return conflictMarkerLines |
|
6094 |
|
6095 ########################################################################### |
|
6096 ## Warning handling methods below |
|
6097 ########################################################################### |
|
6098 |
|
6099 def toggleWarning( |
|
6100 self, line, col, warning, msg="", warningType=WarningCode): |
|
6101 """ |
|
6102 Public method to toggle a warning indicator. |
|
6103 |
|
6104 Note: This method is used to set pyflakes and code style warnings. |
|
6105 |
|
6106 @param line line number of the warning |
|
6107 @param col column of the warning |
|
6108 @param warning flag indicating if the warning marker should be |
|
6109 set or deleted (boolean) |
|
6110 @param msg warning message (string) |
|
6111 @keyparam warningType type of warning message (integer) |
|
6112 """ |
|
6113 if line == 0: |
|
6114 line = 1 |
|
6115 # hack to show a warning marker, if line is reported to be 0 |
|
6116 if warning: |
|
6117 # set/amend a new warning marker |
|
6118 warn = (msg, warningType) |
|
6119 markers = self.markersAtLine(line - 1) |
|
6120 if not (markers & (1 << self.warning)): |
|
6121 handle = self.markerAdd(line - 1, self.warning) |
|
6122 self.warnings[handle] = [warn] |
|
6123 self.syntaxerrorToggled.emit(self) |
|
6124 else: |
|
6125 for handle in list(self.warnings.keys()): |
|
6126 if self.markerLine(handle) == line - 1 and \ |
|
6127 warn not in self.warnings[handle]: |
|
6128 self.warnings[handle].append(warn) |
|
6129 else: |
|
6130 for handle in list(self.warnings.keys()): |
|
6131 if self.markerLine(handle) == line - 1: |
|
6132 del self.warnings[handle] |
|
6133 self.markerDeleteHandle(handle) |
|
6134 self.syntaxerrorToggled.emit(self) |
|
6135 |
|
6136 self.__setAnnotation(line - 1) |
|
6137 self.__markerMap.update() |
|
6138 |
|
6139 def getWarnings(self): |
|
6140 """ |
|
6141 Public method to retrieve the warning markers. |
|
6142 |
|
6143 @return sorted list of all lines containing a warning |
|
6144 (list of integer) |
|
6145 """ |
|
6146 fwlist = [] |
|
6147 for handle in list(self.warnings.keys()): |
|
6148 fwlist.append(self.markerLine(handle) + 1) |
|
6149 |
|
6150 fwlist.sort() |
|
6151 return fwlist |
|
6152 |
|
6153 def getWarningLines(self): |
|
6154 """ |
|
6155 Public method to get the lines containing a warning. |
|
6156 |
|
6157 @return list of lines containing a warning (list of integer) |
|
6158 """ |
|
6159 lines = [] |
|
6160 line = -1 |
|
6161 while True: |
|
6162 line = self.markerFindNext(line + 1, 1 << self.warning) |
|
6163 if line < 0: |
|
6164 break |
|
6165 else: |
|
6166 lines.append(line) |
|
6167 return lines |
|
6168 |
|
6169 def hasWarnings(self): |
|
6170 """ |
|
6171 Public method to check for the presence of warnings. |
|
6172 |
|
6173 @return flag indicating the presence of warnings (boolean) |
|
6174 """ |
|
6175 return len(self.warnings) > 0 |
|
6176 |
|
6177 def nextWarning(self): |
|
6178 """ |
|
6179 Public slot to handle the 'Next warning' context menu action. |
|
6180 """ |
|
6181 line, index = self.getCursorPosition() |
|
6182 if line == self.lines() - 1: |
|
6183 line = 0 |
|
6184 else: |
|
6185 line += 1 |
|
6186 fwline = self.markerFindNext(line, 1 << self.warning) |
|
6187 if fwline < 0: |
|
6188 # wrap around |
|
6189 fwline = self.markerFindNext(0, 1 << self.warning) |
|
6190 if fwline >= 0: |
|
6191 self.setCursorPosition(fwline, 0) |
|
6192 self.ensureLineVisible(fwline) |
|
6193 |
|
6194 def previousWarning(self): |
|
6195 """ |
|
6196 Public slot to handle the 'Previous warning' context menu action. |
|
6197 """ |
|
6198 line, index = self.getCursorPosition() |
|
6199 if line == 0: |
|
6200 line = self.lines() - 1 |
|
6201 else: |
|
6202 line -= 1 |
|
6203 fwline = self.markerFindPrevious(line, 1 << self.warning) |
|
6204 if fwline < 0: |
|
6205 # wrap around |
|
6206 fwline = self.markerFindPrevious( |
|
6207 self.lines() - 1, 1 << self.warning) |
|
6208 if fwline >= 0: |
|
6209 self.setCursorPosition(fwline, 0) |
|
6210 self.ensureLineVisible(fwline) |
|
6211 |
|
6212 def clearFlakesWarnings(self): |
|
6213 """ |
|
6214 Public slot to clear all pyflakes warnings. |
|
6215 """ |
|
6216 self.__clearTypedWarning(Editor.WarningCode) |
|
6217 |
|
6218 def clearStyleWarnings(self): |
|
6219 """ |
|
6220 Public slot to clear all style warnings. |
|
6221 """ |
|
6222 self.__clearTypedWarning(Editor.WarningStyle) |
|
6223 |
|
6224 def __clearTypedWarning(self, warningKind): |
|
6225 """ |
|
6226 Private method to clear warnings of a specific kind. |
|
6227 |
|
6228 @param warningKind kind of warning to clear (Editor.WarningCode, |
|
6229 Editor.WarningStyle) |
|
6230 """ |
|
6231 assert warningKind in [Editor.WarningCode, Editor.WarningStyle] |
|
6232 |
|
6233 for handle in list(self.warnings.keys()): |
|
6234 warnings = [] |
|
6235 for msg, warningType in self.warnings[handle]: |
|
6236 if warningType == warningKind: |
|
6237 continue |
|
6238 |
|
6239 warnings.append((msg, warningType)) |
|
6240 |
|
6241 if warnings: |
|
6242 self.warnings[handle] = warnings |
|
6243 self.__setAnnotation(self.markerLine(handle)) |
|
6244 else: |
|
6245 del self.warnings[handle] |
|
6246 self.__setAnnotation(self.markerLine(handle)) |
|
6247 self.markerDeleteHandle(handle) |
|
6248 self.syntaxerrorToggled.emit(self) |
|
6249 self.__markerMap.update() |
|
6250 |
|
6251 def clearWarnings(self): |
|
6252 """ |
|
6253 Public slot to clear all warnings. |
|
6254 """ |
|
6255 for handle in self.warnings: |
|
6256 self.warnings[handle] = [] |
|
6257 self.__setAnnotation(self.markerLine(handle)) |
|
6258 self.markerDeleteHandle(handle) |
|
6259 self.warnings = {} |
|
6260 self.syntaxerrorToggled.emit(self) |
|
6261 self.__markerMap.update() |
|
6262 |
|
6263 def __showWarning(self, line=-1): |
|
6264 """ |
|
6265 Private slot to handle the 'Show warning' context menu action. |
|
6266 |
|
6267 @param line line number to show the warning for (integer) |
|
6268 """ |
|
6269 if line == -1: |
|
6270 line = self.line |
|
6271 |
|
6272 for handle in list(self.warnings.keys()): |
|
6273 if self.markerLine(handle) == line: |
|
6274 E5MessageBox.warning( |
|
6275 self, |
|
6276 self.tr("Warning"), |
|
6277 '\n'.join([w[0] for w in self.warnings[handle]])) |
|
6278 break |
|
6279 else: |
|
6280 E5MessageBox.warning( |
|
6281 self, |
|
6282 self.tr("Warning"), |
|
6283 self.tr("No warning messages available.")) |
|
6284 |
|
6285 ########################################################################### |
|
6286 ## Annotation handling methods below |
|
6287 ########################################################################### |
|
6288 |
|
6289 def __setAnnotationStyles(self): |
|
6290 """ |
|
6291 Private slot to define the style used by inline annotations. |
|
6292 """ |
|
6293 if hasattr(QsciScintilla, "annotate"): |
|
6294 self.annotationWarningStyle = \ |
|
6295 QsciScintilla.STYLE_LASTPREDEFINED + 1 |
|
6296 self.SendScintilla( |
|
6297 QsciScintilla.SCI_STYLESETFORE, |
|
6298 self.annotationWarningStyle, |
|
6299 Preferences.getEditorColour("AnnotationsWarningForeground")) |
|
6300 self.SendScintilla( |
|
6301 QsciScintilla.SCI_STYLESETBACK, |
|
6302 self.annotationWarningStyle, |
|
6303 Preferences.getEditorColour("AnnotationsWarningBackground")) |
|
6304 |
|
6305 self.annotationErrorStyle = self.annotationWarningStyle + 1 |
|
6306 self.SendScintilla( |
|
6307 QsciScintilla.SCI_STYLESETFORE, |
|
6308 self.annotationErrorStyle, |
|
6309 Preferences.getEditorColour("AnnotationsErrorForeground")) |
|
6310 self.SendScintilla( |
|
6311 QsciScintilla.SCI_STYLESETBACK, |
|
6312 self.annotationErrorStyle, |
|
6313 Preferences.getEditorColour("AnnotationsErrorBackground")) |
|
6314 |
|
6315 self.annotationStyleStyle = self.annotationErrorStyle + 1 |
|
6316 self.SendScintilla( |
|
6317 QsciScintilla.SCI_STYLESETFORE, |
|
6318 self.annotationStyleStyle, |
|
6319 Preferences.getEditorColour("AnnotationsStyleForeground")) |
|
6320 self.SendScintilla( |
|
6321 QsciScintilla.SCI_STYLESETBACK, |
|
6322 self.annotationStyleStyle, |
|
6323 Preferences.getEditorColour("AnnotationsStyleBackground")) |
|
6324 |
|
6325 def __setAnnotation(self, line): |
|
6326 """ |
|
6327 Private method to set the annotations for the given line. |
|
6328 |
|
6329 @param line number of the line that needs annotation (integer) |
|
6330 """ |
|
6331 if hasattr(QsciScintilla, "annotate"): |
|
6332 warningAnnotations = [] |
|
6333 errorAnnotations = [] |
|
6334 styleAnnotations = [] |
|
6335 |
|
6336 # step 1: do warnings |
|
6337 for handle in self.warnings.keys(): |
|
6338 if self.markerLine(handle) == line: |
|
6339 for msg, warningType in self.warnings[handle]: |
|
6340 if warningType == self.WarningStyle: |
|
6341 styleAnnotations.append( |
|
6342 self.tr("Style: {0}").format(msg)) |
|
6343 else: |
|
6344 warningAnnotations.append( |
|
6345 self.tr("Warning: {0}").format(msg)) |
|
6346 |
|
6347 # step 2: do syntax errors |
|
6348 for handle in self.syntaxerrors.keys(): |
|
6349 if self.markerLine(handle) == line: |
|
6350 for msg, _ in self.syntaxerrors[handle]: |
|
6351 errorAnnotations.append( |
|
6352 self.tr("Error: {0}").format(msg)) |
|
6353 |
|
6354 annotations = [] |
|
6355 if styleAnnotations: |
|
6356 annotationStyleTxt = "\n".join(styleAnnotations) |
|
6357 if warningAnnotations or errorAnnotations: |
|
6358 annotationStyleTxt += "\n" |
|
6359 annotations.append(QsciStyledText( |
|
6360 annotationStyleTxt, self.annotationStyleStyle)) |
|
6361 |
|
6362 if warningAnnotations: |
|
6363 annotationWarningTxt = "\n".join(warningAnnotations) |
|
6364 if errorAnnotations: |
|
6365 annotationWarningTxt += "\n" |
|
6366 annotations.append(QsciStyledText( |
|
6367 annotationWarningTxt, self.annotationWarningStyle)) |
|
6368 |
|
6369 if errorAnnotations: |
|
6370 annotationErrorTxt = "\n".join(errorAnnotations) |
|
6371 annotations.append(QsciStyledText( |
|
6372 annotationErrorTxt, self.annotationErrorStyle)) |
|
6373 |
|
6374 if annotations: |
|
6375 self.annotate(line, annotations) |
|
6376 else: |
|
6377 self.clearAnnotations(line) |
|
6378 |
|
6379 def __refreshAnnotations(self): |
|
6380 """ |
|
6381 Private method to refresh the annotations. |
|
6382 """ |
|
6383 if hasattr(QsciScintilla, "annotate"): |
|
6384 self.clearAnnotations() |
|
6385 for handle in list(self.warnings.keys()) + \ |
|
6386 list(self.syntaxerrors.keys()): |
|
6387 line = self.markerLine(handle) |
|
6388 self.__setAnnotation(line) |
|
6389 |
|
6390 ################################################################# |
|
6391 ## Fold handling methods |
|
6392 ################################################################# |
|
6393 |
|
6394 def toggleCurrentFold(self): |
|
6395 """ |
|
6396 Public slot to toggle the fold containing the current line. |
|
6397 """ |
|
6398 line, index = self.getCursorPosition() |
|
6399 self.foldLine(line) |
|
6400 |
|
6401 def expandFoldWithChildren(self, line=-1): |
|
6402 """ |
|
6403 Public slot to expand the current fold including its children. |
|
6404 |
|
6405 @param line number of line to be expanded |
|
6406 @type int |
|
6407 """ |
|
6408 if line == -1: |
|
6409 line, index = self.getCursorPosition() |
|
6410 |
|
6411 self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line, |
|
6412 QsciScintilla.SC_FOLDACTION_EXPAND) |
|
6413 |
|
6414 def collapseFoldWithChildren(self, line=-1): |
|
6415 """ |
|
6416 Public slot to collapse the current fold including its children. |
|
6417 |
|
6418 @param line number of line to be expanded |
|
6419 @type int |
|
6420 """ |
|
6421 if line == -1: |
|
6422 line, index = self.getCursorPosition() |
|
6423 |
|
6424 self.SendScintilla(QsciScintilla.SCI_FOLDCHILDREN, line, |
|
6425 QsciScintilla.SC_FOLDACTION_CONTRACT) |
|
6426 |
|
6427 def __contextMenuExpandFoldWithChildren(self): |
|
6428 """ |
|
6429 Private slot to handle the context menu expand with children action. |
|
6430 """ |
|
6431 self.expandFoldWithChildren(self.line) |
|
6432 |
|
6433 def __contextMenuCollapseFoldWithChildren(self): |
|
6434 """ |
|
6435 Private slot to handle the context menu collapse with children action. |
|
6436 """ |
|
6437 self.collapseFoldWithChildren(self.line) |
|
6438 |
|
6439 ################################################################# |
|
6440 ## Macro handling methods |
|
6441 ################################################################# |
|
6442 |
|
6443 def __getMacroName(self): |
|
6444 """ |
|
6445 Private method to select a macro name from the list of macros. |
|
6446 |
|
6447 @return Tuple of macro name and a flag, indicating, if the user |
|
6448 pressed ok or canceled the operation. (string, boolean) |
|
6449 """ |
|
6450 qs = [] |
|
6451 for s in list(self.macros.keys()): |
|
6452 qs.append(s) |
|
6453 qs.sort() |
|
6454 return QInputDialog.getItem( |
|
6455 self, |
|
6456 self.tr("Macro Name"), |
|
6457 self.tr("Select a macro name:"), |
|
6458 qs, |
|
6459 0, False) |
|
6460 |
|
6461 def macroRun(self): |
|
6462 """ |
|
6463 Public method to execute a macro. |
|
6464 """ |
|
6465 name, ok = self.__getMacroName() |
|
6466 if ok and name: |
|
6467 self.macros[name].play() |
|
6468 |
|
6469 def macroDelete(self): |
|
6470 """ |
|
6471 Public method to delete a macro. |
|
6472 """ |
|
6473 name, ok = self.__getMacroName() |
|
6474 if ok and name: |
|
6475 del self.macros[name] |
|
6476 |
|
6477 def macroLoad(self): |
|
6478 """ |
|
6479 Public method to load a macro from a file. |
|
6480 """ |
|
6481 configDir = Utilities.getConfigDir() |
|
6482 fname = E5FileDialog.getOpenFileName( |
|
6483 self, |
|
6484 self.tr("Load macro file"), |
|
6485 configDir, |
|
6486 self.tr("Macro files (*.macro)")) |
|
6487 |
|
6488 if not fname: |
|
6489 return # user aborted |
|
6490 |
|
6491 try: |
|
6492 f = open(fname, "r", encoding="utf-8") |
|
6493 lines = f.readlines() |
|
6494 f.close() |
|
6495 except IOError: |
|
6496 E5MessageBox.critical( |
|
6497 self, |
|
6498 self.tr("Error loading macro"), |
|
6499 self.tr( |
|
6500 "<p>The macro file <b>{0}</b> could not be read.</p>") |
|
6501 .format(fname)) |
|
6502 return |
|
6503 |
|
6504 if len(lines) != 2: |
|
6505 E5MessageBox.critical( |
|
6506 self, |
|
6507 self.tr("Error loading macro"), |
|
6508 self.tr("<p>The macro file <b>{0}</b> is corrupt.</p>") |
|
6509 .format(fname)) |
|
6510 return |
|
6511 |
|
6512 macro = QsciMacro(lines[1], self) |
|
6513 self.macros[lines[0].strip()] = macro |
|
6514 |
|
6515 def macroSave(self): |
|
6516 """ |
|
6517 Public method to save a macro to a file. |
|
6518 """ |
|
6519 configDir = Utilities.getConfigDir() |
|
6520 |
|
6521 name, ok = self.__getMacroName() |
|
6522 if not ok or not name: |
|
6523 return # user abort |
|
6524 |
|
6525 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
|
6526 self, |
|
6527 self.tr("Save macro file"), |
|
6528 configDir, |
|
6529 self.tr("Macro files (*.macro)"), |
|
6530 "", |
|
6531 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
|
6532 |
|
6533 if not fname: |
|
6534 return # user aborted |
|
6535 |
|
6536 ext = QFileInfo(fname).suffix() |
|
6537 if not ext: |
|
6538 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
6539 if ex: |
|
6540 fname += ex |
|
6541 if QFileInfo(fname).exists(): |
|
6542 res = E5MessageBox.yesNo( |
|
6543 self, |
|
6544 self.tr("Save macro"), |
|
6545 self.tr("<p>The macro file <b>{0}</b> already exists." |
|
6546 " Overwrite it?</p>").format(fname), |
|
6547 icon=E5MessageBox.Warning) |
|
6548 if not res: |
|
6549 return |
|
6550 fname = Utilities.toNativeSeparators(fname) |
|
6551 |
|
6552 try: |
|
6553 f = open(fname, "w", encoding="utf-8") |
|
6554 f.write("{0}{1}".format(name, "\n")) |
|
6555 f.write(self.macros[name].save()) |
|
6556 f.close() |
|
6557 except IOError: |
|
6558 E5MessageBox.critical( |
|
6559 self, |
|
6560 self.tr("Error saving macro"), |
|
6561 self.tr( |
|
6562 "<p>The macro file <b>{0}</b> could not be written.</p>") |
|
6563 .format(fname)) |
|
6564 return |
|
6565 |
|
6566 def macroRecordingStart(self): |
|
6567 """ |
|
6568 Public method to start macro recording. |
|
6569 """ |
|
6570 if self.recording: |
|
6571 res = E5MessageBox.yesNo( |
|
6572 self, |
|
6573 self.tr("Start Macro Recording"), |
|
6574 self.tr("Macro recording is already active. Start new?"), |
|
6575 icon=E5MessageBox.Warning, |
|
6576 yesDefault=True) |
|
6577 if res: |
|
6578 self.macroRecordingStop() |
|
6579 else: |
|
6580 return |
|
6581 else: |
|
6582 self.recording = True |
|
6583 |
|
6584 self.curMacro = QsciMacro(self) |
|
6585 self.curMacro.startRecording() |
|
6586 |
|
6587 def macroRecordingStop(self): |
|
6588 """ |
|
6589 Public method to stop macro recording. |
|
6590 """ |
|
6591 if not self.recording: |
|
6592 return # we are not recording |
|
6593 |
|
6594 self.curMacro.endRecording() |
|
6595 self.recording = False |
|
6596 |
|
6597 name, ok = QInputDialog.getText( |
|
6598 self, |
|
6599 self.tr("Macro Recording"), |
|
6600 self.tr("Enter name of the macro:"), |
|
6601 QLineEdit.Normal) |
|
6602 |
|
6603 if ok and name: |
|
6604 self.macros[name] = self.curMacro |
|
6605 |
|
6606 self.curMacro = None |
|
6607 |
|
6608 ################################################################# |
|
6609 ## Overwritten methods |
|
6610 ################################################################# |
|
6611 |
|
6612 def undo(self): |
|
6613 """ |
|
6614 Public method to undo the last recorded change. |
|
6615 """ |
|
6616 super(Editor, self).undo() |
|
6617 self.undoAvailable.emit(self.isUndoAvailable()) |
|
6618 self.redoAvailable.emit(self.isRedoAvailable()) |
|
6619 |
|
6620 def redo(self): |
|
6621 """ |
|
6622 Public method to redo the last recorded change. |
|
6623 """ |
|
6624 super(Editor, self).redo() |
|
6625 self.undoAvailable.emit(self.isUndoAvailable()) |
|
6626 self.redoAvailable.emit(self.isRedoAvailable()) |
|
6627 |
|
6628 def close(self, alsoDelete=False): |
|
6629 """ |
|
6630 Public method called when the window gets closed. |
|
6631 |
|
6632 This overwritten method redirects the action to our |
|
6633 ViewManager.closeEditor, which in turn calls our closeIt |
|
6634 method. |
|
6635 |
|
6636 @param alsoDelete ignored |
|
6637 @return flag indicating a successful close of the editor (boolean) |
|
6638 """ |
|
6639 return self.vm.closeEditor(self) |
|
6640 |
|
6641 def closeIt(self): |
|
6642 """ |
|
6643 Public method called by the viewmanager to finally get rid of us. |
|
6644 """ |
|
6645 if Preferences.getEditor("ClearBreaksOnClose") and not self.__clones: |
|
6646 self.__menuClearBreakpoints() |
|
6647 |
|
6648 for clone in self.__clones[:]: |
|
6649 self.removeClone(clone) |
|
6650 clone.removeClone(self) |
|
6651 |
|
6652 self.breakpointModel.rowsAboutToBeRemoved.disconnect( |
|
6653 self.__deleteBreakPoints) |
|
6654 self.breakpointModel.dataAboutToBeChanged.disconnect( |
|
6655 self.__breakPointDataAboutToBeChanged) |
|
6656 self.breakpointModel.dataChanged.disconnect( |
|
6657 self.__changeBreakPoints) |
|
6658 self.breakpointModel.rowsInserted.disconnect( |
|
6659 self.__addBreakPoints) |
|
6660 |
|
6661 if self.syntaxCheckService is not None: |
|
6662 self.syntaxCheckService.syntaxChecked.disconnect( |
|
6663 self.__processSyntaxCheckResult) |
|
6664 self.syntaxCheckService.error.disconnect( |
|
6665 self.__processSyntaxCheckError) |
|
6666 |
|
6667 if self.spell: |
|
6668 self.spell.stopIncrementalCheck() |
|
6669 |
|
6670 try: |
|
6671 self.project.projectPropertiesChanged.disconnect( |
|
6672 self.__projectPropertiesChanged) |
|
6673 except TypeError: |
|
6674 pass |
|
6675 |
|
6676 if self.fileName: |
|
6677 self.taskViewer.clearFileTasks(self.fileName, True) |
|
6678 |
|
6679 super(Editor, self).close() |
|
6680 |
|
6681 def keyPressEvent(self, ev): |
|
6682 """ |
|
6683 Protected method to handle the user input a key at a time. |
|
6684 |
|
6685 @param ev key event (QKeyEvent) |
|
6686 """ |
|
6687 txt = ev.text() |
|
6688 |
|
6689 # See it is text to insert. |
|
6690 if len(txt) and txt >= " ": |
|
6691 super(Editor, self).keyPressEvent(ev) |
|
6692 else: |
|
6693 ev.ignore() |
|
6694 |
|
6695 def focusInEvent(self, event): |
|
6696 """ |
|
6697 Protected method called when the editor receives focus. |
|
6698 |
|
6699 This method checks for modifications of the current file and |
|
6700 rereads it upon request. The cursor is placed at the current position |
|
6701 assuming, that it is in the vicinity of the old position after the |
|
6702 reread. |
|
6703 |
|
6704 @param event the event object (QFocusEvent) |
|
6705 """ |
|
6706 self.recolor() |
|
6707 self.vm.editActGrp.setEnabled(True) |
|
6708 self.vm.editorActGrp.setEnabled(True) |
|
6709 self.vm.copyActGrp.setEnabled(True) |
|
6710 self.vm.viewActGrp.setEnabled(True) |
|
6711 self.vm.searchActGrp.setEnabled(True) |
|
6712 try: |
|
6713 self.setCaretWidth(self.caretWidth) |
|
6714 except AttributeError: |
|
6715 pass |
|
6716 self.__updateReadOnly(False) |
|
6717 if self.vm.editorsCheckFocusInEnabled() and \ |
|
6718 not self.inReopenPrompt and self.fileName and \ |
|
6719 QFileInfo(self.fileName).lastModified().toString() != \ |
|
6720 self.lastModified.toString(): |
|
6721 self.inReopenPrompt = True |
|
6722 if Preferences.getEditor("AutoReopen") and not self.isModified(): |
|
6723 self.refresh() |
|
6724 else: |
|
6725 msg = self.tr( |
|
6726 """<p>The file <b>{0}</b> has been changed while it""" |
|
6727 """ was opened in eric6. Reread it?</p>""")\ |
|
6728 .format(self.fileName) |
|
6729 yesDefault = True |
|
6730 if self.isModified(): |
|
6731 msg += self.tr( |
|
6732 """<br><b>Warning:</b> You will lose""" |
|
6733 """ your changes upon reopening it.""") |
|
6734 yesDefault = False |
|
6735 res = E5MessageBox.yesNo( |
|
6736 self, |
|
6737 self.tr("File changed"), msg, |
|
6738 icon=E5MessageBox.Warning, |
|
6739 yesDefault=yesDefault) |
|
6740 if res: |
|
6741 self.refresh() |
|
6742 else: |
|
6743 # do not prompt for this change again... |
|
6744 self.lastModified = QFileInfo(self.fileName).lastModified() |
|
6745 self.inReopenPrompt = False |
|
6746 |
|
6747 self.setCursorFlashTime(QApplication.cursorFlashTime()) |
|
6748 |
|
6749 super(Editor, self).focusInEvent(event) |
|
6750 |
|
6751 def focusOutEvent(self, event): |
|
6752 """ |
|
6753 Protected method called when the editor loses focus. |
|
6754 |
|
6755 @param event the event object (QFocusEvent) |
|
6756 """ |
|
6757 self.vm.editorActGrp.setEnabled(False) |
|
6758 self.setCaretWidth(0) |
|
6759 |
|
6760 super(Editor, self).focusOutEvent(event) |
|
6761 |
|
6762 def changeEvent(self, evt): |
|
6763 """ |
|
6764 Protected method called to process an event. |
|
6765 |
|
6766 This implements special handling for the events showMaximized, |
|
6767 showMinimized and showNormal. The windows caption is shortened |
|
6768 for the minimized mode and reset to the full filename for the |
|
6769 other modes. This is to make the editor windows work nicer |
|
6770 with the QWorkspace. |
|
6771 |
|
6772 @param evt the event, that was generated (QEvent) |
|
6773 """ |
|
6774 if evt.type() == QEvent.WindowStateChange and \ |
|
6775 bool(self.fileName): |
|
6776 if self.windowState() == Qt.WindowStates(Qt.WindowMinimized): |
|
6777 cap = os.path.basename(self.fileName) |
|
6778 else: |
|
6779 cap = self.fileName |
|
6780 if self.isReadOnly(): |
|
6781 cap = self.tr("{0} (ro)").format(cap) |
|
6782 self.setWindowTitle(cap) |
|
6783 |
|
6784 super(Editor, self).changeEvent(evt) |
|
6785 |
|
6786 def mousePressEvent(self, event): |
|
6787 """ |
|
6788 Protected method to handle the mouse press event. |
|
6789 |
|
6790 @param event the mouse press event (QMouseEvent) |
|
6791 """ |
|
6792 self.vm.eventFilter(self, event) |
|
6793 super(Editor, self).mousePressEvent(event) |
|
6794 |
|
6795 def wheelEvent(self, evt): |
|
6796 """ |
|
6797 Protected method to handle wheel events. |
|
6798 |
|
6799 @param evt reference to the wheel event (QWheelEvent) |
|
6800 """ |
|
6801 if qVersionTuple() >= (5, 0, 0): |
|
6802 delta = evt.angleDelta().y() |
|
6803 else: |
|
6804 delta = evt.delta() |
|
6805 if evt.modifiers() & Qt.ControlModifier: |
|
6806 if delta < 0: |
|
6807 self.zoomOut() |
|
6808 elif delta > 0: |
|
6809 self.zoomIn() |
|
6810 evt.accept() |
|
6811 return |
|
6812 |
|
6813 if evt.modifiers() & Qt.ShiftModifier: |
|
6814 if delta < 0: |
|
6815 self.gotoMethodClass(False) |
|
6816 elif delta > 0: |
|
6817 self.gotoMethodClass(True) |
|
6818 evt.accept() |
|
6819 return |
|
6820 |
|
6821 super(Editor, self).wheelEvent(evt) |
|
6822 |
|
6823 def event(self, evt): |
|
6824 """ |
|
6825 Public method handling events. |
|
6826 |
|
6827 @param evt reference to the event (QEvent) |
|
6828 @return flag indicating, if the event was handled (boolean) |
|
6829 """ |
|
6830 if evt.type() == QEvent.Gesture: |
|
6831 self.gestureEvent(evt) |
|
6832 return True |
|
6833 |
|
6834 return super(Editor, self).event(evt) |
|
6835 |
|
6836 def gestureEvent(self, evt): |
|
6837 """ |
|
6838 Protected method handling gesture events. |
|
6839 |
|
6840 @param evt reference to the gesture event (QGestureEvent |
|
6841 """ |
|
6842 pinch = evt.gesture(Qt.PinchGesture) |
|
6843 if pinch: |
|
6844 if pinch.state() == Qt.GestureStarted: |
|
6845 zoom = (self.getZoom() + 10) / 10.0 |
|
6846 pinch.setTotalScaleFactor(zoom) |
|
6847 elif pinch.state() == Qt.GestureUpdated: |
|
6848 zoom = int(pinch.totalScaleFactor() * 10) - 10 |
|
6849 if zoom <= -9: |
|
6850 zoom = -9 |
|
6851 pinch.setTotalScaleFactor(0.1) |
|
6852 elif zoom >= 20: |
|
6853 zoom = 20 |
|
6854 pinch.setTotalScaleFactor(3.0) |
|
6855 self.zoomTo(zoom) |
|
6856 evt.accept() |
|
6857 |
|
6858 def resizeEvent(self, evt): |
|
6859 """ |
|
6860 Protected method handling resize events. |
|
6861 |
|
6862 @param evt reference to the resize event (QResizeEvent) |
|
6863 """ |
|
6864 super(Editor, self).resizeEvent(evt) |
|
6865 self.__markerMap.calculateGeometry() |
|
6866 |
|
6867 def viewportEvent(self, evt): |
|
6868 """ |
|
6869 Protected method handling event of the viewport. |
|
6870 |
|
6871 @param evt reference to the event (QEvent) |
|
6872 @return flag indiating that the event was handled (boolean) |
|
6873 """ |
|
6874 self.__markerMap.calculateGeometry() |
|
6875 return super(Editor, self).viewportEvent(evt) |
|
6876 |
|
6877 def __updateReadOnly(self, bForce=True): |
|
6878 """ |
|
6879 Private method to update the readOnly information for this editor. |
|
6880 |
|
6881 If bForce is True, then updates everything regardless if |
|
6882 the attributes have actually changed, such as during |
|
6883 initialization time. A signal is emitted after the |
|
6884 caption change. |
|
6885 |
|
6886 @param bForce True to force change, False to only update and emit |
|
6887 signal if there was an attribute change. |
|
6888 """ |
|
6889 if self.fileName == "": |
|
6890 return |
|
6891 |
|
6892 readOnly = not QFileInfo(self.fileName).isWritable() or \ |
|
6893 self.isReadOnly() |
|
6894 if not bForce and (readOnly == self.isReadOnly()): |
|
6895 return |
|
6896 |
|
6897 cap = self.fileName |
|
6898 if readOnly: |
|
6899 cap = self.tr("{0} (ro)".format(cap)) |
|
6900 self.setReadOnly(readOnly) |
|
6901 self.setWindowTitle(cap) |
|
6902 self.captionChanged.emit(cap, self) |
|
6903 |
|
6904 def refresh(self): |
|
6905 """ |
|
6906 Public slot to refresh the editor contents. |
|
6907 """ |
|
6908 # save cursor position |
|
6909 cline, cindex = self.getCursorPosition() |
|
6910 |
|
6911 # save bookmarks and breakpoints and clear them |
|
6912 bmlist = self.getBookmarks() |
|
6913 self.clearBookmarks() |
|
6914 |
|
6915 # clear syntax error markers |
|
6916 self.clearSyntaxError() |
|
6917 |
|
6918 # clear flakes warning markers |
|
6919 self.clearWarnings() |
|
6920 |
|
6921 # clear breakpoint markers |
|
6922 for handle in list(self.breaks.keys()): |
|
6923 self.markerDeleteHandle(handle) |
|
6924 self.breaks = {} |
|
6925 |
|
6926 if not os.path.exists(self.fileName): |
|
6927 # close the file, if it was deleted in the background |
|
6928 self.close() |
|
6929 return |
|
6930 |
|
6931 # reread the file |
|
6932 try: |
|
6933 self.readFile(self.fileName) |
|
6934 except IOError: |
|
6935 # do not prompt for this change again... |
|
6936 self.lastModified = QDateTime.currentDateTime() |
|
6937 self.setModified(False) |
|
6938 |
|
6939 # re-initialize the online change tracer |
|
6940 self.__reinitOnlineChangeTrace() |
|
6941 |
|
6942 # reset cursor position |
|
6943 self.setCursorPosition(cline, cindex) |
|
6944 self.ensureCursorVisible() |
|
6945 |
|
6946 # reset bookmarks and breakpoints to their old position |
|
6947 if bmlist: |
|
6948 for bm in bmlist: |
|
6949 self.toggleBookmark(bm) |
|
6950 self.__restoreBreakpoints() |
|
6951 |
|
6952 self.editorSaved.emit(self.fileName) |
|
6953 self.checkSyntax() |
|
6954 |
|
6955 self.__markerMap.update() |
|
6956 |
|
6957 self.refreshed.emit() |
|
6958 |
|
6959 def setMonospaced(self, on): |
|
6960 """ |
|
6961 Public method to set/reset a monospaced font. |
|
6962 |
|
6963 @param on flag to indicate usage of a monospace font (boolean) |
|
6964 """ |
|
6965 if on: |
|
6966 if not self.lexer_: |
|
6967 f = Preferences.getEditorOtherFonts("MonospacedFont") |
|
6968 self.monospacedStyles(f) |
|
6969 else: |
|
6970 if not self.lexer_: |
|
6971 self.clearStyles() |
|
6972 self.__setMarginsDisplay() |
|
6973 self.setFont(Preferences.getEditorOtherFonts("DefaultFont")) |
|
6974 |
|
6975 self.useMonospaced = on |
|
6976 |
|
6977 def clearStyles(self): |
|
6978 """ |
|
6979 Public method to set the styles according the selected Qt style |
|
6980 or the selected editor colours. |
|
6981 """ |
|
6982 super(Editor, self).clearStyles() |
|
6983 if Preferences.getEditor("OverrideEditAreaColours"): |
|
6984 self.setColor(Preferences.getEditorColour("EditAreaForeground")) |
|
6985 self.setPaper(Preferences.getEditorColour("EditAreaBackground")) |
|
6986 |
|
6987 ################################################################# |
|
6988 ## Drag and Drop Support |
|
6989 ################################################################# |
|
6990 |
|
6991 def dragEnterEvent(self, event): |
|
6992 """ |
|
6993 Protected method to handle the drag enter event. |
|
6994 |
|
6995 @param event the drag enter event (QDragEnterEvent) |
|
6996 """ |
|
6997 self.inDragDrop = event.mimeData().hasUrls() |
|
6998 if self.inDragDrop: |
|
6999 event.acceptProposedAction() |
|
7000 else: |
|
7001 super(Editor, self).dragEnterEvent(event) |
|
7002 |
|
7003 def dragMoveEvent(self, event): |
|
7004 """ |
|
7005 Protected method to handle the drag move event. |
|
7006 |
|
7007 @param event the drag move event (QDragMoveEvent) |
|
7008 """ |
|
7009 if self.inDragDrop: |
|
7010 event.accept() |
|
7011 else: |
|
7012 super(Editor, self).dragMoveEvent(event) |
|
7013 |
|
7014 def dragLeaveEvent(self, event): |
|
7015 """ |
|
7016 Protected method to handle the drag leave event. |
|
7017 |
|
7018 @param event the drag leave event (QDragLeaveEvent) |
|
7019 """ |
|
7020 if self.inDragDrop: |
|
7021 self.inDragDrop = False |
|
7022 event.accept() |
|
7023 else: |
|
7024 super(Editor, self).dragLeaveEvent(event) |
|
7025 |
|
7026 def dropEvent(self, event): |
|
7027 """ |
|
7028 Protected method to handle the drop event. |
|
7029 |
|
7030 @param event the drop event (QDropEvent) |
|
7031 """ |
|
7032 if event.mimeData().hasUrls(): |
|
7033 for url in event.mimeData().urls(): |
|
7034 fname = url.toLocalFile() |
|
7035 if fname: |
|
7036 if not QFileInfo(fname).isDir(): |
|
7037 self.vm.openSourceFile(fname) |
|
7038 else: |
|
7039 E5MessageBox.information( |
|
7040 self, |
|
7041 self.tr("Drop Error"), |
|
7042 self.tr("""<p><b>{0}</b> is not a file.</p>""") |
|
7043 .format(fname)) |
|
7044 event.acceptProposedAction() |
|
7045 else: |
|
7046 super(Editor, self).dropEvent(event) |
|
7047 |
|
7048 self.inDragDrop = False |
|
7049 |
|
7050 ################################################################# |
|
7051 ## Support for Qt resources files |
|
7052 ################################################################# |
|
7053 |
|
7054 def __initContextMenuResources(self): |
|
7055 """ |
|
7056 Private method used to setup the Resources context sub menu. |
|
7057 |
|
7058 @return reference to the generated menu (QMenu) |
|
7059 """ |
|
7060 menu = QMenu(self.tr('Resources')) |
|
7061 |
|
7062 menu.addAction( |
|
7063 self.tr('Add file...'), self.__addFileResource) |
|
7064 menu.addAction( |
|
7065 self.tr('Add files...'), self.__addFileResources) |
|
7066 menu.addAction( |
|
7067 self.tr('Add aliased file...'), |
|
7068 self.__addFileAliasResource) |
|
7069 menu.addAction( |
|
7070 self.tr('Add localized resource...'), |
|
7071 self.__addLocalizedResource) |
|
7072 menu.addSeparator() |
|
7073 menu.addAction( |
|
7074 self.tr('Add resource frame'), self.__addResourceFrame) |
|
7075 |
|
7076 menu.aboutToShow.connect(self.__showContextMenuResources) |
|
7077 |
|
7078 return menu |
|
7079 |
|
7080 def __showContextMenuResources(self): |
|
7081 """ |
|
7082 Private slot handling the aboutToShow signal of the resources context |
|
7083 menu. |
|
7084 """ |
|
7085 self.showMenu.emit("Resources", self.resourcesMenu, self) |
|
7086 |
|
7087 def __addFileResource(self): |
|
7088 """ |
|
7089 Private method to handle the Add file context menu action. |
|
7090 """ |
|
7091 dirStr = os.path.dirname(self.fileName) |
|
7092 file = E5FileDialog.getOpenFileName( |
|
7093 self, |
|
7094 self.tr("Add file resource"), |
|
7095 dirStr, |
|
7096 "") |
|
7097 if file: |
|
7098 relFile = QDir(dirStr).relativeFilePath(file) |
|
7099 line, index = self.getCursorPosition() |
|
7100 self.insert(" <file>{0}</file>\n".format(relFile)) |
|
7101 self.setCursorPosition(line + 1, index) |
|
7102 |
|
7103 def __addFileResources(self): |
|
7104 """ |
|
7105 Private method to handle the Add files context menu action. |
|
7106 """ |
|
7107 dirStr = os.path.dirname(self.fileName) |
|
7108 files = E5FileDialog.getOpenFileNames( |
|
7109 self, |
|
7110 self.tr("Add file resources"), |
|
7111 dirStr, |
|
7112 "") |
|
7113 if files: |
|
7114 myDir = QDir(dirStr) |
|
7115 filesText = "" |
|
7116 for file in files: |
|
7117 relFile = myDir.relativeFilePath(file) |
|
7118 filesText += " <file>{0}</file>\n".format(relFile) |
|
7119 line, index = self.getCursorPosition() |
|
7120 self.insert(filesText) |
|
7121 self.setCursorPosition(line + len(files), index) |
|
7122 |
|
7123 def __addFileAliasResource(self): |
|
7124 """ |
|
7125 Private method to handle the Add aliased file context menu action. |
|
7126 """ |
|
7127 dirStr = os.path.dirname(self.fileName) |
|
7128 file = E5FileDialog.getOpenFileName( |
|
7129 self, |
|
7130 self.tr("Add aliased file resource"), |
|
7131 dirStr, |
|
7132 "") |
|
7133 if file: |
|
7134 relFile = QDir(dirStr).relativeFilePath(file) |
|
7135 alias, ok = QInputDialog.getText( |
|
7136 self, |
|
7137 self.tr("Add aliased file resource"), |
|
7138 self.tr("Alias for file <b>{0}</b>:").format(relFile), |
|
7139 QLineEdit.Normal, |
|
7140 relFile) |
|
7141 if ok and alias: |
|
7142 line, index = self.getCursorPosition() |
|
7143 self.insert(' <file alias="{1}">{0}</file>\n' |
|
7144 .format(relFile, alias)) |
|
7145 self.setCursorPosition(line + 1, index) |
|
7146 |
|
7147 def __addLocalizedResource(self): |
|
7148 """ |
|
7149 Private method to handle the Add localized resource context menu |
|
7150 action. |
|
7151 """ |
|
7152 from Project.AddLanguageDialog import AddLanguageDialog |
|
7153 dlg = AddLanguageDialog(self) |
|
7154 if dlg.exec_() == QDialog.Accepted: |
|
7155 lang = dlg.getSelectedLanguage() |
|
7156 line, index = self.getCursorPosition() |
|
7157 self.insert('<qresource lang="{0}">\n</qresource>\n'.format(lang)) |
|
7158 self.setCursorPosition(line + 2, index) |
|
7159 |
|
7160 def __addResourceFrame(self): |
|
7161 """ |
|
7162 Private method to handle the Add resource frame context menu action. |
|
7163 """ |
|
7164 line, index = self.getCursorPosition() |
|
7165 self.insert('<!DOCTYPE RCC>\n' |
|
7166 '<RCC version="1.0">\n' |
|
7167 '<qresource>\n' |
|
7168 '</qresource>\n' |
|
7169 '</RCC>\n') |
|
7170 self.setCursorPosition(line + 5, index) |
|
7171 |
|
7172 ################################################################# |
|
7173 ## Support for diagrams below |
|
7174 ################################################################# |
|
7175 |
|
7176 def __showClassDiagram(self): |
|
7177 """ |
|
7178 Private method to handle the Class Diagram context menu action. |
|
7179 """ |
|
7180 from Graphics.UMLDialog import UMLDialog |
|
7181 if not self.checkDirty(): |
|
7182 return |
|
7183 |
|
7184 self.classDiagram = UMLDialog( |
|
7185 UMLDialog.ClassDiagram, self.project, self.fileName, |
|
7186 self, noAttrs=False) |
|
7187 self.classDiagram.show() |
|
7188 |
|
7189 def __showPackageDiagram(self): |
|
7190 """ |
|
7191 Private method to handle the Package Diagram context menu action. |
|
7192 """ |
|
7193 from Graphics.UMLDialog import UMLDialog |
|
7194 if not self.checkDirty(): |
|
7195 return |
|
7196 |
|
7197 package = os.path.isdir(self.fileName) and \ |
|
7198 self.fileName or os.path.dirname(self.fileName) |
|
7199 res = E5MessageBox.yesNo( |
|
7200 self, |
|
7201 self.tr("Package Diagram"), |
|
7202 self.tr("""Include class attributes?"""), |
|
7203 yesDefault=True) |
|
7204 self.packageDiagram = UMLDialog( |
|
7205 UMLDialog.PackageDiagram, self.project, package, |
|
7206 self, noAttrs=not res) |
|
7207 self.packageDiagram.show() |
|
7208 |
|
7209 def __showImportsDiagram(self): |
|
7210 """ |
|
7211 Private method to handle the Imports Diagram context menu action. |
|
7212 """ |
|
7213 from Graphics.UMLDialog import UMLDialog |
|
7214 if not self.checkDirty(): |
|
7215 return |
|
7216 |
|
7217 package = os.path.isdir(self.fileName) and self.fileName \ |
|
7218 or os.path.dirname(self.fileName) |
|
7219 res = E5MessageBox.yesNo( |
|
7220 self, |
|
7221 self.tr("Imports Diagram"), |
|
7222 self.tr("""Include imports from external modules?""")) |
|
7223 self.importsDiagram = UMLDialog( |
|
7224 UMLDialog.ImportsDiagram, self.project, package, |
|
7225 self, showExternalImports=res) |
|
7226 self.importsDiagram.show() |
|
7227 |
|
7228 def __showApplicationDiagram(self): |
|
7229 """ |
|
7230 Private method to handle the Imports Diagram context menu action. |
|
7231 """ |
|
7232 from Graphics.UMLDialog import UMLDialog |
|
7233 res = E5MessageBox.yesNo( |
|
7234 self, |
|
7235 self.tr("Application Diagram"), |
|
7236 self.tr("""Include module names?"""), |
|
7237 yesDefault=True) |
|
7238 self.applicationDiagram = UMLDialog( |
|
7239 UMLDialog.ApplicationDiagram, self.project, |
|
7240 self, noModules=not res) |
|
7241 self.applicationDiagram.show() |
|
7242 |
|
7243 def __loadDiagram(self): |
|
7244 """ |
|
7245 Private slot to load a diagram from file. |
|
7246 """ |
|
7247 from Graphics.UMLDialog import UMLDialog |
|
7248 self.loadedDiagram = UMLDialog( |
|
7249 UMLDialog.NoDiagram, self.project, parent=self) |
|
7250 if self.loadedDiagram.load(): |
|
7251 self.loadedDiagram.show(fromFile=True) |
|
7252 else: |
|
7253 self.loadedDiagram = None |
|
7254 |
|
7255 ####################################################################### |
|
7256 ## Typing aids related methods below |
|
7257 ####################################################################### |
|
7258 |
|
7259 def __toggleTypingAids(self): |
|
7260 """ |
|
7261 Private slot to toggle the typing aids. |
|
7262 """ |
|
7263 if self.menuActs["TypingAidsEnabled"].isChecked(): |
|
7264 self.completer.setEnabled(True) |
|
7265 else: |
|
7266 self.completer.setEnabled(False) |
|
7267 |
|
7268 ####################################################################### |
|
7269 ## Auto-completing templates |
|
7270 ####################################################################### |
|
7271 |
|
7272 def editorCommand(self, cmd): |
|
7273 """ |
|
7274 Public method to perform a simple editor command. |
|
7275 |
|
7276 @param cmd the scintilla command to be performed |
|
7277 """ |
|
7278 if cmd == QsciScintilla.SCI_TAB: |
|
7279 try: |
|
7280 templateViewer = e5App().getObject("TemplateViewer") |
|
7281 except KeyError: |
|
7282 # template viewer is not active |
|
7283 templateViewer = None |
|
7284 |
|
7285 if templateViewer is not None: |
|
7286 line, index = self.getCursorPosition() |
|
7287 tmplName = self.getWordLeft(line, index) |
|
7288 if tmplName: |
|
7289 if templateViewer.hasTemplate(tmplName, |
|
7290 self.getLanguage()): |
|
7291 self.__applyTemplate(tmplName, self.getLanguage()) |
|
7292 return |
|
7293 else: |
|
7294 templateNames = templateViewer.getTemplateNames( |
|
7295 tmplName, self.getLanguage()) |
|
7296 if len(templateNames) == 1: |
|
7297 self.__applyTemplate(templateNames[0], |
|
7298 self.getLanguage()) |
|
7299 return |
|
7300 elif len(templateNames) > 1: |
|
7301 self.showUserList( |
|
7302 TemplateCompletionListID, |
|
7303 ["{0}?{1:d}".format(t, self.TemplateImageID) |
|
7304 for t in templateNames]) |
|
7305 return |
|
7306 |
|
7307 super(Editor, self).editorCommand(cmd) |
|
7308 |
|
7309 def __applyTemplate(self, templateName, language): |
|
7310 """ |
|
7311 Private method to apply a template by name. |
|
7312 |
|
7313 @param templateName name of the template to apply (string) |
|
7314 @param language name of the language (group) to get the template |
|
7315 from (string) |
|
7316 """ |
|
7317 try: |
|
7318 templateViewer = e5App().getObject("TemplateViewer") |
|
7319 except KeyError: |
|
7320 # template viewer is not active |
|
7321 return |
|
7322 |
|
7323 if templateViewer.hasTemplate(templateName, self.getLanguage()): |
|
7324 self.extendSelectionWordLeft() |
|
7325 templateViewer.applyNamedTemplate(templateName, |
|
7326 self.getLanguage()) |
|
7327 |
|
7328 ####################################################################### |
|
7329 ## Project related methods |
|
7330 ####################################################################### |
|
7331 |
|
7332 def __projectPropertiesChanged(self): |
|
7333 """ |
|
7334 Private slot to handle changes of the project properties. |
|
7335 """ |
|
7336 if self.spell: |
|
7337 pwl, pel = self.project.getProjectDictionaries() |
|
7338 self.__setSpellingLanguage(self.project.getProjectSpellLanguage(), |
|
7339 pwl=pwl, pel=pel) |
|
7340 |
|
7341 editorConfigEol = self.__getEditorConfig("EOLMode", nodefault=True) |
|
7342 if editorConfigEol is not None: |
|
7343 self.setEolMode(editorConfigEol) |
|
7344 else: |
|
7345 self.setEolModeByEolString(self.project.getEolString()) |
|
7346 self.convertEols(self.eolMode()) |
|
7347 |
|
7348 def addedToProject(self): |
|
7349 """ |
|
7350 Public method to signal, that this editor has been added to a project. |
|
7351 """ |
|
7352 if self.spell: |
|
7353 pwl, pel = self.project.getProjectDictionaries() |
|
7354 self.__setSpellingLanguage(self.project.getProjectSpellLanguage(), |
|
7355 pwl=pwl, pel=pel) |
|
7356 |
|
7357 self.project.projectPropertiesChanged.connect( |
|
7358 self.__projectPropertiesChanged) |
|
7359 |
|
7360 def projectOpened(self): |
|
7361 """ |
|
7362 Public slot to handle the opening of a project. |
|
7363 """ |
|
7364 if self.fileName and \ |
|
7365 self.project.isProjectSource(self.fileName): |
|
7366 self.project.projectPropertiesChanged.connect( |
|
7367 self.__projectPropertiesChanged) |
|
7368 self.setSpellingForProject() |
|
7369 |
|
7370 def projectClosed(self): |
|
7371 """ |
|
7372 Public slot to handle the closing of a project. |
|
7373 """ |
|
7374 try: |
|
7375 self.project.projectPropertiesChanged.disconnect( |
|
7376 self.__projectPropertiesChanged) |
|
7377 except TypeError: |
|
7378 pass |
|
7379 |
|
7380 ####################################################################### |
|
7381 ## Spell checking related methods |
|
7382 ####################################################################### |
|
7383 |
|
7384 def __setSpellingLanguage(self, language, pwl="", pel=""): |
|
7385 """ |
|
7386 Private slot to set the spell checking language. |
|
7387 |
|
7388 @param language spell checking language to be set (string) |
|
7389 @keyparam pwl name of the personal/project word list (string) |
|
7390 @keyparam pel name of the personal/project exclude list (string) |
|
7391 """ |
|
7392 if self.spell and self.spell.getLanguage() != language: |
|
7393 self.spell.setLanguage(language, pwl=pwl, pel=pel) |
|
7394 self.spell.checkDocumentIncrementally() |
|
7395 |
|
7396 def __setSpelling(self): |
|
7397 """ |
|
7398 Private method to initialize the spell checking functionality. |
|
7399 """ |
|
7400 if Preferences.getEditor("SpellCheckingEnabled"): |
|
7401 self.__spellCheckStringsOnly = Preferences.getEditor( |
|
7402 "SpellCheckStringsOnly") |
|
7403 if self.spell is None: |
|
7404 from .SpellChecker import SpellChecker |
|
7405 self.spell = SpellChecker(self, self.spellingIndicator, |
|
7406 checkRegion=self.isSpellCheckRegion) |
|
7407 self.setSpellingForProject() |
|
7408 self.spell.setMinimumWordSize( |
|
7409 Preferences.getEditor("SpellCheckingMinWordSize")) |
|
7410 |
|
7411 self.setAutoSpellChecking() |
|
7412 else: |
|
7413 self.spell = None |
|
7414 self.clearAllIndicators(self.spellingIndicator) |
|
7415 |
|
7416 def setSpellingForProject(self): |
|
7417 """ |
|
7418 Public method to set the spell checking options for files belonging |
|
7419 to the current project. |
|
7420 """ |
|
7421 if self.fileName and \ |
|
7422 self.project.isOpen() and \ |
|
7423 self.project.isProjectSource(self.fileName): |
|
7424 pwl, pel = self.project.getProjectDictionaries() |
|
7425 self.__setSpellingLanguage(self.project.getProjectSpellLanguage(), |
|
7426 pwl=pwl, pel=pel) |
|
7427 |
|
7428 def setAutoSpellChecking(self): |
|
7429 """ |
|
7430 Public method to set the automatic spell checking. |
|
7431 """ |
|
7432 if Preferences.getEditor("AutoSpellCheckingEnabled"): |
|
7433 try: |
|
7434 self.SCN_CHARADDED.connect( |
|
7435 self.__spellCharAdded, Qt.UniqueConnection) |
|
7436 except TypeError: |
|
7437 pass |
|
7438 self.spell.checkDocumentIncrementally() |
|
7439 else: |
|
7440 try: |
|
7441 self.SCN_CHARADDED.disconnect(self.__spellCharAdded) |
|
7442 except TypeError: |
|
7443 pass |
|
7444 self.clearAllIndicators(self.spellingIndicator) |
|
7445 |
|
7446 def isSpellCheckRegion(self, pos): |
|
7447 """ |
|
7448 Public method to check, if the given position is within a region, that |
|
7449 should be spell checked. |
|
7450 |
|
7451 @param pos position to be checked (integer) |
|
7452 @return flag indicating pos is in a spell check region (boolean) |
|
7453 """ |
|
7454 if self.__spellCheckStringsOnly: |
|
7455 style = self.styleAt(pos) |
|
7456 if self.lexer_ is not None: |
|
7457 return self.lexer_.isCommentStyle(style) or \ |
|
7458 self.lexer_.isStringStyle(style) |
|
7459 return True |
|
7460 |
|
7461 @pyqtSlot(int) |
|
7462 def __spellCharAdded(self, charNumber): |
|
7463 """ |
|
7464 Private slot called to handle the user entering a character. |
|
7465 |
|
7466 @param charNumber value of the character entered (integer) |
|
7467 """ |
|
7468 if self.spell: |
|
7469 if not chr(charNumber).isalnum(): |
|
7470 self.spell.checkWord( |
|
7471 self.positionBefore(self.currentPosition()), True) |
|
7472 elif self.hasIndicator( |
|
7473 self.spellingIndicator, self.currentPosition()): |
|
7474 self.spell.checkWord(self.currentPosition()) |
|
7475 |
|
7476 def checkSpelling(self): |
|
7477 """ |
|
7478 Public slot to perform an interactive spell check of the document. |
|
7479 """ |
|
7480 if self.spell: |
|
7481 cline, cindex = self.getCursorPosition() |
|
7482 from .SpellCheckingDialog import SpellCheckingDialog |
|
7483 dlg = SpellCheckingDialog(self.spell, 0, self.length(), self) |
|
7484 dlg.exec_() |
|
7485 self.setCursorPosition(cline, cindex) |
|
7486 if Preferences.getEditor("AutoSpellCheckingEnabled"): |
|
7487 self.spell.checkDocumentIncrementally() |
|
7488 |
|
7489 def __checkSpellingSelection(self): |
|
7490 """ |
|
7491 Private slot to spell check the current selection. |
|
7492 """ |
|
7493 from .SpellCheckingDialog import SpellCheckingDialog |
|
7494 sline, sindex, eline, eindex = self.getSelection() |
|
7495 startPos = self.positionFromLineIndex(sline, sindex) |
|
7496 endPos = self.positionFromLineIndex(eline, eindex) |
|
7497 dlg = SpellCheckingDialog(self.spell, startPos, endPos, self) |
|
7498 dlg.exec_() |
|
7499 |
|
7500 def __checkSpellingWord(self): |
|
7501 """ |
|
7502 Private slot to check the word below the spelling context menu. |
|
7503 """ |
|
7504 from .SpellCheckingDialog import SpellCheckingDialog |
|
7505 line, index = self.lineIndexFromPosition(self.spellingMenuPos) |
|
7506 wordStart, wordEnd = self.getWordBoundaries(line, index) |
|
7507 wordStartPos = self.positionFromLineIndex(line, wordStart) |
|
7508 wordEndPos = self.positionFromLineIndex(line, wordEnd) |
|
7509 dlg = SpellCheckingDialog(self.spell, wordStartPos, wordEndPos, self) |
|
7510 dlg.exec_() |
|
7511 |
|
7512 def __showContextMenuSpelling(self): |
|
7513 """ |
|
7514 Private slot to set up the spelling menu before it is shown. |
|
7515 """ |
|
7516 self.spellingMenu.clear() |
|
7517 self.spellingSuggActs = [] |
|
7518 line, index = self.lineIndexFromPosition(self.spellingMenuPos) |
|
7519 word = self.getWord(line, index) |
|
7520 suggestions = self.spell.getSuggestions(word) |
|
7521 for suggestion in suggestions[:5]: |
|
7522 self.spellingSuggActs.append( |
|
7523 self.spellingMenu.addAction(suggestion)) |
|
7524 if suggestions: |
|
7525 self.spellingMenu.addSeparator() |
|
7526 self.spellingMenu.addAction( |
|
7527 UI.PixmapCache.getIcon("spellchecking.png"), |
|
7528 self.tr("Check spelling..."), self.__checkSpellingWord) |
|
7529 self.spellingMenu.addAction( |
|
7530 self.tr("Add to dictionary"), self.__addToSpellingDictionary) |
|
7531 self.spellingMenu.addAction( |
|
7532 self.tr("Ignore All"), self.__ignoreSpellingAlways) |
|
7533 |
|
7534 self.showMenu.emit("Spelling", self.spellingMenu, self) |
|
7535 |
|
7536 def __contextMenuSpellingTriggered(self, action): |
|
7537 """ |
|
7538 Private slot to handle the selection of a suggestion of the spelling |
|
7539 context menu. |
|
7540 |
|
7541 @param action reference to the action that was selected (QAction) |
|
7542 """ |
|
7543 if action in self.spellingSuggActs: |
|
7544 replacement = action.text() |
|
7545 line, index = self.lineIndexFromPosition(self.spellingMenuPos) |
|
7546 wordStart, wordEnd = self.getWordBoundaries(line, index) |
|
7547 self.setSelection(line, wordStart, line, wordEnd) |
|
7548 self.beginUndoAction() |
|
7549 self.removeSelectedText() |
|
7550 self.insert(replacement) |
|
7551 self.endUndoAction() |
|
7552 |
|
7553 def __addToSpellingDictionary(self): |
|
7554 """ |
|
7555 Private slot to add the word below the spelling context menu to the |
|
7556 dictionary. |
|
7557 """ |
|
7558 line, index = self.lineIndexFromPosition(self.spellingMenuPos) |
|
7559 word = self.getWord(line, index) |
|
7560 self.spell.add(word) |
|
7561 |
|
7562 wordStart, wordEnd = self.getWordBoundaries(line, index) |
|
7563 self.clearIndicator(self.spellingIndicator, line, wordStart, |
|
7564 line, wordEnd) |
|
7565 if Preferences.getEditor("AutoSpellCheckingEnabled"): |
|
7566 self.spell.checkDocumentIncrementally() |
|
7567 |
|
7568 def __removeFromSpellingDictionary(self): |
|
7569 """ |
|
7570 Private slot to remove the word below the context menu to the |
|
7571 dictionary. |
|
7572 """ |
|
7573 line, index = self.lineIndexFromPosition(self.spellingMenuPos) |
|
7574 word = self.getWord(line, index) |
|
7575 self.spell.remove(word) |
|
7576 |
|
7577 if Preferences.getEditor("AutoSpellCheckingEnabled"): |
|
7578 self.spell.checkDocumentIncrementally() |
|
7579 |
|
7580 def __ignoreSpellingAlways(self): |
|
7581 """ |
|
7582 Private to always ignore the word below the spelling context menu. |
|
7583 """ |
|
7584 line, index = self.lineIndexFromPosition(self.spellingMenuPos) |
|
7585 word = self.getWord(line, index) |
|
7586 self.spell.ignoreAlways(word) |
|
7587 if Preferences.getEditor("AutoSpellCheckingEnabled"): |
|
7588 self.spell.checkDocumentIncrementally() |
|
7589 |
|
7590 ####################################################################### |
|
7591 ## Cooperation related methods |
|
7592 ####################################################################### |
|
7593 |
|
7594 def getSharingStatus(self): |
|
7595 """ |
|
7596 Public method to get some share status info. |
|
7597 |
|
7598 @return tuple indicating, if the editor is sharable, the sharing |
|
7599 status, if it is inside a locally initiated shared edit session |
|
7600 and if it is inside a remotely initiated shared edit session |
|
7601 (boolean, boolean, boolean, boolean) |
|
7602 """ |
|
7603 return bool(self.fileName) and \ |
|
7604 self.project.isOpen() and \ |
|
7605 self.project.isProjectFile(self.fileName), \ |
|
7606 self.__isShared, self.__inSharedEdit, self.__inRemoteSharedEdit |
|
7607 |
|
7608 def shareConnected(self, connected): |
|
7609 """ |
|
7610 Public slot to handle a change of the connected state. |
|
7611 |
|
7612 @param connected flag indicating the connected state (boolean) |
|
7613 """ |
|
7614 if not connected: |
|
7615 self.__inRemoteSharedEdit = False |
|
7616 self.setReadOnly(False) |
|
7617 self.__updateReadOnly() |
|
7618 self.cancelSharedEdit(send=False) |
|
7619 self.__isSyncing = False |
|
7620 self.__receivedWhileSyncing = [] |
|
7621 |
|
7622 def shareEditor(self, share): |
|
7623 """ |
|
7624 Public slot to set the shared status of the editor. |
|
7625 |
|
7626 @param share flag indicating the share status (boolean) |
|
7627 """ |
|
7628 self.__isShared = share |
|
7629 if not share: |
|
7630 self.shareConnected(False) |
|
7631 |
|
7632 def startSharedEdit(self): |
|
7633 """ |
|
7634 Public slot to start a shared edit session for the editor. |
|
7635 """ |
|
7636 self.__inSharedEdit = True |
|
7637 self.__savedText = self.text() |
|
7638 hashStr = str( |
|
7639 QCryptographicHash.hash( |
|
7640 Utilities.encode(self.__savedText, self.encoding)[0], |
|
7641 QCryptographicHash.Sha1).toHex(), |
|
7642 encoding="utf-8") |
|
7643 self.__send(Editor.StartEditToken, hashStr) |
|
7644 |
|
7645 def sendSharedEdit(self): |
|
7646 """ |
|
7647 Public slot to end a shared edit session for the editor and |
|
7648 send the changes. |
|
7649 """ |
|
7650 commands = self.__calculateChanges(self.__savedText, self.text()) |
|
7651 self.__send(Editor.EndEditToken, commands) |
|
7652 self.__inSharedEdit = False |
|
7653 self.__savedText = "" |
|
7654 |
|
7655 def cancelSharedEdit(self, send=True): |
|
7656 """ |
|
7657 Public slot to cancel a shared edit session for the editor. |
|
7658 |
|
7659 @keyparam send flag indicating to send the CancelEdit command (boolean) |
|
7660 """ |
|
7661 self.__inSharedEdit = False |
|
7662 self.__savedText = "" |
|
7663 if send: |
|
7664 self.__send(Editor.CancelEditToken) |
|
7665 |
|
7666 def __send(self, token, args=None): |
|
7667 """ |
|
7668 Private method to send an editor command to remote editors. |
|
7669 |
|
7670 @param token command token (string) |
|
7671 @param args arguments for the command (string) |
|
7672 """ |
|
7673 if self.vm.isConnected(): |
|
7674 msg = "" |
|
7675 if token in (Editor.StartEditToken, |
|
7676 Editor.EndEditToken, |
|
7677 Editor.RequestSyncToken, |
|
7678 Editor.SyncToken): |
|
7679 msg = "{0}{1}{2}".format( |
|
7680 token, |
|
7681 Editor.Separator, |
|
7682 args |
|
7683 ) |
|
7684 elif token == Editor.CancelEditToken: |
|
7685 msg = "{0}{1}c".format( |
|
7686 token, |
|
7687 Editor.Separator |
|
7688 ) |
|
7689 |
|
7690 self.vm.send(self.fileName, msg) |
|
7691 |
|
7692 def receive(self, command): |
|
7693 """ |
|
7694 Public slot to handle received editor commands. |
|
7695 |
|
7696 @param command command string (string) |
|
7697 """ |
|
7698 if self.__isShared: |
|
7699 if self.__isSyncing and \ |
|
7700 not command.startswith(Editor.SyncToken + Editor.Separator): |
|
7701 self.__receivedWhileSyncing.append(command) |
|
7702 else: |
|
7703 self.__dispatchCommand(command) |
|
7704 |
|
7705 def __dispatchCommand(self, command): |
|
7706 """ |
|
7707 Private method to dispatch received commands. |
|
7708 |
|
7709 @param command command to be processed (string) |
|
7710 """ |
|
7711 token, argsString = command.split(Editor.Separator, 1) |
|
7712 if token == Editor.StartEditToken: |
|
7713 self.__processStartEditCommand(argsString) |
|
7714 elif token == Editor.CancelEditToken: |
|
7715 self.shareConnected(False) |
|
7716 elif token == Editor.EndEditToken: |
|
7717 self.__processEndEditCommand(argsString) |
|
7718 elif token == Editor.RequestSyncToken: |
|
7719 self.__processRequestSyncCommand(argsString) |
|
7720 elif token == Editor.SyncToken: |
|
7721 self.__processSyncCommand(argsString) |
|
7722 |
|
7723 def __processStartEditCommand(self, argsString): |
|
7724 """ |
|
7725 Private slot to process a remote StartEdit command. |
|
7726 |
|
7727 @param argsString string containing the command parameters (string) |
|
7728 """ |
|
7729 if not self.__inSharedEdit and not self.__inRemoteSharedEdit: |
|
7730 self.__inRemoteSharedEdit = True |
|
7731 self.setReadOnly(True) |
|
7732 self.__updateReadOnly() |
|
7733 hashStr = str( |
|
7734 QCryptographicHash.hash( |
|
7735 Utilities.encode(self.text(), self.encoding)[0], |
|
7736 QCryptographicHash.Sha1).toHex(), |
|
7737 encoding="utf-8") |
|
7738 if hashStr != argsString: |
|
7739 # text is different to the remote site, request to sync it |
|
7740 self.__isSyncing = True |
|
7741 self.__send(Editor.RequestSyncToken, argsString) |
|
7742 |
|
7743 def __calculateChanges(self, old, new): |
|
7744 """ |
|
7745 Private method to determine change commands to convert old text into |
|
7746 new text. |
|
7747 |
|
7748 @param old old text (string) |
|
7749 @param new new text (string) |
|
7750 @return commands to change old into new (string) |
|
7751 """ |
|
7752 oldL = old.splitlines() |
|
7753 newL = new.splitlines() |
|
7754 matcher = difflib.SequenceMatcher(None, oldL, newL) |
|
7755 |
|
7756 formatStr = "@@{0} {1} {2} {3}" |
|
7757 commands = [] |
|
7758 for token, i1, i2, j1, j2 in matcher.get_opcodes(): |
|
7759 if token == "insert": |
|
7760 commands.append(formatStr.format("i", j1, j2 - j1, -1)) |
|
7761 commands.extend(newL[j1:j2]) |
|
7762 elif token == "delete": |
|
7763 commands.append(formatStr.format("d", j1, i2 - i1, -1)) |
|
7764 elif token == "replace": |
|
7765 commands.append(formatStr.format("r", j1, i2 - i1, j2 - j1)) |
|
7766 commands.extend(newL[j1:j2]) |
|
7767 |
|
7768 return "\n".join(commands) + "\n" |
|
7769 |
|
7770 def __processEndEditCommand(self, argsString): |
|
7771 """ |
|
7772 Private slot to process a remote EndEdit command. |
|
7773 |
|
7774 @param argsString string containing the command parameters (string) |
|
7775 """ |
|
7776 commands = argsString.splitlines() |
|
7777 sep = self.getLineSeparator() |
|
7778 cur = self.getCursorPosition() |
|
7779 |
|
7780 self.setReadOnly(False) |
|
7781 self.beginUndoAction() |
|
7782 while commands: |
|
7783 commandLine = commands.pop(0) |
|
7784 if not commandLine.startswith("@@"): |
|
7785 continue |
|
7786 |
|
7787 args = commandLine.split() |
|
7788 command = args.pop(0) |
|
7789 pos, l1, l2 = [int(arg) for arg in args] |
|
7790 if command == "@@i": |
|
7791 txt = sep.join(commands[0:l1]) + sep |
|
7792 self.insertAt(txt, pos, 0) |
|
7793 del commands[0:l1] |
|
7794 elif command == "@@d": |
|
7795 self.setSelection(pos, 0, pos + l1, 0) |
|
7796 self.removeSelectedText() |
|
7797 elif command == "@@r": |
|
7798 self.setSelection(pos, 0, pos + l1, 0) |
|
7799 self.removeSelectedText() |
|
7800 txt = sep.join(commands[0:l2]) + sep |
|
7801 self.insertAt(txt, pos, 0) |
|
7802 del commands[0:l2] |
|
7803 self.endUndoAction() |
|
7804 self.__updateReadOnly() |
|
7805 self.__inRemoteSharedEdit = False |
|
7806 |
|
7807 self.setCursorPosition(*cur) |
|
7808 |
|
7809 def __processRequestSyncCommand(self, argsString): |
|
7810 """ |
|
7811 Private slot to process a remote RequestSync command. |
|
7812 |
|
7813 @param argsString string containing the command parameters (string) |
|
7814 """ |
|
7815 if self.__inSharedEdit: |
|
7816 hashStr = str( |
|
7817 QCryptographicHash.hash( |
|
7818 Utilities.encode(self.__savedText, self.encoding)[0], |
|
7819 QCryptographicHash.Sha1).toHex(), |
|
7820 encoding="utf-8") |
|
7821 |
|
7822 if hashStr == argsString: |
|
7823 self.__send(Editor.SyncToken, self.__savedText) |
|
7824 |
|
7825 def __processSyncCommand(self, argsString): |
|
7826 """ |
|
7827 Private slot to process a remote Sync command. |
|
7828 |
|
7829 @param argsString string containing the command parameters (string) |
|
7830 """ |
|
7831 if self.__isSyncing: |
|
7832 cur = self.getCursorPosition() |
|
7833 |
|
7834 self.setReadOnly(False) |
|
7835 self.beginUndoAction() |
|
7836 self.selectAll() |
|
7837 self.removeSelectedText() |
|
7838 self.insertAt(argsString, 0, 0) |
|
7839 self.endUndoAction() |
|
7840 self.setReadOnly(True) |
|
7841 |
|
7842 self.setCursorPosition(*cur) |
|
7843 |
|
7844 while self.__receivedWhileSyncing: |
|
7845 command = self.__receivedWhileSyncing.pop(0) |
|
7846 self.__dispatchCommand(command) |
|
7847 |
|
7848 self.__isSyncing = False |
|
7849 |
|
7850 ####################################################################### |
|
7851 ## Special search related methods |
|
7852 ####################################################################### |
|
7853 |
|
7854 def searchCurrentWordForward(self): |
|
7855 """ |
|
7856 Public slot to search the current word forward. |
|
7857 """ |
|
7858 self.__searchCurrentWord(forward=True) |
|
7859 |
|
7860 def searchCurrentWordBackward(self): |
|
7861 """ |
|
7862 Public slot to search the current word backward. |
|
7863 """ |
|
7864 self.__searchCurrentWord(forward=False) |
|
7865 |
|
7866 def __searchCurrentWord(self, forward=True): |
|
7867 """ |
|
7868 Private slot to search the next occurrence of the current word. |
|
7869 |
|
7870 @param forward flag indicating the search direction (boolean) |
|
7871 """ |
|
7872 self.hideFindIndicator() |
|
7873 line, index = self.getCursorPosition() |
|
7874 word = self.getCurrentWord() |
|
7875 wordStart, wordEnd = self.getCurrentWordBoundaries() |
|
7876 wordStartPos = self.positionFromLineIndex(line, wordStart) |
|
7877 wordEndPos = self.positionFromLineIndex(line, wordEnd) |
|
7878 |
|
7879 regExp = re.compile(r"\b{0}\b".format(word)) |
|
7880 if forward: |
|
7881 startPos = wordEndPos |
|
7882 else: |
|
7883 startPos = wordStartPos |
|
7884 |
|
7885 matches = [m for m in regExp.finditer(self.text())] |
|
7886 if matches: |
|
7887 if forward: |
|
7888 matchesAfter = [m for m in matches if m.start() >= startPos] |
|
7889 if matchesAfter: |
|
7890 match = matchesAfter[0] |
|
7891 else: |
|
7892 # wrap around |
|
7893 match = matches[0] |
|
7894 else: |
|
7895 matchesBefore = [m for m in matches if m.start() < startPos] |
|
7896 if matchesBefore: |
|
7897 match = matchesBefore[-1] |
|
7898 else: |
|
7899 # wrap around |
|
7900 match = matches[-1] |
|
7901 line, index = self.lineIndexFromPosition(match.start()) |
|
7902 self.setSelection(line, index + len(match.group(0)), line, index) |
|
7903 self.showFindIndicator(line, index, |
|
7904 line, index + len(match.group(0))) |
|
7905 |
|
7906 ####################################################################### |
|
7907 ## Sort related methods |
|
7908 ####################################################################### |
|
7909 |
|
7910 def sortLines(self): |
|
7911 """ |
|
7912 Public slot to sort the lines spanned by a rectangular selection. |
|
7913 """ |
|
7914 if not self.selectionIsRectangle(): |
|
7915 return |
|
7916 |
|
7917 from .SortOptionsDialog import SortOptionsDialog |
|
7918 dlg = SortOptionsDialog() |
|
7919 if dlg.exec_() == QDialog.Accepted: |
|
7920 ascending, alnum, caseSensitive = dlg.getData() |
|
7921 origStartLine, origStartIndex, origEndLine, origEndIndex = \ |
|
7922 self.getRectangularSelection() |
|
7923 # convert to upper-left to lower-right |
|
7924 startLine = min(origStartLine, origEndLine) |
|
7925 startIndex = min(origStartIndex, origEndIndex) |
|
7926 endLine = max(origStartLine, origEndLine) |
|
7927 endIndex = max(origStartIndex, origEndIndex) |
|
7928 |
|
7929 # step 1: extract the text of the rectangular selection and |
|
7930 # the lines |
|
7931 selText = {} |
|
7932 txtLines = {} |
|
7933 for line in range(startLine, endLine + 1): |
|
7934 txtLines[line] = self.text(line) |
|
7935 txt = txtLines[line][startIndex:endIndex].strip() |
|
7936 if not alnum: |
|
7937 try: |
|
7938 txt = float(txt) |
|
7939 except ValueError: |
|
7940 E5MessageBox.critical( |
|
7941 self, |
|
7942 self.tr("Sort Lines"), |
|
7943 self.tr( |
|
7944 """The selection contains illegal data for a""" |
|
7945 """ numerical sort.""")) |
|
7946 return |
|
7947 |
|
7948 if txt in selText: |
|
7949 selText[txt].append(line) |
|
7950 else: |
|
7951 selText[txt] = [line] |
|
7952 |
|
7953 # step 2: calculate the sort parameters |
|
7954 reverse = not ascending |
|
7955 if alnum and not caseSensitive: |
|
7956 keyFun = str.lower |
|
7957 else: |
|
7958 keyFun = None |
|
7959 |
|
7960 # step 3: sort the lines |
|
7961 eol = self.getLineSeparator() |
|
7962 lastWithEol = True |
|
7963 newLines = [] |
|
7964 for txt in sorted(selText.keys(), key=keyFun, reverse=reverse): |
|
7965 for line in selText[txt]: |
|
7966 txt = txtLines[line] |
|
7967 if not txt.endswith(eol): |
|
7968 lastWithEol = False |
|
7969 txt += eol |
|
7970 newLines.append(txt) |
|
7971 if not lastWithEol: |
|
7972 newLines[-1] = newLines[-1][:-len(eol)] |
|
7973 |
|
7974 # step 4: replace the lines by the sorted ones |
|
7975 self.setSelection(startLine, 0, endLine + 1, 0) |
|
7976 self.beginUndoAction() |
|
7977 self.replaceSelectedText("".join(newLines)) |
|
7978 self.endUndoAction() |
|
7979 |
|
7980 # step 5: reset the rectangular selection |
|
7981 self.setRectangularSelection(origStartLine, origStartIndex, |
|
7982 origEndLine, origEndIndex) |
|
7983 self.selectionChanged.emit() |
|
7984 |
|
7985 ####################################################################### |
|
7986 ## Mouse click handler related methods |
|
7987 ####################################################################### |
|
7988 |
|
7989 def mouseReleaseEvent(self, evt): |
|
7990 """ |
|
7991 Protected method calling a registered mouse click handler function. |
|
7992 |
|
7993 @param evt event object |
|
7994 @type QMouseEvent |
|
7995 """ |
|
7996 modifiers = evt.modifiers() |
|
7997 button = evt.button() |
|
7998 key = (int(modifiers), int(button)) |
|
7999 |
|
8000 self.vm.eventFilter(self, evt) |
|
8001 super(Editor, self).mouseReleaseEvent(evt) |
|
8002 |
|
8003 if button != Qt.NoButton and \ |
|
8004 Preferences.getEditor("MouseClickHandlersEnabled") and \ |
|
8005 key in self.__mouseClickHandlers: |
|
8006 evt.accept() |
|
8007 self.__mouseClickHandlers[key][1](self) |
|
8008 |
|
8009 def setMouseClickHandler(self, name, modifiers, button, function): |
|
8010 """ |
|
8011 Public method to set a mouse click handler. |
|
8012 |
|
8013 @param name name of the plug-in (or 'internal') setting this handler |
|
8014 @type str |
|
8015 @param modifiers keyboard modifiers of the handler |
|
8016 @type Qt.KeyboardModifiers or int |
|
8017 @param button mouse button of the handler |
|
8018 @type Qt.MouseButton or int |
|
8019 @param function handler function |
|
8020 @type func |
|
8021 @return flag indicating success |
|
8022 @rtype bool |
|
8023 """ |
|
8024 if int(button): |
|
8025 key = (int(modifiers), int(button)) |
|
8026 if key in self.__mouseClickHandlers: |
|
8027 E5MessageBox.warning( |
|
8028 self, |
|
8029 self.tr("Register Mouse Click Handler"), |
|
8030 self.tr("""A mouse click handler for "{0}" was already""" |
|
8031 """ registered by "{1}". Aborting request by""" |
|
8032 """ "{2}"...""").format( |
|
8033 MouseUtilities.MouseButtonModifier2String( |
|
8034 modifiers, button), |
|
8035 self.__mouseClickHandlers[key][0], |
|
8036 name)) |
|
8037 return False |
|
8038 |
|
8039 self.__mouseClickHandlers[key] = (name, function) |
|
8040 return True |
|
8041 |
|
8042 return False |
|
8043 |
|
8044 def getMouseClickHandler(self, modifiers, button): |
|
8045 """ |
|
8046 Public method to get a registered mouse click handler. |
|
8047 |
|
8048 @param modifiers keyboard modifiers of the handler |
|
8049 @type Qt.KeyboardModifiers |
|
8050 @param button mouse button of the handler |
|
8051 @type Qt.MouseButton |
|
8052 @return plug-in name and registered function |
|
8053 @rtype tuple of str and func |
|
8054 """ |
|
8055 key = (int(modifiers), int(button)) |
|
8056 if key in self.__mouseClickHandlers: |
|
8057 return self.__mouseClickHandlers[key] |
|
8058 else: |
|
8059 return ("", None) |
|
8060 |
|
8061 def getMouseClickHandlers(self, name): |
|
8062 """ |
|
8063 Public method to get all registered mouse click handlers of |
|
8064 a plug-in. |
|
8065 |
|
8066 @param name name of the plug-in |
|
8067 @type str |
|
8068 @return registered mouse click handlers as list of modifiers, |
|
8069 mouse button and function |
|
8070 @rtype list of tuple of (Qt.KeyboardModifiers, Qt.MouseButton,func) |
|
8071 """ |
|
8072 lst = [] |
|
8073 for key, value in self.__mouseClickHandlers.items(): |
|
8074 if value[0] == name: |
|
8075 lst.append((key[0], key[1], value[1])) |
|
8076 return lst |
|
8077 |
|
8078 def removeMouseClickHandler(self, modifiers, button): |
|
8079 """ |
|
8080 Public method to un-registered a mouse click handler. |
|
8081 |
|
8082 @param modifiers keyboard modifiers of the handler |
|
8083 @type Qt.KeyboardModifiers |
|
8084 @param button mouse button of the handler |
|
8085 @type Qt.MouseButton |
|
8086 """ |
|
8087 key = (int(modifiers), int(button)) |
|
8088 if key in self.__mouseClickHandlers: |
|
8089 del self.__mouseClickHandlers[key] |
|
8090 |
|
8091 def removeMouseClickHandlers(self, name): |
|
8092 """ |
|
8093 Public method to un-registered all mouse click handlers of |
|
8094 a plug-in. |
|
8095 |
|
8096 @param name name of the plug-in |
|
8097 @type str |
|
8098 """ |
|
8099 keys = [] |
|
8100 for key in self.__mouseClickHandlers: |
|
8101 if self.__mouseClickHandlers[key][0] == name: |
|
8102 keys.append(key) |
|
8103 for key in keys: |
|
8104 del self.__mouseClickHandlers[key] |
|
8105 |
|
8106 def __executeSelection(self): |
|
8107 """ |
|
8108 Private slot to execute the selected text in the shell window. |
|
8109 """ |
|
8110 txt = self.selectedText() |
|
8111 e5App().getObject("Shell").executeLines(txt) |
|
8112 |
|
8113 ####################################################################### |
|
8114 ## Methods implementing the interface to EditorConfig |
|
8115 ####################################################################### |
|
8116 |
|
8117 def __loadEditorConfig(self, fileName=""): |
|
8118 """ |
|
8119 Private method to load the EditorConfig properties. |
|
8120 |
|
8121 @param fileName name of the file |
|
8122 @type str |
|
8123 """ |
|
8124 if not fileName: |
|
8125 fileName = self.fileName |
|
8126 |
|
8127 self.__editorConfig = self.__loadEditorConfigObject(fileName) |
|
8128 |
|
8129 if fileName: |
|
8130 self.__setTabAndIndent() |
|
8131 |
|
8132 def __loadEditorConfigObject(self, fileName): |
|
8133 """ |
|
8134 Private method to load the EditorConfig properties for the given |
|
8135 file name. |
|
8136 |
|
8137 @param fileName name of the file |
|
8138 @type str |
|
8139 @return EditorConfig dictionary |
|
8140 @rtype dict |
|
8141 """ |
|
8142 editorConfig = {} |
|
8143 |
|
8144 if fileName: |
|
8145 try: |
|
8146 editorConfig = editorconfig.get_properties(fileName) |
|
8147 except editorconfig.EditorConfigError: |
|
8148 E5MessageBox.warning( |
|
8149 self, |
|
8150 self.tr("EditorConfig Properties"), |
|
8151 self.tr("""<p>The EditorConfig properties for file""" |
|
8152 """ <b>{0}</b> could not be loaded.</p>""") |
|
8153 .format(fileName)) |
|
8154 |
|
8155 return editorConfig |
|
8156 |
|
8157 def __getEditorConfig(self, option, nodefault=False, config=None): |
|
8158 """ |
|
8159 Private method to get the requested option via EditorConfig. |
|
8160 |
|
8161 If there is no EditorConfig defined, the equivalent built-in option |
|
8162 will be used (Preferences.getEditor(). The option must be given as the |
|
8163 Preferences option key. The mapping to the EditorConfig option name |
|
8164 will be done within this method. |
|
8165 |
|
8166 @param option Preferences option key |
|
8167 @type str |
|
8168 @param nodefault flag indicating to not get the default value from |
|
8169 Preferences but return None instead |
|
8170 @type bool |
|
8171 @param config reference to an EditorConfig object or None |
|
8172 @type dict |
|
8173 @return value of requested setting or None if nothing was found and |
|
8174 nodefault parameter was True |
|
8175 @rtype any |
|
8176 """ |
|
8177 if config is None: |
|
8178 config = self.__editorConfig |
|
8179 |
|
8180 if not config: |
|
8181 if nodefault: |
|
8182 return None |
|
8183 else: |
|
8184 return Preferences.getEditor(option) |
|
8185 |
|
8186 try: |
|
8187 if option == "EOLMode": |
|
8188 value = config["end_of_line"] |
|
8189 if value == "lf": |
|
8190 value = QsciScintilla.EolUnix |
|
8191 elif value == "crlf": |
|
8192 value = QsciScintilla.EolWindows |
|
8193 elif value == "cr": |
|
8194 value = QsciScintilla.EolMac |
|
8195 else: |
|
8196 value = None |
|
8197 elif option == "DefaultEncoding": |
|
8198 value = config["charset"] |
|
8199 elif option == "InsertFinalNewline": |
|
8200 value = Utilities.toBool(config["insert_final_newline"]) |
|
8201 elif option == "StripTrailingWhitespace": |
|
8202 value = Utilities.toBool(config["trim_trailing_whitespace"]) |
|
8203 elif option == "TabWidth": |
|
8204 value = int(config["tab_width"]) |
|
8205 elif option == "IndentWidth": |
|
8206 value = config["indent_size"] |
|
8207 if value == "tab": |
|
8208 value = self.__getEditorConfig("TabWidth", config=config) |
|
8209 else: |
|
8210 value = int(value) |
|
8211 elif option == "TabForIndentation": |
|
8212 value = config["indent_style"] == "tab" |
|
8213 except KeyError: |
|
8214 value = None |
|
8215 |
|
8216 if value is None and not nodefault: |
|
8217 # use Preferences in case of error |
|
8218 value = Preferences.getEditor(option) |
|
8219 |
|
8220 return value |
|
8221 |
|
8222 def getEditorConfig(self, option): |
|
8223 """ |
|
8224 Public method to get the requested option via EditorConfig. |
|
8225 |
|
8226 @param option Preferences option key |
|
8227 @type str |
|
8228 @return value of requested setting |
|
8229 @rtype any |
|
8230 """ |
|
8231 return self.__getEditorConfig(option) |
|
8232 |
|
8233 def mouseDoubleClickEvent(self, evt): |
|
8234 """ |
|
8235 Protected method to handle mouse double click events. |
|
8236 |
|
8237 @param evt reference to the mouse event |
|
8238 @type QMouseEvent |
|
8239 """ |
|
8240 super(Editor, self).mouseDoubleClickEvent(evt) |
|
8241 |
|
8242 self.mouseDoubleClick.emit(evt.pos(), evt.buttons()) |