QScintilla/Editor.py

changeset 0
de9c2efb9d02
child 7
c679fb30c8f3
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the editor component of the eric4 IDE.
8 """
9
10 import os
11 import re
12 import types
13
14 from PyQt4.Qsci import QsciScintilla, QsciMacro
15 from PyQt4.QtCore import *
16 from PyQt4.QtGui import *
17
18 from E4Gui.E4Application import e4App
19
20 import Exporters
21 import Lexers
22 import TypingCompleters
23 from QsciScintillaCompat import QsciScintillaCompat, QSCINTILLA_VERSION
24 from SpellChecker import SpellChecker
25 from SpellCheckingDialog import SpellCheckingDialog
26
27 from Debugger.EditBreakpointDialog import EditBreakpointDialog
28
29 from DebugClients.Python.coverage import coverage
30
31 from DataViews.CodeMetricsDialog import CodeMetricsDialog
32 from DataViews.PyCoverageDialog import PyCoverageDialog
33 from DataViews.PyProfileDialog import PyProfileDialog
34
35 from Printer import Printer
36
37 import Preferences
38 import Utilities
39
40 import UI.PixmapCache
41
42 EditorAutoCompletionListID = 1
43 TemplateCompletionListID = 2
44
45 class Editor(QsciScintillaCompat):
46 """
47 Class implementing the editor component of the eric4 IDE.
48
49 @signal modificationStatusChanged(boolean, editor) emitted when the
50 modification status has changed
51 @signal undoAvailable(boolean) emitted to signal the undo availability
52 @signal redoAvailable(boolean) emitted to signal the redo availability
53 @signal cursorChanged(string, int, int) emitted when the cursor position
54 was changed
55 @signal editorAboutToBeSaved(string) emitted before the editor is saved
56 @signal editorSaved(string) emitted after the editor has been saved
57 @signal editorRenamed(string) emitted after the editor got a new name
58 (i.e. after a 'Save As')
59 @signal captionChanged(string, editor) emitted when the caption is
60 updated. Typically due to a readOnly attribute change.
61 @signal breakpointToggled(editor) emitted when a breakpoint is toggled
62 @signal bookmarkToggled(editor) emitted when a bookmark is toggled
63 @signal syntaxerrorToggled(editor) emitted when a syntax error was discovered
64 @signal autoCompletionAPIsAvailable(avail) emitted after the autocompletion
65 function has been configured
66 @signal coverageMarkersShown(boolean) emitted after the coverage markers have been
67 shown or cleared
68 @signal taskMarkersUpdated(editor) emitted when the task markers were updated
69 @signal showMenu(string, QMenu, editor) emitted when a menu is about to be shown.
70 The name of the menu, a reference to the menu and a reference to the
71 editor are given.
72 @signal languageChanged(language) emitted when the editors language was set. The
73 language is passed as a parameter.
74 @signal eolChanged(eol) emitted when the editors eol type was set. The eol string
75 is passed as a parameter.
76 @signal encodingChanged(encoding) emitted when the editors encoding was set. The
77 encoding name is passed as a parameter.
78 """
79 ClassID = 1
80 ClassProtectedID = 2
81 ClassPrivateID = 3
82 MethodID = 4
83 MethodProtectedID = 5
84 MethodPrivateID = 6
85 AttributeID = 7
86 AttributeProtectedID = 8
87 AttributePrivateID = 9
88 EnumID = 10
89
90 FromDocumentID = 99
91
92 TemplateImageID = 100
93
94 def __init__(self, dbs, fn = None, vm = None,
95 filetype = "", editor = None, tv = None):
96 """
97 Constructor
98
99 @param dbs reference to the debug server object
100 @param fn name of the file to be opened (string). If it is None,
101 a new (empty) editor is opened
102 @param vm reference to the view manager object (ViewManager.ViewManager)
103 @param filetype type of the source file (string)
104 @param editor reference to an Editor object, if this is a cloned view
105 @param tv reference to the task viewer object
106 """
107 QsciScintillaCompat.__init__(self)
108 self.setAttribute(Qt.WA_DeleteOnClose)
109 self.setAttribute(Qt.WA_KeyCompression)
110 self.setUtf8(True)
111
112 self.pyExtensions = dbs.getExtensions('Python')
113 self.py3Extensions = dbs.getExtensions('Python3')
114 self.rbExtensions = dbs.getExtensions('Ruby')
115
116 self.dbs = dbs
117 self.taskViewer = tv
118 self.fileName = fn
119 self.vm = vm
120 self.filetype = filetype
121 self.noName = ""
122
123 # clear some variables
124 self.lastHighlight = None # remember the last highlighted line
125 self.lastErrorMarker = None # remember the last error line
126 self.lastCurrMarker = None # remember the last current line
127
128 self.breaks = {} # key: marker handle,
129 # value: (lineno, condition, temporary,
130 # enabled, ignorecount)
131 self.bookmarks = [] # bookmarks are just a list of handles to the
132 # bookmark markers
133 self.syntaxerrors = {} # key: marker handle
134 # value: error message
135 self.notcoveredMarkers = [] # just a list of marker handles
136
137 self.condHistory = []
138 self.lexer_ = None
139 self.__lexerReset = False
140 self.completer = None
141 self.encoding = Preferences.getEditor("DefaultEncoding")
142 self.apiLanguage = ''
143 self.lastModified = 0
144 self.line = -1
145 self.inReopenPrompt = False
146 # true if the prompt to reload a changed source is present
147 self.inFileRenamed = False # true if we are propagating a rename action
148 self.inLanguageChanged = False # true if we are propagating a language change
149 self.inEolChanged = False # true if we are propagating an eol change
150 self.inEncodingChanged = False # true if we are propagating an encoding change
151 self.inDragDrop = False # true if we are in drop mode
152 self.__hasTaskMarkers = False # no task markers present
153
154 self.macros = {} # list of defined macros
155 self.curMacro = None
156 self.recording = False
157
158 self.acAPI = False
159
160 # list of clones
161 self.__clones = []
162
163 # clear QScintilla defined keyboard commands
164 # we do our own handling through the view manager
165 self.clearAlternateKeys()
166 self.clearKeys()
167
168 # initialise the mark occurrences timer
169 self.__markOccurrencesTimer = QTimer(self)
170 self.__markOccurrencesTimer.setSingleShot(True)
171 self.__markOccurrencesTimer.setInterval(
172 Preferences.getEditor("MarkOccurrencesTimeout"))
173 self.connect(self.__markOccurrencesTimer, SIGNAL("timeout()"),
174 self.__markOccurrences)
175 self.__markedText = ""
176
177 # initialise some spellchecking stuff
178 self.spell = None
179 self.lastLine = 0
180 self.lastIndex = 0
181
182 self.connect(self, SIGNAL('modificationChanged(bool)'),
183 self.__modificationChanged)
184 self.connect(self, SIGNAL('cursorPositionChanged(int,int)'),
185 self.__cursorPositionChanged)
186 self.connect(self, SIGNAL('modificationAttempted()'),
187 self.__modificationReadOnly)
188 self.connect(self, SIGNAL('userListActivated(int, const QString)'),
189 self.__completionListSelected)
190
191 # margins layout
192 if QSCINTILLA_VERSION() >= 0x020301:
193 self.__unifiedMargins = Preferences.getEditor("UnifiedMargins")
194 else:
195 self.__unifiedMargins = True
196
197 # define the margins markers
198 self.breakpoint = \
199 self.markerDefine(UI.PixmapCache.getPixmap("break.png"))
200 self.cbreakpoint = \
201 self.markerDefine(UI.PixmapCache.getPixmap("cBreak.png"))
202 self.tbreakpoint = \
203 self.markerDefine(UI.PixmapCache.getPixmap("tBreak.png"))
204 self.tcbreakpoint = \
205 self.markerDefine(UI.PixmapCache.getPixmap("tCBreak.png"))
206 self.dbreakpoint = \
207 self.markerDefine(UI.PixmapCache.getPixmap("breakDisabled.png"))
208 self.bookmark = \
209 self.markerDefine(UI.PixmapCache.getPixmap("bookmark.png"))
210 self.syntaxerror = \
211 self.markerDefine(UI.PixmapCache.getPixmap("syntaxError.png"))
212 self.notcovered = \
213 self.markerDefine(UI.PixmapCache.getPixmap("notcovered.png"))
214 self.taskmarker = \
215 self.markerDefine(UI.PixmapCache.getPixmap("task.png"))
216
217 # define the line markers
218 self.currentline = self.markerDefine(QsciScintilla.Background)
219 self.errorline = self.markerDefine(QsciScintilla.Background)
220 self.__setLineMarkerColours()
221
222 self.breakpointMask = (1 << self.breakpoint) | \
223 (1 << self.cbreakpoint) | \
224 (1 << self.tbreakpoint) | \
225 (1 << self.tcbreakpoint) | \
226 (1 << self.dbreakpoint)
227
228 # configure the margins
229 self.__setMarginsDisplay()
230
231 self.connect(self, SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
232 self.__marginClicked)
233
234 # set the eol mode
235 self.__setEolMode()
236
237 self.isResourcesFile = False
238 if editor is None:
239 if self.fileName is not None:
240 if (QFileInfo(self.fileName).size() / 1024) > \
241 Preferences.getEditor("WarnFilesize"):
242 res = QMessageBox.warning(None,
243 self.trUtf8("Open File"),
244 self.trUtf8("""<p>The size of the file <b>{0}</b>"""
245 """ is <b>{1} KB</b>."""
246 """ Do you really want to load it?</p>""")\
247 .format(self.fileName,
248 QFileInfo(self.fileName).size() / 1024),
249 QMessageBox.StandardButtons(\
250 QMessageBox.No | \
251 QMessageBox.Yes),
252 QMessageBox.No)
253 if res == QMessageBox.No or res == QMessageBox.Cancel:
254 raise IOError()
255 line0 = self.readLine0(self.fileName)
256 bindName = self.__bindName(line0)
257 self.__bindLexer(bindName)
258 self.readFile(self.fileName, True)
259 self.__bindLexer(bindName)
260 self.__bindCompleter(bindName)
261 self.__autoSyntaxCheck()
262 self.isResourcesFile = self.fileName.endswith(".qrc")
263
264 self.recolor()
265 else:
266 # clone the given editor
267 self.setDocument(editor.document())
268 self.breaks = editor.breaks
269 self.bookmarks = editor.bookmarks
270 self.syntaxerrors = editor.syntaxerrors
271 self.notcoveredMarkers = editor.notcoveredMarkers
272 self.isResourcesFile = editor.isResourcesFile
273 self.lastModified = editor.lastModified
274
275 self.addClone(editor)
276 editor.addClone(self)
277
278 self.gotoLine(0)
279
280 # set the text display
281 self.__setTextDisplay()
282
283 # set the autocompletion and calltips function
284 self.__acHookFunction = None
285 self.__setAutoCompletion()
286 self.__ctHookFunction = None
287 self.__setCallTips()
288
289 sh = self.sizeHint()
290 if sh.height() < 300:
291 sh.setHeight(300)
292 self.resize(sh)
293
294 # Make sure tabbing through a QWorkspace works.
295 self.setFocusPolicy(Qt.StrongFocus)
296
297 self.__updateReadOnly(True)
298
299 self.setWhatsThis(self.trUtf8(
300 """<b>A Source Editor Window</b>"""
301 """<p>This window is used to display and edit a source file."""
302 """ You can open as many of these as you like. The name of the file"""
303 """ is displayed in the window's titlebar.</p>"""
304 """<p>In order to set breakpoints just click in the space between"""
305 """ the line numbers and the fold markers. Via the context menu"""
306 """ of the margins they may be edited.</p>"""
307 """<p>In order to set bookmarks just Shift click in the space between"""
308 """ the line numbers and the fold markers.</p>"""
309 """<p>These actions can be reversed via the context menu.</p>"""
310 """<p>Ctrl clicking on a syntax error marker shows some info"""
311 """ about this error.</p>"""
312 ))
313
314 # Set the editors size, if it is too big for the view manager.
315 if self.vm is not None:
316 req = self.size()
317 bnd = req.boundedTo(self.vm.size())
318
319 if bnd.width() < req.width() or bnd.height() < req.height():
320 self.resize(bnd)
321
322 # set the autosave flag
323 self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
324 self.autosaveManuallyDisabled = False
325
326 self.__initContextMenu()
327 self.__initContextMenuMargins()
328
329 self.__checkEol()
330 if editor is None:
331 self.__checkLanguage()
332 self.__checkEncoding()
333 else:
334 # it's a clone
335 self.languageChanged(editor.apiLanguage, propagate = False)
336 self.__encodingChanged(editor.encoding, propagate = False)
337
338 self.coverageMarkersShown = False # flag remembering the current status of the
339 # code coverage markers
340
341 self.setAcceptDrops(True)
342
343 # breakpoint handling
344 self.breakpointModel = self.dbs.getBreakPointModel()
345 self.__restoreBreakpoints()
346 self.connect(self.breakpointModel,
347 SIGNAL("rowsAboutToBeRemoved(const QModelIndex &, int, int)"),
348 self.__deleteBreakPoints)
349 self.connect(self.breakpointModel,
350 SIGNAL("dataAboutToBeChanged(const QModelIndex &, const QModelIndex &)"),
351 self.__breakPointDataAboutToBeChanged)
352 self.connect(self.breakpointModel,
353 SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"),
354 self.__changeBreakPoints)
355 self.connect(self.breakpointModel,
356 SIGNAL("rowsInserted(const QModelIndex &, int, int)"),
357 self.__addBreakPoints)
358 self.connect(self, SIGNAL("linesChanged()"), self.__linesChanged)
359
360 # establish connection to some ViewManager action groups
361 self.addActions(self.vm.editorActGrp.actions())
362 self.addActions(self.vm.editActGrp.actions())
363 self.addActions(self.vm.copyActGrp.actions())
364 self.addActions(self.vm.viewActGrp.actions())
365
366 # register images to be shown in autocompletion lists
367 self.__registerImages()
368
369 def __registerImages(self):
370 """
371 Private method to register images for autocompletion lists.
372 """
373 self.registerImage(self.ClassID,
374 UI.PixmapCache.getPixmap("class.png"))
375 self.registerImage(self.ClassProtectedID,
376 UI.PixmapCache.getPixmap("class_protected.png"))
377 self.registerImage(self.ClassPrivateID,
378 UI.PixmapCache.getPixmap("class_private.png"))
379 self.registerImage(self.MethodID,
380 UI.PixmapCache.getPixmap("method.png"))
381 self.registerImage(self.MethodProtectedID,
382 UI.PixmapCache.getPixmap("method_protected.png"))
383 self.registerImage(self.MethodPrivateID,
384 UI.PixmapCache.getPixmap("method_private.png"))
385 self.registerImage(self.AttributeID,
386 UI.PixmapCache.getPixmap("attribute.png"))
387 self.registerImage(self.AttributeProtectedID,
388 UI.PixmapCache.getPixmap("attribute_protected.png"))
389 self.registerImage(self.AttributePrivateID,
390 UI.PixmapCache.getPixmap("attribute_private.png"))
391 self.registerImage(self.EnumID,
392 UI.PixmapCache.getPixmap("enum.png"))
393
394 self.registerImage(self.FromDocumentID,
395 UI.PixmapCache.getPixmap("editor.png"))
396
397 self.registerImage(self.TemplateImageID,
398 UI.PixmapCache.getPixmap("templateViewer.png"))
399
400 def addClone(self, editor):
401 """
402 Public method to add a clone to our list.
403
404 @param clone reference to the cloned editor (Editor)
405 """
406 self.__clones.append(editor)
407
408 self.connect(editor, SIGNAL('editorRenamed'), self.fileRenamed)
409 self.connect(editor, SIGNAL('languageChanged'), self.languageChanged)
410 self.connect(editor, SIGNAL('eolChanged'), self.__eolChanged)
411 self.connect(editor, SIGNAL('encodingChanged'), self.__encodingChanged)
412
413 def removeClone(self, editor):
414 """
415 Public method to remove a clone from our list.
416
417 @param clone reference to the cloned editor (Editor)
418 """
419 if editor in self.__clones:
420 self.disconnect(editor, SIGNAL('editorRenamed'), self.fileRenamed)
421 self.disconnect(editor, SIGNAL('languageChanged'), self.languageChanged)
422 self.disconnect(editor, SIGNAL('eolChanged'), self.__eolChanged)
423 self.disconnect(editor, SIGNAL('encodingChanged'), self.__encodingChanged)
424 self.__clones.remove(editor)
425
426 def __bindName(self, line0):
427 """
428 Private method to generate a dummy filename for binding a lexer.
429
430 @param line0 first line of text to use in the generation process (string)
431 """
432 bindName = self.fileName
433
434 if line0.startswith("<?xml"):
435 # override extension for XML files
436 bindName = "dummy.xml"
437
438 # check filetype
439 if self.filetype == "Python":
440 bindName = "dummy.py"
441 elif self.filetype == "Ruby":
442 bindName = "dummy.rb"
443 elif self.filetype == "D":
444 bindName = "dummy.d"
445 elif self.filetype == "Properties":
446 bindName = "dummy.ini"
447
448 # #! marker detection
449 if line0.startswith("#!"):
450 if "python3" in line0:
451 bindName = "dummy.py"
452 self.filetype = "Python3"
453 elif "python2" in line0:
454 bindName = "dummy.py"
455 self.filetype = "Python"
456 elif "python" in line0:
457 bindName = "dummy.py"
458 self.filetype = "Python"
459 elif ("/bash" in line0 or "/sh" in line0):
460 bindName = "dummy.sh"
461 elif "ruby" in line0:
462 bindName = "dummy.rb"
463 self.filetype = "Ruby"
464 elif "perl" in line0:
465 bindName = "dummy.pl"
466 elif "lua" in line0:
467 bindName = "dummy.lua"
468 elif "dmd" in line0:
469 bindName = "dummy.d"
470 self.filetype = "D"
471 return bindName
472
473 def getMenu(self, menuName):
474 """
475 Public method to get a reference to the main context menu or a submenu.
476
477 @param menuName name of the menu (string)
478 @return reference to the requested menu (QMenu) or None
479 """
480 try:
481 return self.__menus[menuName]
482 except KeyError:
483 return None
484
485 def hasMiniMenu(self):
486 """
487 Public method to check the miniMenu flag.
488
489 @return flag indicating a minimized context menu (boolean)
490 """
491 return self.miniMenu
492
493 def __initContextMenu(self):
494 """
495 Private method used to setup the context menu
496 """
497 self.miniMenu = Preferences.getEditor("MiniContextMenu")
498
499 self.menuActs = {}
500 self.menu = QMenu()
501 self.__menus = {
502 "Main" : self.menu,
503 }
504
505 self.languagesMenu = self.__initContextMenuLanguages()
506 self.__menus["Languages"] = self.languagesMenu
507 if self.isResourcesFile:
508 self.resourcesMenu = self.__initContextMenuResources()
509 self.__menus["Resources"] = self.resourcesMenu
510 else:
511 self.checksMenu = self.__initContextMenuChecks()
512 self.showMenu = self.__initContextMenuShow()
513 self.graphicsMenu = self.__initContextMenuGraphics()
514 self.autocompletionMenu = self.__initContextMenuAutocompletion()
515 self.__menus["Checks"] = self.checksMenu
516 self.__menus["Show"] = self.showMenu
517 self.__menus["Graphics"] = self.graphicsMenu
518 self.__menus["Autocompletion"] = self.autocompletionMenu
519 self.exportersMenu = self.__initContextMenuExporters()
520 self.__menus["Exporters"] = self.exportersMenu
521 self.eolMenu = self.__initContextMenuEol()
522 self.__menus["Eol"] = self.eolMenu
523 self.encodingsMenu = self.__initContextMenuEncodings()
524 self.__menus["Encodings"] = self.encodingsMenu
525
526 self.menuActs["Undo"] = \
527 self.menu.addAction(UI.PixmapCache.getIcon("editUndo.png"),
528 self.trUtf8('Undo'), self.undo)
529 self.menuActs["Redo"] = \
530 self.menu.addAction(UI.PixmapCache.getIcon("editRedo.png"),
531 self.trUtf8('Redo'), self.redo)
532 self.menuActs["Revert"] = \
533 self.menu.addAction(self.trUtf8("Revert to last saved state"),
534 self.revertToUnmodified)
535 self.menu.addSeparator()
536 self.menuActs["Cut"] = \
537 self.menu.addAction(UI.PixmapCache.getIcon("editCut.png"),
538 self.trUtf8('Cut'), self.cut)
539 self.menuActs["Copy"] = \
540 self.menu.addAction(UI.PixmapCache.getIcon("editCopy.png"),
541 self.trUtf8('Copy'), self.copy)
542 self.menu.addAction(UI.PixmapCache.getIcon("editPaste.png"),
543 self.trUtf8('Paste'), self.paste)
544 if not self.miniMenu:
545 self.menu.addSeparator()
546 self.menu.addAction(UI.PixmapCache.getIcon("editIndent.png"),
547 self.trUtf8('Indent'), self.indentLineOrSelection)
548 self.menu.addAction(UI.PixmapCache.getIcon("editUnindent.png"),
549 self.trUtf8('Unindent'), self.unindentLineOrSelection)
550 self.menuActs["Comment"] = \
551 self.menu.addAction(UI.PixmapCache.getIcon("editComment.png"),
552 self.trUtf8('Comment'), self.commentLineOrSelection)
553 self.menuActs["Uncomment"] = \
554 self.menu.addAction(UI.PixmapCache.getIcon("editUncomment.png"),
555 self.trUtf8('Uncomment'), self.uncommentLineOrSelection)
556 self.menuActs["StreamComment"] = \
557 self.menu.addAction(self.trUtf8('Stream Comment'),
558 self.streamCommentLineOrSelection)
559 self.menuActs["BoxComment"] = \
560 self.menu.addAction(self.trUtf8('Box Comment'),
561 self.boxCommentLineOrSelection)
562 self.menu.addSeparator()
563 self.menu.addAction(self.trUtf8('Select to brace'),
564 self.selectToMatchingBrace)
565 self.menu.addAction(self.trUtf8('Select all'), self.__selectAll)
566 self.menu.addAction(self.trUtf8('Deselect all'), self.__deselectAll)
567 self.menu.addSeparator()
568 self.menuActs["SpellCheck"] = \
569 self.menu.addAction(UI.PixmapCache.getIcon("spellchecking.png"),
570 self.trUtf8('Check spelling...'), self.checkSpelling)
571 self.menuActs["SpellCheckSelection"] = \
572 self.menu.addAction(UI.PixmapCache.getIcon("spellchecking.png"),
573 self.trUtf8('Check spelling of selection...'),
574 self.__checkSpellingSelection)
575 self.menuActs["SpellCheckRemove"] = \
576 self.menu.addAction(self.trUtf8("Remove from dictionary"),
577 self.__removeFromSpellingDictionary)
578 self.menu.addSeparator()
579 self.menu.addAction(self.trUtf8('Shorten empty lines'),
580 self.shortenEmptyLines)
581 self.menu.addSeparator()
582 self.menuActs["Languages"] = self.menu.addMenu(self.languagesMenu)
583 self.menuActs["Encodings"] = self.menu.addMenu(self.encodingsMenu)
584 self.menuActs["Eol"] = self.menu.addMenu(self.eolMenu)
585 self.menu.addSeparator()
586 self.menuActs["MonospacedFont"] = \
587 self.menu.addAction(self.trUtf8("Use Monospaced Font"),
588 self.handleMonospacedEnable)
589 self.menuActs["MonospacedFont"].setCheckable(True)
590 self.menuActs["MonospacedFont"].setChecked(self.useMonospaced)
591 self.menuActs["AutosaveEnable"] = \
592 self.menu.addAction(self.trUtf8("Autosave enabled"),
593 self.__autosaveEnable)
594 self.menuActs["AutosaveEnable"].setCheckable(True)
595 self.menuActs["AutosaveEnable"].setChecked(self.autosaveEnabled)
596 self.menuActs["TypingAidsEnabled"] = \
597 self.menu.addAction(self.trUtf8("Typing aids enabled"),
598 self.__toggleTypingAids)
599 self.menuActs["TypingAidsEnabled"].setCheckable(True)
600 self.menuActs["TypingAidsEnabled"].setEnabled(self.completer is not None)
601 self.menuActs["TypingAidsEnabled"].setChecked(\
602 self.completer is not None and self.completer.isEnabled())
603 self.menuActs["AutoCompletionEnable"] = \
604 self.menu.addAction(self.trUtf8("Autocompletion enabled"),
605 self.__toggleAutoCompletionEnable)
606 self.menuActs["AutoCompletionEnable"].setCheckable(True)
607 self.menuActs["AutoCompletionEnable"].setChecked(\
608 self.autoCompletionThreshold() != -1)
609 if not self.isResourcesFile:
610 self.menu.addMenu(self.autocompletionMenu)
611 self.menu.addSeparator()
612 if self.isResourcesFile:
613 self.menu.addMenu(self.resourcesMenu)
614 else:
615 self.menuActs["Check"] = self.menu.addMenu(self.checksMenu)
616 self.menu.addSeparator()
617 self.menuActs["Show"] = self.menu.addMenu(self.showMenu)
618 self.menu.addSeparator()
619 self.menuActs["Diagrams"] = self.menu.addMenu(self.graphicsMenu)
620 self.menu.addSeparator()
621 self.menu.addAction(self.trUtf8('New view'), self.__newView)
622 act = self.menu.addAction(self.trUtf8('New view (with new split)'),
623 self.__newViewNewSplit)
624 if not self.vm.canSplit():
625 act.setEnabled(False)
626 self.menu.addAction(UI.PixmapCache.getIcon("close.png"),
627 self.trUtf8('Close'), self.__contextClose)
628 self.menu.addSeparator()
629 self.menuActs["Save"] = \
630 self.menu.addAction(UI.PixmapCache.getIcon("fileSave.png"),
631 self.trUtf8('Save'), self.__contextSave)
632 self.menu.addAction(UI.PixmapCache.getIcon("fileSaveAs.png"),
633 self.trUtf8('Save As...'), self.__contextSaveAs)
634 if not self.miniMenu:
635 self.menu.addMenu(self.exportersMenu)
636 self.menu.addSeparator()
637 self.menu.addAction(UI.PixmapCache.getIcon("printPreview.png"),
638 self.trUtf8("Print Preview"), self.printPreviewFile)
639 self.menu.addAction(UI.PixmapCache.getIcon("print.png"),
640 self.trUtf8('Print'), self.printFile)
641
642 self.connect(self.menu, SIGNAL('aboutToShow()'), self.__showContextMenu)
643
644 self.spellingMenu = QMenu()
645 self.__menus["Spelling"] = self.spellingMenu
646
647 self.connect(self.spellingMenu, SIGNAL('aboutToShow()'),
648 self.__showContextMenuSpelling)
649 self.connect(self.spellingMenu, SIGNAL('triggered(QAction *)'),
650 self.__contextMenuSpellingTriggered)
651
652 def __initContextMenuAutocompletion(self):
653 """
654 Private method used to setup the Checks context sub menu.
655 """
656 menu = QMenu(self.trUtf8('Autocomplete'))
657
658 self.menuActs["acDynamic"] = \
659 menu.addAction(self.trUtf8('dynamic'),
660 self.autoComplete)
661 menu.addSeparator()
662 menu.addAction(self.trUtf8('from Document'),
663 self.autoCompleteFromDocument)
664 self.menuActs["acAPI"] = \
665 menu.addAction(self.trUtf8('from APIs'),
666 self.autoCompleteFromAPIs)
667 self.menuActs["acAPIDocument"] = \
668 menu.addAction(self.trUtf8('from Document and APIs'),
669 self.autoCompleteFromAll)
670 menu.addSeparator()
671 self.menuActs["calltip"] = \
672 menu.addAction(self.trUtf8('Calltip'), self.callTip)
673
674 self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuAutocompletion)
675
676 return menu
677
678 def __initContextMenuChecks(self):
679 """
680 Private method used to setup the Checks context sub menu.
681 """
682 menu = QMenu(self.trUtf8('Check'))
683 self.connect(menu, SIGNAL("aboutToShow()"), self.__showContextMenuChecks)
684 return menu
685
686 def __initContextMenuShow(self):
687 """
688 Private method used to setup the Show context sub menu.
689 """
690 menu = QMenu(self.trUtf8('Show'))
691
692 menu.addAction(self.trUtf8('Code metrics...'), self.__showCodeMetrics)
693 self.coverageMenuAct = \
694 menu.addAction(self.trUtf8('Code coverage...'), self.__showCodeCoverage)
695 self.coverageShowAnnotationMenuAct = \
696 menu.addAction(self.trUtf8('Show code coverage annotations'),
697 self.__codeCoverageShowAnnotations)
698 self.coverageHideAnnotationMenuAct = \
699 menu.addAction(self.trUtf8('Hide code coverage annotations'),
700 self.__codeCoverageHideAnnotations)
701 self.profileMenuAct = \
702 menu.addAction(self.trUtf8('Profile data...'), self.__showProfileData)
703
704 self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuShow)
705
706 return menu
707
708 def __initContextMenuGraphics(self):
709 """
710 Private method used to setup the diagrams context sub menu.
711 """
712 menu = QMenu(self.trUtf8('Diagrams'))
713
714 menu.addAction(self.trUtf8('Class Diagram...'),
715 self.__showClassDiagram)
716 menu.addAction(self.trUtf8('Package Diagram...'),
717 self.__showPackageDiagram)
718 menu.addAction(self.trUtf8('Imports Diagram...'),
719 self.__showImportsDiagram)
720 self.applicationDiagramMenuAct = \
721 menu.addAction(self.trUtf8('Application Diagram...'),
722 self.__showApplicationDiagram)
723
724 self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuGraphics)
725
726 return menu
727
728 def __initContextMenuLanguages(self):
729 """
730 Private method used to setup the Languages context sub menu.
731 """
732 menu = QMenu(self.trUtf8("Languages"))
733
734 self.languagesActGrp = QActionGroup(self)
735 self.noLanguageAct = menu.addAction(self.trUtf8("No Language"))
736 self.noLanguageAct.setCheckable(True)
737 self.noLanguageAct.setData(QVariant("None"))
738 self.languagesActGrp.addAction(self.noLanguageAct)
739 menu.addSeparator()
740
741 self.supportedLanguages = {}
742 supportedLanguages = Lexers.getSupportedLanguages()
743 languages = supportedLanguages.keys()
744 languages.sort()
745 for language in languages:
746 if language != "Guessed":
747 self.supportedLanguages[language] = supportedLanguages[language][:]
748 act = menu.addAction(self.supportedLanguages[language][0])
749 act.setCheckable(True)
750 act.setData(QVariant(language))
751 self.supportedLanguages[language].append(act)
752 self.languagesActGrp.addAction(act)
753
754 menu.addSeparator()
755 self.pygmentsAct = menu.addAction(self.trUtf8("Guessed"))
756 self.pygmentsAct.setCheckable(True)
757 self.pygmentsAct.setData(QVariant("Guessed"))
758 self.languagesActGrp.addAction(self.pygmentsAct)
759 self.pygmentsSelAct = menu.addAction(self.trUtf8("Alternatives"))
760 self.pygmentsSelAct.setData(QVariant("Alternatives"))
761
762 self.connect(menu, SIGNAL('triggered(QAction *)'), self.__languageMenuTriggered)
763 self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuLanguages)
764
765 return menu
766
767 def __initContextMenuEncodings(self):
768 """
769 Private method used to setup the Encodings context sub menu.
770 """
771 self.supportedEncodings = {}
772
773 menu = QMenu(self.trUtf8("Encodings"))
774
775 self.encodingsActGrp = QActionGroup(self)
776
777 for encoding in sorted(Utilities.supportedCodecs):
778 act = menu.addAction(encoding)
779 act.setCheckable(True)
780 act.setData(QVariant(encoding))
781 self.supportedEncodings[encoding] = act
782 self.encodingsActGrp.addAction(act)
783
784 self.connect(menu, SIGNAL('triggered(QAction *)'), self.__encodingsMenuTriggered)
785 self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuEncodings)
786
787 return menu
788
789 def __initContextMenuEol(self):
790 """
791 Private method to setup the eol context sub menu.
792 """
793 self.supportedEols = {}
794
795 menu = QMenu(self.trUtf8("End-of-Line Type"))
796
797 self.eolActGrp = QActionGroup(self)
798
799 act = menu.addAction(self.trUtf8("Unix"))
800 act.setCheckable(True)
801 act.setData(QVariant('\n'))
802 self.supportedEols['\n'] = act
803 self.eolActGrp.addAction(act)
804
805 act = menu.addAction(self.trUtf8("Windows"))
806 act.setCheckable(True)
807 act.setData(QVariant('\r\n'))
808 self.supportedEols['\r\n'] = act
809 self.eolActGrp.addAction(act)
810
811 act = menu.addAction(self.trUtf8("Macintosh"))
812 act.setCheckable(True)
813 act.setData(QVariant('\r'))
814 self.supportedEols['\r'] = act
815 self.eolActGrp.addAction(act)
816
817 self.connect(menu, SIGNAL('triggered(QAction *)'), self.__eolMenuTriggered)
818 self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuEol)
819
820 return menu
821
822 def __initContextMenuExporters(self):
823 """
824 Private method used to setup the Exporters context sub menu.
825 """
826 menu = QMenu(self.trUtf8("Export as"))
827
828 supportedExporters = Exporters.getSupportedFormats()
829 exporters = supportedExporters.keys()
830 exporters.sort()
831 for exporter in exporters:
832 act = menu.addAction(supportedExporters[exporter])
833 act.setData(QVariant(exporter))
834
835 self.connect(menu, SIGNAL('triggered(QAction *)'), self.__exportMenuTriggered)
836
837 return menu
838
839 def __initContextMenuMargins(self):
840 """
841 Private method used to setup the context menu for the margins
842 """
843 self.marginMenuActs = {}
844
845 if self.__unifiedMargins:
846 self.__initContextMenuUnifiedMargins()
847 else:
848 self.__initContextMenuSeparateMargins()
849
850 def __initContextMenuSeparateMargins(self):
851 """
852 Private method used to setup the context menu for the separated margins
853 """
854 # bookmark margin
855 self.bmMarginMenu = QMenu()
856
857 self.bmMarginMenu.addAction(self.trUtf8('Toggle bookmark'),
858 self.menuToggleBookmark)
859 self.marginMenuActs["NextBookmark"] = \
860 self.bmMarginMenu.addAction(self.trUtf8('Next bookmark'),
861 self.nextBookmark)
862 self.marginMenuActs["PreviousBookmark"] = \
863 self.bmMarginMenu.addAction(self.trUtf8('Previous bookmark'),
864 self.previousBookmark)
865 self.marginMenuActs["ClearBookmark"] = \
866 self.bmMarginMenu.addAction(self.trUtf8('Clear all bookmarks'),
867 self.clearBookmarks)
868
869 self.connect(self.bmMarginMenu, SIGNAL('aboutToShow()'),
870 self.__showContextMenuMargin)
871
872 # breakpoint margin
873 self.bpMarginMenu = QMenu()
874
875 self.marginMenuActs["Breakpoint"] = \
876 self.bpMarginMenu.addAction(self.trUtf8('Toggle breakpoint'),
877 self.menuToggleBreakpoint)
878 self.marginMenuActs["TempBreakpoint"] = \
879 self.bpMarginMenu.addAction(self.trUtf8('Toggle temporary breakpoint'),
880 self.__menuToggleTemporaryBreakpoint)
881 self.marginMenuActs["EditBreakpoint"] = \
882 self.bpMarginMenu.addAction(self.trUtf8('Edit breakpoint...'),
883 self.menuEditBreakpoint)
884 self.marginMenuActs["EnableBreakpoint"] = \
885 self.bpMarginMenu.addAction(self.trUtf8('Enable breakpoint'),
886 self.__menuToggleBreakpointEnabled)
887 self.marginMenuActs["NextBreakpoint"] = \
888 self.bpMarginMenu.addAction(self.trUtf8('Next breakpoint'),
889 self.menuNextBreakpoint)
890 self.marginMenuActs["PreviousBreakpoint"] = \
891 self.bpMarginMenu.addAction(self.trUtf8('Previous breakpoint'),
892 self.menuPreviousBreakpoint)
893 self.marginMenuActs["ClearBreakpoint"] = \
894 self.bpMarginMenu.addAction(self.trUtf8('Clear all breakpoints'),
895 self.__menuClearBreakpoints)
896
897 self.connect(self.bpMarginMenu, SIGNAL('aboutToShow()'),
898 self.__showContextMenuMargin)
899
900 # indicator margin
901 self.indicMarginMenu = QMenu()
902
903 self.marginMenuActs["GotoSyntaxError"] = \
904 self.indicMarginMenu.addAction(self.trUtf8('Goto syntax error'),
905 self.gotoSyntaxError)
906 self.marginMenuActs["ShowSyntaxError"] = \
907 self.indicMarginMenu.addAction(self.trUtf8('Show syntax error message'),
908 self.__showSyntaxError)
909 self.marginMenuActs["ClearSyntaxError"] = \
910 self.indicMarginMenu.addAction(self.trUtf8('Clear syntax error'),
911 self.clearSyntaxError)
912 self.indicMarginMenu.addSeparator()
913 self.marginMenuActs["NextCoverageMarker"] = \
914 self.indicMarginMenu.addAction(self.trUtf8('Next uncovered line'),
915 self.nextUncovered)
916 self.marginMenuActs["PreviousCoverageMarker"] = \
917 self.indicMarginMenu.addAction(self.trUtf8('Previous uncovered line'),
918 self.previousUncovered)
919 self.indicMarginMenu.addSeparator()
920 self.marginMenuActs["NextTaskMarker"] = \
921 self.indicMarginMenu.addAction(self.trUtf8('Next task'),
922 self.nextTask)
923 self.marginMenuActs["PreviousTaskMarker"] = \
924 self.indicMarginMenu.addAction(self.trUtf8('Previous task'),
925 self.previousTask)
926
927 self.connect(self.indicMarginMenu, SIGNAL('aboutToShow()'),
928 self.__showContextMenuMargin)
929
930 def __initContextMenuUnifiedMargins(self):
931 """
932 Private method used to setup the context menu for the unified margins
933 """
934 self.marginMenu = QMenu()
935
936 self.marginMenu.addAction(self.trUtf8('Toggle bookmark'),
937 self.menuToggleBookmark)
938 self.marginMenuActs["NextBookmark"] = \
939 self.marginMenu.addAction(self.trUtf8('Next bookmark'),
940 self.nextBookmark)
941 self.marginMenuActs["PreviousBookmark"] = \
942 self.marginMenu.addAction(self.trUtf8('Previous bookmark'),
943 self.previousBookmark)
944 self.marginMenuActs["ClearBookmark"] = \
945 self.marginMenu.addAction(self.trUtf8('Clear all bookmarks'),
946 self.clearBookmarks)
947 self.marginMenu.addSeparator()
948 self.marginMenuActs["GotoSyntaxError"] = \
949 self.marginMenu.addAction(self.trUtf8('Goto syntax error'),
950 self.gotoSyntaxError)
951 self.marginMenuActs["ShowSyntaxError"] = \
952 self.marginMenu.addAction(self.trUtf8('Show syntax error message'),
953 self.__showSyntaxError)
954 self.marginMenuActs["ClearSyntaxError"] = \
955 self.marginMenu.addAction(self.trUtf8('Clear syntax error'),
956 self.clearSyntaxError)
957 self.marginMenu.addSeparator()
958 self.marginMenuActs["Breakpoint"] = \
959 self.marginMenu.addAction(self.trUtf8('Toggle breakpoint'),
960 self.menuToggleBreakpoint)
961 self.marginMenuActs["TempBreakpoint"] = \
962 self.marginMenu.addAction(self.trUtf8('Toggle temporary breakpoint'),
963 self.__menuToggleTemporaryBreakpoint)
964 self.marginMenuActs["EditBreakpoint"] = \
965 self.marginMenu.addAction(self.trUtf8('Edit breakpoint...'),
966 self.menuEditBreakpoint)
967 self.marginMenuActs["EnableBreakpoint"] = \
968 self.marginMenu.addAction(self.trUtf8('Enable breakpoint'),
969 self.__menuToggleBreakpointEnabled)
970 self.marginMenuActs["NextBreakpoint"] = \
971 self.marginMenu.addAction(self.trUtf8('Next breakpoint'),
972 self.menuNextBreakpoint)
973 self.marginMenuActs["PreviousBreakpoint"] = \
974 self.marginMenu.addAction(self.trUtf8('Previous breakpoint'),
975 self.menuPreviousBreakpoint)
976 self.marginMenuActs["ClearBreakpoint"] = \
977 self.marginMenu.addAction(self.trUtf8('Clear all breakpoints'),
978 self.__menuClearBreakpoints)
979 self.marginMenu.addSeparator()
980 self.marginMenuActs["NextCoverageMarker"] = \
981 self.marginMenu.addAction(self.trUtf8('Next uncovered line'),
982 self.nextUncovered)
983 self.marginMenuActs["PreviousCoverageMarker"] = \
984 self.marginMenu.addAction(self.trUtf8('Previous uncovered line'),
985 self.previousUncovered)
986 self.marginMenu.addSeparator()
987 self.marginMenuActs["NextTaskMarker"] = \
988 self.marginMenu.addAction(self.trUtf8('Next task'),
989 self.nextTask)
990 self.marginMenuActs["PreviousTaskMarker"] = \
991 self.marginMenu.addAction(self.trUtf8('Previous task'),
992 self.previousTask)
993 self.marginMenu.addSeparator()
994 self.marginMenuActs["LMBbookmarks"] = \
995 self.marginMenu.addAction(self.trUtf8('LMB toggles bookmarks'),
996 self.__lmBbookmarks)
997 self.marginMenuActs["LMBbookmarks"].setCheckable(True)
998 self.marginMenuActs["LMBbookmarks"].setChecked(False)
999 self.marginMenuActs["LMBbreakpoints"] = \
1000 self.marginMenu.addAction(self.trUtf8('LMB toggles breakpoints'),
1001 self.__lmBbreakpoints)
1002 self.marginMenuActs["LMBbreakpoints"].setCheckable(True)
1003 self.marginMenuActs["LMBbreakpoints"].setChecked(True)
1004
1005 self.connect(self.marginMenu, SIGNAL('aboutToShow()'),
1006 self.__showContextMenuMargin)
1007
1008 def __exportMenuTriggered(self, act):
1009 """
1010 Private method to handle the selection of an export format.
1011
1012 @param act reference to the action that was triggered (QAction)
1013 """
1014 exporterFormat = act.data().toString()
1015 self.exportFile(exporterFormat)
1016
1017 def exportFile(self, exporterFormat):
1018 """
1019 Public method to export the file.
1020
1021 @param exporterFormat format the file should be exported into (string)
1022 """
1023 if exporterFormat:
1024 exporter = Exporters.getExporter(exporterFormat, self)
1025 if exporter:
1026 exporter.exportSource()
1027 else:
1028 QMessageBox.critical(self,
1029 self.trUtf8("Export source"),
1030 self.trUtf8("""<p>No exporter available for the """
1031 """export format <b>{0}</b>. Aborting...</p>""")\
1032 .format(exporterFormat),
1033 QMessageBox.StandardButtons(\
1034 QMessageBox.Ok))
1035 else:
1036 QMessageBox.critical(self,
1037 self.trUtf8("Export source"),
1038 self.trUtf8("""No export format given. Aborting..."""),
1039 QMessageBox.StandardButtons(\
1040 QMessageBox.Ok))
1041
1042 def __showContextMenuLanguages(self):
1043 """
1044 Private slot handling the aboutToShow signal of the languages context menu.
1045 """
1046 if self.apiLanguage.startswith("Pygments|"):
1047 self.pygmentsSelAct.setText(
1048 self.trUtf8("Alternatives ({0})").format(self.getLanguage()))
1049 else:
1050 self.pygmentsSelAct.setText(self.trUtf8("Alternatives"))
1051 self.emit(SIGNAL("showMenu"), "Languages", self.languagesMenu, self)
1052
1053 def __selectPygmentsLexer(self):
1054 """
1055 Private method to select a specific pygments lexer.
1056
1057 @return name of the selected pygments lexer (string)
1058 """
1059 from pygments.lexers import get_all_lexers
1060 lexerList = sorted([l[0] for l in get_all_lexers()])
1061 try:
1062 lexerSel = lexerList.index(self.getLanguage())
1063 except ValueError:
1064 lexerSel = 0
1065 lexerName, ok = QInputDialog.getItem(\
1066 self,
1067 self.trUtf8("Pygments Lexer"),
1068 self.trUtf8("Select the Pygments lexer to apply."),
1069 lexerList,
1070 lexerSel,
1071 False)
1072 if ok and lexerName:
1073 return lexerName
1074 else:
1075 return ""
1076
1077 def __languageMenuTriggered(self, act):
1078 """
1079 Private method to handle the selection of a lexer language.
1080
1081 @param act reference to the action that was triggered (QAction)
1082 """
1083 if act == self.noLanguageAct:
1084 self.__resetLanguage()
1085 elif act == self.pygmentsAct:
1086 self.setLanguage("dummy.pygments")
1087 elif act == self.pygmentsSelAct:
1088 language = self.__selectPygmentsLexer()
1089 if language:
1090 self.setLanguage("dummy.pygments", pyname = language)
1091 else:
1092 language = act.data().toString()
1093 if language:
1094 self.setLanguage(self.supportedLanguages[language][1])
1095
1096 def languageChanged(self, language, propagate = True):
1097 """
1098 Public slot handling a change of a connected editor's language.
1099
1100 @param language language to be set (string)
1101 @keyparam propagate flag indicating to propagate the change (boolean)
1102 """
1103 if language == '':
1104 self.__resetLanguage(propagate = propagate)
1105 elif language == "Guessed":
1106 self.setLanguage("dummy.pygments")
1107 elif language.startswith("Pygments|"):
1108 pyname = language.split("|", 1)[1]
1109 self.setLanguage("dummy.pygments", pyname = pyname)
1110 else:
1111 self.setLanguage(self.supportedLanguages[language][1], propagate = propagate)
1112
1113 def __resetLanguage(self, propagate = True):
1114 """
1115 Private method used to reset the language selection.
1116
1117 @keyparam propagate flag indicating to propagate the change (boolean)
1118 """
1119 if self.lexer_ is not None and \
1120 (self.lexer_.lexer() == "container" or self.lexer_.lexer() is None):
1121 self.disconnect(self, SIGNAL("SCN_STYLENEEDED(int)"), self.__styleNeeded)
1122
1123 self.apiLanguage = ""
1124 self.lexer_ = None
1125 self.__lexerReset = True
1126 self.setLexer()
1127 self.setMonospaced(self.useMonospaced)
1128 if self.completer is not None:
1129 self.completer.setEnabled(False)
1130 self.completer = None
1131 self.__setTextDisplay()
1132
1133 if not self.inLanguageChanged and propagate:
1134 self.inLanguageChanged = True
1135 self.emit(SIGNAL('languageChanged'), self.apiLanguage)
1136 self.inLanguageChanged = False
1137
1138 def setLanguage(self, filename, initTextDisplay = True, propagate = True,
1139 pyname = ""):
1140 """
1141 Public method to set a lexer language.
1142
1143 @param filename filename used to determine the associated lexer language (string)
1144 @param initTextDisplay flag indicating an initialization of the text display
1145 is required as well (boolean)
1146 @keyparam propagate flag indicating to propagate the change (boolean)
1147 @keyparam pyname name of the pygments lexer to use (string)
1148 """
1149 self.__lexerReset = False
1150 self.__bindLexer(filename, pyname = pyname)
1151 self.__bindCompleter(filename)
1152 self.recolor()
1153 self.__checkLanguage()
1154
1155 # set the text display
1156 if initTextDisplay:
1157 self.__setTextDisplay()
1158
1159 # set the autocompletion and calltips function
1160 self.__setAutoCompletion()
1161 self.__setCallTips()
1162
1163 if not self.inLanguageChanged and propagate:
1164 self.inLanguageChanged = True
1165 self.emit(SIGNAL('languageChanged'), self.apiLanguage)
1166 self.inLanguageChanged = False
1167
1168 def __checkLanguage(self):
1169 """
1170 Private method to check the selected language of the language submenu.
1171 """
1172 if self.apiLanguage == "":
1173 self.noLanguageAct.setChecked(True)
1174 elif self.apiLanguage == "Guessed":
1175 self.pygmentsAct.setChecked(True)
1176 elif self.apiLanguage.startswith("Pygments|"):
1177 act = self.languagesActGrp.checkedAction()
1178 if act:
1179 act.setChecked(False)
1180 else:
1181 self.supportedLanguages[self.apiLanguage][2].setChecked(True)
1182
1183 def projectLexerAssociationsChanged(self):
1184 """
1185 Public slot to handle changes of the project lexer associations.
1186 """
1187 self.setLanguage(self.fileName)
1188
1189 def __showContextMenuEncodings(self):
1190 """
1191 Private slot handling the aboutToShow signal of the encodings context menu.
1192 """
1193 self.emit(SIGNAL("showMenu"), "Encodings", self.encodingsMenu, self)
1194
1195 def __encodingsMenuTriggered(self, act):
1196 """
1197 Private method to handle the selection of an encoding.
1198
1199 @param act reference to the action that was triggered (QAction)
1200 """
1201 encoding = act.data().toString()
1202 self.__encodingChanged("%s-selected" % encoding)
1203
1204 def __checkEncoding(self):
1205 """
1206 Private method to check the selected encoding of the encodings submenu.
1207 """
1208 try:
1209 self.supportedEncodings[self.__normalizedEncoding()].setChecked(True)
1210 except (AttributeError, KeyError):
1211 pass
1212
1213 def __encodingChanged(self, encoding, propagate = True):
1214 """
1215 Private slot to handle a change of the encoding.
1216
1217 @keyparam propagate flag indicating to propagate the change (boolean)
1218 """
1219 self.encoding = encoding
1220 self.__checkEncoding()
1221
1222 if not self.inEncodingChanged and propagate:
1223 self.inEncodingChanged = True
1224 self.emit(SIGNAL("encodingChanged"), self.encoding)
1225 self.inEncodingChanged = False
1226
1227 def __normalizedEncoding(self):
1228 """
1229 Private method to calculate the normalized encoding string.
1230
1231 @return normalized encoding (string)
1232 """
1233 return self.encoding.replace("-default", "")\
1234 .replace("-guessed", "")\
1235 .replace("-selected", "")
1236
1237 def __showContextMenuEol(self):
1238 """
1239 Private slot handling the aboutToShow signal of the eol context menu.
1240 """
1241 self.emit(SIGNAL("showMenu"), "Eol", self.eolMenu, self)
1242
1243 def __eolMenuTriggered(self, act):
1244 """
1245 Private method to handle the selection of an eol type.
1246
1247 @param act reference to the action that was triggered (QAction)
1248 """
1249 eol = act.data().toString()
1250 self.setEolModeByEolString(eol)
1251 self.convertEols(self.eolMode())
1252
1253 def __checkEol(self):
1254 """
1255 Private method to check the selected eol type of the eol submenu.
1256 """
1257 try:
1258 self.supportedEols[self.getLineSeparator()].setChecked(True)
1259 except AttributeError:
1260 pass
1261
1262 def __eolChanged(self):
1263 """
1264 Private slot to handle a change of the eol mode.
1265 """
1266 self.__checkEol()
1267
1268 if not self.inEolChanged:
1269 self.inEolChanged = True
1270 eol = self.getLineSeparator()
1271 self.emit(SIGNAL("eolChanged"), eol)
1272 self.inEolChanged = False
1273
1274 def __bindLexer(self, filename, pyname = ""):
1275 """
1276 Private slot to set the correct lexer depending on language.
1277
1278 @param filename filename used to determine the associated lexer language (string)
1279 @keyparam pyname name of the pygments lexer to use (string)
1280 """
1281 if self.lexer_ is not None and \
1282 (self.lexer_.lexer() == "container" or self.lexer_.lexer() is None):
1283 self.disconnect(self, SIGNAL("SCN_STYLENEEDED(int)"), self.__styleNeeded)
1284
1285 language = ""
1286 project = e4App().getObject("Project")
1287 if project.isOpen() and project.isProjectFile(filename):
1288 language = project.getEditorLexerAssoc(os.path.basename(filename))
1289 if not language:
1290 filename = os.path.basename(filename)
1291 language = Preferences.getEditorLexerAssoc(filename)
1292 if language.startswith("Pygments|"):
1293 pyname = language.split("|", 1)[1]
1294 language = ""
1295
1296 self.lexer_ = Lexers.getLexer(language, self, pyname = pyname)
1297 if self.lexer_ is None:
1298 self.setLexer()
1299 self.apiLanguage = ""
1300 return
1301
1302 if pyname:
1303 self.apiLanguage = "Pygments|%s" % pyname
1304 else:
1305 self.apiLanguage = self.lexer_.language()
1306 if self.apiLanguage == "POV":
1307 self.apiLanguage = "Povray"
1308 self.setLexer(self.lexer_)
1309 self.__setMarginsDisplay()
1310 if self.lexer_.lexer() == "container" or self.lexer_.lexer() is None:
1311 self.setStyleBits(self.lexer_.styleBitsNeeded())
1312 self.connect(self, SIGNAL("SCN_STYLENEEDED(int)"), self.__styleNeeded)
1313
1314 # get the font for style 0 and set it as the default font
1315 key = 'Scintilla/%s/style0/font' % self.lexer_.language()
1316 fontVariant = Preferences.Prefs.settings.value(key)
1317 if fontVariant.isValid():
1318 fdesc = fontVariant.toStringList()
1319 font = QFont(fdesc[0], int(fdesc[1]))
1320 self.lexer_.setDefaultFont(font)
1321 self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla")
1322
1323 # now set the lexer properties
1324 self.lexer_.initProperties()
1325
1326 # initialize the auto indent style of the lexer
1327 ais = self.lexer_.autoIndentStyle()
1328
1329 # initialize the lexer APIs settings
1330 api = self.vm.getAPIsManager().getAPIs(self.apiLanguage)
1331 if api is not None:
1332 self.lexer_.setAPIs(api.getQsciAPIs())
1333 self.acAPI = True
1334 else:
1335 self.acAPI = False
1336 self.emit(SIGNAL("autoCompletionAPIsAvailable"), self.acAPI)
1337
1338 def __styleNeeded(self, position):
1339 """
1340 Private slot to handle the need for more styling.
1341
1342 @param position end position, that needs styling (integer)
1343 """
1344 self.lexer_.styleText(self.getEndStyled(), position)
1345
1346 def getLexer(self):
1347 """
1348 Public method to retrieve a reference to the lexer object.
1349
1350 @return the lexer object (Lexer)
1351 """
1352 return self.lexer_
1353
1354 def getLanguage(self):
1355 """
1356 Public method to retrieve the language of the editor.
1357
1358 @return language of the editor (string)
1359 """
1360 if self.apiLanguage == "Guessed" or self.apiLanguage.startswith("Pygments|"):
1361 return self.lexer_.name()
1362 else:
1363 return self.apiLanguage
1364
1365 def __bindCompleter(self, filename):
1366 """
1367 Private slot to set the correct typing completer depending on language.
1368
1369 @param filename filename used to determine the associated typing
1370 completer language (string)
1371 """
1372 if self.completer is not None:
1373 self.completer.setEnabled(False)
1374 self.completer = None
1375
1376 filename = os.path.basename(filename)
1377 apiLanguage = Preferences.getEditorLexerAssoc(filename)
1378
1379 self.completer = TypingCompleters.getCompleter(apiLanguage, self)
1380
1381 def getCompleter(self):
1382 """
1383 Public method to retrieve a reference to the completer object.
1384
1385 @return the completer object (CompleterBase)
1386 """
1387 return self.completer
1388
1389 def __modificationChanged(self, m):
1390 """
1391 Private slot to handle the modificationChanged signal.
1392
1393 It emits the signal modificationStatusChanged with parameters
1394 m and self.
1395
1396 @param m modification status
1397 """
1398 if not m and self.fileName is not None:
1399 self.lastModified = QFileInfo(self.fileName).lastModified()
1400 if Preferences.getEditor("AutoCheckSyntax"):
1401 self.clearSyntaxError()
1402 self.emit(SIGNAL('modificationStatusChanged'), m, self)
1403 self.emit(SIGNAL('undoAvailable'), self.isUndoAvailable())
1404 self.emit(SIGNAL('redoAvailable'), self.isRedoAvailable())
1405
1406 def __cursorPositionChanged(self, line, index):
1407 """
1408 Private slot to handle the cursorPositionChanged signal.
1409
1410 It emits the signal cursorChanged with parameters fileName,
1411 line and pos.
1412
1413 @param line line number of the cursor
1414 @param index position in line of the cursor
1415 """
1416 self.emit(SIGNAL('cursorChanged'), self.fileName, line+1, index)
1417
1418 if Preferences.getEditor("MarkOccurrencesEnabled"):
1419 self.__markOccurrencesTimer.stop()
1420 self.__markOccurrencesTimer.start()
1421
1422 if self.spell is not None:
1423 # do spell checking
1424 doSpelling = True
1425 if self.lastLine == line:
1426 start, end = self.getWordBoundaries(line, index, useWordChars = False)
1427 if start <= self.lastIndex and self.lastIndex <= end:
1428 doSpelling = False
1429 if doSpelling:
1430 pos = self.positionFromLineIndex(self.lastLine, self.lastIndex)
1431 self.spell.checkWord(pos)
1432
1433 self.lastLine = line
1434 self.lastIndex = index
1435
1436 def __modificationReadOnly(self):
1437 """
1438 Private slot to handle the modificationAttempted signal.
1439 """
1440 QMessageBox.warning(None,
1441 self.trUtf8("Modification of Read Only file"),
1442 self.trUtf8("""You are attempting to change a read only file. """
1443 """Please save to a different file first."""))
1444
1445 def setNoName(self, noName):
1446 """
1447 Public method to set the display string for an unnamed editor.
1448
1449 @param noName display string for this unnamed editor (string)
1450 """
1451 self.noName = noName
1452
1453 def getNoName(self):
1454 """
1455 Public method to get the display string for an unnamed editor.
1456
1457 @return display string for this unnamed editor (string)
1458 """
1459 return self.noName
1460
1461 def getFileName(self):
1462 """
1463 Public method to return the name of the file being displayed.
1464
1465 @return filename of the displayed file (string)
1466 """
1467 return self.fileName
1468
1469 def getFileType(self):
1470 """
1471 Public method to return the type of the file being displayed.
1472
1473 @return type of the displayed file (string)
1474 """
1475 return self.filetype
1476
1477 def getEncoding(self):
1478 """
1479 Public method to return the current encoding.
1480
1481 @return current encoding (string)
1482 """
1483 return self.encoding
1484
1485 def getFolds(self):
1486 """
1487 Public method to get a list line numbers of collapsed folds.
1488
1489 @return list of line numbers of folded lines (list of integer)
1490 """
1491 line = 0
1492 folds = []
1493 maxline = self.lines()
1494 while line < maxline:
1495 if self.foldHeaderAt(line) and not self.foldExpandedAt(line):
1496 folds.append(line)
1497 line += 1
1498 return folds
1499
1500 def isPyFile(self):
1501 """
1502 Public method to return a flag indicating a Python file.
1503
1504 @return flag indicating a Python file (boolean)
1505 """
1506 return self.filetype == "Python" or \
1507 (self.fileName is not None and \
1508 os.path.splitext(self.fileName)[1] in self.pyExtensions)
1509
1510 def isPy3File(self):
1511 """
1512 Public method to return a flag indicating a Python3 file.
1513
1514 @return flag indicating a Python3 file (boolean)
1515 """
1516 return self.filetype == "Python3" or \
1517 (self.fileName is not None and \
1518 os.path.splitext(self.fileName)[1] in self.py3Extensions)
1519
1520 def isRubyFile(self):
1521 """
1522 Public method to return a flag indicating a Ruby file.
1523
1524 @return flag indicating a Ruby file (boolean)
1525 """
1526 return self.filetype == "Ruby" or \
1527 (self.fileName is not None and \
1528 os.path.splitext(self.fileName)[1] in self.rbExtensions)
1529
1530 def highlightVisible(self):
1531 """
1532 Public method to make sure that the highlight is visible.
1533 """
1534 if self.lastHighlight is not None:
1535 lineno = self.markerLine(self.lastHighlight)
1536 self.ensureVisible(lineno+1)
1537
1538 def highlight(self, line = None, error = False, syntaxError = False):
1539 """
1540 Public method to highlight (or de-highlight) a particular line.
1541
1542 @param line line number to highlight (integer)
1543 @param error flag indicating whether the error highlight should be used (boolean)
1544 @param syntaxError flag indicating a syntax error (boolean)
1545 """
1546 if line is None:
1547 self.lastHighlight = None
1548 if self.lastErrorMarker is not None:
1549 self.markerDeleteHandle(self.lastErrorMarker)
1550 self.lastErrorMarker = None
1551 if self.lastCurrMarker is not None:
1552 self.markerDeleteHandle(self.lastCurrMarker)
1553 self.lastCurrMarker = None
1554 else:
1555 if error:
1556 if self.lastErrorMarker is not None:
1557 self.markerDeleteHandle(self.lastErrorMarker)
1558 self.lastErrorMarker = self.markerAdd(line-1, self.errorline)
1559 self.lastHighlight = self.lastErrorMarker
1560 else:
1561 if self.lastCurrMarker is not None:
1562 self.markerDeleteHandle(self.lastCurrMarker)
1563 self.lastCurrMarker = self.markerAdd(line-1, self.currentline)
1564 self.lastHighlight = self.lastCurrMarker
1565 self.setCursorPosition(line-1, 0)
1566
1567 def getHighlightPosition(self):
1568 """
1569 Public method to return the position of the highlight bar.
1570
1571 @return line number of the highlight bar (integer)
1572 """
1573 if self.lastHighlight is not None:
1574 return self.markerLine(self.lastHighlight)
1575 else:
1576 return 1
1577
1578 ############################################################################
1579 ## Breakpoint handling methods below
1580 ############################################################################
1581
1582 def __linesChanged(self):
1583 """
1584 Private method to track text changes.
1585
1586 This method checks, if lines have been inserted or removed in order to
1587 update the breakpoints.
1588 """
1589 if self.breaks:
1590 bps = [] # list of breakpoints
1591 for handle, (ln, cond, temp, enabled, ignorecount) in self.breaks.items():
1592 line = self.markerLine(handle) + 1
1593 bps.append((ln, line, (cond, temp, enabled, ignorecount)))
1594 self.markerDeleteHandle(handle)
1595 self.breaks = {}
1596 for bp in bps:
1597 index = self.breakpointModel.getBreakPointIndex(self.fileName, bp[0])
1598 self.breakpointModel.setBreakPointByIndex(index,
1599 self.fileName, bp[1], bp[2])
1600
1601 def __restoreBreakpoints(self):
1602 """
1603 Private method to restore the breakpoints.
1604 """
1605 for handle in self.breaks.keys():
1606 self.markerDeleteHandle(handle)
1607 self.__addBreakPoints(QModelIndex(), 0, self.breakpointModel.rowCount() - 1)
1608
1609 def __deleteBreakPoints(self, parentIndex, start, end):
1610 """
1611 Private slot to delete breakpoints.
1612
1613 @param parentIndex index of parent item (QModelIndex)
1614 @param start start row (integer)
1615 @param end end row (integer)
1616 """
1617 for row in range(start, end + 1):
1618 index = self.breakpointModel.index(row, 0, parentIndex)
1619 fn, lineno = self.breakpointModel.getBreakPointByIndex(index)[0:2]
1620 if fn == self.fileName:
1621 self.clearBreakpoint(lineno)
1622
1623 def __changeBreakPoints(self, startIndex, endIndex):
1624 """
1625 Private slot to set changed breakpoints.
1626
1627 @param indexes indexes of changed breakpoints.
1628 """
1629 self.__addBreakPoints(QModelIndex(), startIndex.row(), endIndex.row())
1630
1631 def __breakPointDataAboutToBeChanged(self, startIndex, endIndex):
1632 """
1633 Private slot to handle the dataAboutToBeChanged signal of the breakpoint model.
1634
1635 @param startIndex start index of the rows to be changed (QModelIndex)
1636 @param endIndex end index of the rows to be changed (QModelIndex)
1637 """
1638 self.__deleteBreakPoints(QModelIndex(), startIndex.row(), endIndex.row())
1639
1640 def __addBreakPoints(self, parentIndex, start, end):
1641 """
1642 Private slot to add breakpoints.
1643
1644 @param parentIndex index of parent item (QModelIndex)
1645 @param start start row (integer)
1646 @param end end row (integer)
1647 """
1648 for row in range(start, end + 1):
1649 index = self.breakpointModel.index(row, 0, parentIndex)
1650 fn, line, cond, temp, enabled, ignorecount = \
1651 self.breakpointModel.getBreakPointByIndex(index)[:6]
1652 if fn == self.fileName:
1653 self.newBreakpointWithProperties(line, (cond, temp, enabled, ignorecount))
1654
1655 def clearBreakpoint(self, line):
1656 """
1657 Public method to clear a breakpoint.
1658
1659 Note: This doesn't clear the breakpoint in the debugger,
1660 it just deletes it from the editor internal list of breakpoints.
1661
1662 @param line linenumber of the breakpoint (integer)
1663 """
1664 for handle, (ln, _, _, _, _) in self.breaks.items():
1665 if self.markerLine(handle) == line-1:
1666 break
1667 else:
1668 # not found, simply ignore it
1669 return
1670
1671 del self.breaks[handle]
1672 self.markerDeleteHandle(handle)
1673
1674 def newBreakpointWithProperties(self, line, properties):
1675 """
1676 Private method to set a new breakpoint and its properties.
1677
1678 @param line line number of the breakpoint (integer)
1679 @param properties properties for the breakpoint (tuple)
1680 (condition, temporary flag, enabled flag, ignore count)
1681 """
1682 if not properties[2]:
1683 marker = self.dbreakpoint
1684 elif properties[0]:
1685 marker = properties[1] and self.tcbreakpoint or self.cbreakpoint
1686 else:
1687 marker = properties[1] and self.tbreakpoint or self.breakpoint
1688
1689 handle = self.markerAdd(line-1, marker)
1690 self.breaks[handle] = (line,) + properties
1691 self.emit(SIGNAL('breakpointToggled'), self)
1692
1693 def __toggleBreakpoint(self, line, temporary = False):
1694 """
1695 Private method to toggle a breakpoint.
1696
1697 @param line line number of the breakpoint (integer)
1698 @param temporary flag indicating a temporary breakpoint (boolean)
1699 """
1700 for handle, (ln, _, _, _, _) in self.breaks.items():
1701 if self.markerLine(handle) == line - 1:
1702 # delete breakpoint or toggle it to the next state
1703 index = self.breakpointModel.getBreakPointIndex(self.fileName, line)
1704 if Preferences.getDebugger("ThreeStateBreakPoints") and \
1705 not self.breakpointModel.isBreakPointTemporaryByIndex(index):
1706 self.breakpointModel.deleteBreakPointByIndex(index)
1707 self.__addBreakPoint(line, True)
1708 else:
1709 self.breakpointModel.deleteBreakPointByIndex(index)
1710 self.emit(SIGNAL('breakpointToggled'), self)
1711 break
1712 else:
1713 self.__addBreakPoint(line, temporary)
1714
1715 def __addBreakPoint(self, line, temporary):
1716 """
1717 Private method to add a new breakpoint.
1718
1719 @param line line number of the breakpoint (integer)
1720 @param temporary flag indicating a temporary breakpoint (boolean)
1721 """
1722 if self.fileName and \
1723 (self.isPyFile() or self.isPy3File() or self.isRubyFile()):
1724 self.breakpointModel.addBreakPoint(self.fileName, line,
1725 ('', temporary, True, 0))
1726 self.emit(SIGNAL('breakpointToggled'), self)
1727
1728 def __toggleBreakpointEnabled(self, line):
1729 """
1730 Private method to toggle a breakpoints enabled status.
1731
1732 @param line line number of the breakpoint (integer)
1733 """
1734 for handle, (ln, cond, temp, enabled, ignorecount) in self.breaks.items():
1735 if self.markerLine(handle) == line - 1:
1736 break
1737 else:
1738 # no breakpoint found on that line
1739 return
1740
1741 index = self.breakpointModel.getBreakPointIndex(self.fileName, line)
1742 self.breakpointModel.setBreakPointEnabledByIndex(index, not enabled)
1743
1744 def curLineHasBreakpoint(self):
1745 """
1746 Public method to check for the presence of a breakpoint at the current line.
1747
1748 @return flag indicating the presence of a breakpoint (boolean)
1749 """
1750 line, _ = self.getCursorPosition()
1751 return self.markersAtLine(line) & self.breakpointMask != 0
1752
1753 def hasBreakpoints(self):
1754 """
1755 Public method to check for the presence of breakpoints.
1756
1757 @return flag indicating the presence of breakpoints (boolean)
1758 """
1759 return len(self.breaks) > 0
1760
1761 def __menuToggleTemporaryBreakpoint(self):
1762 """
1763 Private slot to handle the 'Toggle temporary breakpoint' context menu action.
1764 """
1765 if self.line < 0:
1766 self.line, index = self.getCursorPosition()
1767 self.line += 1
1768 self.__toggleBreakpoint(self.line, 1)
1769 self.line = -1
1770
1771 def menuToggleBreakpoint(self):
1772 """
1773 Public slot to handle the 'Toggle breakpoint' context menu action.
1774 """
1775 if self.line < 0:
1776 self.line, index = self.getCursorPosition()
1777 self.line += 1
1778 self.__toggleBreakpoint(self.line)
1779 self.line = -1
1780
1781 def __menuToggleBreakpointEnabled(self):
1782 """
1783 Private slot to handle the 'Enable/Disable breakpoint' context menu action.
1784 """
1785 if self.line < 0:
1786 self.line, index = self.getCursorPosition()
1787 self.line += 1
1788 self.__toggleBreakpointEnabled(self.line)
1789 self.line = -1
1790
1791 def menuEditBreakpoint(self, line = None):
1792 """
1793 Public slot to handle the 'Edit breakpoint' context menu action.
1794
1795 @param line linenumber of the breakpoint to edit
1796 """
1797 if line is not None:
1798 self.line = line - 1
1799 if self.line < 0:
1800 self.line, index = self.getCursorPosition()
1801 found = False
1802 for handle, (ln, cond, temp, enabled, ignorecount) in self.breaks.items():
1803 if self.markerLine(handle) == self.line:
1804 found = True
1805 break
1806
1807 if found:
1808 index = self.breakpointModel.getBreakPointIndex(self.fileName, ln)
1809 if not index.isValid():
1810 return
1811
1812 dlg = EditBreakpointDialog((self.fileName, ln),
1813 (cond, temp, enabled, ignorecount),
1814 self.condHistory, self, modal = True)
1815 if dlg.exec_() == QDialog.Accepted:
1816 cond, temp, enabled, ignorecount = dlg.getData()
1817 self.breakpointModel.setBreakPointByIndex(index,
1818 self.fileName, ln, (cond, temp, enabled, ignorecount))
1819
1820 self.line = -1
1821
1822 def menuNextBreakpoint(self):
1823 """
1824 Public slot to handle the 'Next breakpoint' context menu action.
1825 """
1826 line, index = self.getCursorPosition()
1827 if line == self.lines()-1:
1828 line = 0
1829 else:
1830 line += 1
1831 bpline = self.markerFindNext(line, self.breakpointMask)
1832 if bpline < 0:
1833 # wrap around
1834 bpline = self.markerFindNext(0, self.breakpointMask)
1835 if bpline >= 0:
1836 self.setCursorPosition(bpline, 0)
1837 self.ensureLineVisible(bpline)
1838
1839 def menuPreviousBreakpoint(self):
1840 """
1841 Public slot to handle the 'Previous breakpoint' context menu action.
1842 """
1843 line, index = self.getCursorPosition()
1844 if line == 0:
1845 line = self.lines()-1
1846 else:
1847 line -= 1
1848 bpline = self.markerFindPrevious(line, self.breakpointMask)
1849 if bpline < 0:
1850 # wrap around
1851 bpline = self.markerFindPrevious(self.lines()-1, self.breakpointMask)
1852 if bpline >= 0:
1853 self.setCursorPosition(bpline, 0)
1854 self.ensureLineVisible(bpline)
1855
1856 def __menuClearBreakpoints(self):
1857 """
1858 Private slot to handle the 'Clear all breakpoints' context menu action.
1859 """
1860 self.__clearBreakpoints(self.fileName)
1861
1862 def __clearBreakpoints(self, fileName):
1863 """
1864 Private slot to clear all breakpoints.
1865 """
1866 idxList = []
1867 for handle, (ln, _, _, _, _) in self.breaks.items():
1868 index = self.breakpointModel.getBreakPointIndex(fileName, ln)
1869 if index.isValid():
1870 idxList.append(index)
1871 if idxList:
1872 self.breakpointModel.deleteBreakPoints(idxList)
1873
1874 ############################################################################
1875 ## Bookmark handling methods below
1876 ############################################################################
1877
1878 def toggleBookmark(self, line):
1879 """
1880 Public method to toggle a bookmark.
1881
1882 @param line line number of the bookmark (integer)
1883 """
1884 for handle in self.bookmarks:
1885 if self.markerLine(handle) == line - 1:
1886 break
1887 else:
1888 # set a new bookmark
1889 handle = self.markerAdd(line - 1, self.bookmark)
1890 self.bookmarks.append(handle)
1891 self.emit(SIGNAL('bookmarkToggled'), self)
1892 return
1893
1894 self.bookmarks.remove(handle)
1895 self.markerDeleteHandle(handle)
1896 self.emit(SIGNAL('bookmarkToggled'), self)
1897
1898 def getBookmarks(self):
1899 """
1900 Public method to retrieve the bookmarks.
1901
1902 @return sorted list of all lines containing a bookmark
1903 (list of integer)
1904 """
1905 bmlist = []
1906 for handle in self.bookmarks:
1907 bmlist.append(self.markerLine(handle) + 1)
1908
1909 bmlist.sort()
1910 return bmlist
1911
1912 def hasBookmarks(self):
1913 """
1914 Public method to check for the presence of bookmarks.
1915
1916 @return flag indicating the presence of bookmarks (boolean)
1917 """
1918 return len(self.bookmarks) > 0
1919
1920 def menuToggleBookmark(self):
1921 """
1922 Public slot to handle the 'Toggle bookmark' context menu action.
1923 """
1924 if self.line < 0:
1925 self.line, index = self.getCursorPosition()
1926 self.line += 1
1927 self.toggleBookmark(self.line)
1928 self.line = -1
1929
1930 def nextBookmark(self):
1931 """
1932 Public slot to handle the 'Next bookmark' context menu action.
1933 """
1934 line, index = self.getCursorPosition()
1935 if line == self.lines()-1:
1936 line = 0
1937 else:
1938 line += 1
1939 bmline = self.markerFindNext(line, 1 << self.bookmark)
1940 if bmline < 0:
1941 # wrap around
1942 bmline = self.markerFindNext(0, 1 << self.bookmark)
1943 if bmline >= 0:
1944 self.setCursorPosition(bmline, 0)
1945 self.ensureLineVisible(bmline)
1946
1947 def previousBookmark(self):
1948 """
1949 Public slot to handle the 'Previous bookmark' context menu action.
1950 """
1951 line, index = self.getCursorPosition()
1952 if line == 0:
1953 line = self.lines()-1
1954 else:
1955 line -= 1
1956 bmline = self.markerFindPrevious(line, 1 << self.bookmark)
1957 if bmline < 0:
1958 # wrap around
1959 bmline = self.markerFindPrevious(self.lines() - 1, 1 << self.bookmark)
1960 if bmline >= 0:
1961 self.setCursorPosition(bmline, 0)
1962 self.ensureLineVisible(bmline)
1963
1964 def clearBookmarks(self):
1965 """
1966 Public slot to handle the 'Clear all bookmarks' context menu action.
1967 """
1968 for handle in self.bookmarks:
1969 self.markerDeleteHandle(handle)
1970 self.bookmarks = []
1971 self.emit(SIGNAL('bookmarkToggled'), self)
1972
1973 ############################################################################
1974 ## Printing methods below
1975 ############################################################################
1976
1977 def printFile(self):
1978 """
1979 Public slot to print the text.
1980 """
1981 printer = Printer(mode = QPrinter.HighResolution)
1982 sb = e4App().getObject("UserInterface").statusBar()
1983 printDialog = QPrintDialog(printer, self)
1984 if self.hasSelectedText():
1985 printDialog.addEnabledOption(QAbstractPrintDialog.PrintSelection)
1986 if printDialog.exec_() == QDialog.Accepted:
1987 sb.showMessage(self.trUtf8('Printing...'))
1988 QApplication.processEvents()
1989 fn = self.getFileName()
1990 if fn is not None:
1991 printer.setDocName(os.path.basename(fn))
1992 else:
1993 printer.setDocName(self.noName)
1994 if printDialog.printRange() == QAbstractPrintDialog.Selection:
1995 # get the selection
1996 fromLine, fromIndex, toLine, toIndex = self.getSelection()
1997 if toIndex == 0:
1998 toLine -= 1
1999 # Qscintilla seems to print one line more than told
2000 res = printer.printRange(self, fromLine, toLine-1)
2001 else:
2002 res = printer.printRange(self)
2003 if res:
2004 sb.showMessage(self.trUtf8('Printing completed'), 2000)
2005 else:
2006 sb.showMessage(self.trUtf8('Error while printing'), 2000)
2007 QApplication.processEvents()
2008 else:
2009 sb.showMessage(self.trUtf8('Printing aborted'), 2000)
2010 QApplication.processEvents()
2011
2012 def printPreviewFile(self):
2013 """
2014 Public slot to show a print preview of the text.
2015 """
2016 from PyQt4.QtGui import QPrintPreviewDialog
2017
2018 printer = Printer(mode = QPrinter.HighResolution)
2019 fn = self.getFileName()
2020 if fn is not None:
2021 printer.setDocName(os.path.basename(fn))
2022 else:
2023 printer.setDocName(self.noName)
2024 preview = QPrintPreviewDialog(printer, self)
2025 self.connect(preview, SIGNAL("paintRequested(QPrinter*)"), self.__printPreview)
2026 preview.exec_()
2027
2028 def __printPreview(self, printer):
2029 """
2030 Private slot to generate a print preview.
2031
2032 @param printer reference to the printer object (QScintilla.Printer.Printer)
2033 """
2034 printer.printRange(self)
2035
2036 ############################################################################
2037 ## Task handling methods below
2038 ############################################################################
2039
2040 def hasTaskMarkers(self):
2041 """
2042 Public method to determine, if this editor contains any task markers.
2043
2044 @return flag indicating the presence of task markers (boolean)
2045 """
2046 return self.__hasTaskMarkers
2047
2048 def nextTask(self):
2049 """
2050 Public slot to handle the 'Next task' context menu action.
2051 """
2052 line, index = self.getCursorPosition()
2053 if line == self.lines()-1:
2054 line = 0
2055 else:
2056 line += 1
2057 taskline = self.markerFindNext(line, 1 << self.taskmarker)
2058 if taskline < 0:
2059 # wrap around
2060 taskline = self.markerFindNext(0, 1 << self.taskmarker)
2061 if taskline >= 0:
2062 self.setCursorPosition(taskline, 0)
2063 self.ensureLineVisible(taskline)
2064
2065 def previousTask(self):
2066 """
2067 Public slot to handle the 'Previous task' context menu action.
2068 """
2069 line, index = self.getCursorPosition()
2070 if line == 0:
2071 line = self.lines()-1
2072 else:
2073 line -= 1
2074 taskline = self.markerFindPrevious(line, 1 << self.taskmarker)
2075 if taskline < 0:
2076 # wrap around
2077 taskline = self.markerFindPrevious(self.lines() - 1, 1 << self.taskmarker)
2078 if taskline >= 0:
2079 self.setCursorPosition(taskline, 0)
2080 self.ensureLineVisible(taskline)
2081
2082 def extractTasks(self):
2083 """
2084 Public slot to extract all tasks.
2085 """
2086 todoMarkers = Preferences.getTasks("TasksMarkers").split()
2087 bugfixMarkers = Preferences.getTasks("TasksMarkersBugfix").split()
2088 txtList = self.text().split(self.getLineSeparator())
2089
2090 # clear all task markers and tasks
2091 self.markerDeleteAll(self.taskmarker)
2092 self.taskViewer.clearFileTasks(self.fileName)
2093 self.__hasTaskMarkers = False
2094
2095 # now search tasks and record them
2096 lineIndex = -1
2097 for line in txtList:
2098 lineIndex += 1
2099 shouldContinue = False
2100 # normal tasks first
2101 for tasksMarker in todoMarkers:
2102 index = line.find(tasksMarker)
2103 if index > -1:
2104 task = line[index:]
2105 self.markerAdd(lineIndex, self.taskmarker)
2106 self.taskViewer.addFileTask(task, self.fileName, lineIndex + 1, False)
2107 self.__hasTaskMarkers = True
2108 shouldContinue = True
2109 break
2110 if shouldContinue:
2111 continue
2112
2113 # bugfix tasks second
2114 for tasksMarker in bugfixMarkers:
2115 index = line.find(tasksMarker)
2116 if index > -1:
2117 task = line[index:]
2118 self.markerAdd(lineIndex, self.taskmarker)
2119 self.taskViewer.addFileTask(task, self.fileName, lineIndex+1, True)
2120 self.__hasTaskMarkers = True
2121 break
2122 self.emit(SIGNAL('taskMarkersUpdated'), self)
2123
2124 ############################################################################
2125 ## File handling methods below
2126 ############################################################################
2127
2128 def checkDirty(self):
2129 """
2130 Public method to check dirty status and open a message window.
2131
2132 @return flag indicating successful reset of the dirty flag (boolean)
2133 """
2134 if self.isModified():
2135 fn = self.fileName
2136 if fn is None:
2137 fn = self.noName
2138 res = QMessageBox.warning(self.vm,
2139 self.trUtf8("File Modified"),
2140 self.trUtf8("<p>The file <b>{0}</b> has unsaved changes.</p>")
2141 .format(fn),
2142 QMessageBox.StandardButtons(\
2143 QMessageBox.Abort | \
2144 QMessageBox.Discard | \
2145 QMessageBox.Save),
2146 QMessageBox.Save)
2147 if res == QMessageBox.Save:
2148 ok, newName = self.saveFile()
2149 if ok:
2150 self.vm.setEditorName(self, newName)
2151 return ok
2152 elif res == QMessageBox.Abort or res == QMessageBox.Cancel:
2153 return False
2154
2155 return True
2156
2157 def revertToUnmodified(self):
2158 """
2159 Public method to revert back to the last saved state.
2160 """
2161 undo_ = True
2162 while self.isModified():
2163 if undo_:
2164 # try undo first
2165 if self.isUndoAvailable():
2166 self.undo()
2167 else:
2168 undo_ = False
2169 else:
2170 # try redo next
2171 if self.isRedoAvailable():
2172 self.redo()
2173 else:
2174 break
2175 # Couldn't find the unmodified state
2176
2177 def readLine0(self, fn, createIt = False):
2178 """
2179 Public slot to read the first line from a file.
2180
2181 @param fn filename to read from (string)
2182 @param createIt flag indicating the creation of a new file, if the given
2183 one doesn't exist (boolean)
2184 @return first line of the file (string)
2185 """
2186 try:
2187 if createIt and not os.path.exists(fn):
2188 f = open(fn, "wb")
2189 f.close()
2190 f = open(fn, 'rb')
2191 except IOError:
2192 QMessageBox.critical(self.vm, self.trUtf8('Open File'),
2193 self.trUtf8('<p>The file <b>{0}</b> could not be opened.</p>')
2194 .format(fn))
2195 raise
2196
2197 txt = f.readline()
2198 f.close()
2199 return txt
2200
2201 def readFile(self, fn, createIt = False):
2202 """
2203 Public slot to read the text from a file.
2204
2205 @param fn filename to read from (string)
2206 @param createIt flag indicating the creation of a new file, if the given
2207 one doesn't exist (boolean)
2208 """
2209 try:
2210 if createIt and not os.path.exists(fn):
2211 f = open(fn, "wb")
2212 f.close()
2213 f = open(fn, 'rb')
2214 except IOError:
2215 QMessageBox.critical(self.vm, self.trUtf8('Open File'),
2216 self.trUtf8('<p>The file <b>{0}</b> could not be opened.</p>')
2217 .format(fn))
2218 raise
2219
2220 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
2221
2222 if fn.endswith('.ts') or fn.endswith('.ui'):
2223 # special treatment for Qt-Linguist and Qt-Designer files
2224 txt = f.read()
2225 self.encoding = 'latin-1'
2226 else:
2227 txt, self.encoding = Utilities.decode(f.read())
2228 f.close()
2229 fileEol = self.detectEolString(txt)
2230
2231 modified = False
2232 if (not Preferences.getEditor("TabForIndentation")) and \
2233 Preferences.getEditor("ConvertTabsOnLoad") and \
2234 not (self.lexer_ and \
2235 self.lexer_.alwaysKeepTabs()):
2236 txtExpanded = txt.expandtabs(Preferences.getEditor("TabWidth"))
2237 if txtExpanded != txt:
2238 modified = True
2239 txt = txtExpanded
2240 del txtExpanded
2241
2242 self.setText(txt)
2243
2244 # perform automatic eol conversion
2245 if Preferences.getEditor("AutomaticEOLConversion"):
2246 self.convertEols(self.eolMode())
2247 else:
2248 self.setEolModeByEolString(fileEol)
2249
2250 self.extractTasks()
2251
2252 QApplication.restoreOverrideCursor()
2253
2254 self.setModified(modified)
2255 self.lastModified = QFileInfo(self.fileName).lastModified()
2256
2257 def setEolModeByEolString(self, eolStr):
2258 """
2259 Public method to set the eol mode given the eol string.
2260
2261 @param eolStr eol string (string)
2262 """
2263 if eolStr == '\r\n':
2264 self.setEolMode(QsciScintilla.EolMode(QsciScintilla.EolWindows))
2265 elif eolStr == '\n':
2266 self.setEolMode(QsciScintilla.EolMode(QsciScintilla.EolUnix))
2267 elif eolStr == '\r':
2268 self.setEolMode(QsciScintilla.EolMode(QsciScintilla.EolMac))
2269 self.__eolChanged()
2270
2271 def __removeTrailingWhitespace(self):
2272 """
2273 Private method to remove trailing whitespace.
2274 """
2275 searchRE = r"[ \t]+$" # whitespace at the end of a line
2276
2277 ok = self.findFirstTarget(searchRE, True, False, False, 0, 0)
2278 self.beginUndoAction()
2279 while ok:
2280 self.replaceTarget("")
2281 ok = self.findNextTarget()
2282 self.endUndoAction()
2283
2284 def writeFile(self, fn):
2285 """
2286 Public slot to write the text to a file.
2287
2288 @param fn filename to write to (string)
2289 @return flag indicating success
2290 """
2291 if Preferences.getEditor("StripTrailingWhitespace"):
2292 self.__removeTrailingWhitespace()
2293
2294 txt = self.text()
2295 # work around glitch in scintilla: always make sure,
2296 # that the last line is terminated properly
2297 eol = self.getLineSeparator()
2298 if eol:
2299 if len(txt) >= len(eol):
2300 if txt[-len(eol):] != eol:
2301 txt += eol
2302 else:
2303 txt += eol
2304 try:
2305 txt, self.encoding = Utilities.encode(txt, self.encoding)
2306 except Utilities.CodingError, e:
2307 QMessageBox.critical(self, self.trUtf8('Save File'),
2308 self.trUtf8('<p>The file <b>{0}</b> could not be saved.<br/>'
2309 'Reason: {1}</p>')
2310 .format(fn, unicode(e)))
2311 return False
2312
2313 # create a backup file, if the option is set
2314 createBackup = Preferences.getEditor("CreateBackupFile")
2315 if createBackup:
2316 if os.path.islink(fn):
2317 fn = os.path.realpath(fn)
2318 bfn = '%s~' % fn
2319 try:
2320 permissions = os.stat(fn).st_mode
2321 perms_valid = True
2322 except EnvironmentError:
2323 # if there was an error, ignore it
2324 perms_valid = False
2325 try:
2326 os.remove(bfn)
2327 except EnvironmentError:
2328 # if there was an error, ignore it
2329 pass
2330 try:
2331 os.rename(fn, bfn)
2332 except EnvironmentError:
2333 # if there was an error, ignore it
2334 pass
2335
2336 # now write text to the file fn
2337 try:
2338 f = open(fn, 'wb')
2339 f.write(txt)
2340 f.close()
2341 if createBackup and perms_valid:
2342 os.chmod(fn, permissions)
2343 return True
2344 except IOError, why:
2345 QMessageBox.critical(self, self.trUtf8('Save File'),
2346 self.trUtf8('<p>The file <b>{0}</b> could not be saved.<br/>'
2347 'Reason: {1}</p>')
2348 .format(fn, unicode(why)))
2349 return False
2350
2351 def saveFile(self, saveas = False, path = None):
2352 """
2353 Public slot to save the text to a file.
2354
2355 @param saveas flag indicating a 'save as' action (boolean)
2356 @param path directory to save the file in (string)
2357 @return tuple of two values (boolean, string) giving a success indicator and
2358 the name of the saved file
2359 """
2360 if not saveas and not self.isModified():
2361 return (False, None) # do nothing if text wasn't changed
2362
2363 newName = None
2364 if saveas or self.fileName is None:
2365 saveas = True
2366 if not path and self.fileName is not None:
2367 path = os.path.dirname(self.fileName)
2368 if path is None:
2369 path = ""
2370 defaultFilter = Preferences.getEditor("DefaultSaveFilter")
2371 fn, selectedFilter = QFileDialog.getSaveFileNameAndFilter(
2372 self,
2373 self.trUtf8("Save File"),
2374 path,
2375 Lexers.getSaveFileFiltersList(True, True),
2376 defaultFilter,
2377 QFileDialog.Options(QFileDialog.DontConfirmOverwrite))
2378
2379 if fn:
2380 ext = QFileInfo(fn).suffix()
2381 if not ext:
2382 ex = selectedFilter.split("(*")[1].split(")")[0]
2383 if ex:
2384 fn += ex
2385 if QFileInfo(fn).exists():
2386 res = QMessageBox.warning(self,
2387 self.trUtf8("Save File"),
2388 self.trUtf8("<p>The file <b>{0}</b> already exists.</p>")
2389 .format(fn),
2390 QMessageBox.StandardButtons(\
2391 QMessageBox.Abort | \
2392 QMessageBox.Save),
2393 QMessageBox.Abort)
2394 if res == QMessageBox.Abort or res == QMessageBox.Cancel:
2395 return (False, None)
2396 fn = Utilities.toNativeSeparators(fn)
2397 newName = fn
2398 else:
2399 return (False, None)
2400 else:
2401 fn = self.fileName
2402
2403 self.emit(SIGNAL('editorAboutToBeSaved'), self.fileName)
2404 if self.writeFile(fn):
2405 if saveas:
2406 self.__clearBreakpoints(self.fileName)
2407 self.fileName = fn
2408 self.setModified(False)
2409 self.setReadOnly(False)
2410 self.setWindowTitle(self.fileName)
2411 if self.lexer_ is None and not self.__lexerReset:
2412 self.setLanguage(self.fileName)
2413
2414 if saveas:
2415 self.isResourcesFile = self.fileName.endswith(".qrc")
2416 self.__initContextMenu()
2417 self.emit(SIGNAL('editorRenamed'), self.fileName)
2418 self.lastModified = QFileInfo(self.fileName).lastModified()
2419 if newName is not None:
2420 self.vm.addToRecentList(newName)
2421 self.emit(SIGNAL('editorSaved'), self.fileName)
2422 self.__autoSyntaxCheck()
2423 self.extractTasks()
2424 return (True, self.fileName)
2425 else:
2426 self.lastModified = QFileInfo(fn).lastModified()
2427 return (False, None)
2428
2429 def saveFileAs(self, path = None):
2430 """
2431 Public slot to save a file with a new name.
2432
2433 @param path directory to save the file in (string)
2434 @return tuple of two values (boolean, string) giving a success indicator and
2435 the name of the saved file
2436 """
2437 return self.saveFile(True, path)
2438
2439 def handleRenamed(self, fn):
2440 """
2441 Public slot to handle the editorRenamed signal.
2442
2443 @param fn filename to be set for the editor (string).
2444 """
2445 self.__clearBreakpoints(fn)
2446
2447 self.fileName = fn
2448 self.setWindowTitle(self.fileName)
2449
2450 if self.lexer_ is None:
2451 self.setLanguage(self.fileName)
2452
2453 self.lastModified = QFileInfo(self.fileName).lastModified()
2454 self.vm.setEditorName(self, self.fileName)
2455 self.__updateReadOnly(True)
2456
2457 def fileRenamed(self, fn):
2458 """
2459 Public slot to handle the editorRenamed signal.
2460
2461 @param fn filename to be set for the editor (string).
2462 """
2463 self.handleRenamed(fn)
2464 if not self.inFileRenamed:
2465 self.inFileRenamed = True
2466 self.emit(SIGNAL('editorRenamed'), self.fileName)
2467 self.inFileRenamed = False
2468
2469 ############################################################################
2470 ## Utility methods below
2471 ############################################################################
2472
2473 def ensureVisible(self, line):
2474 """
2475 Public slot to ensure, that the specified line is visible.
2476
2477 @param line line number to make visible
2478 """
2479 self.ensureLineVisible(line-1)
2480
2481 def ensureVisibleTop(self, line):
2482 """
2483 Public slot to ensure, that the specified line is visible at the top
2484 of the editor.
2485
2486 @param line line number to make visible
2487 """
2488 topLine = self.firstVisibleLine()
2489 linesToScroll = line - topLine
2490 self.scrollVertical(linesToScroll)
2491
2492 def __marginClicked(self, margin, line, modifiers):
2493 """
2494 Private slot to handle the marginClicked signal.
2495
2496 @param margin id of the clicked margin (integer)
2497 @param line line number of the click (integer)
2498 @param modifiers keyboard modifiers (Qt.KeyboardModifiers)
2499 """
2500 if self.__unifiedMargins:
2501 if margin == 1:
2502 if modifiers & Qt.KeyboardModifiers(Qt.ShiftModifier):
2503 if self.marginMenuActs["LMBbreakpoints"].isChecked():
2504 self.toggleBookmark(line + 1)
2505 else:
2506 self.__toggleBreakpoint(line + 1)
2507 elif modifiers & Qt.KeyboardModifiers(Qt.ControlModifier):
2508 if self.markersAtLine(line) & (1 << self.syntaxerror):
2509 self.__showSyntaxError(line)
2510 else:
2511 if self.marginMenuActs["LMBbreakpoints"].isChecked():
2512 self.__toggleBreakpoint(line + 1)
2513 else:
2514 self.toggleBookmark(line + 1)
2515 else:
2516 if margin == self.__bmMargin:
2517 self.toggleBookmark(line + 1)
2518 elif margin == self.__bpMargin:
2519 self.__toggleBreakpoint(line + 1)
2520 elif margin == self.__indicMargin:
2521 if self.markersAtLine(line) & (1 << self.syntaxerror):
2522 self.__showSyntaxError(line)
2523
2524 def handleMonospacedEnable(self):
2525 """
2526 Private slot to handle the Use Monospaced Font context menu entry.
2527 """
2528 if self.menuActs["MonospacedFont"].isChecked():
2529 self.setMonospaced(True)
2530 else:
2531 if self.lexer_:
2532 self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla")
2533 self.lexer_.initProperties()
2534 self.setMonospaced(False)
2535 self.__setMarginsDisplay()
2536
2537 def getWordBoundaries(self, line, index, useWordChars = True):
2538 """
2539 Public method to get the word boundaries at a position.
2540
2541 @param line number of line to look at (int)
2542 @param index position to look at (int)
2543 @keyparam useWordChars flag indicating to use the wordCharacters
2544 method (boolean)
2545 @return tuple with start and end indices of the word at the position
2546 (integer, integer)
2547 """
2548 text = self.text(line)
2549 if self.caseSensitive():
2550 cs = Qt.CaseSensitive
2551 else:
2552 cs = Qt.CaseInsensitive
2553 wc = self.wordCharacters()
2554 if wc is None or not useWordChars:
2555 regExp = QRegExp('[^\w_]', cs)
2556 else:
2557 regExp = QRegExp('[^%s]' % re.escape(wc), cs)
2558 start = regExp.lastIndexIn(text, index) + 1
2559 end = regExp.indexIn(text, index)
2560 if start == end + 1 and index > 0:
2561 # we are on a word boundary, try again
2562 start = regExp.lastIndexIn(text, index - 1) + 1
2563 if start == -1:
2564 start = 0
2565 if end == -1:
2566 end = len(text)
2567
2568 return (start, end)
2569
2570 def getWord(self, line, index, direction = 0, useWordChars = True):
2571 """
2572 Public method to get the word at a position.
2573
2574 @param line number of line to look at (int)
2575 @param index position to look at (int)
2576 @param direction direction to look in (0 = whole word, 1 = left, 2 = right)
2577 @keyparam useWordChars flag indicating to use the wordCharacters
2578 method (boolean)
2579 @return the word at that position (string)
2580 """
2581 start, end = self.getWordBoundaries(line, index, useWordChars)
2582 if direction == 1:
2583 end = index
2584 elif direction == 2:
2585 start = index
2586 if end > start:
2587 text = self.text(line)
2588 word = text[start:end]
2589 else:
2590 word = ''
2591 return word
2592
2593 def getWordLeft(self, line, index):
2594 """
2595 Public method to get the word to the left of a position.
2596
2597 @param line number of line to look at (int)
2598 @param index position to look at (int)
2599 @return the word to the left of that position (string)
2600 """
2601 return self.getWord(line, index, 1)
2602
2603 def getWordRight(self, line, index):
2604 """
2605 Public method to get the word to the right of a position.
2606
2607 @param line number of line to look at (int)
2608 @param index position to look at (int)
2609 @return the word to the right of that position (string)
2610 """
2611 return self.getWord(line, index, 2)
2612
2613 def getCurrentWord(self):
2614 """
2615 Public method to get the word at the current position.
2616
2617 @return the word at that current position (string)
2618 """
2619 line, index = self.getCursorPosition()
2620 return self.getWord(line, index)
2621
2622 def selectWord(self, line, index):
2623 """
2624 Public method to select the word at a position.
2625
2626 @param line number of line to look at (int)
2627 @param index position to look at (int)
2628 """
2629 start, end = self.getWordBoundaries(line, index)
2630 self.setSelection(line, start, line, end)
2631
2632 def selectCurrentWord(self):
2633 """
2634 Public method to select the current word.
2635 """
2636 line, index = self.getCursorPosition()
2637 self.selectWord(line, index)
2638
2639 def __getCharacter(self, pos):
2640 """
2641 Private method to get the character to the left of the current position
2642 in the current line.
2643
2644 @param pos position to get character at (integer)
2645 @return requested character or "", if there are no more (string) and
2646 the next position (i.e. pos - 1)
2647 """
2648 if pos <= 0:
2649 return "", pos
2650
2651 pos = self.positionBefore(pos)
2652 ch = self.charAt(pos)
2653
2654 # Don't go past the end of the previous line
2655 if ch == '\n' or ch == '\r':
2656 return "", pos
2657
2658 return ch, pos
2659
2660 def getSearchText(self, selectionOnly = False):
2661 """
2662 Public method to determine the selection or the current word for the next
2663 search operation.
2664
2665 @param selectionOnly flag indicating that only selected text should be
2666 returned (boolean)
2667 @return selection or current word (string)
2668 """
2669 if self.hasSelectedText():
2670 text = self.selectedText()
2671 if '\r' in text or '\n' in text:
2672 # the selection contains at least a newline, it is
2673 # unlikely to be the expression to search for
2674 return ''
2675
2676 return text
2677
2678 if not selectionOnly:
2679 # no selected text, determine the word at the current position
2680 return self.getCurrentWord()
2681
2682 return ''
2683
2684 def setSearchIndicator(self, startPos, indicLength):
2685 """
2686 Public method to set a search indicator for the given range.
2687
2688 @param startPos start position of the indicator (integer)
2689 @param indicLength length of the indicator (integer)
2690 """
2691 self.setIndicatorRange(self.searchIndicator, startPos, indicLength)
2692
2693 def clearSearchIndicators(self):
2694 """
2695 Public method to clear all search indicators.
2696 """
2697 self.clearAllIndicators(self.searchIndicator)
2698 self.__markedText = ""
2699
2700 def __markOccurrences(self):
2701 """
2702 Private method to mark all occurrences of the current word.
2703 """
2704 word = self.getCurrentWord()
2705 if not word:
2706 self.clearSearchIndicators()
2707 return
2708
2709 if self.__markedText == word:
2710 return
2711
2712 self.clearSearchIndicators()
2713 ok = self.findFirstTarget(word, False, self.caseSensitive(), True, 0, 0)
2714 while ok:
2715 tgtPos, tgtLen = self.getFoundTarget()
2716 self.setSearchIndicator(tgtPos, tgtLen)
2717 ok = self.findNextTarget()
2718 self.__markedText = word
2719
2720 ############################################################################
2721 ## Comment handling methods below
2722 ############################################################################
2723
2724 def commentLine(self):
2725 """
2726 Public slot to comment the current line.
2727 """
2728 if self.lexer_ is None or not self.lexer_.canBlockComment():
2729 return
2730
2731 line, index = self.getCursorPosition()
2732 self.beginUndoAction()
2733 if Preferences.getEditor("CommentColumn0"):
2734 self.insertAt(self.lexer_.commentStr(), line, 0)
2735 else:
2736 self.insertAt(self.lexer_.commentStr(), line, self.indentation(line))
2737 self.endUndoAction()
2738
2739 def uncommentLine(self):
2740 """
2741 Public slot to uncomment the current line.
2742 """
2743 if self.lexer_ is None or not self.lexer_.canBlockComment():
2744 return
2745
2746 commentStr = self.lexer_.commentStr()
2747 line, index = self.getCursorPosition()
2748
2749 # check if line starts with our comment string (i.e. was commented
2750 # by our comment...() slots
2751 if not self.text(line).strip().startswith(commentStr):
2752 return
2753
2754 # now remove the comment string
2755 self.beginUndoAction()
2756 if Preferences.getEditor("CommentColumn0"):
2757 self.setSelection(line, 0, line, len(commentStr))
2758 else:
2759 self.setSelection(line, self.indentation(line),
2760 line, self.indentation(line) + len(commentStr))
2761 self.removeSelectedText()
2762 self.endUndoAction()
2763
2764 def commentSelection(self):
2765 """
2766 Public slot to comment the current selection.
2767 """
2768 if self.lexer_ is None or not self.lexer_.canBlockComment():
2769 return
2770
2771 if not self.hasSelectedText():
2772 return
2773
2774 commentStr = self.lexer_.commentStr()
2775
2776 # get the selection boundaries
2777 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
2778 if indexTo == 0:
2779 endLine = lineTo - 1
2780 else:
2781 endLine = lineTo
2782
2783 self.beginUndoAction()
2784 # iterate over the lines
2785 for line in range(lineFrom, endLine + 1):
2786 if Preferences.getEditor("CommentColumn0"):
2787 self.insertAt(commentStr, line, 0)
2788 else:
2789 self.insertAt(commentStr, line, self.indentation(line))
2790
2791 # change the selection accordingly
2792 self.setSelection(lineFrom, 0, endLine + 1, 0)
2793 self.endUndoAction()
2794
2795 def uncommentSelection(self):
2796 """
2797 Public slot to uncomment the current selection.
2798 """
2799 if self.lexer_ is None or not self.lexer_.canBlockComment():
2800 return
2801
2802 if not self.hasSelectedText():
2803 return
2804
2805 commentStr = self.lexer_.commentStr()
2806
2807 # get the selection boundaries
2808 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
2809 if indexTo == 0:
2810 endLine = lineTo - 1
2811 else:
2812 endLine = lineTo
2813
2814 self.beginUndoAction()
2815 # iterate over the lines
2816 for line in range(lineFrom, endLine+1):
2817 # check if line starts with our comment string (i.e. was commented
2818 # by our comment...() slots
2819 if not self.text(line).strip().startswith(commentStr):
2820 continue
2821
2822 if Preferences.getEditor("CommentColumn0"):
2823 self.setSelection(line, 0, line, len(commentStr))
2824 else:
2825 self.setSelection(line, self.indentation(line),
2826 line, self.indentation(line) + len(commentStr))
2827 self.removeSelectedText()
2828
2829 # adjust selection start
2830 if line == lineFrom:
2831 indexFrom -= len(commentStr)
2832 if indexFrom < 0:
2833 indexFrom = 0
2834
2835 # adjust selection end
2836 if line == lineTo:
2837 indexTo -= len(commentStr)
2838 if indexTo < 0:
2839 indexTo = 0
2840
2841 # change the selection accordingly
2842 self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
2843 self.endUndoAction()
2844
2845 def commentLineOrSelection(self):
2846 """
2847 Public slot to comment the current line or current selection.
2848 """
2849 if self.hasSelectedText():
2850 self.commentSelection()
2851 else:
2852 self.commentLine()
2853
2854 def uncommentLineOrSelection(self):
2855 """
2856 Public slot to uncomment the current line or current selection.
2857 """
2858 if self.hasSelectedText():
2859 self.uncommentSelection()
2860 else:
2861 self.uncommentLine()
2862
2863 def streamCommentLine(self):
2864 """
2865 Public slot to stream comment the current line.
2866 """
2867 if self.lexer_ is None or not self.lexer_.canStreamComment():
2868 return
2869
2870 commentStr = self.lexer_.streamCommentStr()
2871 line, index = self.getCursorPosition()
2872
2873 self.beginUndoAction()
2874 self.insertAt(commentStr['end'], line, self.lineLength(line))
2875 self.insertAt(commentStr['start'], line, 0)
2876 self.endUndoAction()
2877
2878 def streamCommentSelection(self):
2879 """
2880 Public slot to comment the current selection.
2881 """
2882 if self.lexer_ is None or not self.lexer_.canStreamComment():
2883 return
2884
2885 if not self.hasSelectedText():
2886 return
2887
2888 commentStr = self.lexer_.streamCommentStr()
2889
2890 # get the selection boundaries
2891 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
2892 if indexTo == 0:
2893 endLine = lineTo - 1
2894 endIndex = self.lineLength(endLine)
2895 else:
2896 endLine = lineTo
2897 endIndex = indexTo
2898
2899 self.beginUndoAction()
2900 self.insertAt(commentStr['end'], endLine, endIndex)
2901 self.insertAt(commentStr['start'], lineFrom, indexFrom)
2902
2903 # change the selection accordingly
2904 if indexTo > 0:
2905 indexTo += len(commentStr['end'])
2906 if lineFrom == endLine:
2907 indexTo += len(commentStr['start'])
2908 self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
2909 self.endUndoAction()
2910
2911 def streamCommentLineOrSelection(self):
2912 """
2913 Public slot to stream comment the current line or current selection.
2914 """
2915 if self.hasSelectedText():
2916 self.streamCommentSelection()
2917 else:
2918 self.streamCommentLine()
2919
2920 def boxCommentLine(self):
2921 """
2922 Public slot to box comment the current line.
2923 """
2924 if self.lexer_ is None or not self.lexer_.canBoxComment():
2925 return
2926
2927 commentStr = self.lexer_.boxCommentStr()
2928 line, index = self.getCursorPosition()
2929
2930 eol = self.getLineSeparator()
2931 self.beginUndoAction()
2932 self.insertAt(eol, line, self.lineLength(line))
2933 self.insertAt(commentStr['end'], line + 1, 0)
2934 self.insertAt(commentStr['middle'], line, 0)
2935 self.insertAt(eol, line, 0)
2936 self.insertAt(commentStr['start'], line, 0)
2937 self.endUndoAction()
2938
2939 def boxCommentSelection(self):
2940 """
2941 Public slot to box comment the current selection.
2942 """
2943 if self.lexer_ is None or not self.lexer_.canBoxComment():
2944 return
2945
2946 if not self.hasSelectedText():
2947 return
2948
2949 commentStr = self.lexer_.boxCommentStr()
2950
2951 # get the selection boundaries
2952 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
2953 if indexTo == 0:
2954 endLine = lineTo - 1
2955 else:
2956 endLine = lineTo
2957
2958 self.beginUndoAction()
2959 # iterate over the lines
2960 for line in range(lineFrom, endLine + 1):
2961 self.insertAt(commentStr['middle'], line, 0)
2962
2963 # now do the comments before and after the selection
2964 eol = self.getLineSeparator()
2965 self.insertAt(eol, endLine, self.lineLength(endLine))
2966 self.insertAt(commentStr['end'], endLine + 1, 0)
2967 self.insertAt(eol, lineFrom, 0)
2968 self.insertAt(commentStr['start'], lineFrom, 0)
2969
2970 # change the selection accordingly
2971 self.setSelection(lineFrom, 0, endLine + 3, 0)
2972 self.endUndoAction()
2973
2974 def boxCommentLineOrSelection(self):
2975 """
2976 Public slot to box comment the current line or current selection.
2977 """
2978 if self.hasSelectedText():
2979 self.boxCommentSelection()
2980 else:
2981 self.boxCommentLine()
2982
2983 ############################################################################
2984 ## Indentation handling methods below
2985 ############################################################################
2986
2987 def __indentLine(self, indent = True):
2988 """
2989 Private method to indent or unindent the current line.
2990
2991 @param indent flag indicating an indent operation (boolean)
2992 <br />If the flag is true, an indent operation is performed.
2993 Otherwise the current line is unindented.
2994 """
2995 line, index = self.getCursorPosition()
2996 self.beginUndoAction()
2997 if indent:
2998 self.indent(line)
2999 else:
3000 self.unindent(line)
3001 self.endUndoAction()
3002 if indent:
3003 self.setCursorPosition(line, index + self.indentationWidth())
3004 else:
3005 self.setCursorPosition(line, index - self.indentationWidth())
3006
3007 def __indentSelection(self, indent = True):
3008 """
3009 Private method to indent or unindent the current selection.
3010
3011 @param indent flag indicating an indent operation (boolean)
3012 <br />If the flag is true, an indent operation is performed.
3013 Otherwise the current line is unindented.
3014 """
3015 if not self.hasSelectedText():
3016 return
3017
3018 # get the selection
3019 lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
3020
3021 if indexTo == 0:
3022 endLine = lineTo - 1
3023 else:
3024 endLine = lineTo
3025
3026 self.beginUndoAction()
3027 # iterate over the lines
3028 for line in range(lineFrom, endLine + 1):
3029 if indent:
3030 self.indent(line)
3031 else:
3032 self.unindent(line)
3033 self.endUndoAction()
3034 if indent:
3035 if indexTo == 0:
3036 self.setSelection(lineFrom, indexFrom + self.indentationWidth(),
3037 lineTo, 0)
3038 else:
3039 self.setSelection(lineFrom, indexFrom + self.indentationWidth(),
3040 lineTo, indexTo + self.indentationWidth())
3041 else:
3042 indexStart = indexFrom - self.indentationWidth()
3043 if indexStart < 0:
3044 indexStart = 0
3045 indexEnd = indexTo - self.indentationWidth()
3046 if indexEnd < 0:
3047 indexEnd = 0
3048 self.setSelection(lineFrom, indexStart, lineTo, indexEnd)
3049
3050 def indentLineOrSelection(self):
3051 """
3052 Public slot to indent the current line or current selection
3053 """
3054 if self.hasSelectedText():
3055 self.__indentSelection(True)
3056 else:
3057 self.__indentLine(True)
3058
3059 def unindentLineOrSelection(self):
3060 """
3061 Public slot to unindent the current line or current selection.
3062 """
3063 if self.hasSelectedText():
3064 self.__indentSelection(False)
3065 else:
3066 self.__indentLine(False)
3067
3068 def smartIndentLineOrSelection(self):
3069 """
3070 Public slot to indent current line smartly.
3071 """
3072 if self.hasSelectedText():
3073 if self.lexer_ and self.lexer_.hasSmartIndent():
3074 self.lexer_.smartIndentSelection(self)
3075 else:
3076 self.__indentSelection(True)
3077 else:
3078 if self.lexer_ and self.lexer_.hasSmartIndent():
3079 self.lexer_.smartIndentLine(self)
3080 else:
3081 self.__indentLine(True)
3082
3083 def gotoLine(self, line):
3084 """
3085 Public slot to jump to the beginning of a line.
3086
3087 @param line line number to go to (integer)
3088 """
3089 self.setCursorPosition(line - 1, 0)
3090 self.ensureVisible(line)
3091
3092 ############################################################################
3093 ## Setup methods below
3094 ############################################################################
3095
3096 def readSettings(self):
3097 """
3098 Public slot to read the settings into our lexer.
3099 """
3100 # read the lexer settings and reinit the properties
3101 if self.lexer_ is not None:
3102 self.lexer_.readSettings(Preferences.Prefs.settings, "Scintilla")
3103 self.lexer_.initProperties()
3104
3105 # initialize the auto indent style of the lexer
3106 ais = self.lexer_.autoIndentStyle()
3107
3108 # read the typing completer settings
3109 if self.completer is not None:
3110 self.completer.readSettings()
3111
3112 # set the margins layout
3113 if QSCINTILLA_VERSION() >= 0x020301:
3114 self.__unifiedMargins = Preferences.getEditor("UnifiedMargins")
3115
3116 # set the line marker colours
3117 self.__setLineMarkerColours()
3118
3119 # set the text display
3120 self.__setTextDisplay()
3121
3122 # set margin 0 and 2 configuration
3123 self.__setMarginsDisplay()
3124
3125 # set the autocompletion and calltips function
3126 self.__setAutoCompletion()
3127 self.__setCallTips()
3128
3129 # set the autosave flags
3130 self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
3131
3132 if Preferences.getEditor("MiniContextMenu") != self.miniMenu:
3133 # regenerate context menu
3134 self.__initContextMenu()
3135 else:
3136 # set checked context menu items
3137 self.menuActs["AutoCompletionEnable"].setChecked(\
3138 self.autoCompletionThreshold() != -1)
3139 self.menuActs["MonospacedFont"].setChecked(\
3140 self.useMonospaced)
3141 self.menuActs["AutosaveEnable"].setChecked(\
3142 self.autosaveEnabled and not self.autosaveManuallyDisabled)
3143
3144 # regenerate the margins context menu(s)
3145 self.__initContextMenuMargins()
3146
3147 if Preferences.getEditor("MarkOccurrencesEnabled"):
3148 self.__markOccurrencesTimer.setInterval(
3149 Preferences.getEditor("MarkOccurrencesTimeout"))
3150 else:
3151 self.__markOccurrencesTimer.stop()
3152 self.clearSearchIndicators()
3153
3154 def __setLineMarkerColours(self):
3155 """
3156 Private method to set the line marker colours.
3157 """
3158 self.setMarkerForegroundColor(Preferences.getEditorColour("CurrentMarker"),
3159 self.currentline)
3160 self.setMarkerBackgroundColor(Preferences.getEditorColour("CurrentMarker"),
3161 self.currentline)
3162 self.setMarkerForegroundColor(Preferences.getEditorColour("ErrorMarker"),
3163 self.errorline)
3164 self.setMarkerBackgroundColor(Preferences.getEditorColour("ErrorMarker"),
3165 self.errorline)
3166
3167 def __setMarginsDisplay(self):
3168 """
3169 Private method to configure margins 0 and 2.
3170 """
3171 # set the settings for all margins
3172 self.setMarginsFont(Preferences.getEditorOtherFonts("MarginsFont"))
3173 self.setMarginsForegroundColor(Preferences.getEditorColour("MarginsForeground"))
3174 self.setMarginsBackgroundColor(Preferences.getEditorColour("MarginsBackground"))
3175
3176 # reset standard margins settings
3177 for margin in range(5):
3178 self.setMarginLineNumbers(margin, False)
3179 self.setMarginMarkerMask(margin, 0)
3180 self.setMarginWidth(margin, 0)
3181 self.setMarginSensitivity(margin, False)
3182
3183 # set marker margin(s) settings
3184 if self.__unifiedMargins:
3185 margin1Mask = (1 << self.breakpoint) | \
3186 (1 << self.cbreakpoint) | \
3187 (1 << self.tbreakpoint) | \
3188 (1 << self.tcbreakpoint) | \
3189 (1 << self.dbreakpoint) | \
3190 (1 << self.currentline) | \
3191 (1 << self.errorline) | \
3192 (1 << self.bookmark) | \
3193 (1 << self.syntaxerror) | \
3194 (1 << self.notcovered) | \
3195 (1 << self.taskmarker)
3196 self.setMarginWidth(1, 16)
3197 self.setMarginSensitivity(1, True)
3198 self.setMarginMarkerMask(1, margin1Mask)
3199
3200 self.__linenoMargin = 0
3201 self.__foldMargin = 2
3202 else:
3203
3204 self.__bmMargin = 0
3205 self.__linenoMargin = 1
3206 self.__bpMargin = 2
3207 self.__foldMargin = 3
3208 self.__indicMargin = 4
3209
3210 marginBmMask = (1 << self.bookmark)
3211 self.setMarginWidth(self.__bmMargin, 16)
3212 self.setMarginSensitivity(self.__bmMargin, True)
3213 self.setMarginMarkerMask(self.__bmMargin, marginBmMask)
3214
3215 marginBpMask = (1 << self.breakpoint) | \
3216 (1 << self.cbreakpoint) | \
3217 (1 << self.tbreakpoint) | \
3218 (1 << self.tcbreakpoint) | \
3219 (1 << self.dbreakpoint) | \
3220 (1 << self.currentline) | \
3221 (1 << self.errorline)
3222 self.setMarginWidth(self.__bpMargin, 16)
3223 self.setMarginSensitivity(self.__bpMargin, True)
3224 self.setMarginMarkerMask(self.__bpMargin, marginBpMask)
3225
3226 marginIndicMask = (1 << self.syntaxerror) | \
3227 (1 << self.notcovered) | \
3228 (1 << self.taskmarker)
3229 self.setMarginWidth(self.__indicMargin, 16)
3230 self.setMarginSensitivity(self.__indicMargin, True)
3231 self.setMarginMarkerMask(self.__indicMargin, marginIndicMask)
3232
3233 # set linenumber margin settings
3234 linenoMargin = Preferences.getEditor("LinenoMargin")
3235 self.setMarginLineNumbers(self.__linenoMargin, linenoMargin)
3236 if linenoMargin:
3237 self.setMarginWidth(self.__linenoMargin,
3238 ' ' + '8' * Preferences.getEditor("LinenoWidth"))
3239 else:
3240 self.setMarginWidth(self.__linenoMargin, 0)
3241
3242 # set folding margin settings
3243 if Preferences.getEditor("FoldingMargin"):
3244 self.setMarginWidth(self.__foldMargin, 16)
3245 folding = Preferences.getEditor("FoldingStyle")
3246 try:
3247 folding = QsciScintilla.FoldStyle(folding)
3248 except AttributeError:
3249 pass
3250 try:
3251 self.setFolding(folding, self.__foldMargin)
3252 except TypeError:
3253 self.setFolding(folding)
3254 self.setFoldMarginColors(Preferences.getEditorColour("FoldmarginBackground"),
3255 Preferences.getEditorColour("FoldmarginBackground"))
3256 else:
3257 self.setMarginWidth(self.__foldMargin, 0)
3258 try:
3259 self.setFolding(QsciScintilla.NoFoldStyle, self.__foldMargin)
3260 except TypeError:
3261 self.setFolding(QsciScintilla.NoFoldStyle)
3262
3263 def __setTextDisplay(self):
3264 """
3265 Private method to configure the text display.
3266 """
3267 self.setTabWidth(Preferences.getEditor("TabWidth"))
3268 self.setIndentationWidth(Preferences.getEditor("IndentWidth"))
3269 if self.lexer_ and self.lexer_.alwaysKeepTabs():
3270 self.setIndentationsUseTabs(True)
3271 else:
3272 self.setIndentationsUseTabs(Preferences.getEditor("TabForIndentation"))
3273 self.setTabIndents(Preferences.getEditor("TabIndents"))
3274 self.setBackspaceUnindents(Preferences.getEditor("TabIndents"))
3275 self.setIndentationGuides(Preferences.getEditor("IndentationGuides"))
3276 if Preferences.getEditor("ShowWhitespace"):
3277 self.setWhitespaceVisibility(QsciScintilla.WsVisible)
3278 else:
3279 self.setWhitespaceVisibility(QsciScintilla.WsInvisible)
3280 self.setEolVisibility(Preferences.getEditor("ShowEOL"))
3281 self.setAutoIndent(Preferences.getEditor("AutoIndentation"))
3282 if Preferences.getEditor("BraceHighlighting"):
3283 self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
3284 else:
3285 self.setBraceMatching(QsciScintilla.NoBraceMatch)
3286 self.setMatchedBraceForegroundColor(
3287 Preferences.getEditorColour("MatchingBrace"))
3288 self.setMatchedBraceBackgroundColor(
3289 Preferences.getEditorColour("MatchingBraceBack"))
3290 self.setUnmatchedBraceForegroundColor(
3291 Preferences.getEditorColour("NonmatchingBrace"))
3292 self.setUnmatchedBraceBackgroundColor(
3293 Preferences.getEditorColour("NonmatchingBraceBack"))
3294 if Preferences.getEditor("CustomSelectionColours"):
3295 self.setSelectionBackgroundColor(\
3296 Preferences.getEditorColour("SelectionBackground"))
3297 else:
3298 self.setSelectionBackgroundColor(\
3299 QApplication.palette().color(QPalette.Highlight))
3300 if Preferences.getEditor("ColourizeSelText"):
3301 self.resetSelectionForegroundColor()
3302 elif Preferences.getEditor("CustomSelectionColours"):
3303 self.setSelectionForegroundColor(\
3304 Preferences.getEditorColour("SelectionForeground"))
3305 else:
3306 self.setSelectionForegroundColor(\
3307 QApplication.palette().color(QPalette.HighlightedText))
3308 self.setSelectionToEol(Preferences.getEditor("ExtendSelectionToEol"))
3309 self.setCaretForegroundColor(
3310 Preferences.getEditorColour("CaretForeground"))
3311 self.setCaretLineBackgroundColor(
3312 Preferences.getEditorColour("CaretLineBackground"))
3313 self.setCaretLineVisible(Preferences.getEditor("CaretLineVisible"))
3314 self.caretWidth = Preferences.getEditor("CaretWidth")
3315 self.setCaretWidth(self.caretWidth)
3316 self.useMonospaced = Preferences.getEditor("UseMonospacedFont")
3317 self.setMonospaced(self.useMonospaced)
3318 edgeMode = Preferences.getEditor("EdgeMode")
3319 edge = QsciScintilla.EdgeMode(edgeMode)
3320 self.setEdgeMode(edge)
3321 if edgeMode:
3322 self.setEdgeColumn(Preferences.getEditor("EdgeColumn"))
3323 self.setEdgeColor(Preferences.getEditorColour("Edge"))
3324
3325 if Preferences.getEditor("WrapLongLines"):
3326 self.setWrapMode(QsciScintilla.WrapWord)
3327 self.setWrapVisualFlags(\
3328 QsciScintilla.WrapFlagByBorder, QsciScintilla.WrapFlagByBorder)
3329 else:
3330 self.setWrapMode(QsciScintilla.WrapNone)
3331 self.setWrapVisualFlags(\
3332 QsciScintilla.WrapFlagNone, QsciScintilla.WrapFlagNone)
3333
3334 self.searchIndicator = QsciScintilla.INDIC_CONTAINER
3335 self.indicatorDefine(self.searchIndicator, QsciScintilla.INDIC_BOX,
3336 Preferences.getEditorColour("SearchMarkers"))
3337 if not Preferences.getEditor("SearchMarkersEnabled") and \
3338 not Preferences.getEditor("QuickSearchMarkersEnabled") and \
3339 not Preferences.getEditor("MarkOccurrencesEnabled"):
3340 self.clearAllIndicators(self.searchIndicator)
3341
3342 self.spellingIndicator = QsciScintilla.INDIC_CONTAINER + 1
3343 self.indicatorDefine(self.spellingIndicator, QsciScintilla.INDIC_SQUIGGLE,
3344 Preferences.getEditorColour("SpellingMarkers"))
3345 self.__setSpelling()
3346
3347 def __setEolMode(self):
3348 """
3349 Private method to configure the eol mode of the editor.
3350 """
3351 eolMode = Preferences.getEditor("EOLMode")
3352 eolMode = QsciScintilla.EolMode(eolMode)
3353 self.setEolMode(eolMode)
3354 self.__eolChanged()
3355
3356 def __setAutoCompletion(self):
3357 """
3358 Private method to configure the autocompletion function.
3359 """
3360 if self.lexer_:
3361 self.setAutoCompletionFillupsEnabled(
3362 Preferences.getEditor("AutoCompletionFillups"))
3363 self.setAutoCompletionCaseSensitivity(
3364 Preferences.getEditor("AutoCompletionCaseSensitivity"))
3365 self.setAutoCompletionReplaceWord(
3366 Preferences.getEditor("AutoCompletionReplaceWord"))
3367 self.setAutoCompletionShowSingle(
3368 Preferences.getEditor("AutoCompletionShowSingle"))
3369 autoCompletionSource = Preferences.getEditor("AutoCompletionSource")
3370 if autoCompletionSource == QsciScintilla.AcsDocument:
3371 self.setAutoCompletionSource(QsciScintilla.AcsDocument)
3372 elif autoCompletionSource == QsciScintilla.AcsAPIs:
3373 self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
3374 else:
3375 self.setAutoCompletionSource(QsciScintilla.AcsAll)
3376 if Preferences.getEditor("AutoCompletionEnabled"):
3377 if self.__acHookFunction is None:
3378 self.setAutoCompletionThreshold(
3379 Preferences.getEditor("AutoCompletionThreshold"))
3380 else:
3381 self.setAutoCompletionThreshold(0)
3382 else:
3383 self.setAutoCompletionThreshold(-1)
3384 self.setAutoCompletionSource(QsciScintilla.AcsNone)
3385
3386 def __setCallTips(self):
3387 """
3388 Private method to configure the calltips function.
3389 """
3390 if Preferences.getEditor("CallTipsEnabled"):
3391 self.setCallTipsBackgroundColor(
3392 Preferences.getEditorColour("CallTipsBackground"))
3393 self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible"))
3394 calltipsStyle = Preferences.getEditor("CallTipsStyle")
3395 if calltipsStyle == QsciScintilla.CallTipsNoContext:
3396 self.setCallTipsStyle(QsciScintilla.CallTipsNoContext)
3397 elif calltipsStyle == QsciScintilla.CallTipsNoAutoCompletionContext:
3398 self.setCallTipsStyle(QsciScintilla.CallTipsNoAutoCompletionContext)
3399 else:
3400 self.setCallTipsStyle(QsciScintilla.CallTipsContext)
3401 else:
3402 self.setCallTipsStyle(QsciScintilla.CallTipsNone)
3403
3404 ############################################################################
3405 ## Autocompletion handling methods below
3406 ############################################################################
3407
3408 def canAutoCompleteFromAPIs(self):
3409 """
3410 Public method to check for API availablity.
3411
3412 @return flag indicating autocompletion from APIs is available (boolean)
3413 """
3414 return self.acAPI
3415
3416 def autoCompleteQScintilla(self):
3417 """
3418 Public method to perform an autocompletion using QScintilla methods.
3419 """
3420 acs = Preferences.getEditor("AutoCompletionSource")
3421 if acs == QsciScintilla.AcsDocument:
3422 self.autoCompleteFromDocument()
3423 elif acs == QsciScintilla.AcsAPIs:
3424 self.autoCompleteFromAPIs()
3425 elif acs == QsciScintilla.AcsAll:
3426 self.autoCompleteFromAll()
3427 else:
3428 QMessageBox.information(None,
3429 self.trUtf8("Autocompletion"),
3430 self.trUtf8("""Autocompletion is not available because"""
3431 """ there is no autocompletion source set."""))
3432
3433 def setAutoCompletionEnabled(self, enable):
3434 """
3435 Public method to enable/disable autocompletion.
3436
3437 @param enable flag indicating the desired autocompletion status (boolean)
3438 """
3439 if enable:
3440 self.setAutoCompletionThreshold(
3441 Preferences.getEditor("AutoCompletionThreshold"))
3442 autoCompletionSource = Preferences.getEditor("AutoCompletionSource")
3443 if autoCompletionSource == QsciScintilla.AcsDocument:
3444 self.setAutoCompletionSource(QsciScintilla.AcsDocument)
3445 elif autoCompletionSource == QsciScintilla.AcsAPIs:
3446 self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
3447 else:
3448 self.setAutoCompletionSource(QsciScintilla.AcsAll)
3449 else:
3450 self.setAutoCompletionThreshold(-1)
3451 self.setAutoCompletionSource(QsciScintilla.AcsNone)
3452
3453 def __toggleAutoCompletionEnable(self):
3454 """
3455 Private slot to handle the Enable Autocompletion context menu entry.
3456 """
3457 if self.menuActs["AutoCompletionEnable"].isChecked():
3458 self.setAutoCompletionEnabled(True)
3459 else:
3460 self.setAutoCompletionEnabled(False)
3461
3462 #################################################################
3463 ## Support for autocompletion hook methods
3464 #################################################################
3465
3466 def __charAdded(self, charNumber):
3467 """
3468 Public slot called to handle the user entering a character.
3469
3470 @param charNumber value of the character entered (integer)
3471 """
3472 if self.isListActive():
3473 char = unichr(charNumber)
3474 if self.__isStartChar(char):
3475 self.cancelList()
3476 self.autoComplete(auto = True, context = True)
3477 return
3478 elif char == '(':
3479 self.cancelList()
3480
3481 if self.callTipsStyle() != QsciScintilla.CallTipsNone and \
3482 self.lexer_ is not None and unichr(charNumber) in '()':
3483 self.callTip()
3484
3485 if not self.isCallTipActive():
3486 char = unichr(charNumber)
3487 if self.__isStartChar(char):
3488 self.autoComplete(auto = True, context = True)
3489 return
3490
3491 line, col = self.getCursorPosition()
3492 txt = self.getWordLeft(line, col)
3493 if len(txt) >= Preferences.getEditor("AutoCompletionThreshold"):
3494 self.autoComplete(auto = True, context = False)
3495 return
3496
3497 def __isStartChar(self, ch):
3498 """
3499 Private method to check, if a character is an autocompletion start character.
3500
3501 @param ch character to be checked (one character string)
3502 @return flag indicating the result (boolean)
3503 """
3504 if self.lexer_ is None:
3505 return False
3506
3507 wseps = self.lexer_.autoCompletionWordSeparators()
3508 for wsep in wseps:
3509 if wsep.endswith(ch):
3510 return True
3511
3512 return False
3513
3514 def setAutoCompletionHook(self, func):
3515 """
3516 Public method to set an autocompletion hook.
3517
3518 @param func Function to be set to handle autocompletion. func
3519 should be a function taking a reference to the editor and
3520 a boolean indicating to complete a context.
3521 """
3522 if self.autoCompletionThreshold() > 0:
3523 self.setAutoCompletionThreshold(0)
3524 self.__acHookFunction = func
3525 self.connect(self, SIGNAL("SCN_CHARADDED(int)"), self.__charAdded)
3526
3527 def unsetAutoCompletionHook(self):
3528 """
3529 Public method to unset a previously installed autocompletion hook.
3530 """
3531 self.disconnect(self, SIGNAL("SCN_CHARADDED(int)"), self.__charAdded)
3532 self.__acHookFunction = None
3533 if self.autoCompletionThreshold() == 0:
3534 self.setAutoCompletionThreshold(
3535 Preferences.getEditor("AutoCompletionThreshold"))
3536
3537 def autoCompletionHook(self):
3538 """
3539 Public method to get the autocompletion hook function.
3540
3541 @return function set by setAutoCompletionHook()
3542 """
3543 return self.__acHookFunction
3544
3545 def autoComplete(self, auto = False, context = True):
3546 """
3547 Public method to start autocompletion.
3548
3549 @keyparam auto flag indicating a call from the __charAdded method (boolean)
3550 @keyparam context flag indicating to complete a context (boolean)
3551 """
3552 if auto and self.autoCompletionThreshold() == -1:
3553 # autocompletion is disabled
3554 return
3555
3556 if self.__acHookFunction is not None:
3557 self.__acHookFunction(self, context)
3558 elif not auto:
3559 self.autoCompleteQScintilla()
3560 elif self.autoCompletionSource() != QsciScintilla.AcsNone:
3561 self.autoCompleteQScintilla()
3562
3563 def callTip(self):
3564 """
3565 Public method to show calltips.
3566 """
3567 if self.__ctHookFunction is not None:
3568 self.__callTip()
3569 else:
3570 QsciScintillaCompat.callTip(self)
3571
3572 def __callTip(self):
3573 """
3574 Private method to show call tips provided by a plugin.
3575 """
3576 pos = self.currentPosition()
3577
3578 # move backward to the start of the current calltip working out which argument
3579 # to highlight
3580 commas = 0
3581 found = False
3582 ch, pos = self.__getCharacter(pos)
3583 while ch:
3584 if ch == ',':
3585 commas += 1
3586 elif ch == ')':
3587 depth = 1
3588
3589 # ignore everything back to the start of the corresponding parenthesis
3590 ch, pos = self.__getCharacter(pos)
3591 while ch:
3592 if ch == ')':
3593 depth += 1
3594 elif ch == '(':
3595 depth -= 1
3596 if depth == 0:
3597 break
3598 ch, pos = self.__getCharacter(pos)
3599 elif ch == '(':
3600 found = True
3601 break
3602
3603 ch, pos = self.__getCharacter(pos)
3604
3605 self.SendScintilla(QsciScintilla.SCI_CALLTIPCANCEL)
3606
3607 if not found:
3608 return
3609
3610 try:
3611 callTips = self.__ctHookFunction(self, pos, commas)
3612 except TypeError:
3613 # for backward compatibility
3614 callTips = self.__ctHookFunction(self, pos)
3615 if len(callTips) == 0:
3616 if Preferences.getEditor("CallTipsScintillaOnFail"):
3617 # try QScintilla calltips
3618 QsciScintillaCompat.callTip(self)
3619 return
3620
3621 ctshift = 0
3622 for ct in callTips:
3623 shift = ct.index("(")
3624 if ctshift < shift:
3625 ctshift = shift
3626
3627 cv = self.callTipsVisible()
3628 if cv > 0:
3629 # this is just a safe guard
3630 ct = "\n".join(callTips[:cv])
3631 else:
3632 # until here and unindent below
3633 ct = "\n".join(callTips)
3634
3635 self.SendScintilla(QsciScintilla.SCI_CALLTIPSHOW,
3636 self.__adjustedCallTipPosition(ctshift, pos), ct)
3637 if '\n' in ct:
3638 return
3639
3640 # Highlight the current argument
3641 if commas == 0:
3642 astart = ct.find('(')
3643 else:
3644 astart = ct.find(',')
3645 commas -= 1
3646 while astart != -1 and commas > 0:
3647 astart = ct.find(',', astart + 1)
3648 commas -= 1
3649
3650 if astart == -1:
3651 return
3652
3653 depth = 0
3654 for aend in range(astart + 1, len(ct)):
3655 ch = ct[aend]
3656
3657 if ch == ',' and depth == 0:
3658 break
3659 elif ch == '(':
3660 depth += 1
3661 elif ch == ')':
3662 if depth == 0:
3663 break
3664
3665 depth -= 1
3666
3667 if astart != aend:
3668 self.SendScintilla(QsciScintilla.SCI_CALLTIPSETHLT, astart, aend)
3669
3670 def __adjustedCallTipPosition(self, ctshift, pos):
3671 """
3672 Private method to calculate an adjusted position for showing calltips.
3673
3674 @param ctshift amount the calltip shall be shifted (integer)
3675 @param pos position into the text (integer)
3676 @return new position for the calltip (integer)
3677 """
3678 ct = pos
3679 if ctshift:
3680 ctmin = self.SendScintilla(QsciScintilla.SCI_POSITIONFROMLINE,
3681 self.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION, ct))
3682 if ct - ctshift < ctmin:
3683 ct = ctmin
3684 else:
3685 ct = ct - ctshift
3686 return ct
3687
3688 def setCallTipHook(self, func):
3689 """
3690 Public method to set a calltip hook.
3691
3692 @param func Function to be set to determine calltips. func
3693 should be a function taking a reference to the editor,
3694 a position into the text and the amount of commas to the
3695 left of the cursor. It should return the possible
3696 calltips as a list of strings.
3697 """
3698 self.__ctHookFunction = func
3699
3700 def unsetCallTipHook(self):
3701 """
3702 Public method to unset a calltip hook.
3703 """
3704 self.__ctHookFunction = None
3705
3706 def callTipHook(self):
3707 """
3708 Public method to get the calltip hook function.
3709
3710 @return function set by setCallTipHook()
3711 """
3712 return self.__ctHookFunction
3713
3714 #################################################################
3715 ## Methods needed by the context menu
3716 #################################################################
3717
3718 def __marginNumber(self, xPos):
3719 """
3720 Private method to calculate the margin number based on a x position.
3721
3722 @param xPos x position (integer)
3723 @return margin number (integer, -1 for no margin)
3724 """
3725 width = 0
3726 for margin in range(5):
3727 width += self.marginWidth(margin)
3728 if xPos <= width:
3729 return margin
3730 return -1
3731
3732 def contextMenuEvent(self, evt):
3733 """
3734 Private method implementing the context menu event.
3735
3736 @param evt the context menu event (QContextMenuEvent)
3737 """
3738 evt.accept()
3739 if self.__marginNumber(evt.x()) == -1:
3740 self.spellingMenuPos = self.positionFromPoint(evt.pos())
3741 if self.spellingMenuPos >= 0 and \
3742 self.spell is not None and \
3743 self.hasIndicator(self.spellingIndicator, self.spellingMenuPos):
3744 self.spellingMenu.popup(evt.globalPos())
3745 else:
3746 self.menu.popup(evt.globalPos())
3747 else:
3748 self.line = self.lineAt(evt.pos())
3749 if self.__unifiedMargins:
3750 self.marginMenu.popup(evt.globalPos())
3751 else:
3752 if self.__marginNumber(evt.x()) in [self.__bmMargin, self.__linenoMargin]:
3753 self.bmMarginMenu.popup(evt.globalPos())
3754 elif self.__marginNumber(evt.x()) == self.__bpMargin:
3755 self.bpMarginMenu.popup(evt.globalPos())
3756 elif self.__marginNumber(evt.x()) == self.__indicMargin:
3757 self.indicMarginMenu.popup(evt.globalPos())
3758
3759 def __showContextMenu(self):
3760 """
3761 Private slot handling the aboutToShow signal of the context menu.
3762 """
3763 self.menuActs["Save"].setEnabled(self.isModified())
3764 self.menuActs["Undo"].setEnabled(self.isUndoAvailable())
3765 self.menuActs["Redo"].setEnabled(self.isRedoAvailable())
3766 self.menuActs["Revert"].setEnabled(self.isModified())
3767 if not self.miniMenu:
3768 self.menuActs["Cut"].setEnabled(self.hasSelectedText())
3769 self.menuActs["Copy"].setEnabled(self.hasSelectedText())
3770 if not self.isResourcesFile:
3771 if self.fileName and \
3772 (self.isPyFile() or self.isPy3File()):
3773 self.menuActs["Show"].setEnabled(True)
3774 else:
3775 self.menuActs["Show"].setEnabled(False)
3776 if self.fileName and \
3777 (self.isPyFile() or self.isPy3File() or self.isRubyFile()):
3778 self.menuActs["Diagrams"].setEnabled(True)
3779 else:
3780 self.menuActs["Diagrams"].setEnabled(False)
3781 if not self.miniMenu:
3782 if self.lexer_ is not None:
3783 self.menuActs["Comment"].setEnabled(self.lexer_.canBlockComment())
3784 self.menuActs["Uncomment"].setEnabled(self.lexer_.canBlockComment())
3785 self.menuActs["StreamComment"].setEnabled(self.lexer_.canStreamComment())
3786 self.menuActs["BoxComment"].setEnabled(self.lexer_.canBoxComment())
3787 else:
3788 self.menuActs["Comment"].setEnabled(False)
3789 self.menuActs["Uncomment"].setEnabled(False)
3790 self.menuActs["StreamComment"].setEnabled(False)
3791 self.menuActs["BoxComment"].setEnabled(False)
3792
3793 self.menuActs["TypingAidsEnabled"].setEnabled(self.completer is not None)
3794 self.menuActs["TypingAidsEnabled"].setChecked(\
3795 self.completer is not None and self.completer.isEnabled())
3796
3797 spellingAvailable = SpellChecker.isAvailable()
3798 self.menuActs["SpellCheck"].setEnabled(spellingAvailable)
3799 self.menuActs["SpellCheckSelection"].setEnabled(
3800 spellingAvailable and self.hasSelectedText())
3801 self.menuActs["SpellCheckRemove"].setEnabled(
3802 spellingAvailable and self.spellingMenuPos >= 0)
3803
3804 self.emit(SIGNAL("showMenu"), "Main", self.menu, self)
3805
3806 def __showContextMenuAutocompletion(self):
3807 """
3808 Private slot called before the autocompletion menu is shown.
3809 """
3810 self.menuActs["acDynamic"].setEnabled(
3811 self.acAPI or self.__acHookFunction is not None)
3812 self.menuActs["acAPI"].setEnabled(self.acAPI)
3813 self.menuActs["acAPIDocument"].setEnabled(self.acAPI)
3814 self.menuActs["calltip"].setEnabled(self.acAPI)
3815
3816 self.emit(SIGNAL("showMenu"), "Autocompletion", self.autocompletionMenu, self)
3817
3818 def __showContextMenuShow(self):
3819 """
3820 Private slot called before the show menu is shown.
3821 """
3822 prEnable = False
3823 coEnable = False
3824
3825 # first check if the file belongs to a project
3826 project = e4App().getObject("Project")
3827 if project.isOpen() and project.isProjectSource(self.fileName):
3828 fn = project.getMainScript(True)
3829 if fn is not None:
3830 tfn = Utilities.getTestFileName(fn)
3831 basename = os.path.splitext(fn)[0]
3832 tbasename = os.path.splitext(tfn)[0]
3833 prEnable = prEnable or \
3834 os.path.isfile("%s.profile" % basename) or \
3835 os.path.isfile("%s.profile" % tbasename)
3836 coEnable = coEnable or \
3837 os.path.isfile("%s.coverage" % basename) or \
3838 os.path.isfile("%s.coverage" % tbasename)
3839
3840 # now check ourself
3841 fn = self.getFileName()
3842 if fn is not None:
3843 tfn = Utilities.getTestFileName(fn)
3844 basename = os.path.splitext(fn)[0]
3845 tbasename = os.path.splitext(tfn)[0]
3846 prEnable = prEnable or \
3847 os.path.isfile("%s.profile" % basename) or \
3848 os.path.isfile("%s.profile" % tbasename)
3849 coEnable = coEnable or \
3850 os.path.isfile("%s.coverage" % basename) or \
3851 os.path.isfile("%s.coverage" % tbasename)
3852
3853 self.profileMenuAct.setEnabled(prEnable)
3854 self.coverageMenuAct.setEnabled(coEnable)
3855 self.coverageShowAnnotationMenuAct.setEnabled(\
3856 coEnable and not self.coverageMarkersShown)
3857 self.coverageHideAnnotationMenuAct.setEnabled(\
3858 self.coverageMarkersShown)
3859
3860 self.emit(SIGNAL("showMenu"), "Show", self.showMenu, self)
3861
3862 def __showContextMenuGraphics(self):
3863 """
3864 Private slot handling the aboutToShow signal of the diagrams context menu.
3865 """
3866 project = e4App().getObject("Project")
3867 if project.isOpen() and project.isProjectSource(self.fileName):
3868 self.applicationDiagramMenuAct.setEnabled(True)
3869 else:
3870 self.applicationDiagramMenuAct.setEnabled(False)
3871
3872 self.emit(SIGNAL("showMenu"), "Graphics", self.graphicsMenu, self)
3873
3874 def __showContextMenuMargin(self):
3875 """
3876 Private slot handling the aboutToShow signal of the margins context menu.
3877 """
3878 if self.fileName and \
3879 (self.isPyFile() or self.isPy3File() or self.isRubyFile()):
3880 self.marginMenuActs["Breakpoint"].setEnabled(True)
3881 self.marginMenuActs["TempBreakpoint"].setEnabled(True)
3882 if self.markersAtLine(self.line) & self.breakpointMask:
3883 self.marginMenuActs["EditBreakpoint"].setEnabled(True)
3884 self.marginMenuActs["EnableBreakpoint"].setEnabled(True)
3885 else:
3886 self.marginMenuActs["EditBreakpoint"].setEnabled(False)
3887 self.marginMenuActs["EnableBreakpoint"].setEnabled(False)
3888 if self.markersAtLine(self.line) & (1 << self.dbreakpoint):
3889 self.marginMenuActs["EnableBreakpoint"].setText(\
3890 self.trUtf8('Enable breakpoint'))
3891 else:
3892 self.marginMenuActs["EnableBreakpoint"].setText(\
3893 self.trUtf8('Disable breakpoint'))
3894 if self.breaks:
3895 self.marginMenuActs["NextBreakpoint"].setEnabled(True)
3896 self.marginMenuActs["PreviousBreakpoint"].setEnabled(True)
3897 self.marginMenuActs["ClearBreakpoint"].setEnabled(True)
3898 else:
3899 self.marginMenuActs["NextBreakpoint"].setEnabled(False)
3900 self.marginMenuActs["PreviousBreakpoint"].setEnabled(False)
3901 self.marginMenuActs["ClearBreakpoint"].setEnabled(False)
3902 else:
3903 self.marginMenuActs["Breakpoint"].setEnabled(False)
3904 self.marginMenuActs["TempBreakpoint"].setEnabled(False)
3905 self.marginMenuActs["EditBreakpoint"].setEnabled(False)
3906 self.marginMenuActs["EnableBreakpoint"].setEnabled(False)
3907 self.marginMenuActs["NextBreakpoint"].setEnabled(False)
3908 self.marginMenuActs["PreviousBreakpoint"].setEnabled(False)
3909 self.marginMenuActs["ClearBreakpoint"].setEnabled(False)
3910
3911 if self.bookmarks:
3912 self.marginMenuActs["NextBookmark"].setEnabled(True)
3913 self.marginMenuActs["PreviousBookmark"].setEnabled(True)
3914 self.marginMenuActs["ClearBookmark"].setEnabled(True)
3915 else:
3916 self.marginMenuActs["NextBookmark"].setEnabled(False)
3917 self.marginMenuActs["PreviousBookmark"].setEnabled(False)
3918 self.marginMenuActs["ClearBookmark"].setEnabled(False)
3919
3920 if len(self.syntaxerrors):
3921 self.marginMenuActs["GotoSyntaxError"].setEnabled(True)
3922 self.marginMenuActs["ClearSyntaxError"].setEnabled(True)
3923 if self.markersAtLine(self.line) & (1 << self.syntaxerror):
3924 self.marginMenuActs["ShowSyntaxError"].setEnabled(True)
3925 else:
3926 self.marginMenuActs["ShowSyntaxError"].setEnabled(False)
3927 else:
3928 self.marginMenuActs["GotoSyntaxError"].setEnabled(False)
3929 self.marginMenuActs["ClearSyntaxError"].setEnabled(False)
3930 self.marginMenuActs["ShowSyntaxError"].setEnabled(False)
3931
3932 if self.notcoveredMarkers:
3933 self.marginMenuActs["NextCoverageMarker"].setEnabled(True)
3934 self.marginMenuActs["PreviousCoverageMarker"].setEnabled(True)
3935 else:
3936 self.marginMenuActs["NextCoverageMarker"].setEnabled(False)
3937 self.marginMenuActs["PreviousCoverageMarker"].setEnabled(False)
3938
3939 if self.__hasTaskMarkers:
3940 self.marginMenuActs["PreviousTaskMarker"].setEnabled(True)
3941 self.marginMenuActs["NextTaskMarker"].setEnabled(True)
3942 else:
3943 self.marginMenuActs["PreviousTaskMarker"].setEnabled(False)
3944 self.marginMenuActs["NextTaskMarker"].setEnabled(False)
3945
3946 self.emit(SIGNAL("showMenu"), "Margin", self.sender(), self)
3947
3948 def __showContextMenuChecks(self):
3949 """
3950 Private slot handling the aboutToShow signal of the checks context menu.
3951 """
3952 self.emit(SIGNAL("showMenu"), "Checks", self.checksMenu, self)
3953
3954 def __contextSave(self):
3955 """
3956 Private slot handling the save context menu entry.
3957 """
3958 ok, newName = self.saveFile()
3959 if ok:
3960 self.vm.setEditorName(self, newName)
3961
3962 def __contextSaveAs(self):
3963 """
3964 Private slot handling the save as context menu entry.
3965 """
3966 ok, newName = self.saveFileAs()
3967 if ok:
3968 self.vm.setEditorName(self, newName)
3969
3970 def __contextClose(self):
3971 """
3972 Private slot handling the close context menu entry.
3973 """
3974 self.vm.closeEditor(self)
3975
3976 def __newView(self):
3977 """
3978 Private slot to create a new view to an open document.
3979 """
3980 self.vm.newEditorView(self.fileName, self, self.filetype)
3981
3982 def __newViewNewSplit(self):
3983 """
3984 Private slot to create a new view to an open document.
3985 """
3986 self.vm.addSplit()
3987 self.vm.newEditorView(self.fileName, self, self.filetype)
3988
3989 def __selectAll(self):
3990 """
3991 Private slot handling the select all context menu action.
3992 """
3993 self.selectAll(True)
3994
3995 def __deselectAll(self):
3996 """
3997 Private slot handling the deselect all context menu action.
3998 """
3999 self.selectAll(False)
4000
4001 def shortenEmptyLines(self):
4002 """
4003 Public slot to compress lines consisting solely of whitespace characters.
4004 """
4005 searchRE = r"^[ \t]+$"
4006
4007 ok = self.findFirstTarget(searchRE, True, False, False, 0, 0)
4008 self.beginUndoAction()
4009 while ok:
4010 self.replaceTarget("")
4011 ok = self.findNextTarget()
4012 self.endUndoAction()
4013
4014 def __autosaveEnable(self):
4015 """
4016 Private slot handling the autosave enable context menu action.
4017 """
4018 if self.menuActs["AutosaveEnable"].isChecked():
4019 self.autosaveManuallyDisabled = False
4020 else:
4021 self.autosaveManuallyDisabled = True
4022
4023 def shouldAutosave(self):
4024 """
4025 Public slot to check the autosave flags.
4026
4027 @return flag indicating this editor should be saved (boolean)
4028 """
4029 return self.fileName is not None and \
4030 not self.autosaveManuallyDisabled and \
4031 not self.isReadOnly()
4032
4033 def __autoSyntaxCheck(self):
4034 """
4035 Private method to perform an automatic syntax check of the file.
4036 """
4037 if Preferences.getEditor("AutoCheckSyntax"):
4038 self.clearSyntaxError()
4039 if self.isPyFile():
4040 syntaxError, _fn, errorline, _code, _error = \
4041 Utilities.compile(self.fileName, self.text())
4042 if syntaxError:
4043 self.toggleSyntaxError(int(errorline), 1, _error)
4044
4045 def __showCodeMetrics(self):
4046 """
4047 Private method to handle the code metrics context menu action.
4048 """
4049 if not self.checkDirty():
4050 return
4051
4052 self.codemetrics = CodeMetricsDialog()
4053 self.codemetrics.show()
4054 self.codemetrics.start(self.fileName)
4055
4056 def __getCodeCoverageFile(self):
4057 """
4058 Private method to get the filename of the file containing coverage info.
4059
4060 @return filename of the coverage file (string)
4061 """
4062 files = []
4063
4064 # first check if the file belongs to a project and there is
4065 # a project coverage file
4066 project = e4App().getObject("Project")
4067 if project.isOpen() and project.isProjectSource(self.fileName):
4068 fn = project.getMainScript(True)
4069 if fn is not None:
4070 tfn = Utilities.getTestFileName(fn)
4071 basename = os.path.splitext(fn)[0]
4072 tbasename = os.path.splitext(tfn)[0]
4073
4074 f = "%s.coverage" % basename
4075 tf = "%s.coverage" % tbasename
4076 if os.path.isfile(f):
4077 files.append(f)
4078 if os.path.isfile(tf):
4079 files.append(tf)
4080
4081 # now check, if there are coverage files belonging to ourself
4082 fn = self.getFileName()
4083 if fn is not None:
4084 tfn = Utilities.getTestFileName(fn)
4085 basename = os.path.splitext(fn)[0]
4086 tbasename = os.path.splitext(tfn)[0]
4087
4088 f = "%s.coverage" % basename
4089 tf = "%s.coverage" % tbasename
4090 if os.path.isfile(f) and not f in files:
4091 files.append(f)
4092 if os.path.isfile(tf) and not tf in files:
4093 files.append(tf)
4094
4095 if files:
4096 if len(files) > 1:
4097 fn, ok = QInputDialog.getItem(\
4098 self,
4099 self.trUtf8("Code Coverage"),
4100 self.trUtf8("Please select a coverage file"),
4101 files,
4102 0, False)
4103 if not ok:
4104 return
4105 else:
4106 fn = files[0]
4107 else:
4108 fn = None
4109
4110 return fn
4111
4112 def __showCodeCoverage(self):
4113 """
4114 Private method to handle the code coverage context menu action.
4115 """
4116 fn = self.__getCodeCoverageFile()
4117 if fn:
4118 self.codecoverage = PyCoverageDialog()
4119 self.codecoverage.show()
4120 self.codecoverage.start(fn, self.fileName)
4121
4122 def __codeCoverageShowAnnotations(self):
4123 """
4124 Private method to handle the show code coverage annotations context menu action.
4125 """
4126 fn = self.__getCodeCoverageFile()
4127 if fn:
4128 cover = coverage(data_file = fn)
4129 cover.use_cache(True)
4130 cover.load()
4131 missing = cover.analysis2(self.fileName)[3]
4132 if missing:
4133 for line in missing:
4134 handle = self.markerAdd(line - 1, self.notcovered)
4135 self.notcoveredMarkers.append(handle)
4136 self.emit(SIGNAL('coverageMarkersShown'), True)
4137 self.coverageMarkersShown = True
4138 else:
4139 QMessageBox.information(None,
4140 self.trUtf8("Show Code Coverage Annotations"),
4141 self.trUtf8("""All lines have been covered."""))
4142 else:
4143 QMessageBox.warning(None,
4144 self.trUtf8("Show Code Coverage Annotations"),
4145 self.trUtf8("""There is no coverage file available."""))
4146
4147 def __codeCoverageHideAnnotations(self):
4148 """
4149 Private method to handle the hide code coverage annotations context menu action.
4150 """
4151 for handle in self.notcoveredMarkers:
4152 self.markerDeleteHandle(handle)
4153 self.notcoveredMarkers = []
4154 self.emit(SIGNAL('coverageMarkersShown'), False)
4155 self.coverageMarkersShown = False
4156
4157 def hasCoverageMarkers(self):
4158 """
4159 Public method to test, if there are coverage markers.
4160 """
4161 return len(self.notcoveredMarkers) > 0
4162
4163 def nextUncovered(self):
4164 """
4165 Public slot to handle the 'Next uncovered' context menu action.
4166 """
4167 line, index = self.getCursorPosition()
4168 if line == self.lines()-1:
4169 line = 0
4170 else:
4171 line += 1
4172 ucline = self.markerFindNext(line, 1 << self.notcovered)
4173 if ucline < 0:
4174 # wrap around
4175 ucline = self.markerFindNext(0, 1 << self.notcovered)
4176 if ucline >= 0:
4177 self.setCursorPosition(ucline, 0)
4178 self.ensureLineVisible(ucline)
4179
4180 def previousUncovered(self):
4181 """
4182 Public slot to handle the 'Previous uncovered' context menu action.
4183 """
4184 line, index = self.getCursorPosition()
4185 if line == 0:
4186 line = self.lines()-1
4187 else:
4188 line -= 1
4189 ucline = self.markerFindPrevious(line, 1 << self.notcovered)
4190 if ucline < 0:
4191 # wrap around
4192 ucline = self.markerFindPrevious(self.lines() - 1, 1 << self.notcovered)
4193 if ucline >= 0:
4194 self.setCursorPosition(ucline, 0)
4195 self.ensureLineVisible(ucline)
4196
4197 def __showProfileData(self):
4198 """
4199 Private method to handle the show profile data context menu action.
4200 """
4201 files = []
4202
4203 # first check if the file belongs to a project and there is
4204 # a project profile file
4205 project = e4App().getObject("Project")
4206 if project.isOpen() and project.isProjectSource(self.fileName):
4207 fn = project.getMainScript(True)
4208 if fn is not None:
4209 tfn = Utilities.getTestFileName(fn)
4210 basename = os.path.splitext(fn)[0]
4211 tbasename = os.path.splitext(tfn)[0]
4212
4213 f = "%s.profile" % basename
4214 tf = "%s.profile" % tbasename
4215 if os.path.isfile(f):
4216 files.append(f)
4217 if os.path.isfile(tf):
4218 files.append(tf)
4219
4220 # now check, if there are profile files belonging to ourself
4221 fn = self.getFileName()
4222 if fn is not None:
4223 tfn = Utilities.getTestFileName(fn)
4224 basename = os.path.splitext(fn)[0]
4225 tbasename = os.path.splitext(tfn)[0]
4226
4227 f = "%s.profile" % basename
4228 tf = "%s.profile" % tbasename
4229 if os.path.isfile(f) and not f in files:
4230 files.append(f)
4231 if os.path.isfile(tf) and not tf in files:
4232 files.append(tf)
4233
4234 if files:
4235 if len(files) > 1:
4236 fn, ok = QInputDialog.getItem(\
4237 self,
4238 self.trUtf8("Profile Data"),
4239 self.trUtf8("Please select a profile file"),
4240 files,
4241 0, False)
4242 if not ok:
4243 return
4244 else:
4245 fn = files[0]
4246 else:
4247 return
4248
4249 self.profiledata = PyProfileDialog()
4250 self.profiledata.show()
4251 self.profiledata.start(fn, self.fileName)
4252
4253 def __lmBbookmarks(self):
4254 """
4255 Private method to handle the 'LMB toggles bookmark' context menu action.
4256 """
4257 self.marginMenuActs["LMBbookmarks"].setChecked(True)
4258 self.marginMenuActs["LMBbreakpoints"].setChecked(False)
4259
4260 def __lmBbreakpoints(self):
4261 """
4262 Private method to handle the 'LMB toggles breakpoint' context menu action.
4263 """
4264 self.marginMenuActs["LMBbookmarks"].setChecked(True)
4265 self.marginMenuActs["LMBbreakpoints"].setChecked(False)
4266
4267 ############################################################################
4268 ## Syntax error handling methods below
4269 ############################################################################
4270
4271 def toggleSyntaxError(self, line, error, msg = ""):
4272 """
4273 Public method to toggle a syntax error indicator.
4274
4275 @param line line number of the syntax error
4276 @param error flag indicating if the error marker should be
4277 set or deleted (boolean)
4278 @param msg error message (string)
4279 """
4280 if line == 0:
4281 line = 1
4282 # hack to show a syntax error marker, if line is reported to be 0
4283 if error:
4284 # set a new syntax error marker
4285 markers = self.markersAtLine(line - 1)
4286 if not (markers & (1 << self.syntaxerror)):
4287 handle = self.markerAdd(line - 1, self.syntaxerror)
4288 self.syntaxerrors[handle] = msg
4289 self.emit(SIGNAL('syntaxerrorToggled'), self)
4290 else:
4291 for handle in self.syntaxerrors.keys():
4292 if self.markerLine(handle) == line - 1:
4293 del self.syntaxerrors[handle]
4294 self.markerDeleteHandle(handle)
4295 self.emit(SIGNAL('syntaxerrorToggled'), self)
4296
4297 def getSyntaxErrors(self):
4298 """
4299 Public method to retrieve the syntax error markers.
4300
4301 @return sorted list of all lines containing a syntax error
4302 (list of integer)
4303 """
4304 selist = []
4305 for handle in self.syntaxerrors.keys():
4306 selist.append(self.markerLine(handle) + 1)
4307
4308 selist.sort()
4309 return selist
4310
4311 def hasSyntaxErrors(self):
4312 """
4313 Public method to check for the presence of bookmarks.
4314
4315 @return flag indicating the presence of bookmarks (boolean)
4316 """
4317 return len(self.syntaxerrors) > 0
4318
4319 def gotoSyntaxError(self):
4320 """
4321 Public slot to handle the 'Goto syntax error' context menu action.
4322 """
4323 seline = self.markerFindNext(0, 1 << self.syntaxerror)
4324 if seline >= 0:
4325 self.setCursorPosition(seline, 0)
4326 self.ensureLineVisible(seline)
4327
4328 def clearSyntaxError(self):
4329 """
4330 Public slot to handle the 'Clear all syntax error' context menu action.
4331 """
4332 for handle in self.syntaxerrors.keys():
4333 line = self.markerLine(handle) + 1
4334 self.toggleSyntaxError(line, False)
4335
4336 def __showSyntaxError(self, line = -1):
4337 """
4338 Private slot to handle the 'Show syntax error message' context menu action.
4339
4340 @param line line number to show the syntax error for (integer)
4341 """
4342 if line == -1:
4343 line = self.line
4344
4345 for handle in self.syntaxerrors.keys():
4346 if self.markerLine(handle) == line:
4347 QMessageBox.critical(None,
4348 self.trUtf8("Syntax Error"),
4349 self.syntaxerrors[handle])
4350 break
4351 else:
4352 QMessageBox.critical(None,
4353 self.trUtf8("Syntax Error"),
4354 self.trUtf8("No syntax error message available."))
4355
4356 #################################################################
4357 ## Macro handling methods
4358 #################################################################
4359
4360 def __getMacroName(self):
4361 """
4362 Private method to select a macro name from the list of macros.
4363
4364 @return Tuple of macro name and a flag, indicating, if the user pressed ok or
4365 canceled the operation. (string, boolean)
4366 """
4367 qs = []
4368 for s in self.macros.keys():
4369 qs.append(s)
4370 qs.sort()
4371 return QInputDialog.getItem(\
4372 self,
4373 self.trUtf8("Macro Name"),
4374 self.trUtf8("Select a macro name:"),
4375 qs,
4376 0, False)
4377
4378 def macroRun(self):
4379 """
4380 Public method to execute a macro.
4381 """
4382 name, ok = self.__getMacroName()
4383 if ok and name:
4384 self.macros[name].play()
4385
4386 def macroDelete(self):
4387 """
4388 Public method to delete a macro.
4389 """
4390 name, ok = self.__getMacroName()
4391 if ok and name:
4392 del self.macros[name]
4393
4394 def macroLoad(self):
4395 """
4396 Public method to load a macro from a file.
4397 """
4398 configDir = Utilities.getConfigDir()
4399 fname = QFileDialog.getOpenFileName(
4400 self,
4401 self.trUtf8("Load macro file"),
4402 configDir,
4403 self.trUtf8("Macro files (*.macro)"))
4404
4405 if not fname:
4406 return # user aborted
4407
4408 try:
4409 f = open(fname, "rb")
4410 lines = f.readlines()
4411 f.close()
4412 except IOError:
4413 QMessageBox.critical(self,
4414 self.trUtf8("Error loading macro"),
4415 self.trUtf8("<p>The macro file <b>{0}</b> could not be read.</p>")
4416 .format(fname))
4417 return
4418
4419 if len(lines) != 2:
4420 QMessageBox.critical(self,
4421 self.trUtf8("Error loading macro"),
4422 self.trUtf8("<p>The macro file <b>{0}</b> is corrupt.</p>")
4423 .format(fname))
4424 return
4425
4426 macro = QsciMacro(lines[1], self)
4427 self.macros[lines[0].strip()] = macro
4428
4429 def macroSave(self):
4430 """
4431 Public method to save a macro to a file.
4432 """
4433 configDir = Utilities.getConfigDir()
4434
4435 name, ok = self.__getMacroName()
4436 if not ok or not name:
4437 return # user abort
4438
4439 fname, selectedFilter = QFileDialog.getSaveFileNameAndFilter(\
4440 self,
4441 self.trUtf8("Save macro file"),
4442 configDir,
4443 self.trUtf8("Macro files (*.macro)"),
4444 "",
4445 QFileDialog.Options(QFileDialog.DontConfirmOverwrite))
4446
4447 if not fname:
4448 return # user aborted
4449
4450 ext = QFileInfo(fname).suffix()
4451 if not ext:
4452 ex = selectedFilter.split("(*")[1].split(")")[0]
4453 if ex:
4454 fname += ex
4455 if QFileInfo(fname).exists():
4456 res = QMessageBox.warning(self,
4457 self.trUtf8("Save macro"),
4458 self.trUtf8("<p>The macro file <b>{0}</b> already exists.</p>")
4459 .format(fname),
4460 QMessageBox.StandardButtons(\
4461 QMessageBox.Abort | \
4462 QMessageBox.Save),
4463 QMessageBox.Abort)
4464 if res == QMessageBox.Abort or res == QMessageBox.Cancel:
4465 return
4466 fname = Utilities.toNativeSeparators(fname)
4467
4468 try:
4469 f = open(fname, "wb")
4470 f.write("%s%s" % (name, os.linesep))
4471 f.write(self.macros[name].save())
4472 f.close()
4473 except IOError:
4474 QMessageBox.critical(self,
4475 self.trUtf8("Error saving macro"),
4476 self.trUtf8("<p>The macro file <b>{0}</b> could not be written.</p>")
4477 .format(fname))
4478 return
4479
4480 def macroRecordingStart(self):
4481 """
4482 Public method to start macro recording.
4483 """
4484 if self.recording:
4485 res = QMessageBox.warning(self,
4486 self.trUtf8("Start Macro Recording"),
4487 self.trUtf8("Macro recording is already active. Start new?"),
4488 QMessageBox.StandardButtons(\
4489 QMessageBox.No | \
4490 QMessageBox.Yes),
4491 QMessageBox.Yes)
4492 if res == QMessageBox.Yes:
4493 self.macroRecordingStop()
4494 else:
4495 return
4496 else:
4497 self.recording = True
4498
4499 self.curMacro = QsciMacro(self)
4500 self.curMacro.startRecording()
4501
4502 def macroRecordingStop(self):
4503 """
4504 Public method to stop macro recording.
4505 """
4506 if not self.recording:
4507 return # we are not recording
4508
4509 self.curMacro.endRecording()
4510 self.recording = False
4511
4512 name, ok = QInputDialog.getText(\
4513 self,
4514 self.trUtf8("Macro Recording"),
4515 self.trUtf8("Enter name of the macro:"),
4516 QLineEdit.Normal)
4517
4518 if ok and name:
4519 self.macros[name] = self.curMacro
4520
4521 self.curMacro = None
4522
4523 #################################################################
4524 ## Overwritten methods
4525 #################################################################
4526
4527 def undo(self):
4528 """
4529 Public method to undo the last recorded change.
4530 """
4531 QsciScintillaCompat.undo(self)
4532 self.emit(SIGNAL('undoAvailable'), self.isUndoAvailable())
4533 self.emit(SIGNAL('redoAvailable'), self.isRedoAvailable())
4534
4535 def redo(self):
4536 """
4537 Public method to redo the last recorded change.
4538 """
4539 QsciScintillaCompat.redo(self)
4540 self.emit(SIGNAL('undoAvailable'), self.isUndoAvailable())
4541 self.emit(SIGNAL('redoAvailable'), self.isRedoAvailable())
4542
4543 def close(self, alsoDelete = False):
4544 """
4545 Public method called when the window gets closed.
4546
4547 This overwritten method redirects the action to our
4548 ViewManager.closeEditor, which in turn calls our closeIt
4549 method.
4550
4551 @param alsoDelete ignored
4552 """
4553 return self.vm.closeEditor(self)
4554
4555 def closeIt(self):
4556 """
4557 Public method called by the viewmanager to finally get rid of us.
4558 """
4559 if Preferences.getEditor("ClearBreaksOnClose"):
4560 self.__menuClearBreakpoints()
4561
4562 for clone in self.__clones[:]:
4563 clone.removeClone(self)
4564
4565 self.disconnect(self.breakpointModel,
4566 SIGNAL("rowsAboutToBeRemoved(const QModelIndex &, int, int)"),
4567 self.__deleteBreakPoints)
4568 self.disconnect(self.breakpointModel,
4569 SIGNAL("dataAboutToBeChanged(const QModelIndex &, const QModelIndex &)"),
4570 self.__breakPointDataAboutToBeChanged)
4571 self.disconnect(self.breakpointModel,
4572 SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"),
4573 self.__changeBreakPoints)
4574 self.disconnect(self.breakpointModel,
4575 SIGNAL("rowsInserted(const QModelIndex &, int, int)"),
4576 self.__addBreakPoints)
4577
4578 self.disconnect(e4App().getObject("Project"), SIGNAL("projectPropertiesChanged"),
4579 self.__projectPropertiesChanged)
4580
4581 if self.spell:
4582 self.spell.stopIncrementalCheck()
4583
4584 QsciScintillaCompat.close(self)
4585
4586 def keyPressEvent(self, ev):
4587 """
4588 Re-implemented to handle the user input a key at a time.
4589
4590 @param ev key event (QKeyEvent)
4591 """
4592 txt = ev.text()
4593 key = ev.key()
4594
4595 # See it is text to insert.
4596 if len(txt) and txt >= " ":
4597 QsciScintillaCompat.keyPressEvent(self, ev)
4598 else:
4599 ev.ignore()
4600
4601 def focusInEvent(self, event):
4602 """
4603 Protected method called when the editor receives focus.
4604
4605 This method checks for modifications of the current file and
4606 rereads it upon request. The cursor is placed at the current position
4607 assuming, that it is in the vicinity of the old position after the reread.
4608
4609 @param event the event object (QFocusEvent)
4610 """
4611 self.vm.editActGrp.setEnabled(True)
4612 self.vm.editorActGrp.setEnabled(True)
4613 self.vm.copyActGrp.setEnabled(True)
4614 self.vm.viewActGrp.setEnabled(True)
4615 try:
4616 self.setCaretWidth(self.caretWidth)
4617 except AttributeError:
4618 pass
4619 self.__updateReadOnly(False)
4620 if self.vm.editorsCheckFocusInEnabled() and \
4621 not self.inReopenPrompt and self.fileName and \
4622 QFileInfo(self.fileName).lastModified().toString() != \
4623 self.lastModified.toString():
4624 if Preferences.getEditor("AutoReopen") and not self.isModified():
4625 self.refresh()
4626 else:
4627 self.inReopenPrompt = True
4628 msg = self.trUtf8(\
4629 """<p>The file <b>{0}</b> has been changed while it was opened in"""
4630 """ eric4. Reread it?</p>""").format(self.fileName)
4631 default = QMessageBox.No
4632 if self.isModified():
4633 msg += self.trUtf8(\
4634 """<br><b>Warning:</b> You will loose"""
4635 """ your changes upon reopening it.""")
4636 default = QMessageBox.Ok
4637 res = QMessageBox.warning(None,
4638 self.trUtf8("File changed"), msg,
4639 QMessageBox.StandardButtons(\
4640 QMessageBox.Yes | \
4641 QMessageBox.No),
4642 default)
4643 if res == QMessageBox.Yes:
4644 self.refresh()
4645 else:
4646 # do not prompt for this change again...
4647 self.lastModified = QFileInfo(self.fileName).lastModified()
4648 self.inReopenPrompt = False
4649
4650 QsciScintillaCompat.focusInEvent(self, event)
4651
4652 def focusOutEvent(self, event):
4653 """
4654 Public method called when the editor loses focus.
4655
4656 @param event the event object (QFocusEvent)
4657 """
4658 self.vm.editorActGrp.setEnabled(False)
4659 self.setCaretWidth(0)
4660
4661 QsciScintillaCompat.focusOutEvent(self, event)
4662
4663 def changeEvent(self, evt):
4664 """
4665 Protected method called to process an event.
4666
4667 This implements special handling for the events showMaximized,
4668 showMinimized and showNormal. The windows caption is shortened
4669 for the minimized mode and reset to the full filename for the
4670 other modes. This is to make the editor windows work nicer
4671 with the QWorkspace.
4672
4673 @param evt the event, that was generated (QEvent)
4674 @return flag indicating if the event could be processed (bool)
4675 """
4676 if evt.type() == QEvent.WindowStateChange and \
4677 self.fileName is not None:
4678 if self.windowState() == Qt.WindowStates(Qt.WindowMinimized):
4679 cap = os.path.basename(self.fileName)
4680 else:
4681 cap = self.fileName
4682 if self.isReadOnly():
4683 cap = self.trUtf8("{0} (ro)").format(cap)
4684 self.setWindowTitle(cap)
4685
4686 QsciScintillaCompat.changeEvent(self, evt)
4687
4688 def mousePressEvent(self, event):
4689 """
4690 Protected method to handle the mouse press event.
4691
4692 @param event the mouse press event (QMouseEvent)
4693 """
4694 self.vm.eventFilter(self, event)
4695 QsciScintillaCompat.mousePressEvent(self, event)
4696
4697 def __updateReadOnly(self, bForce = True):
4698 """
4699 Private method to update the readOnly information for this editor.
4700
4701 If bForce is True, then updates everything regardless if
4702 the attributes have actually changed, such as during
4703 initialization time. A signal is emitted after the
4704 caption change.
4705
4706 @param bForce True to force change, False to only update and emit
4707 signal if there was an attribute change.
4708 """
4709 if self.fileName is None:
4710 return
4711 readOnly = not QFileInfo(self.fileName).isWritable()
4712 if not bForce and (readOnly == self.isReadOnly()):
4713 return
4714 cap = self.fileName
4715 if readOnly:
4716 cap = self.trUtf8("{0} (ro)".format(cap))
4717 self.setReadOnly(readOnly)
4718 self.setWindowTitle(cap)
4719 self.emit(SIGNAL('captionChanged'), cap, self)
4720
4721 def refresh(self):
4722 """
4723 Public slot to refresh the editor contents.
4724 """
4725 # save cursor position
4726 cline, cindex = self.getCursorPosition()
4727
4728 # save bookmarks and breakpoints and clear them
4729 bmlist = self.getBookmarks()
4730 self.clearBookmarks()
4731
4732 # clear syntax error markers
4733 self.clearSyntaxError()
4734
4735 # clear breakpoint markers
4736 for handle in self.breaks.keys():
4737 self.markerDeleteHandle(handle)
4738 self.breaks = {}
4739
4740 # reread the file
4741 try:
4742 self.readFile(self.fileName)
4743 except IOError:
4744 # do not prompt for this change again...
4745 self.lastModified = QDateTime.currentDateTime()
4746 self.setModified(False)
4747
4748 # reset cursor position
4749 self.setCursorPosition(cline, cindex)
4750 self.ensureCursorVisible()
4751
4752 # reset bookmarks and breakpoints to their old position
4753 if bmlist:
4754 for bm in bmlist:
4755 self.toggleBookmark(bm)
4756 self.__restoreBreakpoints()
4757
4758 self.emit(SIGNAL('editorSaved'), self.fileName)
4759 self.__autoSyntaxCheck()
4760
4761 def setMonospaced(self, on):
4762 """
4763 Public method to set/reset a monospaced font.
4764
4765 @param on flag to indicate usage of a monospace font (boolean)
4766 """
4767 if on:
4768 f = Preferences.getEditorOtherFonts("MonospacedFont")
4769 self.monospacedStyles(f)
4770 else:
4771 if not self.lexer_:
4772 self.clearStyles()
4773 self.__setMarginsDisplay()
4774 self.setFont(Preferences.getEditorOtherFonts("DefaultFont"))
4775
4776 self.useMonospaced = on
4777
4778 #################################################################
4779 ## Drag and Drop Support
4780 #################################################################
4781
4782 def dragEnterEvent(self, event):
4783 """
4784 Protected method to handle the drag enter event.
4785
4786 @param event the drag enter event (QDragEnterEvent)
4787 """
4788 self.inDragDrop = event.mimeData().hasUrls()
4789 if self.inDragDrop:
4790 event.acceptProposedAction()
4791 else:
4792 QsciScintillaCompat.dragEnterEvent(self, event)
4793
4794 def dragMoveEvent(self, event):
4795 """
4796 Protected method to handle the drag move event.
4797
4798 @param event the drag move event (QDragMoveEvent)
4799 """
4800 if self.inDragDrop:
4801 event.accept()
4802 else:
4803 QsciScintillaCompat.dragMoveEvent(self, event)
4804
4805 def dragLeaveEvent(self, event):
4806 """
4807 Protected method to handle the drag leave event.
4808
4809 @param event the drag leave event (QDragLeaveEvent)
4810 """
4811 if self.inDragDrop:
4812 self.inDragDrop = False
4813 event.accept()
4814 else:
4815 QsciScintillaCompat.dragLeaveEvent(self, event)
4816
4817 def dropEvent(self, event):
4818 """
4819 Protected method to handle the drop event.
4820
4821 @param event the drop event (QDropEvent)
4822 """
4823 if event.mimeData().hasUrls():
4824 for url in event.mimeData().urls():
4825 fname = url.toLocalFile()
4826 if fname:
4827 if not QFileInfo(fname).isDir():
4828 self.vm.openSourceFile(fname)
4829 else:
4830 QMessageBox.information(None,
4831 self.trUtf8("Drop Error"),
4832 self.trUtf8("""<p><b>{0}</b> is not a file.</p>""")
4833 .format(fname))
4834 event.acceptProposedAction()
4835 else:
4836 QsciScintillaCompat.dropEvent(self, event)
4837
4838 self.inDragDrop = False
4839
4840 #################################################################
4841 ## Support for Qt resources files
4842 #################################################################
4843
4844 def __initContextMenuResources(self):
4845 """
4846 Private method used to setup the Resources context sub menu.
4847 """
4848 menu = QMenu(self.trUtf8('Resources'))
4849
4850 menu.addAction(self.trUtf8('Add file...'),
4851 self.__addFileResource)
4852 menu.addAction(self.trUtf8('Add files...'),
4853 self.__addFileResources)
4854 menu.addAction(self.trUtf8('Add aliased file...'),
4855 self.__addFileAliasResource)
4856 menu.addAction(self.trUtf8('Add localized resource...'),
4857 self.__addLocalizedResource)
4858 menu.addSeparator()
4859 menu.addAction(self.trUtf8('Add resource frame'),
4860 self.__addResourceFrame)
4861
4862 self.connect(menu, SIGNAL('aboutToShow()'), self.__showContextMenuResources)
4863
4864 return menu
4865
4866 def __showContextMenuResources(self):
4867 """
4868 Private slot handling the aboutToShow signal of the resources context menu.
4869 """
4870 self.emit(SIGNAL("showMenu"), "Resources", self.resourcesMenu, self)
4871
4872 def __addFileResource(self):
4873 """
4874 Private method to handle the Add file context menu action.
4875 """
4876 dirStr = os.path.dirname(self.fileName)
4877 file = QFileDialog.getOpenFileName(\
4878 self,
4879 self.trUtf8("Add file resource"),
4880 dirStr,
4881 "")
4882 if file:
4883 relFile = QDir(dirStr).relativeFilePath(file)
4884 line, index = self.getCursorPosition()
4885 self.insert(" <file>{0}</file>\n".format(relFile))
4886 self.setCursorPosition(line + 1, index)
4887
4888 def __addFileResources(self):
4889 """
4890 Private method to handle the Add files context menu action.
4891 """
4892 dirStr = os.path.dirname(self.fileName)
4893 files = QFileDialog.getOpenFileNames(\
4894 self,
4895 self.trUtf8("Add file resources"),
4896 dirStr,
4897 "")
4898 if files:
4899 myDir = QDir(dirStr)
4900 filesText = ""
4901 for file in files:
4902 relFile = myDir.relativeFilePath(file)
4903 filesText += " <file>{0}</file>\n".format(relFile)
4904 line, index = self.getCursorPosition()
4905 self.insert(filesText)
4906 self.setCursorPosition(line + len(files), index)
4907
4908 def __addFileAliasResource(self):
4909 """
4910 Private method to handle the Add aliased file context menu action.
4911 """
4912 dirStr = os.path.dirname(self.fileName)
4913 file = QFileDialog.getOpenFileName(\
4914 self,
4915 self.trUtf8("Add aliased file resource"),
4916 dirStr,
4917 "")
4918 if file:
4919 relFile = QDir(dirStr).relativeFilePath(file)
4920 alias, ok = QInputDialog.getText(\
4921 self,
4922 self.trUtf8("Add aliased file resource"),
4923 self.trUtf8("Alias for file <b>{0}</b>:").format(relFile),
4924 QLineEdit.Normal,
4925 relFile)
4926 if ok and alias:
4927 line, index = self.getCursorPosition()
4928 self.insert(' <file alias="{1}">{0}</file>\n'\
4929 .format(relFile, alias))
4930 self.setCursorPosition(line + 1, index)
4931
4932 def __addLocalizedResource(self):
4933 """
4934 Private method to handle the Add localized resource context menu action.
4935 """
4936 from Project.AddLanguageDialog import AddLanguageDialog
4937 dlg = AddLanguageDialog(self)
4938 if dlg.exec_() == QDialog.Accepted:
4939 lang = dlg.getSelectedLanguage()
4940 line, index = self.getCursorPosition()
4941 self.insert('<qresource lang="{0}">\n</qresource>\n'.format(lang))
4942 self.setCursorPosition(line + 2, index)
4943
4944 def __addResourceFrame(self):
4945 """
4946 Private method to handle the Add resource frame context menu action.
4947 """
4948 line, index = self.getCursorPosition()
4949 self.insert('<!DOCTYPE RCC>\n'
4950 '<RCC version="1.0">\n'
4951 '<qresource>\n'
4952 '</qresource>\n'
4953 '</RCC>\n')
4954 self.setCursorPosition(line + 5, index)
4955
4956 #################################################################
4957 ## Support for diagrams below
4958 #################################################################
4959
4960 def __showClassDiagram(self):
4961 """
4962 Private method to handle the Class Diagram context menu action.
4963 """
4964 from Graphics.UMLClassDiagram import UMLClassDiagram
4965 if not self.checkDirty():
4966 return
4967
4968 self.classDiagram = UMLClassDiagram(self.fileName, self, noAttrs = False)
4969 self.classDiagram.show()
4970
4971 def __showPackageDiagram(self):
4972 """
4973 Private method to handle the Package Diagram context menu action.
4974 """
4975 from Graphics.PackageDiagram import PackageDiagram
4976 if not self.checkDirty():
4977 return
4978
4979 package = os.path.isdir(self.fileName) and self.fileName \
4980 or os.path.dirname(self.fileName)
4981 res = QMessageBox.question(None,
4982 self.trUtf8("Package Diagram"),
4983 self.trUtf8("""Include class attributes?"""),
4984 QMessageBox.StandardButtons(\
4985 QMessageBox.No | \
4986 QMessageBox.Yes),
4987 QMessageBox.Yes)
4988 self.packageDiagram = PackageDiagram(package, self,
4989 noAttrs = (res == QMessageBox.No))
4990 self.packageDiagram.show()
4991
4992 def __showImportsDiagram(self):
4993 """
4994 Private method to handle the Imports Diagram context menu action.
4995 """
4996 from Graphics.ImportsDiagram import ImportsDiagram
4997 if not self.checkDirty():
4998 return
4999
5000 package = os.path.isdir(self.fileName) and self.fileName \
5001 or os.path.dirname(self.fileName)
5002 res = QMessageBox.question(None,
5003 self.trUtf8("Imports Diagram"),
5004 self.trUtf8("""Include imports from external modules?"""),
5005 QMessageBox.StandardButtons(\
5006 QMessageBox.No | \
5007 QMessageBox.Yes),
5008 QMessageBox.No)
5009 self.importsDiagram = ImportsDiagram(package, self,
5010 showExternalImports = (res == QMessageBox.Yes))
5011 self.importsDiagram.show()
5012
5013 def __showApplicationDiagram(self):
5014 """
5015 Private method to handle the Imports Diagram context menu action.
5016 """
5017 from Graphics.ApplicationDiagram import ApplicationDiagram
5018 res = QMessageBox.question(None,
5019 self.trUtf8("Application Diagram"),
5020 self.trUtf8("""Include module names?"""),
5021 QMessageBox.StandardButtons(\
5022 QMessageBox.No | \
5023 QMessageBox.Yes),
5024 QMessageBox.Yes)
5025 self.applicationDiagram = ApplicationDiagram(e4App().getObject("Project"),
5026 self, noModules = (res == QMessageBox.No))
5027 self.applicationDiagram.show()
5028
5029 #######################################################################
5030 ## Typing aids related methods below
5031 #######################################################################
5032
5033 def __toggleTypingAids(self):
5034 """
5035 Private slot to toggle the typing aids.
5036 """
5037 if self.menuActs["TypingAidsEnabled"].isChecked():
5038 self.completer.setEnabled(True)
5039 else:
5040 self.completer.setEnabled(False)
5041
5042 #######################################################################
5043 ## Autocompleting templates
5044 #######################################################################
5045
5046 def editorCommand(self, cmd):
5047 """
5048 Public method to perform a simple editor command.
5049
5050 @param cmd the scintilla command to be performed
5051 """
5052 if cmd == QsciScintilla.SCI_TAB:
5053 line, index = self.getCursorPosition()
5054 tmplName = self.getWordLeft(line, index)
5055 if tmplName:
5056 if e4App().getObject("TemplateViewer").hasTemplate(tmplName):
5057 self.__applyTemplate(tmplName)
5058 return
5059 else:
5060 templateNames = \
5061 e4App().getObject("TemplateViewer").getTemplateNames(tmplName)
5062 if len(templateNames) == 1:
5063 self.__applyTemplate(templateNames[0])
5064 return
5065 elif len(templateNames) > 1:
5066 self.showUserList(TemplateCompletionListID,
5067 ["%s?%d" % (t, self.TemplateImageID) for t in templateNames])
5068 return
5069
5070 QsciScintillaCompat.editorCommand(self, cmd)
5071
5072 def __completionListSelected(self, id, txt):
5073 """
5074 Private slot to handle the selection from the completion list.
5075
5076 @param id the ID of the user list (should be 1) (integer)
5077 @param txt the selected text (string)
5078 """
5079 if id == TemplateCompletionListID:
5080 self.__applyTemplate(txt)
5081
5082 def __applyTemplate(self, templateName):
5083 """
5084 Private method to apply a template by name.
5085
5086 @param templateName name of the template to apply (string)
5087 """
5088 if e4App().getObject("TemplateViewer").hasTemplate(templateName):
5089 self.extendSelectionWordLeft()
5090 e4App().getObject("TemplateViewer").applyNamedTemplate(templateName)
5091
5092 #######################################################################
5093 ## Project related methods
5094 #######################################################################
5095
5096 def __projectPropertiesChanged(self):
5097 """
5098 Private slot to handle changes of the project properties.
5099 """
5100 project = e4App().getObject("Project")
5101 if self.spell:
5102 pwl, pel = project.getProjectDictionaries()
5103 self.__setSpellingLanguage(project.getProjectSpellLanguage(),
5104 pwl = pwl, pel = pel)
5105
5106 def addedToProject(self):
5107 """
5108 Public method to signal, that this editor has been added to a project.
5109 """
5110 project = e4App().getObject("Project")
5111 if self.spell:
5112 pwl, pel = project.getProjectDictionaries()
5113 self.__setSpellingLanguage(project.getProjectSpellLanguage(),
5114 pwl = pwl, pel = pel)
5115 self.connect(project, SIGNAL("projectPropertiesChanged"),
5116 self.__projectPropertiesChanged)
5117
5118 #######################################################################
5119 ## Spellchecking related methods
5120 #######################################################################
5121
5122 def __setSpellingLanguage(self, language, pwl = "", pel = ""):
5123 """
5124 Private slot to set the spell checking language.
5125
5126 @param language spell checking language to be set (string)
5127 @keyparam pwl name of the personal/project word list (string)
5128 @keyparam pel name of the personal/project exclude list (string)
5129 """
5130 if self.spell and self.spell.getLanguage() != language:
5131 self.spell.setLanguage(language, pwl = pwl, pel = pel)
5132 self.spell.checkDocumentIncrementally()
5133
5134 def __setSpelling(self):
5135 """
5136 Private method to initialize the spell checking functionality.
5137 """
5138 if Preferences.getEditor("SpellCheckingEnabled"):
5139 self.__spellCheckStringsOnly = Preferences.getEditor("SpellCheckStringsOnly")
5140 if self.spell is None:
5141 self.spell = SpellChecker(self, self.spellingIndicator,
5142 checkRegion = self.isSpellCheckRegion)
5143 self.setSpellingForProject()
5144 self.connect(e4App().getObject("Project"), SIGNAL("projectPropertiesChanged"),
5145 self.__projectPropertiesChanged)
5146 self.spell.setMinimumWordSize(
5147 Preferences.getEditor("SpellCheckingMinWordSize"))
5148
5149 self.setAutoSpellChecking()
5150 else:
5151 self.spell = None
5152 self.clearAllIndicators(self.spellingIndicator)
5153
5154 def setSpellingForProject(self):
5155 """
5156 Public method to set the spell checking options for files belonging
5157 to the current project.
5158 """
5159 project = e4App().getObject("Project")
5160 if project.isOpen() and project.isProjectSource(self.fileName):
5161 pwl, pel = project.getProjectDictionaries()
5162 self.__setSpellingLanguage(project.getProjectSpellLanguage(),
5163 pwl = pwl, pel = pel)
5164
5165 def setAutoSpellChecking(self):
5166 """
5167 Public method to set the automatic spell checking.
5168 """
5169 if Preferences.getEditor("AutoSpellCheckingEnabled"):
5170 self.connect(self, SIGNAL("SCN_CHARADDED(int)"), self.__spellCharAdded)
5171 self.spell.checkDocumentIncrementally()
5172 else:
5173 self.disconnect(self, SIGNAL("SCN_CHARADDED(int)"), self.__spellCharAdded)
5174 self.clearAllIndicators(self.spellingIndicator)
5175
5176 def isSpellCheckRegion(self, pos):
5177 """
5178 Public method to check, if the given position is within a region, that should
5179 be spell checked.
5180
5181 @param pos position to be checked (integer)
5182 @return flag indicating pos is in a spell check region (boolean)
5183 """
5184 if self.__spellCheckStringsOnly:
5185 style = self.styleAt(pos)
5186 if self.lexer_ is not None:
5187 return self.lexer_.isCommentStyle(style) or \
5188 self.lexer_.isStringStyle(style)
5189 return True
5190
5191 def __spellCharAdded(self, charNumber):
5192 """
5193 Public slot called to handle the user entering a character.
5194
5195 @param charNumber value of the character entered (integer)
5196 """
5197 if self.spell:
5198 if not unichr(charNumber).isalnum():
5199 self.spell.checkWord(self.positionBefore(self.currentPosition()), True)
5200 elif self.hasIndicator(self.spellingIndicator, self.currentPosition()):
5201 self.spell.checkWord(self.currentPosition())
5202
5203 def checkSpelling(self):
5204 """
5205 Public slot to perform an interactive spell check of the document.
5206 """
5207 if self.spell:
5208 cline, cindex = self.getCursorPosition()
5209 dlg = SpellCheckingDialog(self.spell, 0, self.length(), self)
5210 dlg.exec_()
5211 self.setCursorPosition(cline, cindex)
5212 if Preferences.getEditor("AutoSpellCheckingEnabled"):
5213 self.spell.checkDocumentIncrementally()
5214
5215 def __checkSpellingSelection(self):
5216 """
5217 Private slot to spell check the current selection.
5218 """
5219 sline, sindex, eline, eindex = self.getSelection()
5220 startPos = self.positionFromLineIndex(sline, sindex)
5221 endPos = self.positionFromLineIndex(eline, eindex)
5222 dlg = SpellCheckingDialog(self.spell, startPos, endPos, self)
5223 dlg.exec_()
5224
5225 def __checkSpellingWord(self):
5226 """
5227 Private slot to check the word below the spelling context menu.
5228 """
5229 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
5230 wordStart, wordEnd = self.getWordBoundaries(line, index)
5231 wordStartPos = self.positionFromLineIndex(line, wordStart)
5232 wordEndPos = self.positionFromLineIndex(line, wordEnd)
5233 dlg = SpellCheckingDialog(self.spell, wordStartPos, wordEndPos, self)
5234 dlg.exec_()
5235
5236 def __showContextMenuSpelling(self):
5237 """
5238 Private slot to set up the spelling menu before it is shown.
5239 """
5240 self.spellingMenu.clear()
5241 self.spellingSuggActs = []
5242 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
5243 word = self.getWord(line, index)
5244 suggestions = self.spell.getSuggestions(word)
5245 for suggestion in suggestions[:5]:
5246 self.spellingSuggActs.append(self.spellingMenu.addAction(suggestion))
5247 if suggestions:
5248 self.spellingMenu.addSeparator()
5249 self.spellingMenu.addAction(UI.PixmapCache.getIcon("spellchecking.png"),
5250 self.trUtf8("Check spelling..."), self.__checkSpellingWord)
5251 self.spellingMenu.addAction(self.trUtf8("Add to dictionary"),
5252 self.__addToSpellingDictionary)
5253 self.spellingMenu.addAction(self.trUtf8("Ignore All"),
5254 self.__ignoreSpellingAlways)
5255
5256 self.emit(SIGNAL("showMenu"), "Spelling", self.spellingMenu, self)
5257
5258 def __contextMenuSpellingTriggered(self, action):
5259 """
5260 Private slot to handle the selection of a suggestion of the spelling context menu.
5261
5262 @param action reference to the action that was selected (QAction)
5263 """
5264 if action in self.spellingSuggActs:
5265 replacement = action.text()
5266 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
5267 wordStart, wordEnd = self.getWordBoundaries(line, index)
5268 self.setSelection(line, wordStart, line, wordEnd)
5269 self.beginUndoAction()
5270 self.removeSelectedText()
5271 self.insert(replacement)
5272 self.endUndoAction()
5273
5274 def __addToSpellingDictionary(self):
5275 """
5276 Private slot to add the word below the spelling context menu to the dictionary.
5277 """
5278 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
5279 word = self.getWord(line, index)
5280 self.spell.add(word)
5281
5282 wordStart, wordEnd = self.getWordBoundaries(line, index)
5283 self.clearIndicator(self.spellingIndicator, line, wordStart, line, wordEnd)
5284 if Preferences.getEditor("AutoSpellCheckingEnabled"):
5285 self.spell.checkDocumentIncrementally()
5286
5287 def __removeFromSpellingDictionary(self):
5288 """
5289 Private slot to remove the word below the context menu to the dictionary.
5290 """
5291 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
5292 word = self.getWord(line, index)
5293 self.spell.remove(word)
5294
5295 if Preferences.getEditor("AutoSpellCheckingEnabled"):
5296 self.spell.checkDocumentIncrementally()
5297
5298 def __ignoreSpellingAlways(self):
5299 """
5300 Private to always ignore the word below the spelling context menu.
5301 """
5302 line, index = self.lineIndexFromPosition(self.spellingMenuPos)
5303 word = self.getWord(line, index)
5304 self.spell.ignoreAlways(word)
5305 if Preferences.getEditor("AutoSpellCheckingEnabled"):
5306 self.spell.checkDocumentIncrementally()

eric ide

mercurial