class Main(): def __init__(self): self.lexer = QsciLexerPython() self.apis = QsciAPIs(self.lexer) self.lexer.setAPIs(self.apis) self.apis.apiPreparationFinished.connect( self.slotApiPreparationFinished) for fn in API_FILES: ok = self.apis.load(PATH + '/api_raw/' + fn) self.apis.prepare() def slotApiPreparationFinished(self): self.apis.savePrepared(PATH + '/prepared.api') print('Done.') QApplication.quit()
class APIs(QObject): """ Class implementing an API storage entity. @signal apiPreparationFinished() emitted after the API preparation has finished @signal apiPreparationCancelled() emitted after the API preparation has been cancelled @signal apiPreparationStarted() emitted after the API preparation has started """ apiPreparationFinished = pyqtSignal() apiPreparationCancelled = pyqtSignal() apiPreparationStarted = pyqtSignal() def __init__(self, language, forPreparation=False, parent=None): """ Constructor @param language language of the APIs object (string) @param forPreparation flag indicating this object is just needed for a preparation process (boolean) @param parent reference to the parent object (QObject) """ super(APIs, self).__init__(parent) self.setObjectName("APIs_{0}".format(language)) self.__inPreparation = False self.__language = language self.__forPreparation = forPreparation self.__lexer = Lexers.getLexer(self.__language) self.__apifiles = Preferences.getEditorAPI(self.__language) self.__apifiles.sort() if self.__lexer is None: self.__apis = None else: self.__apis = QsciAPIs(self.__lexer) self.__apis.apiPreparationFinished.connect( self.__apiPreparationFinished) self.__apis.apiPreparationCancelled.connect( self.__apiPreparationCancelled) self.__apis.apiPreparationStarted.connect( self.__apiPreparationStarted) self.__loadAPIs() def __loadAPIs(self): """ Private method to load the APIs. """ if self.__apis.isPrepared(): # load a prepared API file if not self.__forPreparation and \ Preferences.getEditor("AutoPrepareAPIs"): self.prepareAPIs() self.__apis.loadPrepared() else: # load the raw files and prepare the API file if not self.__forPreparation and \ Preferences.getEditor("AutoPrepareAPIs"): self.prepareAPIs(ondemand=True) def reloadAPIs(self): """ Public method to reload the API information. """ if not self.__forPreparation and \ Preferences.getEditor("AutoPrepareAPIs"): self.prepareAPIs() self.__loadAPIs() def getQsciAPIs(self): """ Public method to get a reference to QsciAPIs object. @return reference to the QsciAPIs object (QsciAPIs) """ if not self.__forPreparation and \ Preferences.getEditor("AutoPrepareAPIs"): self.prepareAPIs() return self.__apis def isEmpty(self): """ Public method to check, if the object has API files configured. @return flag indicating no API files have been configured (boolean) """ return len(self.__apifiles) == 0 def __apiPreparationFinished(self): """ Private method called to save an API, after it has been prepared. """ self.__apis.savePrepared() self.__inPreparation = False self.apiPreparationFinished.emit() def __apiPreparationCancelled(self): """ Private method called, after the API preparation process has been cancelled. """ self.__inPreparation = False self.apiPreparationCancelled.emit() def __apiPreparationStarted(self): """ Private method called, when the API preparation process started. """ self.__inPreparation = True self.apiPreparationStarted.emit() def prepareAPIs(self, ondemand=False, rawList=None): """ Public method to prepare the APIs if necessary. @keyparam ondemand flag indicating a requested preparation (boolean) @keyparam rawList list of raw API files (list of strings) """ if self.__apis is None or self.__inPreparation: return needsPreparation = False if ondemand: needsPreparation = True else: # check, if a new preparation is necessary preparedAPIs = self.__defaultPreparedName() if preparedAPIs: preparedAPIsInfo = QFileInfo(preparedAPIs) if not preparedAPIsInfo.exists(): needsPreparation = True else: preparedAPIsTime = preparedAPIsInfo.lastModified() apifiles = sorted( Preferences.getEditorAPI(self.__language)) if self.__apifiles != apifiles: needsPreparation = True for apifile in apifiles: if QFileInfo(apifile).lastModified() > \ preparedAPIsTime: needsPreparation = True break if needsPreparation: # do the preparation self.__apis.clear() if rawList: apifiles = rawList else: apifiles = Preferences.getEditorAPI(self.__language) for apifile in apifiles: self.__apis.load(apifile) self.__apis.prepare() self.__apifiles = apifiles def cancelPreparation(self): """ Public slot to cancel the APIs preparation. """ self.__apis and self.__apis.cancelPreparation() def installedAPIFiles(self): """ Public method to get a list of installed API files. @return list of installed API files (list of strings) """ if self.__apis is not None: if Globals.isWindowsPlatform(): qsciPath = os.path.join( Globals.getPyQt5ModulesDirectory(), "qsci") if os.path.exists(qsciPath): # it's the installer if self.__lexer.lexerName() is not None: apidir = os.path.join(qsciPath, "api", self.__lexer.lexerName()) fnames = [] filist = QDir(apidir).entryInfoList( ["*.api"], QDir.Files, QDir.IgnoreCase) for fi in filist: fnames.append(fi.absoluteFilePath()) return fnames else: return [] return self.__apis.installedAPIFiles() else: return [] def __defaultPreparedName(self): """ Private method returning the default name of a prepared API file. @return complete filename for the Prepared APIs file (string) """ if self.__apis is not None: return self.__apis.defaultPreparedName() else: return ""
class APIs(QObject): """ Class implementing an API storage entity. @signal apiPreparationFinished() emitted after the API preparation has finished @signal apiPreparationCancelled() emitted after the API preparation has been cancelled @signal apiPreparationStarted() emitted after the API preparation has started """ apiPreparationFinished = pyqtSignal() apiPreparationCancelled = pyqtSignal() apiPreparationStarted = pyqtSignal() def __init__(self, language, projectType="", forPreparation=False, parent=None): """ Constructor @param language language of the APIs object @type str @param projectType type of the project @type str @param forPreparation flag indicating this object is just needed for a preparation process @type bool @param parent reference to the parent object @type QObject """ super(APIs, self).__init__(parent) if projectType: self.setObjectName("APIs_{0}_{1}".format(language, projectType)) else: self.setObjectName("APIs_{0}".format(language)) self.__inPreparation = False self.__language = language self.__projectType = projectType self.__forPreparation = forPreparation self.__lexer = Lexers.getLexer(self.__language) self.__apifiles = Preferences.getEditorAPI(self.__language, self.__projectType) self.__apifiles.sort() if self.__lexer is None: self.__apis = None else: self.__apis = QsciAPIs(self.__lexer) self.__apis.apiPreparationFinished.connect( self.__apiPreparationFinished) self.__apis.apiPreparationCancelled.connect( self.__apiPreparationCancelled) self.__apis.apiPreparationStarted.connect( self.__apiPreparationStarted) self.__loadAPIs() def __loadAPIs(self): """ Private method to load the APIs. """ if self.__apis.isPrepared(): # load a prepared API file if (not self.__forPreparation and Preferences.getEditor("AutoPrepareAPIs")): self.prepareAPIs() self.__apis.loadPrepared(self.__preparedName()) else: # load the raw files and prepare the API file if (not self.__forPreparation and Preferences.getEditor("AutoPrepareAPIs")): self.prepareAPIs(ondemand=True) def reloadAPIs(self): """ Public method to reload the API information. """ if (not self.__forPreparation and Preferences.getEditor("AutoPrepareAPIs")): self.prepareAPIs() self.__loadAPIs() def getQsciAPIs(self): """ Public method to get a reference to QsciAPIs object. @return reference to the QsciAPIs object (QsciAPIs) """ if (not self.__forPreparation and Preferences.getEditor("AutoPrepareAPIs")): self.prepareAPIs() return self.__apis def isEmpty(self): """ Public method to check, if the object has API files configured. @return flag indicating no API files have been configured (boolean) """ return len(self.__apifiles) == 0 def __apiPreparationFinished(self): """ Private method called to save an API, after it has been prepared. """ self.__apis.savePrepared(self.__preparedName()) self.__inPreparation = False self.apiPreparationFinished.emit() def __apiPreparationCancelled(self): """ Private method called, after the API preparation process has been cancelled. """ self.__inPreparation = False self.apiPreparationCancelled.emit() def __apiPreparationStarted(self): """ Private method called, when the API preparation process started. """ self.__inPreparation = True self.apiPreparationStarted.emit() def prepareAPIs(self, ondemand=False, rawList=None): """ Public method to prepare the APIs if necessary. @keyparam ondemand flag indicating a requested preparation (boolean) @keyparam rawList list of raw API files (list of strings) """ if self.__apis is None or self.__inPreparation: return needsPreparation = False if ondemand: needsPreparation = True else: # check, if a new preparation is necessary preparedAPIs = self.__preparedName() if preparedAPIs: preparedAPIsInfo = QFileInfo(preparedAPIs) if not preparedAPIsInfo.exists(): needsPreparation = True else: preparedAPIsTime = preparedAPIsInfo.lastModified() apifiles = sorted( Preferences.getEditorAPI(self.__language, self.__projectType)) if self.__apifiles != apifiles: needsPreparation = True for apifile in apifiles: if (QFileInfo(apifile).lastModified() > preparedAPIsTime): needsPreparation = True break if needsPreparation: # do the preparation self.__apis.clear() if rawList: apifiles = rawList else: apifiles = Preferences.getEditorAPI(self.__language, self.__projectType) for apifile in apifiles: self.__apis.load(apifile) self.__apis.prepare() self.__apifiles = apifiles def cancelPreparation(self): """ Public slot to cancel the APIs preparation. """ self.__apis and self.__apis.cancelPreparation() def installedAPIFiles(self): """ Public method to get a list of installed API files. @return list of installed API files (list of strings) """ if self.__apis is not None: if Globals.isWindowsPlatform(): qsciPath = os.path.join(Globals.getPyQt5ModulesDirectory(), "qsci") if os.path.exists(qsciPath): # it's the installer if self.__lexer.lexerName() is not None: apidir = os.path.join(qsciPath, "api", self.__lexer.lexerName()) fnames = [] filist = QDir(apidir).entryInfoList(["*.api"], QDir.Files, QDir.IgnoreCase) for fi in filist: fnames.append(fi.absoluteFilePath()) return fnames else: return [] return self.__apis.installedAPIFiles() else: return [] def __preparedName(self): """ Private method returning the default name of a prepared API file. @return complete filename for the Prepared APIs file (string) """ apisDir = os.path.join(Globals.getConfigDir(), "APIs") if self.__apis is not None: if self.__projectType: filename = "{0}_{1}.pap".format(self.__language, self.__projectType) else: filename = "{0}.pap".format(self.__language) return os.path.join(apisDir, filename) else: return ""
class PMCodeEditor(QWidget, Ui_FormEditor): def __init__(self, *args, **kwargs): super(PMCodeEditor, self).__init__(*args, **kwargs) self.setupUi(self) self._lexer = None self._apis = None self._init_editor() self._init_lexer() self._init_apis() self._init_signals() def _init_editor(self): """ 初始化编辑器设置 Returns: """ self.label_status_ln_col.setText(self.tr('行:1 列:1')) self.label_status_length.setText(self.tr('长度:0 行数:1')) self.label_status_sel.setText(self.tr('选中:0 | 0')) self.textEdit.setContextMenuPolicy(Qt.CustomContextMenu) # 设置字体 self.textEdit.setFont(QFont('Source Code Pro', 12)) # Consolas self.textEdit.setMarginsFont(self.textEdit.font()) # 自动换行 self.textEdit.setEolMode(QsciScintilla.EolUnix) # \n换行 self.textEdit.setWrapMode(QsciScintilla.WrapWord) # 自动换行 self.textEdit.setWrapVisualFlags(QsciScintilla.WrapFlagNone) self.textEdit.setWrapIndentMode(QsciScintilla.WrapIndentFixed) # 编码 self.textEdit.setUtf8(True) self.textEdit.SendScintilla(QsciScintilla.SCI_SETCODEPAGE, QsciScintilla.SC_CP_UTF8) # 自动提示 self.textEdit.setAnnotationDisplay( QsciScintilla.AnnotationBoxed) # 提示显示方式 self.textEdit.setAutoCompletionSource( QsciScintilla.AcsAll) # 自动补全。对于所有Ascii字符 self.textEdit.setAutoCompletionReplaceWord(True) self.textEdit.setAutoCompletionCaseSensitivity(False) # 忽略大小写 # self.textEdit.setAutoCompletionUseSingle(QsciScintilla.AcusAlways) self.textEdit.setAutoCompletionThreshold(1) # 输入多少个字符才弹出补全提示 self.textEdit.setCallTipsPosition( QsciScintilla.CallTipsBelowText) # 设置提示位置 self.textEdit.setCallTipsStyle( QsciScintilla.CallTipsNoContext) # 设置提示样式 # 设置折叠样式 self.textEdit.setFolding( QsciScintilla.FoldStyle.BoxedTreeFoldStyle) # 代码折叠 self.textEdit.setFoldMarginColors(QColor(233, 233, 233), Qt.white) # 折叠标签颜色 self.textEdit.SendScintilla(QsciScintilla.SCI_MARKERSETBACK, QsciScintilla.SC_MARKNUM_FOLDERSUB, QColor('0xa0a0a0')) self.textEdit.SendScintilla(QsciScintilla.SCI_MARKERSETBACK, QsciScintilla.SC_MARKNUM_FOLDERMIDTAIL, QColor('0xa0a0a0')) self.textEdit.SendScintilla(QsciScintilla.SCI_MARKERSETBACK, QsciScintilla.SC_MARKNUM_FOLDERTAIL, QColor('0xa0a0a0')) # 设置当前行背景 self.textEdit.setCaretLineVisible(True) self.textEdit.setCaretLineBackgroundColor(QColor(232, 232, 255)) # 设置选中文本颜色 # self.textEdit.setSelectionForegroundColor(QColor(192, 192, 192)) # self.textEdit.setSelectionBackgroundColor(QColor(192, 192, 192)) # 括号匹配 self.textEdit.setBraceMatching( QsciScintilla.StrictBraceMatch) # 大括号严格匹配 self.textEdit.setMatchedBraceBackgroundColor(Qt.blue) self.textEdit.setMatchedBraceForegroundColor(Qt.white) self.textEdit.setUnmatchedBraceBackgroundColor(Qt.red) self.textEdit.setUnmatchedBraceForegroundColor(Qt.white) # 启用活动热点区域的下划线 self.textEdit.setHotspotUnderline(True) self.textEdit.setHotspotWrap(True) # 缩进 self.textEdit.setAutoIndent(True) # 换行后自动缩进 self.textEdit.setTabWidth(4) self.textEdit.setIndentationWidth(4) self.textEdit.setTabIndents(True) # 缩进指南 self.textEdit.setIndentationGuides(True) self.textEdit.setIndentationsUseTabs(False) # 不使用Tab self.textEdit.setBackspaceUnindents(True) # 当一行没有其它字符时删除前面的缩进 self.textEdit.setIndentationGuidesForegroundColor(QColor( 192, 192, 192)) self.textEdit.setIndentationGuidesBackgroundColor(Qt.white) # 显示行号 self.textEdit.setMarginLineNumbers(0, True) self.textEdit.setMarginWidth(0, 50) self.textEdit.setMarginWidth(1, 0) # 行号 # self.textEdit.setMarginWidth(2, 0) # 折叠 self.textEdit.setMarginWidth(3, 0) self.textEdit.setMarginWidth(4, 0) # # 折叠区域 # self.textEdit.setMarginType(3, QsciScintilla.SymbolMargin) # self.textEdit.setMarginLineNumbers(3, False) # self.textEdit.setMarginWidth(3, 15) # self.textEdit.setMarginSensitivity(3, True) # 设置空白字符显示 self.textEdit.setWhitespaceSize(1) # 可见的空白点的尺寸 self.textEdit.setWhitespaceVisibility( QsciScintilla.WsVisible) # 空白的可见性。默认的是空格是无形的 self.textEdit.setWhitespaceForegroundColor(QColor(255, 181, 106)) def _init_lexer(self): """ 初始化语法解析器 Returns: """ self._lexer = QsciLexerPython(self.textEdit) self._lexer.setFont(self.textEdit.font()) self.textEdit.setLexer(self._lexer) def _init_apis(self): """ 加载自定义智能提示文件 Returns: """ self._apis = QsciAPIs(self._lexer) for path in Path(os.path.dirname(__file__)).rglob('*.api'): self._apis.load(str(path.absolute())) self._apis.prepare() def _init_signals(self): """ 初始化信号绑定 Returns: """ # 绑定光标变化信号 self.textEdit.cursorPositionChanged.connect( self.slot_cursor_position_changed) # 绑定内容改变信号 self.textEdit.textChanged.connect(self.slot_text_changed) # 绑定选中变化信号 self.textEdit.selectionChanged.connect(self.slot_selection_changed) # 绑定是否被修改信号 self.textEdit.modificationChanged.connect( self.slot_modification_changed) # 绑定右键菜单信号 self.textEdit.customContextMenuRequested.connect( self.slot_custom_context_menu_requested) def slot_cursor_position_changed(self, line: int, column: int): """ 光标变化槽函数 Args: line: column: Returns: """ self.label_status_ln_col.setText( self.tr('行:{0} 列:{1}').format(line + 1, column + 1)) def slot_text_changed(self): """ 内容变化槽函数 Returns: """ self.label_status_length.setText( self.tr('长度:{0} 行数:{1}').format(self.textEdit.length(), self.textEdit.lines())) def slot_selection_changed(self): """ 选中内容变化槽函数 Returns: """ lineFrom, indexFrom, lineTo, indexTo = self.textEdit.getSelection() lines = 0 if lineFrom == lineTo == -1 else lineTo - lineFrom + 1 self.label_status_sel.setText( self.tr('选中:{0} | {1}').format(len(self.textEdit.selectedText()), lines)) def slot_modification_changed(self, modified: bool): """ 内容被修改槽函数 Args: modified: Returns: """ title = self.windowTitle() if modified: if not title.startswith('*'): self.setWindowTitle('*' + title) else: if title.startswith('*'): self.setWindowTitle(title[1:]) def slot_custom_context_menu_requested(self, pos: QPoint): """ 右键菜单修改 Args: pos: Returns: """ # 创建默认菜单 menu = self.textEdit.createStandardContextMenu() country = QLocale.system().country() is_china = QLocale.system().language( ) == QLocale.Chinese or country in (QLocale.China, QLocale.HongKong, QLocale.Taiwan) # 遍历本身已有的菜单项做中文翻译处理 # 前提是要加载了Qt自带的翻译文件 for action in menu.actions(): if is_china: action.setText( QCoreApplication.translate('QTextControl', action.text())) menu.exec_(self.textEdit.mapToGlobal(pos)) del menu