def __init__(self, nombre_archivo, ext=''): super(Editor, self).__init__() self.__nombre = "" self.texto_modificado = False self.es_nuevo = True self.guardado_actualmente = False # Actualiza flags (espacios en blanco, cursor, sidebar, etc) self.actualizar() # Lexer self._lexer = None self.cargar_lexer(ext) # Autocompletado #FIXME: api = QsciAPIs(self._lexer) for palabra in keywords.keywords: api.add(palabra) api.prepare() self.setAutoCompletionThreshold(1) self.setAutoCompletionSource(QsciScintilla.AcsAPIs) # Indentación self._indentacion = ESettings.get('editor/indentacionAncho') self.send("sci_settabwidth", self._indentacion) # Minimapa self.minimapa = MiniMapa(self) self.connect(self, SIGNAL("selectionChanged()"), self.minimapa.area) self.connect(self, SIGNAL("textChanged()"), self.minimapa.actualizar_codigo) # Thread ocurrencias self.hilo_ocurrencias = ThreadBusqueda() self.connect(self.hilo_ocurrencias, SIGNAL("ocurrenciasThread(PyQt_PyObject)"), self.marcar_palabras) # Analizador de estilo de código self.checker = checker.Checker(self) self.connect(self.checker, SIGNAL("finished()"), self._show_violations) #self.checker.errores.connect(self._marcar_errores) # Fuente fuente = ESettings.get('editor/fuente') tam_fuente = ESettings.get('editor/fuenteTam') self.cargar_fuente(fuente, tam_fuente) self.setMarginsBackgroundColor(QColor(self._tema['sidebar-fondo'])) self.setMarginsForegroundColor(QColor(self._tema['sidebar-fore'])) # Línea actual, cursor self.caret_line(self._tema['caret-background'], self._tema['caret-line'], self._tema['caret-opacidad']) # Márgen if ESettings.get('editor/margen'): self.actualizar_margen() # Brace matching self.match_braces(Base.SloppyBraceMatch) self.match_braces_color(self._tema['brace-background'], self._tema['brace-foreground']) self.unmatch_braces_color(self._tema['brace-unbackground'], self._tema['brace-unforeground'])
class RevsetEntry(QsciScintilla): returnPressed = pyqtSignal() def __init__(self, parent=None): super(RevsetEntry, self).__init__(parent) self.setMarginWidth(1, 0) self.setReadOnly(False) self.setUtf8(True) self.setCaretWidth(10) self.setCaretLineBackgroundColor(QColor("#e6fff0")) self.setCaretLineVisible(True) self.setAutoIndent(True) self.setMatchedBraceBackgroundColor(Qt.yellow) self.setIndentationsUseTabs(False) self.setBraceMatching(QsciScintilla.SloppyBraceMatch) self.setWrapMode(QsciScintilla.WrapWord) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) sp = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) sp.setHorizontalStretch(1) sp.setVerticalStretch(0) self.setSizePolicy(sp) self.setAutoCompletionThreshold(2) self.setAutoCompletionSource(QsciScintilla.AcsAPIs) self.setAutoCompletionFillupsEnabled(True) self.setLexer(QsciLexerPython(self)) self.lexer().setFont(qtlib.getfont('fontcomment').font()) self.apis = QsciAPIs(self.lexer()) def addCompletions(self, *lists): for list in lists: for x, y in list: self.apis.add(x) self.apis.prepare() def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: event.ignore() return if event.key() in (Qt.Key_Enter, Qt.Key_Return): if not self.isListActive(): event.ignore() self.returnPressed.emit() return super(RevsetEntry, self).keyPressEvent(event) def sizeHint(self): return QSize(10, self.fontMetrics().height())
class CSSEditor(BaseEditor): def __init__(self, parent=None, line_num_margin=3, autocomplete_list=None): super(CSSEditor, self).__init__(parent, line_num_margin, autocomplete_list) # Set HTML lexer self.lexer = QsciLexerCSS(self) self.lexer.setDefaultFont(self.editor_font) # Set auto-completion self.api = QsciAPIs(self.lexer) if autocomplete_list is not None: # Add additional completion strings for i in autocomplete_list: self.api.add(i) self.api.prepare() self.setAutoCompletionThreshold(3) self.setAutoCompletionSource(QsciScintilla.AcsAPIs) self.setLexer(self.lexer)
class HTMLEditor(BaseEditor): def __init__(self, parent=None, line_num_margin=3, autocomplete_list=None): super(HTMLEditor, self).__init__(parent, line_num_margin, autocomplete_list) # Set HTML lexer self.lexer = QsciLexerHTML(self) self.lexer.setDefaultFont(self.editor_font) self.lexer.setFont(self.editor_font, QsciLexerHTML.Default) # Text between tags self.lexer.setFont(self.editor_font, QsciLexerHTML.JavaScriptWord) # Text between script tags # Set auto-completion self.api = QsciAPIs(self.lexer) if autocomplete_list is not None: # Add additional completion strings for i in autocomplete_list: self.api.add(i) self.api.prepare() self.setAutoCompletionThreshold(3) self.setAutoCompletionSource(QsciScintilla.AcsAPIs) self.setAutoCompletionUseSingle(QsciScintilla.AcusExplicit) self.setLexer(self.lexer)
def initCompleter(self): dictionary = None if self.db: dictionary = self.db.connector.getSqlDictionary() if not dictionary: # use the generic sql dictionary from .sql_dictionary import getSqlDictionary dictionary = getSqlDictionary() wordlist = [] for name, value in dictionary.iteritems(): wordlist += value # concat lists wordlist = list(set(wordlist)) # remove duplicates api = QsciAPIs(self.editSql.lexer()) for word in wordlist: api.add(word) api.prepare() self.editSql.lexer().setAPIs(api)
class PythonEditor(BaseEditor): def __init__(self, parent=None, line_num_margin=3, autocomplete_list=None): super(PythonEditor, self).__init__(parent, line_num_margin, autocomplete_list) # Set Python lexer self.lexer = QsciLexerPython(self) self.lexer.setDefaultFont(self.editor_font) self.lexer.setFont(self.editor_font, QsciLexerPython.Comment) # Indentation warning ("The indentation is inconsistent when compared to the previous line") self.lexer.setIndentationWarning(QsciLexerPython.Inconsistent) # Set auto-completion self.api = QsciAPIs(self.lexer) if autocomplete_list is not None: # Add additional completion strings for i in autocomplete_list: self.api.add(i) self.api.prepare() self.setAutoCompletionThreshold(3) self.setAutoCompletionSource(QsciScintilla.AcsAll) self.setAutoCompletionUseSingle(QsciScintilla.AcusExplicit) self.setLexer(self.lexer) # PEP8 tabs self.setIndentationsUseTabs(False) self.setAutoIndent(True) self.setIndentationGuides(True) # PEP8 edge column line self.edgecol = 80 # Linters self.linter = 'internal' def clean_code(self): self.setText(autopep8.fix_code(self.text(), options={'aggressive': 2})) def check_code(self, path): self._clear_all_margin_markers() self.lint_data = {} try: warnings = linter.check(self.text(), os.path.basename(path)) for w in warnings: w.type = 'warning' key = w.lineno - 1 if key in self.lint_data.keys(): self.lint_data[key].append(w) else: self.lint_data[key] = [w] self._add_warn_margin_marker(w.lineno - 1) except linter.LinterSyntaxError as e: e.type = 'error' self.lint_data[e.lineno - 1] = e self._add_error_margin_marker(e.lineno - 1) except linter.LinterUnexpectedError as e: print(e) def margin_clicked(self, marnum, linenum, modifiers): if self._margin_popup.isVisible(): self._hide_margin_popup() else: try: warnings = self.lint_data[linenum] desc = '' if isinstance(warnings, linter.LinterError): desc = warnings.message self._show_margin_popup(desc.strip(), bg_color=self.colorErrorBackground) else: for warning in warnings: desc += warning.message % warning.message_args + '\n' self._show_margin_popup(desc.strip(), bg_color=self.colorWarnBackground) except KeyError: pass return True
class CppEditor(QsciScintilla): ''' classdocs ''' def __init__(self, parent=None, fileName=None, readOnlyFiles=[]): ''' Constructor ''' super(CppEditor, self).__init__(parent) self.parent = parent self.roFiles = readOnlyFiles self.setAcceptDrops(False) # drag&drop is on its parent # Set the default font font = QtGui.QFont() font.setFamily('Courier') font.setFixedPitch(True) font.setPointSize(10) self.setFont(font) self.setMarginsFont(font) # C/C++ lexer self.lexer = QsciLexerCPP(self, True) self.lexer.setDefaultFont(font) self.libraryAPIs = QsciAPIs(QsciLexerCPP(self, True)) self.setLexer(self.lexer) self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier') # Auto-indent self.setTabWidth(4) #self.setIndentationsUseTabs(False) self.setAutoIndent(True) # Current line visible with special background color self.setCaretLineVisible(True) self.setCaretLineBackgroundColor(QtGui.QColor("#ffe4e4")) # Enable brace matching self.setBraceMatching(QsciScintilla.SloppyBraceMatch) # Enable folding visual- use boxes self.setFolding(QsciScintilla.BoxedTreeFoldStyle) # show line numbers fontmetrics = QtGui.QFontMetrics(font) self.setMarginsFont(font) self.setMarginWidth(0, fontmetrics.width("00000") + 4) self.setMarginLineNumbers(0, True) self.setMarginsBackgroundColor(QtGui.QColor("#ccccee")) # not too small self.setMinimumSize(400, 200) # set the length of the string before the editor tries to auto-complete self.setAutoCompletionThreshold(3) # tell the editor we are using a QsciAPI for the auto-completion self.setAutoCompletionSource(QsciScintilla.AcsAPIs) # removed remaining right side characters from the current cursor self.setAutoCompletionReplaceWord(True) # "CTRL+Space" autocomplete self.shortcut_ctrl_space = QtGui.QShortcut( QtGui.QKeySequence("Ctrl+Space"), self) self.connect(self.shortcut_ctrl_space, QtCore.SIGNAL('activated()'), self.autoCompleteFromAll) if fileName: self.curFile = fileName self.loadFile(fileName) self.isUntitled = False else: self.curFile = PROJECT_NONAME + USER_CODE_EXT self.setText(__default_content__) self.isUntitled = True self.updateApiKeywords() self.isModified = False self.connect(self, QtCore.SIGNAL('textChanged()'), self.onTextChanged) def onTextChanged(self): self.isModified = True self.parent.onChildContentChanged() def loadFile(self, fileName): qfile = QtCore.QFile(fileName) if not qfile.open(QtCore.QFile.ReadOnly | QtCore.QFile.Text): QtGui.QMessageBox.warning( self, PROJECT_ALIAS, "Cannot read file %s:\n%s." % (fileName, qfile.errorString())) return False ret = True try: # workaround for OS X QFile.readAll() f = open(qfile.fileName(), 'r') self.clear() for line in f.readlines(): self.append(line) except: QtGui.QMessageBox.warning(self, PROJECT_ALIAS, "failed to read %s." % fileName) ret = False finally: f.close() qfile.close() return ret def saveFile(self, fileName): if str(fileName).find(' ') >= 0: QtGui.QMessageBox.warning( self, PROJECT_ALIAS, 'File path "%s" contains space(s). Please save to a valid location.' % fileName) return None qfile = QtCore.QFile(fileName) if not qfile.open(QtCore.QFile.WriteOnly | QtCore.QFile.Text): QtGui.QMessageBox.warning( self, PROJECT_ALIAS, "Cannot write file %s:\n%s." % (fileName, qfile.errorString())) return None try: qfile.writeData(self.text()) except: QtGui.QMessageBox.warning(self, PROJECT_ALIAS, "Failed to save %s." % fileName) qfile.close() return None qfile.close() self.curFile = fileName self.isUntitled = False self.isModified = False return fileName def saveAs(self): fileName = QtGui.QFileDialog.getSaveFileName( self, "Save As", self.curFile, PROJECT_ALIAS + " (*" + USER_CODE_EXT + ");;" + "C source (*.c);;C++ source (*.cpp);;Text File (*.txt);;All files (*.*)" ) if not fileName: return None return self.saveFile(fileName) def save(self): f1 = os.path.abspath(self.curFile) for fname in self.roFiles: if f1 == os.path.abspath(fname): # same file if QtGui.QMessageBox.question(self.parent, "Project is read-only", "This project is marked as \"read-only\".\n" + \ "Please click \"Cancel\" and save this to another location.\n\n" + \ "Continue saving the current project anyway?", "OK", "Cancel"): return None #return self.saveAs() return self.saveFile(self.curFile) if self.isUntitled: return self.saveAs() else: return self.saveFile(self.curFile) def currentFile(self): return self.curFile def modified(self): return self.isModified def updateApiKeywords(self): self.libraryAPIs.clear() self.apiKeywords = self.parent.getDefaultKeywords() headerfiles = [] for line in range(self.lines()): txt = str(self.text(line)).strip() if txt.find('int') == 0: # e.g. reached "int main()" break elif txt.find('#include') == 0: txt = ''.join(txt.split()) temp = txt[len('#includes') - 1:] header = temp[1:-1] # get the header file hfile = os.path.join('libraries', header[:-2], header) if os.path.isfile(hfile): if not (hfile in headerfiles): headerfiles.append(hfile) if len(headerfiles): #print 'parsing: ', headerfiles self.apiKeywords += getLibraryKeywords(headerfiles) #self.apiKeywords = list(set(self.apiKeywords)) # remove duplicates for keyword in self.apiKeywords: self.libraryAPIs.add(keyword) self.libraryAPIs.prepare() self.lexer.setAPIs(self.libraryAPIs) def insertIncludeDirective(self, library=''): directive = '#include <' + library + '.h>\r\n' insert_pos = 0 found_inc = False for line in range(self.lines()): txt = str(self.text(line)).strip() if txt.find('int') == 0: # e.g. reached "int main()" insert_pos = line - 1 break elif txt.find('#include') == 0: found_inc = True elif found_inc: insert_pos = line break if insert_pos < 0 or insert_pos >= self.lines(): insert_pos = 0 self.insertAt(directive, insert_pos, 0) self.updateApiKeywords()
class CommandShell(QsciScintilla): def __init__(self, parent=None): super(CommandShell, self).__init__(parent) self.setMinimumHeight(50) self.setMaximumHeight(50) self.settings = QSettings() self.lex = QsciLexerPython(self) self.apis = QsciAPIs(self.lex) self.lex.setAPIs(self.apis) self.setLexers() self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0) self.SendScintilla(QsciScintilla.SCI_SETVSCROLLBAR, 0) self.setFolding(0) self._start_prompt = _start_prompt self.prompt = self._start_prompt self.currentfunction = None self.setAutoCompletionSource(self.AcsAPIs) self.setAutoCompletionThreshold(1) self.setAutoCompletionReplaceWord(True) self.setCallTipsStyle(QsciScintilla.CallTipsNoContext) self.parent().installEventFilter(self) self.textChanged.connect(self.text_changed) self._lastcompletions = None def text_changed(self): if not self.get_data().strip(): return try: completions = command.completions_for_line(self.get_data()) if completions == self._lastcompletions: self.autoCompleteFromAPIs() return self._lastcompletions = completions self.apis.cancelPreparation() self.apis.clear() for value in completions: data = u"{}".format(value) self.apis.add(data) self.apis.prepare() except command.NoFunction: return def end(self): self.parent().removeEventFilter(self) self.close() def eventFilter(self, object, event): if event.type() == QEvent.Resize: self.adjust_size() return QWidget.eventFilter(self, object, event) def keyPressEvent(self, e): if e.key() in (Qt.Key_Return, Qt.Key_Enter): self.entered() elif e.key() == Qt.Key_Escape: self.close() elif e.key() in (Qt.Key_Backspace, Qt.Key_Delete): _, newindex = self.getCursorPosition() if newindex > len(self.prompt): QsciScintilla.keyPressEvent(self, e) else: QsciScintilla.keyPressEvent(self, e) def show_prompt(self, prompt=_start_prompt, data=None): self.clear() if not prompt == _start_prompt: prompt += ":" text = prompt if data: text = prompt + str(data) self.setText(text) self.prompt = prompt self.move_cursor_to_end() def get_end_pos(self): """Return (line, index) position of the last character""" line = self.lines() - 1 return (line, len(self.text(line))) def move_cursor_to_end(self): """Move cursor to end of text""" line, index = self.get_end_pos() self.setCursorPosition(line, index) self.ensureCursorVisible() self.ensureLineVisible(line) def get_data(self): line = self.text() line = line[len(self.prompt):] return line def entered(self): line = self.get_data() if not self.currentfunction: try: gen = command.parse_line_data(line) except command.NoFunction: return if gen: self.currentfunction = gen line = None else: self.currentfunction = None self.show_prompt() return try: prompt, data = self.currentfunction.send(line) self.show_prompt(prompt, data) except StopIteration: self.currentfunction = None self.show_prompt() def setLexers(self): loadFont = self.settings.value("pythonConsole/fontfamilytext", "Monospace") fontSize = self.settings.value("pythonConsole/fontsize", 10, type=int) font = QFont(loadFont) font.setFixedPitch(True) font.setPointSize(fontSize) font.setStyleHint(QFont.TypeWriter) font.setStretch(QFont.SemiCondensed) font.setLetterSpacing(QFont.PercentageSpacing, 87.0) font.setBold(False) self.lex.setFont(font) self.lex.setDefaultFont(font) self.setLexer(self.lex) def adjust_size(self): fm = QFontMetrics(self.font()) self.setMaximumHeight(20) self.resize(self.parent().width(), 20) self.move(0, self.parent().height() - self.height()) def showEvent(self, event): self.adjust_size() self.show_prompt() self.setFocus() def activated(self): visible = self.isVisible() self.setVisible(not visible)
class MultipleCppEditor(QtGui.QTabWidget): ''' classdocs ''' def __init__(self, parent=None): ''' Constructor ''' super(MultipleCppEditor, self).__init__(parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.prepareLibraryAPIs() self.findDlg = FindDialog(self) self.setAcceptDrops(True) #self.setTabShape(QtGui.QTabWidget.Triangular) self.setMovable(True) self.setTabsClosable(True) self.connect(self, QtCore.SIGNAL('tabCloseRequested(int)'), self.closeFile) self.sampleProjects = [] try: for group in getExampleProjects(scanFirmwareLibs()): for fname in group[1]: self.sampleProjects.append(fname) # print self.sampleProjects except: pass if self.count()==0: self.newFile() def newFile(self): child = CppEditor(self, None, self.sampleProjects) self.addTab(child, PROJECT_NONAME + " * ") self.setCurrentIndex(self.count()-1) self.setTabToolTip(self.currentIndex(), child.currentFile()) def openFile(self, fileName=None): if fileName == None: # prompt open dialog if filename is not specified fileName = QtGui.QFileDialog.getOpenFileName( self, self.tr("Open Source File"), "", PROJECT_ALIAS + " (*" + PROJECT_EXT + ");;" "C Source File (*.c);;Text File (*.txt);;All files (*.*)" ) if fileName == "": return False #check if it's already opened for i in range(self.count()): child = self.widget(i) if fileName == child.currentFile(): # file already opened self.setCurrentIndex(i) return True child = CppEditor(self, fileName, self.sampleProjects) tabtext = os.path.basename( str(fileName) ) if tabtext.lower().find(PROJECT_EXT) == len(tabtext) - len(PROJECT_EXT): tabtext = tabtext[:tabtext.lower().find(PROJECT_EXT)] self.addTab(child, tabtext) self.setCurrentIndex(self.count()-1) self.setTabToolTip(self.currentIndex(), child.currentFile()) return True def saveFile(self): child = self.currentWidget() if child == None: return None rc = child.save() if rc: fileName = child.currentFile() tabtext = os.path.basename( str(fileName) ) if tabtext.lower().find(PROJECT_EXT) == len(tabtext) - len(PROJECT_EXT): tabtext = tabtext[:tabtext.lower().find(PROJECT_EXT)] self.setTabText(self.currentIndex(), tabtext) self.setTabToolTip(self.currentIndex(), fileName) return True return False def saveFileAs(self): child = self.currentWidget() rc = child.saveAs() if rc: fileName = child.currentFile() tabtext = os.path.basename( str(fileName) ) if tabtext.lower().find(PROJECT_EXT) == len(tabtext) - len(PROJECT_EXT): tabtext = tabtext[:tabtext.lower().find(PROJECT_EXT)] self.setTabText(self.currentIndex(), tabtext) self.setTabToolTip(self.currentIndex(), fileName) return True return False def closeFile(self, idx = 0): if self.count()==0: return True# nothing to close # check if the file has changed before closing child = self.widget(idx) if child.isModified: result = QtGui.QMessageBox.question(self, "Modified", 'Save changes on "' + child.currentFile() + '" ?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No, QtGui.QMessageBox.Cancel) if result == QtGui.QMessageBox.Cancel: return False elif result == QtGui.QMessageBox.Yes: if child.save() == None: # file was not save after return False self.removeTab(idx) child.setParent(None) child.close() return True def closeCurrentFile(self): return self.closeFile(self.currentIndex()) def getCurrentFile(self): child = self.currentWidget() if child: return child.currentFile() return None def isCurrentFileModified(self): child = self.currentWidget() if child: return child.modified() return False def editUndo(self): child = self.currentWidget() if child: child.undo() def editRedo(self): child = self.currentWidget() if child: child.redo() def editCut(self): child = self.currentWidget() if child: if child.hasSelectedText(): child.cut() def editCopy(self): child = self.currentWidget() if child: child.copy() def editPaste(self): child = self.currentWidget() if child: child.paste() def editSelectAll(self): child = self.currentWidget() if child: child.selectAll() def editClear(self): child = self.currentWidget() if child: child.clear() def showFindDialog(self): child = self.currentWidget() if child: if child.hasSelectedText(): self.findDlg.setFindText(child.selectedText ()) self.findDlg.show() def findChildText(self, text=None, forwardDirection=True, caseSensitive=False, wrapSearch=True, wholeWord=False, regExp=False): if text: child = self.currentWidget() if child: #print "fw = ", forwardDirection, " cs = ", caseSensitive, " wrap = ", wrapSearch result = child.findFirst( text, regExp, # regular expression caseSensitive, # case sensitive wholeWord, # whole word matches only wrapSearch, # wraps around forwardDirection) # forward return result return False def replaceChildText(self, newText=None): child = self.currentWidget() if child and child.hasSelectedText(): child.replaceSelectedText(newText) # returns None return True return False def replaceFindChildText(self, oldText=None, newText=None, forwardDirection=True, caseSensitive=False, wrapSearch=True, wholeWord=False, regExp=False): if oldText: child = self.currentWidget() if child: child.replace(newText) result = child.findFirst( oldText, regExp, # regular expression caseSensitive, # case sensitive wholeWord, # whole word matches only wrapSearch, # wraps around forwardDirection) # forward return result return False def replaceAllChildText(self, oldText=None, newText=None, caseSensitive=False, wholeWord=False, regExp=False): if oldText: if (not caseSensitive) and (oldText.lower() == newText.lower()): print "nothing to replace" return False child = self.currentWidget() if child: result = child.findFirst( oldText, regExp, # regular expression caseSensitive, # case sensitive wholeWord, # whole word matches only True, # wraps around True) # forward if result: cnt = 0 while cnt < 4096: # limit loop child.replace(newText) cnt += 1 if not child.findNext(): break return True return False def onChildContentChanged(self): title = self.tabText(self.currentIndex()) if not title.contains('*', QtCore.Qt.CaseInsensitive): self.setTabText(self.currentIndex(), title + " * ") def prepareLibraryAPIs(self): self.LibraryAPIs = QsciAPIs(QsciLexerCPP(self,True)) keywords = getLibraryKeywords() for keyword in keywords: self.LibraryAPIs.add( keyword ) self.LibraryAPIs.prepare() def getLibraryAPIs(self): return self.LibraryAPIs def importFirmwareLib(self, library=None): child = self.currentWidget() if child: directive = '#include <' + library + '.h>\r\n' child.insertAt(directive, 0, 0) # insert at first line def dragEnterEvent(self, e): if e.mimeData().hasUrls(): url = str( e.mimeData().urls()[0].toString() ).lower() if url.rfind(PROJECT_EXT) == len(url) - len(PROJECT_EXT): e.accept() return e.ignore() def dropEvent(self, e): try: fname = str(e.mimeData().urls()[0].toLocalFile() ) self.openFile(fname) except: print "drop error" def closeAllTabs(self): for index in range(self.count()): self.setCurrentIndex(index) if not self.closeFile(): return False return True
class CppEditor(QsciScintilla): ''' classdocs ''' def __init__(self, parent=None, fileName=None, readOnlyFiles=[]): ''' Constructor ''' super(CppEditor, self).__init__(parent) self.parent = parent self.roFiles = readOnlyFiles self.setAcceptDrops(False) # drag&drop is on its parent # Set the default font font = QtGui.QFont() font.setFamily('Courier') font.setFixedPitch(True) font.setPointSize(10) self.setFont(font) self.setMarginsFont(font) # C/C++ lexer self.lexer = QsciLexerCPP(self, True) self.lexer.setDefaultFont(font) self.libraryAPIs = QsciAPIs(QsciLexerCPP(self,True)) self.setLexer(self.lexer) self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier') # Auto-indent self.setTabWidth(4) #self.setIndentationsUseTabs(False) self.setAutoIndent(True) # Current line visible with special background color self.setCaretLineVisible(True) self.setCaretLineBackgroundColor(QtGui.QColor("#ffe4e4")) # Enable brace matching self.setBraceMatching(QsciScintilla.SloppyBraceMatch) # Enable folding visual- use boxes self.setFolding(QsciScintilla.BoxedTreeFoldStyle) # show line numbers fontmetrics = QtGui.QFontMetrics(font) self.setMarginsFont(font) self.setMarginWidth(0, fontmetrics.width("00000") + 4) self.setMarginLineNumbers(0, True) self.setMarginsBackgroundColor(QtGui.QColor("#ccccee")) # not too small self.setMinimumSize(400, 200) # set the length of the string before the editor tries to auto-complete self.setAutoCompletionThreshold(3) # tell the editor we are using a QsciAPI for the auto-completion self.setAutoCompletionSource(QsciScintilla.AcsAPIs) # removed remaining right side characters from the current cursor self.setAutoCompletionReplaceWord(True) # "CTRL+Space" autocomplete self.shortcut_ctrl_space = QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Space"), self) self.connect(self.shortcut_ctrl_space, QtCore.SIGNAL('activated()'), self.autoCompleteFromAll) if fileName: self.curFile = fileName self.loadFile(fileName) self.isUntitled = False else: self.curFile = PROJECT_NONAME + USER_CODE_EXT self.setText( __default_content__ ) self.isUntitled = True self.updateApiKeywords() self.isModified = False self.connect(self, QtCore.SIGNAL('textChanged()'), self.onTextChanged ) def onTextChanged(self): self.isModified = True self.parent.onChildContentChanged() def loadFile(self, fileName): qfile = QtCore.QFile(fileName) if not qfile.open(QtCore.QFile.ReadOnly | QtCore.QFile.Text): QtGui.QMessageBox.warning(self, PROJECT_ALIAS, "Cannot read file %s:\n%s." % (fileName, qfile.errorString())) return False ret = True try: # workaround for OS X QFile.readAll() f = open(qfile.fileName(), 'r') self.clear() for line in f.readlines(): self.append(line) except: QtGui.QMessageBox.warning(self, PROJECT_ALIAS, "failed to read %s." % fileName ) ret = False finally: f.close() qfile.close() return ret def saveFile(self, fileName): if str(fileName).find(' ')>=0: QtGui.QMessageBox.warning(self, PROJECT_ALIAS, 'File path "%s" contains space(s). Please save to a valid location.'%fileName) return None qfile = QtCore.QFile(fileName) if not qfile.open(QtCore.QFile.WriteOnly | QtCore.QFile.Text): QtGui.QMessageBox.warning(self, PROJECT_ALIAS, "Cannot write file %s:\n%s." % (fileName, qfile.errorString())) return None try: qfile.writeData(self.text()) except: QtGui.QMessageBox.warning(self, PROJECT_ALIAS, "Failed to save %s." % fileName ) qfile.close() return None qfile.close() self.curFile = fileName self.isUntitled = False self.isModified = False return fileName def saveAs(self): fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As", self.curFile, PROJECT_ALIAS + " (*" + USER_CODE_EXT + ");;" + "C source (*.c);;C++ source (*.cpp);;Text File (*.txt);;All files (*.*)" ) if not fileName: return None return self.saveFile(fileName) def save(self): f1 = os.path.abspath(self.curFile) for fname in self.roFiles: if f1 == os.path.abspath(fname): # same file if QtGui.QMessageBox.question(self.parent, "Project is read-only", "This project is marked as \"read-only\".\n" + \ "Please click \"Cancel\" and save this to another location.\n\n" + \ "Continue saving the current project anyway?", "OK", "Cancel"): return None #return self.saveAs() return self.saveFile(self.curFile) if self.isUntitled: return self.saveAs() else: return self.saveFile(self.curFile) def currentFile(self): return self.curFile def modified(self): return self.isModified def updateApiKeywords(self): self.libraryAPIs.clear() self.apiKeywords = self.parent.getDefaultKeywords() headerfiles = [] for line in range(self.lines()): txt = str(self.text(line)).strip() if txt.find('int') == 0: # e.g. reached "int main()" break elif txt.find('#include') == 0: txt = ''.join(txt.split()) temp = txt[len('#includes')-1 : ] header = temp[1:-1] # get the header file hfile = os.path.join('libraries', header[:-2], header) if os.path.isfile(hfile): if not (hfile in headerfiles): headerfiles.append( hfile ) if len( headerfiles ): #print 'parsing: ', headerfiles self.apiKeywords += getLibraryKeywords( headerfiles ) #self.apiKeywords = list(set(self.apiKeywords)) # remove duplicates for keyword in self.apiKeywords: self.libraryAPIs.add( keyword ) self.libraryAPIs.prepare() self.lexer.setAPIs(self.libraryAPIs) def insertIncludeDirective(self, library=''): directive = '#include <' + library + '.h>\r\n' insert_pos = 0 found_inc = False for line in range(self.lines()): txt = str(self.text(line)).strip() if txt.find('int') == 0: # e.g. reached "int main()" insert_pos = line - 1 break elif txt.find('#include') == 0: found_inc = True elif found_inc: insert_pos = line break if insert_pos < 0 or insert_pos >= self.lines(): insert_pos = 0 self.insertAt(directive, insert_pos, 0) self.updateApiKeywords()
class Editor(base.Base): # Braces BRACE = { '(': ')', '[': ']' } # Quotes QUOTE = { "'": "'", '"': '"' } # Marcadores MARKER_MODIFIED = 3 MARKER_SAVED = 4 # Indicadores WORD_INDICATOR = 0 WARNING_INDICATOR = 1 def __init__(self, obj_file): super(Editor, self).__init__() self.obj_file = obj_file # Asociación con el objeto EdisFile self._font = None # Configuración use_tabs = settings.get_setting('editor/usetabs') self.setIndentationsUseTabs(use_tabs) self.setAutoIndent(settings.get_setting('editor/indent')) self.setBackspaceUnindents(True) # Quita el scrollbar self.send("sci_sethscrollbar", 0) # Configuración de indicadores self.send("sci_indicsetstyle", Editor.WORD_INDICATOR, "indic_box") self.send("sci_indicsetfore", Editor.WORD_INDICATOR, QColor("#FF0000")) self.send("sci_indicsetstyle", Editor.WARNING_INDICATOR, "indic_squiggle") self.send("sci_indicsetfore", Editor.WARNING_INDICATOR, QColor("#0000FF")) # Scheme self.scheme = editor_scheme.get_scheme( settings.get_setting('editor/scheme')) # Folding self.setFolding(QsciScintilla.PlainFoldStyle) # en márgen 2 self.setMarginWidth(3, 5) # 5px de espacios en márgen 3 self.send("sci_markersetfore", QsciScintilla.SC_MARKNUM_FOLDER, QColor(self.scheme['FoldMarkerFore'])) self.send("sci_markersetback", QsciScintilla.SC_MARKNUM_FOLDER, QColor(self.scheme['FoldMarkerBack'])) self.setFoldMarginColors(QColor(self.scheme['FoldMarginBack']), QColor(self.scheme['FoldMarginFore'])) # Marcadores self.markerDefine(QsciScintilla.SC_MARK_LEFTRECT, Editor.MARKER_MODIFIED) self.setMarkerBackgroundColor( QColor(226, 255, 141), Editor.MARKER_MODIFIED) self.markerDefine(QsciScintilla.SC_MARK_LEFTRECT, Editor.MARKER_SAVED) self.setMarkerBackgroundColor(QColor(55, 142, 103), Editor.MARKER_SAVED) # Actualiza flags (espacios en blanco, cursor, sidebar, etc) self.update_options() # Lexer self._lexer = lexer.Lexer() self.setLexer(self._lexer) # Autocompletado self.api = None if settings.get_setting('editor/completion'): self.active_code_completion() # Indentación self._indentation = settings.get_setting('editor/width-indent') self.send("sci_settabwidth", self._indentation) # Minimapa self.minimap = None if settings.get_setting('editor/minimap'): self.minimap = minimap.Minimap(self) self.connect(self, SIGNAL("textChanged()"), self.minimap.update_code) # Thread que busca palabras self.search_thread = editor_helpers.SearchThread() self.connect(self.search_thread, SIGNAL("foundWords(PyQt_PyObject)"), self.mark_words) # Analizador de estilo de código self.checker = None if settings.get_setting('editor/style-checker'): self.load_checker() # Fuente font = settings.get_setting('editor/font') font_size = settings.get_setting('editor/size-font') self.load_font(font, font_size) self.setMarginsBackgroundColor(QColor(self.scheme['SidebarBack'])) self.setMarginsForegroundColor(QColor(self.scheme['SidebarFore'])) # Línea actual self.send("sci_setcaretlinevisible", settings.get_setting('editor/show-caret-line')) self.send("sci_setcaretlineback", QColor(self.scheme['CaretLineBack'])) self.send("sci_setcaretfore", QColor(self.scheme['CaretLineFore'])) self.send("sci_setcaretlinebackalpha", self.scheme['CaretLineAlpha']) # Cursor caret_period = settings.get_setting('editor/cursor-period') self.send("sci_setcaretperiod", caret_period) # Márgen if settings.get_setting('editor/show-margin'): self.update_margin() # Brace matching self.setBraceMatching(int(settings.get_setting('editor/match-brace'))) self.setMatchedBraceBackgroundColor(QColor( self.scheme['MatchedBraceBack'])) self.setMatchedBraceForegroundColor(QColor( self.scheme['MatchedBraceFore'])) self.setUnmatchedBraceBackgroundColor(QColor( self.scheme['UnmatchedBraceBack'])) self.setUnmatchedBraceForegroundColor(QColor( self.scheme['UnmatchedBraceFore'])) # Selecciones Múltiples self.send("sci_setadditionalcaretfore", QColor(157, 64, 40)) self.send("sci_setadditionalcaretsblink", 1) self.send("sci_setadditionalselalpha", 100) self.send("sci_setmultipleselection", 1) self.send("sci_setadditionalselectiontyping", 1) # Conexiones self.connect(self, SIGNAL("linesChanged()"), self.update_sidebar) self.connect(self, SIGNAL("textChanged()"), self._add_marker_modified) @property def filename(self): return self.obj_file.filename @property def is_modified(self): return self.isModified() def load_font(self, fuente, tam): self._font = QFont(fuente, tam) if self._lexer is None: self.setFont(self._font) else: self._lexer.setFont(self._font) self.setMarginsFont(self._font) def load_checker(self, activated=True): if activated and self.checker is not None: return if not activated: self.checker = None self.clear_indicators(Editor.WARNING_INDICATOR) else: self.checker = checker.Checker(self) self.connect(self.checker, SIGNAL("finished()"), self._show_violations) def set_brace_matching(self): match_brace = int(settings.get_setting('editor/match-brace')) self.setBraceMatching(match_brace) def update_options(self): """ Actualiza las opciones del editor """ if settings.get_setting('editor/show-tabs-spaces'): self.setWhitespaceVisibility(self.WsVisible) else: self.setWhitespaceVisibility(self.WsInvisible) self.setIndentationGuides(settings.get_setting('editor/show-guides')) if settings.get_setting('editor/wrap-mode'): self.setWrapMode(self.WrapWord) else: self.setWrapMode(self.WrapNone) self.send("sci_setcaretstyle", settings.get_setting('editor/cursor')) self.setCaretWidth(settings.get_setting('editor/caret-width')) self.setAutoIndent(settings.get_setting('editor/indent')) self.send("sci_setcaretperiod", settings.get_setting('editor/cursor-period')) current_line = settings.get_setting('editor/show-caret-line') self.send("sci_setcaretlinevisible", current_line) self.setEolVisibility(settings.get_setting('editor/eof')) def update_sidebar(self): """ Ajusta el ancho del sidebar """ fmetrics = QFontMetrics(self._font) lines = str(self.lines()) + '0' line_number = settings.get_setting('editor/show-line-number') width = fmetrics.width(lines) if line_number else 0 self.setMarginWidth(0, width) def show_line_numbers(self): line_number = settings.get_setting('editor/show-line-number') self.setMarginLineNumbers(0, line_number) self.update_sidebar() def update_margin(self): """ Actualiza el ancho del márgen de línea """ if settings.get_setting('editor/show-margin'): self.setEdgeMode(QsciScintilla.EdgeLine) ancho = settings.get_setting('editor/width-margin') self.setEdgeColumn(ancho) self.setEdgeColor(QColor(self.scheme['Margin'])) else: self.setEdgeMode(QsciScintilla.EdgeNone) def update_indentation(self): ancho = settings.get_setting('editor/width-indent') self.send("sci_settabwidth", ancho) self._indentation = ancho def mark_words(self, palabras): self.clear_indicators(Editor.WORD_INDICATOR) for p in palabras: self.fillIndicatorRange(p[0], p[1], p[0], p[2], Editor.WORD_INDICATOR) def active_code_completion(self, enabled=True): if self.api is not None and enabled: return if enabled: self.api = QsciAPIs(self._lexer) if settings.get_setting('editor/completion-keywords'): for keyword in keywords.keywords: self.api.add(keyword) self.api.prepare() source = QsciScintilla.AcsAPIs if settings.get_setting('editor/completion-document'): source = QsciScintilla.AcsAll elif settings.get_setting('editor/completion-document'): source = QsciScintilla.AcsDocument else: source = QsciScintilla.AcsNone threshold = settings.get_setting('editor/completion-threshold') self.setAutoCompletionThreshold(threshold) self.setAutoCompletionSource(source) cs = settings.get_setting('editor/completion-cs') self.setAutoCompletionCaseSensitivity(cs) repl_word = settings.get_setting('editor/completion-replace-word') self.setAutoCompletionReplaceWord(repl_word) show_single = settings.get_setting('editor/completion-single') use_single = 2 if show_single else 0 self.setAutoCompletionUseSingle(use_single) else: self.api = None self.setAutoCompletionSource(0) def _add_marker_modified(self): """ Agrega el marcador cuando el texto cambia """ if not settings.get_setting('editor/mark-change'): return nline, _ = self.getCursorPosition() if self.markersAtLine(nline): self.markerDelete(nline) self.markerAdd(nline, Editor.MARKER_MODIFIED) def _show_violations(self): data = self.checker.data self.clear_indicators(Editor.WARNING_INDICATOR) for line, message in list(data.items()): line = int(line) - 1 self.fillIndicatorRange(line, 0, line, self.lineLength(line), Editor.WARNING_INDICATOR) def _text_under_cursor(self): """ Texto seleccionado con el cursor """ line, index = self.getCursorPosition() # Posición del cursor word = self.wordAtLineIndex(line, index) # Palabra en esa pos return word def mouseReleaseEvent(self, e): super(Editor, self).mouseReleaseEvent(e) if e.button() == Qt.LeftButton: word = self._text_under_cursor() if not word: self.clear_indicators(Editor.WORD_INDICATOR) return self.search_thread.find(word, self.text()) def mouseMoveEvent(self, event): super(Editor, self).mouseMoveEvent(event) if self.checker is None: return position = event.pos() line = str(self.lineAt(position) + 1) message = self.checker.data.get(line, None) if message is not None: QToolTip.showText(self.mapToGlobal(position), message) def keyReleaseEvent(self, event): super(Editor, self).keyReleaseEvent(event) line, _ = self.getCursorPosition() self.emit(SIGNAL("linesChanged(int)"), line) def keyPressEvent(self, event): super(Editor, self).keyPressEvent(event) key = event.key() # Borra indicador de palabra if key == Qt.Key_Escape: self.clear_indicators(Editor.WORD_INDICATOR) return # Completado de comillas if key == Qt.Key_Apostrophe: if settings.get_setting('editor/complete-single-quote'): self._complete_quote(event) return if key == Qt.Key_QuoteDbl: if settings.get_setting('editor/complete-double-quote'): self._complete_quote(event) return # Completado de llaves if key == Qt.Key_BraceLeft: if settings.get_setting('editor/complete-brace'): self._complete_key(event) if key in (Qt.Key_BracketLeft, Qt.Key_ParenLeft, Qt.Key_BracketRight, Qt.Key_ParenRight): self._complete_brace(event) def _complete_quote(self, event): """ Autocompleta comillas simples y dobles, si existe el complementario el cursor se mueve un espacio. """ complementary = Editor.QUOTE.get(event.text()) line, index = self.getCursorPosition() text_line = self.text(line) portion = text_line[index - 1:index + 1] if portion in settings.QUOTES: self.setSelection(line, index, line, index + 1) self.replaceSelectedText('') else: self.insertAt(complementary, line, index) def _complete_brace(self, event): """ Autocompleta un brace cuando es abierto '(, ['. Si existe el complementario '), ]' el índice del cursor se mueve un espacio. """ brace_close = settings.BRACES.get(event.text(), None) braces_open = list(Editor.BRACE.keys()) line, index = self.getCursorPosition() if event.text() in settings.BRACES.values(): text_line = self.text(line) found = 0 # Busco un brace cerrado en el texto for token in text_line: if token in settings.BRACES.values(): found += 1 try: # Brace abierto brace_open = text_line[index] except: brace_open = '' if found > 1 and brace_open not in braces_open: # Reemplazo el brace sobrante por un string vacío self.setSelection(line, index, line, index + 1) self.replaceSelectedText('') return elif event.text() in settings.BRACES.keys() and brace_close is not None: # Inserto el brace complementario self.insertAt(brace_close, line, index) def _complete_key(self, event): """ Autocompleta llaves en funciones y estructuras. Si se usa typedef en la definición de la estructura se completa la variable. """ line, index = self.getCursorPosition() text_line = self.text(line) # Estructura if REGEX_STRUCT.match(text_line): if text_line.split()[0] == 'typedef': # Obtengo la variable split_line = text_line.split() if split_line[-1] == '{': typedef = split_line[-2] else: typedef = split_line[-1][:-1] text = "}%s;" % typedef.title() else: text = "};" self.insert("\n") self.insertAt(text, line + 1, 0) return # Función portion = text_line[index - 3: index] if ')' in portion: self.insert("\n") current_indentation = self.indentation(line) self.setIndentation(line + 1, current_indentation) self.insertAt('}', line + 1, current_indentation) def resizeEvent(self, e): super(Editor, self).resizeEvent(e) if self.minimap is not None: self.minimap.update_geometry() def comment(self): # FIXME: tener en cuenta /* */ # FIXME: no funciona si el comentario no inicia en el índice 0 if self.hasSelectedText(): line_from, _, line_to, _ = self.getSelection() # Iterar todas las líneas seleccionadas self.send("sci_beginundoaction") for line in range(line_from, line_to + 1): self.insertAt('//', line, 0) self.send("sci_endundoaction") else: line, _ = self.getCursorPosition() self.insertAt('//', line, 0) def uncomment(self): if self.hasSelectedText(): line_from, _, line_to, _ = self.getSelection() self.send("sci_beginundoaction") for line in range(line_from, line_to + 1): self.setSelection(line, 0, line, 2) if not self.text(line).startswith('//'): continue self.removeSelectedText() self.send("sci_endundoaction") else: line, _ = self.getCursorPosition() if not self.text(line).startswith('//'): return self.setSelection(line, 0, line, 2) self.removeSelectedText() def to_lowercase(self): if self.hasSelectedText(): text = self.selectedText().lower() else: line, _ = self.getCursorPosition() text = self.text(line).lower() self.setSelection(line, 0, line, len(text)) self.replaceSelectedText(text) def to_uppercase(self): if self.hasSelectedText(): text = self.selectedText().upper() else: line, _ = self.getCursorPosition() text = self.text(line).upper() self.setSelection(line, 0, line, len(text)) self.replaceSelectedText(text) def to_title(self): if self.hasSelectedText(): text = self.selectedText().title() else: line, _ = self.getCursorPosition() text = self.text(line).title() self.setSelection(line, 0, line, len(text)) self.replaceSelectedText(text) def duplicate_line(self): self.send("sci_lineduplicate") def delete_line(self): if self.hasSelectedText(): self.send("sci_beginundoaction") desde, desde_indice, hasta, _ = self.getSelection() self.setCursorPosition(desde, desde_indice) while desde != hasta: self.send("sci_linedelete") desde += 1 self.send("sci_endundoaction") else: self.send("sci_linedelete") def indent_more(self): if self.hasSelectedText(): self.send("sci_beginundoaction") desde, _, hasta, _ = self.getSelection() for linea in range(desde, hasta + 1): self.indent(linea) self.send("sci_endundoaction") else: linea, _ = self.getCursorPosition() self.indent(linea) def indent_less(self): if self.hasSelectedText(): self.send("sci_beginundoaction") desde, _, hasta, _ = self.getSelection() for linea in range(desde, hasta + 1): self.unindent(linea) self.send("sci_endundoaction") else: linea, _ = self.getCursorPosition() self.unindent(linea) def move_down(self): self.send("sci_moveselectedlinesdown") def move_up(self): self.send("sci_moveselectedlinesup") def reemplazar_tabs_por_espacios(self): codigo = self.text() codigo_sin_tabs = codigo.replace('\t', ' ' * self._indentation) self.setText(codigo_sin_tabs) def saved(self): # Esta señal sirve para actualizar el árbol de símbolos self.emit(SIGNAL("fileSaved(QString)"), self.obj_file.filename) self.setModified(False) # Itera todas las líneas y si existe un _marker_modified agrega # un _marker_save for nline in range(self.lines()): if self.markersAtLine(nline): self.markerAdd(nline, Editor.MARKER_SAVED) def dropEvent(self, event): self.emit(SIGNAL("dropEvent(PyQt_PyObject)"), event)
editor.setCaretLineVisible(True) editor.setCaretLineBackgroundColor(QtGui.QColor("#CDA869")) ## Margins colors # line numbers margin editor.setMarginsBackgroundColor(QtGui.QColor("#333333")) editor.setMarginsForegroundColor(QtGui.QColor("#CCCCCC")) # folding margin colors (foreground,background) editor.setFoldMarginColors(QtGui.QColor("#99CC66"), QtGui.QColor("#333300")) ## Choose a lexer lexer = QsciLexerPython1() lexer.setDefaultFont(font) editor.setLexer(lexer) myApis = QsciAPIs(lexer) myApis.add(QtCore.QString("SDR")) myApis.add(QtCore.QString("sdr")) myApis.prepare() editor.setAutoCompletionSource(QsciScintilla.AcsAll) editor.setAutoCompletionCaseSensitivity(False) editor.setAutoCompletionThreshold(1) ## Render on screen editor.show() ## Show this file in the editor editor.setText(open("chardet.txt").read()) sys.exit(app.exec_())
class Editor(base.Base): # Braces BRACE = {'(': ')', '[': ']'} # Quotes QUOTE = {"'": "'", '"': '"'} # Marcadores MARKER_MODIFIED = 3 MARKER_SAVED = 4 # Indicadores WORD_INDICATOR = 0 WARNING_INDICATOR = 1 def __init__(self, obj_file): super(Editor, self).__init__() self.obj_file = obj_file # Asociación con el objeto EdisFile self._font = None # Configuración use_tabs = settings.get_setting('editor/usetabs') self.setIndentationsUseTabs(use_tabs) self.setAutoIndent(settings.get_setting('editor/indent')) self.setBackspaceUnindents(True) # Quita el scrollbar self.send("sci_sethscrollbar", 0) # Configuración de indicadores self.send("sci_indicsetstyle", Editor.WORD_INDICATOR, "indic_box") self.send("sci_indicsetfore", Editor.WORD_INDICATOR, QColor("#FF0000")) self.send("sci_indicsetstyle", Editor.WARNING_INDICATOR, "indic_squiggle") self.send("sci_indicsetfore", Editor.WARNING_INDICATOR, QColor("#0000FF")) # Scheme self.scheme = editor_scheme.get_scheme( settings.get_setting('editor/scheme')) # Folding self.setFolding(QsciScintilla.PlainFoldStyle) # en márgen 2 self.setMarginWidth(3, 5) # 5px de espacios en márgen 3 self.send("sci_markersetfore", QsciScintilla.SC_MARKNUM_FOLDER, QColor(self.scheme['FoldMarkerFore'])) self.send("sci_markersetback", QsciScintilla.SC_MARKNUM_FOLDER, QColor(self.scheme['FoldMarkerBack'])) self.setFoldMarginColors(QColor(self.scheme['FoldMarginBack']), QColor(self.scheme['FoldMarginFore'])) # Marcadores self.markerDefine(QsciScintilla.SC_MARK_LEFTRECT, Editor.MARKER_MODIFIED) self.setMarkerBackgroundColor(QColor(226, 255, 141), Editor.MARKER_MODIFIED) self.markerDefine(QsciScintilla.SC_MARK_LEFTRECT, Editor.MARKER_SAVED) self.setMarkerBackgroundColor(QColor(55, 142, 103), Editor.MARKER_SAVED) # Actualiza flags (espacios en blanco, cursor, sidebar, etc) self.update_options() # Lexer self._lexer = lexer.Lexer() self.setLexer(self._lexer) # Autocompletado self.api = None if settings.get_setting('editor/completion'): self.active_code_completion() # Indentación self._indentation = settings.get_setting('editor/width-indent') self.send("sci_settabwidth", self._indentation) # Minimapa self.minimap = None if settings.get_setting('editor/minimap'): self.minimap = minimap.Minimap(self) self.connect(self, SIGNAL("textChanged()"), self.minimap.update_code) # Thread que busca palabras self.search_thread = editor_helpers.SearchThread() self.connect(self.search_thread, SIGNAL("foundWords(PyQt_PyObject)"), self.mark_words) # Analizador de estilo de código self.checker = None if settings.get_setting('editor/style-checker'): self.load_checker() # Fuente font = settings.get_setting('editor/font') font_size = settings.get_setting('editor/size-font') self.load_font(font, font_size) self.setMarginsBackgroundColor(QColor(self.scheme['SidebarBack'])) self.setMarginsForegroundColor(QColor(self.scheme['SidebarFore'])) # Línea actual self.send("sci_setcaretlinevisible", settings.get_setting('editor/show-caret-line')) self.send("sci_setcaretlineback", QColor(self.scheme['CaretLineBack'])) self.send("sci_setcaretfore", QColor(self.scheme['CaretLineFore'])) self.send("sci_setcaretlinebackalpha", self.scheme['CaretLineAlpha']) # Cursor caret_period = settings.get_setting('editor/cursor-period') self.send("sci_setcaretperiod", caret_period) # Márgen if settings.get_setting('editor/show-margin'): self.update_margin() # Brace matching self.setBraceMatching(int(settings.get_setting('editor/match-brace'))) self.setMatchedBraceBackgroundColor( QColor(self.scheme['MatchedBraceBack'])) self.setMatchedBraceForegroundColor( QColor(self.scheme['MatchedBraceFore'])) self.setUnmatchedBraceBackgroundColor( QColor(self.scheme['UnmatchedBraceBack'])) self.setUnmatchedBraceForegroundColor( QColor(self.scheme['UnmatchedBraceFore'])) # Selecciones Múltiples self.send("sci_setadditionalcaretfore", QColor(157, 64, 40)) self.send("sci_setadditionalcaretsblink", 1) self.send("sci_setadditionalselalpha", 100) self.send("sci_setmultipleselection", 1) self.send("sci_setadditionalselectiontyping", 1) # Conexiones self.connect(self, SIGNAL("linesChanged()"), self.update_sidebar) self.connect(self, SIGNAL("textChanged()"), self._add_marker_modified) @property def filename(self): return self.obj_file.filename @property def is_modified(self): return self.isModified() def load_font(self, fuente, tam): self._font = QFont(fuente, tam) if self._lexer is None: self.setFont(self._font) else: self._lexer.setFont(self._font) self.setMarginsFont(self._font) def load_checker(self, activated=True): if activated and self.checker is not None: return if not activated: self.checker = None self.clear_indicators(Editor.WARNING_INDICATOR) else: self.checker = checker.Checker(self) self.connect(self.checker, SIGNAL("finished()"), self._show_violations) def set_brace_matching(self): match_brace = int(settings.get_setting('editor/match-brace')) self.setBraceMatching(match_brace) def update_options(self): """ Actualiza las opciones del editor """ if settings.get_setting('editor/show-tabs-spaces'): self.setWhitespaceVisibility(self.WsVisible) else: self.setWhitespaceVisibility(self.WsInvisible) self.setIndentationGuides(settings.get_setting('editor/show-guides')) if settings.get_setting('editor/wrap-mode'): self.setWrapMode(self.WrapWord) else: self.setWrapMode(self.WrapNone) self.send("sci_setcaretstyle", settings.get_setting('editor/cursor')) self.setCaretWidth(settings.get_setting('editor/caret-width')) self.setAutoIndent(settings.get_setting('editor/indent')) self.send("sci_setcaretperiod", settings.get_setting('editor/cursor-period')) current_line = settings.get_setting('editor/show-caret-line') self.send("sci_setcaretlinevisible", current_line) self.setEolVisibility(settings.get_setting('editor/eof')) def update_sidebar(self): """ Ajusta el ancho del sidebar """ fmetrics = QFontMetrics(self._font) lines = str(self.lines()) + '0' line_number = settings.get_setting('editor/show-line-number') width = fmetrics.width(lines) if line_number else 0 self.setMarginWidth(0, width) def show_line_numbers(self): line_number = settings.get_setting('editor/show-line-number') self.setMarginLineNumbers(0, line_number) self.update_sidebar() def update_margin(self): """ Actualiza el ancho del márgen de línea """ if settings.get_setting('editor/show-margin'): self.setEdgeMode(QsciScintilla.EdgeLine) ancho = settings.get_setting('editor/width-margin') self.setEdgeColumn(ancho) self.setEdgeColor(QColor(self.scheme['Margin'])) else: self.setEdgeMode(QsciScintilla.EdgeNone) def update_indentation(self): ancho = settings.get_setting('editor/width-indent') self.send("sci_settabwidth", ancho) self._indentation = ancho def mark_words(self, palabras): self.clear_indicators(Editor.WORD_INDICATOR) for p in palabras: self.fillIndicatorRange(p[0], p[1], p[0], p[2], Editor.WORD_INDICATOR) def active_code_completion(self, enabled=True): if self.api is not None and enabled: return if enabled: self.api = QsciAPIs(self._lexer) if settings.get_setting('editor/completion-keywords'): for keyword in keywords.keywords: self.api.add(keyword) self.api.prepare() source = QsciScintilla.AcsAPIs if settings.get_setting('editor/completion-document'): source = QsciScintilla.AcsAll elif settings.get_setting('editor/completion-document'): source = QsciScintilla.AcsDocument else: source = QsciScintilla.AcsNone threshold = settings.get_setting('editor/completion-threshold') self.setAutoCompletionThreshold(threshold) self.setAutoCompletionSource(source) cs = settings.get_setting('editor/completion-cs') self.setAutoCompletionCaseSensitivity(cs) repl_word = settings.get_setting('editor/completion-replace-word') self.setAutoCompletionReplaceWord(repl_word) show_single = settings.get_setting('editor/completion-single') use_single = 2 if show_single else 0 self.setAutoCompletionUseSingle(use_single) else: self.api = None self.setAutoCompletionSource(0) def _add_marker_modified(self): """ Agrega el marcador cuando el texto cambia """ if not settings.get_setting('editor/mark-change'): return nline, _ = self.getCursorPosition() if self.markersAtLine(nline): self.markerDelete(nline) self.markerAdd(nline, Editor.MARKER_MODIFIED) def _show_violations(self): data = self.checker.data self.clear_indicators(Editor.WARNING_INDICATOR) for line, message in list(data.items()): line = int(line) - 1 self.fillIndicatorRange(line, 0, line, self.lineLength(line), Editor.WARNING_INDICATOR) def _text_under_cursor(self): """ Texto seleccionado con el cursor """ line, index = self.getCursorPosition() # Posición del cursor word = self.wordAtLineIndex(line, index) # Palabra en esa pos return word def mouseReleaseEvent(self, e): super(Editor, self).mouseReleaseEvent(e) if e.button() == Qt.LeftButton: word = self._text_under_cursor() if not word: self.clear_indicators(Editor.WORD_INDICATOR) return self.search_thread.find(word, self.text()) def mouseMoveEvent(self, event): super(Editor, self).mouseMoveEvent(event) if self.checker is None: return position = event.pos() line = str(self.lineAt(position) + 1) message = self.checker.data.get(line, None) if message is not None: QToolTip.showText(self.mapToGlobal(position), message) def keyReleaseEvent(self, event): super(Editor, self).keyReleaseEvent(event) line, _ = self.getCursorPosition() self.emit(SIGNAL("linesChanged(int)"), line) def keyPressEvent(self, event): super(Editor, self).keyPressEvent(event) key = event.key() # Borra indicador de palabra if key == Qt.Key_Escape: self.clear_indicators(Editor.WORD_INDICATOR) return # Completado de comillas if key == Qt.Key_Apostrophe: if settings.get_setting('editor/complete-single-quote'): self._complete_quote(event) return if key == Qt.Key_QuoteDbl: if settings.get_setting('editor/complete-double-quote'): self._complete_quote(event) return # Completado de llaves if key == Qt.Key_BraceLeft: if settings.get_setting('editor/complete-brace'): self._complete_key(event) if key in (Qt.Key_BracketLeft, Qt.Key_ParenLeft, Qt.Key_BracketRight, Qt.Key_ParenRight): self._complete_brace(event) def _complete_quote(self, event): """ Autocompleta comillas simples y dobles, si existe el complementario el cursor se mueve un espacio. """ complementary = Editor.QUOTE.get(event.text()) line, index = self.getCursorPosition() text_line = self.text(line) portion = text_line[index - 1:index + 1] if portion in settings.QUOTES: self.setSelection(line, index, line, index + 1) self.replaceSelectedText('') else: self.insertAt(complementary, line, index) def _complete_brace(self, event): """ Autocompleta un brace cuando es abierto '(, ['. Si existe el complementario '), ]' el índice del cursor se mueve un espacio. """ brace_close = settings.BRACES.get(event.text(), None) braces_open = list(Editor.BRACE.keys()) line, index = self.getCursorPosition() if event.text() in settings.BRACES.values(): text_line = self.text(line) found = 0 # Busco un brace cerrado en el texto for token in text_line: if token in settings.BRACES.values(): found += 1 try: # Brace abierto brace_open = text_line[index] except: brace_open = '' if found > 1 and brace_open not in braces_open: # Reemplazo el brace sobrante por un string vacío self.setSelection(line, index, line, index + 1) self.replaceSelectedText('') return elif event.text() in settings.BRACES.keys( ) and brace_close is not None: # Inserto el brace complementario self.insertAt(brace_close, line, index) def _complete_key(self, event): """ Autocompleta llaves en funciones y estructuras. Si se usa typedef en la definición de la estructura se completa la variable. """ line, index = self.getCursorPosition() text_line = self.text(line) # Estructura if REGEX_STRUCT.match(text_line): if text_line.split()[0] == 'typedef': # Obtengo la variable split_line = text_line.split() if split_line[-1] == '{': typedef = split_line[-2] else: typedef = split_line[-1][:-1] text = "}%s;" % typedef.title() else: text = "};" self.insert("\n") self.insertAt(text, line + 1, 0) return # Función portion = text_line[index - 3:index] if ')' in portion: self.insert("\n") current_indentation = self.indentation(line) self.setIndentation(line + 1, current_indentation) self.insertAt('}', line + 1, current_indentation) def resizeEvent(self, e): super(Editor, self).resizeEvent(e) if self.minimap is not None: self.minimap.update_geometry() def comment(self): # FIXME: tener en cuenta /* */ # FIXME: no funciona si el comentario no inicia en el índice 0 if self.hasSelectedText(): line_from, _, line_to, _ = self.getSelection() # Iterar todas las líneas seleccionadas self.send("sci_beginundoaction") for line in range(line_from, line_to + 1): self.insertAt('//', line, 0) self.send("sci_endundoaction") else: line, _ = self.getCursorPosition() self.insertAt('//', line, 0) def uncomment(self): if self.hasSelectedText(): line_from, _, line_to, _ = self.getSelection() self.send("sci_beginundoaction") for line in range(line_from, line_to + 1): self.setSelection(line, 0, line, 2) if not self.text(line).startswith('//'): continue self.removeSelectedText() self.send("sci_endundoaction") else: line, _ = self.getCursorPosition() if not self.text(line).startswith('//'): return self.setSelection(line, 0, line, 2) self.removeSelectedText() def to_lowercase(self): if self.hasSelectedText(): text = self.selectedText().lower() else: line, _ = self.getCursorPosition() text = self.text(line).lower() self.setSelection(line, 0, line, len(text)) self.replaceSelectedText(text) def to_uppercase(self): if self.hasSelectedText(): text = self.selectedText().upper() else: line, _ = self.getCursorPosition() text = self.text(line).upper() self.setSelection(line, 0, line, len(text)) self.replaceSelectedText(text) def to_title(self): if self.hasSelectedText(): text = self.selectedText().title() else: line, _ = self.getCursorPosition() text = self.text(line).title() self.setSelection(line, 0, line, len(text)) self.replaceSelectedText(text) def duplicate_line(self): self.send("sci_lineduplicate") def delete_line(self): if self.hasSelectedText(): self.send("sci_beginundoaction") desde, desde_indice, hasta, _ = self.getSelection() self.setCursorPosition(desde, desde_indice) while desde != hasta: self.send("sci_linedelete") desde += 1 self.send("sci_endundoaction") else: self.send("sci_linedelete") def indent_more(self): if self.hasSelectedText(): self.send("sci_beginundoaction") desde, _, hasta, _ = self.getSelection() for linea in range(desde, hasta + 1): self.indent(linea) self.send("sci_endundoaction") else: linea, _ = self.getCursorPosition() self.indent(linea) def indent_less(self): if self.hasSelectedText(): self.send("sci_beginundoaction") desde, _, hasta, _ = self.getSelection() for linea in range(desde, hasta + 1): self.unindent(linea) self.send("sci_endundoaction") else: linea, _ = self.getCursorPosition() self.unindent(linea) def move_down(self): self.send("sci_moveselectedlinesdown") def move_up(self): self.send("sci_moveselectedlinesup") def reemplazar_tabs_por_espacios(self): codigo = self.text() codigo_sin_tabs = codigo.replace('\t', ' ' * self._indentation) self.setText(codigo_sin_tabs) def saved(self): # Esta señal sirve para actualizar el árbol de símbolos self.emit(SIGNAL("fileSaved(QString)"), self.obj_file.filename) self.setModified(False) # Itera todas las líneas y si existe un _marker_modified agrega # un _marker_save for nline in range(self.lines()): if self.markersAtLine(nline): self.markerAdd(nline, Editor.MARKER_SAVED) def dropEvent(self, event): self.emit(SIGNAL("dropEvent(PyQt_PyObject)"), event)