class Editor(QPlainTextEdit): def __init__(self, parent, isReadOnly=False): super().__init__(parent) self.lineNumberArea = QLineNumberArea(self) self.blockCountChanged.connect(self.updateLineNumberAreaWidth) self.updateRequest.connect(self.updateLineNumberArea) self.cursorPositionChanged.connect(self.highlightCurrentLine) self.updateLineNumberAreaWidth(0) self.setReadOnly(isReadOnly) self.parent = parent self.font = QFont() self.size = 12 self.worldList = wordList self.menu_font = QFont() self.menu_font.setFamily(editor["menuFont"]) self.menu_font.setPointSize(editor["menuFontSize"]) self.font.setFamily(editor["editorFont"]) self.font.setPointSize(editor["editorFontSize"]) self.focused = None self.text = None self.replace_tabs = 4 self.setWordWrapMode(4) self.setFont(self.font) self.l = 0 self.highlightingRules = [] self.extraSelections_ = [] self.indexes = None self.setMouseTracking(True) self.completer = None self.info_process = QProcess() self.setTabStopWidth(editor["TabWidth"]) self.createStandardContextMenu() self.setWordWrapMode(QTextOption.NoWrap) self.cursorPositionChanged.connect(self.getColAndRow) self.info_process.readyReadStandardOutput.connect( self.get_pydoc_output) def getTextCursor(self): textCursor = self.textCursor() textCursorPos = textCursor.position() return textCursor, textCursorPos def getColAndRow(self): textCursor = self.getTextCursor()[0] # print("row: {} column: {}".format(textCursor.blockNumber(), textCursor.positionInBlock())) def totalLines(self): return self.blockCount() def is_modified(self): return self.document().isModified() def check(self): cursor = self.textCursor() b = cursor.block() extraSelections = [] if len(b.text()) > 120: selection = QTextEdit.ExtraSelection() # TODO: implement something using flake8 selection.format.setFontUnderline(True) selection.format.setUnderlineColor(QColor("#FF0000")) selection.format.setUnderlineStyle( QTextCharFormat.SpellCheckUnderline) selection.format.setProperty(QTextFormat.FullWidthSelection, True) selection.cursor = self.textCursor() selection.cursor.clearSelection() self.extraSelections_.append(selection) self.setExtraSelections(self.extraSelections_) font = QFont() font.setFamily("Iosevka") font.setPointSize(10) QToolTip.setFont(font) cursor = self.textCursor() current_line = cursor.positionInBlock() QToolTip.showText( QCursor.pos(), "Line too long (" + str(current_line) + "> 120) | violation on line: " + str(b.blockNumber() + 1)) def newFile(self): """This is a wrapper for the function defined in Main""" self.new_action = QAction("New") self.new_action.triggered.connect(self.parent.parent.newFile) def lineNumberAreaWidth(self): digits = 1 max_value = max(1, self.blockCount()) while max_value >= 10: max_value /= 10 digits += 1 space = 10 + self.fontMetrics().width('9') * digits return space def updateLineNumberAreaWidth(self, _): self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) def updateLineNumberArea(self, rect, dy): if dy: self.lineNumberArea.scroll(0, dy) else: self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) if rect.contains(self.viewport().rect()): self.updateLineNumberAreaWidth(0) def resizeEvent(self, event): super().resizeEvent(event) cr = self.contentsRect() self.lineNumberArea.setGeometry( QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) def highlightCurrentLine(self): extraSelections = [] if not self.isReadOnly(): selection = QTextEdit.ExtraSelection() lineColor = QColor("#434343") selection.format.setBackground(lineColor) selection.format.setProperty(QTextFormat.FullWidthSelection, True) selection.cursor = self.textCursor() selection.cursor.clearSelection() extraSelections.append(selection) self.setExtraSelections(extraSelections) self.check() return extraSelections def lineNumberAreaPaintEvent(self, event): painter = QPainter(self.lineNumberArea) painter.fillRect(event.rect(), QColor("#303030")) block = self.firstVisibleBlock() blockNumber = block.blockNumber() top = self.blockBoundingGeometry(block).translated( self.contentOffset()).top() bottom = top + self.blockBoundingRect(block).height() # Just to make sure I use the right font height = self.fontMetrics().height() while block.isValid() and (top <= event.rect().bottom()): if block.isVisible() and (bottom >= event.rect().top()): number = str(blockNumber + 1) painter.setPen(Qt.white) painter.drawText(0, top, self.lineNumberArea.width(), height, Qt.AlignCenter, number) block = block.next() top = bottom bottom = top + self.blockBoundingRect(block).height() blockNumber += 1 def openFile(self): self.open_action = QAction("Open") self.open_action.triggered.connect(self.parent.parent.openFileFromMenu) def runFile(self): self.run_action = QAction("Run") self.run_action.triggered.connect(self.parent.parent.execute_file) def textUnderCursor(self): textCursor = self.textCursor() textCursor.select(QTextCursor.WordUnderCursor) return textCursor.selectedText() def moveCursorPosBack(self): textCursor = self.textCursor() textCursorPos = textCursor.position() textCursor.setPosition(textCursorPos - 1) self.setTextCursor(textCursor) def mouseMoveEvent(self, QMouseEvent): # font = textCursor.block().charFormat().font() # metrics = QFontMetrics(font) # # b = self.document().findBlockByLineNumber(0) # # cursor = QTextCursor(b) # # cursor.select(QTextCursor.BlockUnderCursor) # # cursor.removeSelectedText() # # height = metrics.height() + 2 # y = QMouseEvent.pos().y() #print(y, height) #print(y/height) cursor_main = self.cursorForPosition(QMouseEvent.pos()) if QApplication.queryKeyboardModifiers() == Qt.ControlModifier: cursor_main.select(QTextCursor.WordUnderCursor) text = cursor_main.selectedText() self.text = text if self.text is not None: url = "https://docs.python.org/3/library/functions.html#" + self.text word = self.text # self.parent.parent.showBrowser(url, word) if self.check_func(word): extraSelections = self.highlightCurrentLine() selection = QTextEdit.ExtraSelection() selection.format.setFontUnderline(True) selection.format.setUnderlineColor(QColor("#00d2ff")) selection.format.setForeground(QColor("#00d2ff")) selection.format.setProperty( QTextFormat.FullWidthSelection, True) selection.cursor = self.cursorForPosition( QMouseEvent.pos()) selection.cursor.select(QTextCursor.WordUnderCursor) extraSelections.append(selection) self.setExtraSelections(extraSelections) cursor = QCursor(Qt.PointingHandCursor) # tooltip = QToolTip() QToolTip.setFont( QFont(editor["ToolTipFont"], editor["ToolTipFontSize"])) word_shown = eval(word).__doc__ if len(word_shown) > 2000: word_shown = word_shown[:2000] QToolTip.showText(QCursor.pos(), "{}".format(word_shown)) QApplication.setOverrideCursor(cursor) QApplication.changeOverrideCursor(cursor) else: self.returnCursorToNormal() else: pass else: self.returnCursorToNormal() extraSelections = self.highlightCurrentLine() self.setExtraSelections(extraSelections) super().mouseMoveEvent(QMouseEvent) def returnCursorToNormal(self): cursor = QCursor(Qt.ArrowCursor) QApplication.setOverrideCursor(cursor) QApplication.changeOverrideCursor(cursor) def mousePressEvent(self, e): self.check() if QApplication.queryKeyboardModifiers() == Qt.ControlModifier: super().mousePressEvent(e) self.text = self.textUnderCursor() if self.text is not None: word = self.text # self.parent.parent.showBrowser(url, word) if self.check_func(self.textUnderCursor(), True): extraSelections = self.highlightCurrentLine() selection = QTextEdit.ExtraSelection() selection.format.setFontUnderline(True) selection.format.setUnderlineColor(QColor("#00d2ff")) selection.format.setForeground(QColor("#00d2ff")) selection.format.setProperty( QTextFormat.FullWidthSelection, True) selection.cursor = self.textCursor() selection.cursor.clearSelection() selection.cursor.select(QTextCursor.WordUnderCursor) extraSelections.append(selection) self.setExtraSelections(extraSelections) self.text = self.textUnderCursor() # cursor = QCursor(Qt.PointingHandCursor) # QApplication.setOverrideCursor(cursor) # QApplication.changeOverrideCursor(cursor) else: self.text = None else: super().mousePressEvent(e) QApplication.restoreOverrideCursor() # super().mousePressEvent(e) def check_func(self, word, clicked=False): funcs = [ "abs", "all", "any", "ascii", "bin", "bool", "breakpoint", "bytearray", "bytes", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "exec", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "range", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "vars", "zip", "__import__" ] word_array = list(word) for wo in word_array: if wo in ["{", "}", "'", '"', "[", "]", "(", ")"]: word_array.remove(wo) for w in funcs: if w == "".join(word_array): if clicked: if self.info_process.state() == 0: self.info_process.start("pydoc {}".format(word)) else: pass return True def get_pydoc_output(self): output = self.info_process.readAllStandardOutput().data().decode() self.parent.parent.open_documentation(output, self.info_process.arguments()[0]) def contextMenuEvent(self, event): menu = QMenu() """Initializing actions""" self.newFile() self.openFile() self.runFile() menu.addAction(self.new_action) menu.addAction(self.open_action) menu.addAction(self.run_action) menu.setFont(self.menu_font) menu.exec(event.globalPos()) del menu def mouseReleaseEvent(self, e): self.check() super().mouseReleaseEvent(e) def keyPressEvent(self, e): textCursor = self.textCursor() key = e.key() if e.modifiers( ) == Qt.ControlModifier and key == 16777217: # that key code stands for tab self.parent.parent.switchTabs() isSearch = (e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_F) isDeleteLine = (e.modifiers() == Qt.ControlModifier and e.key() == 16777219 ) # This is for MacOS (CMD+Delete) if isSearch: try: currentWidget = self.parent currentFile = currentWidget.fileName currentEditor = currentWidget.editor textCursor = currentEditor.textCursor() textCursorPos = textCursor.position() if currentWidget is not None: text, okPressed = QInputDialog.getText( self, 'Find', 'Find what: ') if okPressed: if text == "": text = " " self.not_found = NoMatch(text) self.searchtext = text try: with open(currentFile, 'r') as file: contents = file.read() self.indexes = list(find_all(contents, text)) if len(self.indexes) == 0: self.not_found = NoMatch(text) except FileNotFoundError as E: print(E, " on line 245 in the file Editor.py") except (AttributeError, UnboundLocalError) as E: print(E, " on line 228 in the file Editor.py") if isDeleteLine and platform.system( ) == "Darwin": # Check if the os is MacOS textCursor.select(QTextCursor.LineUnderCursor) textCursor.removeSelectedText() if key == Qt.Key_QuoteDbl: self.insertPlainText('"') self.moveCursorPosBack() if e.modifiers() == Qt.ControlModifier and e.key( ) == 61: # Press Ctrl+Equal key to make font bigger self.font.setPointSize(self.size + 1) self.font.setFamily(editor["editorFont"]) self.setFont(self.font) self.size += 1 if e.modifiers() == Qt.ControlModifier and e.key() == 16777217: return if e.modifiers() == Qt.ControlModifier and e.key( ) == 45: # Press Ctrl+Minus key to make font smaller self.font.setPointSize(self.size - 1) self.font.setFamily(editor["editorFont"]) self.setFont(self.font) self.size -= 1 if key == Qt.Key_F3: try: index = self.indexes[0 + self.l] currentWidget = self.parent currentFile = currentWidget.fileName currentEditor = currentWidget.editor textCursor.setPosition(index) textCursor.movePosition(textCursor.Right, textCursor.KeepAnchor, len(self.searchtext)) currentEditor.setTextCursor(textCursor) self.l += 1 except IndexError: self.l = 0 if key == 39: self.insertPlainText("'") self.moveCursorPosBack() if key == Qt.Key_BraceLeft: self.insertPlainText("}") self.moveCursorPosBack() if key == Qt.Key_BracketLeft: self.insertPlainText("]") self.moveCursorPosBack() if key == Qt.Key_ParenLeft: self.insertPlainText(")") self.moveCursorPosBack() if key == Qt.Key_ParenRight: textCursor = self.textCursor() textCursor.select(QTextCursor.WordUnderCursor) if textCursor.selectedText( ) == "()" or "()" in textCursor.selectedText(): return if key == Qt.Key_BraceRight: textCursor = self.textCursor() textCursor.select(QTextCursor.WordUnderCursor) if textCursor.selectedText == "": return if key == 16777219: if self.textUnderCursor() in ['""', '()', '[]', "''", "{}"]: textCursor = self.textCursor() textCursorPos = textCursor.position() textCursor.setPosition(textCursorPos + 1) textCursor.deletePreviousChar() self.setTextCursor(textCursor) if key not in [16777217, 16777219, 16777220]: super().keyPressEvent(e) # print(self.textUnderCursor()) if len(self.textUnderCursor()) == 3: pass return # e.accept() cursor = self.textCursor() if key == 16777217: # and self.replace_tabs: amount = 4 - self.textCursor().positionInBlock() % 4 self.insertPlainText(' ' * amount) elif key == 16777219 and cursor.selectionStart() == cursor.selectionEnd() and self.replace_tabs and \ cursor.positionInBlock(): position = cursor.positionInBlock() end = cursor.position() start = end - (position % 4) if start == end and position >= 4: start -= 4 string = self.toPlainText()[start:end] if not len(string.strip() ): # if length is 0 which is binary for false for i in range(end - start): cursor.deletePreviousChar() else: super().keyPressEvent(e) elif key == 16777220: end = cursor.position() start = end - cursor.positionInBlock() line = self.toPlainText()[start:end] indentation = len(line) - len(line.lstrip()) chars = '\t' if self.replace_tabs: chars = ' ' indentation /= self.replace_tabs if line.endswith(':'): if self.replace_tabs: indentation += 1 super().keyPressEvent(e) self.insertPlainText(chars * int(indentation)) else: super().keyPressEvent(e)
with open(os.path.dirname(__file__) + "/" + candidate, 'r') as file: data = file.read() msg.setDetailedText(data) msg.setText("<h3>Logs</h3>") msg.setInformativeText( "A simple utility to view log files<br>in <code>/var/log</code><br><br><a href='https://github.com/helloSystem/Utilities'>https://github.com/helloSystem/Utilities</a>" ) msg.exec() # Simple singleton: # Ensure that only one instance of this application is running by trying to kill the other ones p = QProcess() p.setProgram("pkill") p.setArguments(["-f", os.path.abspath(__file__)]) cmd = p.program() + " " + " ".join(p.arguments()) print(cmd) p.start() p.waitForFinished() app = QApplication(sys.argv) reader = ProcessOutputReader() console = MyConsole() reader.produce_output.connect(console.append_output) reader.start('sh', ['-c', "tail -n 10 -f /var/log/*.log /var/log/messages"]) console.show() app.exec_()
class Process(QObject): """Abstraction over a running test subprocess process. Reads the log from its stdout and parses it. Attributes: _invalid: A list of lines which could not be parsed. _data: A list of parsed lines. _started: Whether the process was ever started. proc: The QProcess for the underlying process. exit_expected: Whether the process is expected to quit. request: The request object for the current test. Signals: ready: Emitted when the server finished starting up. new_data: Emitted when a new line was parsed. """ ready = pyqtSignal() new_data = pyqtSignal(object) KEYS = ['data'] def __init__(self, request, parent=None): super().__init__(parent) self.request = request self.captured_log = [] self._started = False self._invalid = [] self._data = [] self.proc = QProcess() self.proc.setReadChannel(QProcess.StandardError) self.exit_expected = None # Not started at all yet def _log(self, line): """Add the given line to the captured log output.""" if self.request.config.getoption('--capture') == 'no': print(line) self.captured_log.append(line) def log_summary(self, text): """Log the given line as summary/title.""" text = '\n{line} {text} {line}\n'.format(line='='*30, text=text) self._log(text) def _parse_line(self, line): """Parse the given line from the log. Return: A self.ParseResult member. """ raise NotImplementedError def _executable_args(self): """Get the executable and necessary arguments as a tuple.""" raise NotImplementedError def _default_args(self): """Get the default arguments to use if none were passed to start().""" raise NotImplementedError def _get_data(self): """Get the parsed data for this test. Also waits for 0.5s to make sure any new data is received. Subprocesses are expected to alias this to a public method with a better name. """ self.proc.waitForReadyRead(500) self.read_log() return self._data def _wait_signal(self, signal, timeout=5000, raising=True): """Wait for a signal to be emitted. Should be used in a contextmanager. """ blocker = pytestqt.plugin.SignalBlocker(timeout=timeout, raising=raising) blocker.connect(signal) return blocker @pyqtSlot() def read_log(self): """Read the log from the process' stdout.""" if not hasattr(self, 'proc'): # I have no idea how this happens, but it does... return while self.proc.canReadLine(): line = self.proc.readLine() line = bytes(line).decode('utf-8', errors='ignore').rstrip('\r\n') try: parsed = self._parse_line(line) except InvalidLine: self._invalid.append(line) self._log("INVALID: {}".format(line)) continue if parsed is None: if self._invalid: self._log("IGNORED: {}".format(line)) else: self._data.append(parsed) self.new_data.emit(parsed) def start(self, args=None, *, env=None): """Start the process and wait until it started.""" self._start(args, env=env) self._started = True verbose = self.request.config.getoption('--verbose') timeout = 60 if utils.ON_CI else 20 for _ in range(timeout): with self._wait_signal(self.ready, timeout=1000, raising=False) as blocker: pass if not self.is_running(): if self.exit_expected: return # _start ensures it actually started, but it might quit shortly # afterwards raise ProcessExited('\n' + _render_log(self.captured_log, verbose=verbose)) if blocker.signal_triggered: self._after_start() return raise WaitForTimeout("Timed out while waiting for process start.\n" + _render_log(self.captured_log, verbose=verbose)) def _start(self, args, env): """Actually start the process.""" executable, exec_args = self._executable_args() if args is None: args = self._default_args() procenv = QProcessEnvironment.systemEnvironment() if env is not None: for k, v in env.items(): procenv.insert(k, v) self.proc.readyRead.connect(self.read_log) self.proc.setProcessEnvironment(procenv) self.proc.start(executable, exec_args + args) ok = self.proc.waitForStarted() assert ok assert self.is_running() def _after_start(self): """Do things which should be done immediately after starting.""" def before_test(self): """Restart process before a test if it exited before.""" self._invalid = [] if not self.is_running(): self.start() def after_test(self): """Clean up data after each test. Also checks self._invalid so the test counts as failed if there were unexpected output lines earlier. """ __tracebackhide__ = lambda e: e.errisinstance(ProcessExited) self.captured_log = [] if self._invalid: # Wait for a bit so the full error has a chance to arrive time.sleep(1) # Exit the process to make sure we're in a defined state again self.terminate() self.clear_data() raise InvalidLine('\n' + '\n'.join(self._invalid)) self.clear_data() if not self.is_running() and not self.exit_expected and self._started: raise ProcessExited self.exit_expected = False def clear_data(self): """Clear the collected data.""" self._data.clear() def terminate(self): """Clean up and shut down the process.""" if not self.is_running(): return if quteutils.is_windows: self.proc.kill() else: self.proc.terminate() ok = self.proc.waitForFinished(5000) if not ok: cmdline = ' '.join([self.proc.program()] + self.proc.arguments()) warnings.warn(f"Test process {cmdline} with PID {self.proc.processId()} " "failed to terminate!") self.proc.kill() self.proc.waitForFinished() def is_running(self): """Check if the process is currently running.""" return self.proc.state() == QProcess.Running def _match_data(self, value, expected): """Helper for wait_for to match a given value. The behavior of this method is slightly different depending on the types of the filtered values: - If expected is None, the filter always matches. - If the value is a string or bytes object and the expected value is too, the pattern is treated as a glob pattern (with only * active). - If the value is a string or bytes object and the expected value is a compiled regex, it is used for matching. - If the value is any other type, == is used. Return: A bool """ regex_type = type(re.compile('')) if expected is None: return True elif isinstance(expected, regex_type): return expected.search(value) elif isinstance(value, (bytes, str)): return utils.pattern_match(pattern=expected, value=value) else: return value == expected def _wait_for_existing(self, override_waited_for, after, **kwargs): """Check if there are any line in the history for wait_for. Return: either the found line or None. """ for line in self._data: matches = [] for key, expected in kwargs.items(): value = getattr(line, key) matches.append(self._match_data(value, expected)) if after is None: too_early = False else: too_early = ((line.timestamp, line.msecs) < (after.timestamp, after.msecs)) if (all(matches) and (not line.waited_for or override_waited_for) and not too_early): # If we waited for this line, chances are we don't mean the # same thing the next time we use wait_for and it matches # this line again. line.waited_for = True self._log("\n----> Already found {!r} in the log: {}".format( kwargs.get('message', 'line'), line)) return line return None def _wait_for_new(self, timeout, do_skip, **kwargs): """Wait for a log message which doesn't exist yet. Called via wait_for. """ __tracebackhide__ = lambda e: e.errisinstance(WaitForTimeout) message = kwargs.get('message', None) if message is not None: elided = quteutils.elide(repr(message), 100) self._log("\n----> Waiting for {} in the log".format(elided)) spy = QSignalSpy(self.new_data) elapsed_timer = QElapsedTimer() elapsed_timer.start() while True: # Skip if there are pending messages causing a skip self._maybe_skip() got_signal = spy.wait(timeout) if not got_signal or elapsed_timer.hasExpired(timeout): msg = "Timed out after {}ms waiting for {!r}.".format( timeout, kwargs) if do_skip: pytest.skip(msg) else: raise WaitForTimeout(msg) match = self._wait_for_match(spy, kwargs) if match is not None: if message is not None: self._log("----> found it") return match raise quteutils.Unreachable def _wait_for_match(self, spy, kwargs): """Try matching the kwargs with the given QSignalSpy.""" for args in spy: assert len(args) == 1 line = args[0] matches = [] for key, expected in kwargs.items(): value = getattr(line, key) matches.append(self._match_data(value, expected)) if all(matches): # If we waited for this line, chances are we don't mean the # same thing the next time we use wait_for and it matches # this line again. line.waited_for = True return line return None def _maybe_skip(self): """Can be overridden by subclasses to skip on certain log lines. We can't run pytest.skip directly while parsing the log, as that would lead to a pytest.skip.Exception error in a virtual Qt method, which means pytest-qt fails the test. Instead, we check for skip messages periodically in QuteProc._maybe_skip, and call _maybe_skip after every parsed message in wait_for (where it's most likely that new messages arrive). """ def wait_for(self, timeout=None, *, override_waited_for=False, do_skip=False, divisor=1, after=None, **kwargs): """Wait until a given value is found in the data. Keyword arguments to this function get interpreted as attributes of the searched data. Every given argument is treated as a pattern which the attribute has to match against. Args: timeout: How long to wait for the message. override_waited_for: If set, gets triggered by previous messages again. do_skip: If set, call pytest.skip on a timeout. divisor: A factor to decrease the timeout by. after: If it's an existing line, ensure it's after the given one. Return: The matched line. """ __tracebackhide__ = lambda e: e.errisinstance(WaitForTimeout) if timeout is None: if do_skip: timeout = 2000 elif utils.ON_CI: timeout = 15000 else: timeout = 5000 timeout //= divisor if not kwargs: raise TypeError("No keyword arguments given!") for key in kwargs: assert key in self.KEYS existing = self._wait_for_existing(override_waited_for, after, **kwargs) if existing is not None: return existing else: return self._wait_for_new(timeout=timeout, do_skip=do_skip, **kwargs) def ensure_not_logged(self, delay=500, **kwargs): """Make sure the data matching the given arguments is not logged. If nothing is found in the log, we wait for delay ms to make sure nothing arrives. """ __tracebackhide__ = lambda e: e.errisinstance(BlacklistedMessageError) try: line = self.wait_for(timeout=delay, override_waited_for=True, **kwargs) except WaitForTimeout: return else: raise BlacklistedMessageError(line) def wait_for_quit(self): """Wait until the process has quit.""" self.exit_expected = True with self._wait_signal(self.proc.finished, timeout=15000): pass assert not self.is_running()
class Editor(QPlainTextEdit): def __init__(self, parent, isReadOnly=False): super().__init__(parent) self.setReadOnly(isReadOnly) self.parent = parent self.font = QFont() self.size = 12 self.setUpdatesEnabled(True) self.worldList = wordList self.menuFont = QFont() self.menuFont.setFamily(editor["menuFont"]) self.menuFont.setPointSize(editor["menuFontSize"]) self.font.setFamily(editor["editorFont"]) self.font.setPointSize(editor["editorFontSize"]) self.focused = None self.text = None self.replace_tabs = 4 self.setWordWrapMode(4) self.setFont(self.font) self.l = 0 self.highlightingRules = [] self.extraSelections_ = [] self.currentlyVisibleBlocks: list = [] self.foldableBlocks = [] self.indexes = None self.setMouseTracking(True) self.completer = None self.setTabStopDistance(QFontMetricsF(self.font).width(" ") * 4) self.ignoreLength = None self.foldableLines: list = [] self.info_process = QProcess() self.setTabStopWidth(editor["TabWidth"]) self.createStandardContextMenu() self.setWordWrapMode(QTextOption.NoWrap) self.info_process.readyReadStandardOutput.connect( self.get_pydoc_output) def getTextCursor(self): textCursor = self.textCursor() textCursorPos = textCursor.position() return textCursor, textCursorPos def setFoldLines(self, lines: list): self.foldableLines = lines def visibleBlocks(self): self.currentlyVisibleBlocks = [] block: QTextBlock = self.firstVisibleBlock() height: int = self.height() blockTop: float = self.blockBoundingGeometry(block).translated( self.contentOffset()).top() blockBottom: float = self.blockBoundingGeometry( block).height() + blockTop while block.isValid(): if not blockBottom <= height: break if block.isVisible(): self.currentlyVisibleBlocks.append([blockTop, block]) block: QTextBlock = block.next() blockTop: float = blockBottom blockBottom: float = blockTop + self.blockBoundingRect( block).height() def getFoldableBlocks(self, block: QTextBlock) -> list: rootIndentation: int = len(block.text()) - len(block.text().strip()) nextBlock: QTextBlock = block.next() foldableBlocks: list = [] lastBlock = None while nextBlock.isValid(): strippedText = nextBlock.text().strip() currentBlockIndentation: int = len( nextBlock.text()) - len(strippedText) if currentBlockIndentation <= rootIndentation: if ( len(strippedText) != 0 ): # This evaluates to true when we we reach a block that is not in our scope to fold break if ( len(strippedText) != 0 ): # Last block with actual code in it, no white space contained lastBlock = nextBlock foldableBlocks.append(nextBlock) nextBlock = nextBlock.next() for index, foldableBlock in enumerate(foldableBlocks): if foldableBlocks[index] == lastBlock: return foldableBlocks[:index + 1] def paintEvent(self, e: QPaintEvent): self.visibleBlocks() super().paintEvent(e) def totalLines(self): return self.blockCount() def is_modified(self): return self.document().isModified() def iterate(self): it = self.document().begin() while it != self.document().end(): it = it.next() print(it.text()) def check(self): cursor = self.textCursor() block = cursor.block() expression = QRegExp("\\w{120,}\\b") if len(block.text()) > 120: index = 0 # expression.indexIn(block.text()) layout = block.layout() ranges = layout.formats() format = QTextCharFormat() format.setUnderlineStyle(QTextCharFormat.WaveUnderline) format.setUnderlineColor(QColor("#FF0000")) # while index >= 0: length = len(block.text()) # expression.matchedLength() formatrange = QTextLayout.FormatRange() formatrange.format = format formatrange.length = length formatrange.start = index ranges.append(formatrange) layout.setFormats(ranges) self.document().markContentsDirty(block.position(), block.length()) QToolTip.showText( QCursor.pos(), "Line too long: {} > 120".format(len(block.text()))) def newFile(self): """This is a wrapper for the function defined in Main: """ self.new_action = QAction("New") self.new_action.triggered.connect(self.parent.parent.newFile) def highlightCurrentLine(self): extraSelections = [] if not self.isReadOnly(): selection = QTextEdit.ExtraSelection() lineColor = QColor("#434343") selection.format.setBackground(lineColor) selection.format.setProperty(QTextFormat.FullWidthSelection, True) selection.cursor = self.textCursor() selection.cursor.clearSelection() extraSelections.append(selection) self.setExtraSelections(extraSelections) # self.check() return extraSelections def openFile(self): self.open_action = QAction("Open") self.open_action.triggered.connect(self.parent.parent.openFileFromMenu) def runFile(self): self.run_action = QAction("Run") self.run_action.triggered.connect(self.parent.parent.execute_file) def insert(self): self.insertAction = QAction("Bubblesort") self.insertAction.triggered.connect(self.bubblesort) def insertClass(self): self.insertClassAction = QAction("Create a class") self.insertClassAction.triggered.connect(self.createClass) def insertConcentricCircle(self): self.insertConcentricCircleAction = QAction("Concentric circle") self.insertConcentricCircleAction.triggered.connect( self.concentricCircle) def openBrowserFunction(self): self.openBrowserAct = QAction("Secret") self.openBrowserAct.triggered.connect(self.openBrowser) def openBrowser(self): widget = Browser("https://duckduckgo.com") word = "" index = self.tab.tabs.addTab(widget, "Info about: " + str(word)) self.tab.tabs.setCurrentIndex(index) def concentricCircle(self): self.insertPlainText("""import turtle turtle.penup() for i in range(1, 500, 50): turtle.right(90) # Face South turtle.forward(i) # Move one radius turtle.right(270) # Back to start heading turtle.pendown() # Put the pen back down turtle.circle(i) # Draw a circle turtle.penup() # Pen up while we go home turtle.home() """) def createClass(self): self.insertPlainText("""class Placeholder: def __init__(self): self.solve() def solve(self): pass""") def bubblesort(self): self.insertPlainText("""def bubbleSort(array: list): for i in range(len(array) - 1, 0, -1): for j in range(i): if array[j] > array[j+1]: temp = array[j] array[j] = array[j+1] array[j+1] = temp""") def textUnderCursor(self): textCursor = self.textCursor() pos: int = textCursor.position() textCursor.setPosition(pos - 1) textCursor.select(QTextCursor.WordUnderCursor) return textCursor.selectedText() def moveCursorPosBack(self): textCursor = self.textCursor() textCursorPos = textCursor.position() textCursor.setPosition(textCursorPos - 1) self.setTextCursor(textCursor) def mouseMoveEvent(self, QMouseEvent): # TODO: finish cursor: QCursor = QCursor(Qt.IBeamCursor) QApplication.setOverrideCursor(cursor) QApplication.changeOverrideCursor(cursor) super().mouseMoveEvent(QMouseEvent) def returnCursorToNormal(self): cursor = QCursor(Qt.ArrowCursor) QApplication.setOverrideCursor(cursor) QApplication.changeOverrideCursor(cursor) def getBlockUnderCursor(self, event: QMouseEvent) -> QTextBlock: height = self.fontMetrics().height() y = event.pos().y() for array in self.currentlyVisibleBlocks: if array[0] < y < height + array[0]: return array[1] emptyBlock: QTextBlock = QTextBlock() return emptyBlock def mousePressEvent(self, e: QMouseEvent): super().mousePressEvent(e) return # self.check() if QApplication.queryKeyboardModifiers() == Qt.ControlModifier: super().mousePressEvent(e) self.text = self.textUnderCursor() if self.text is not None: word = self.text # self.parent.parent.showBrowser(url, word) # tag = self.jump_to_def(word) # print(tag[0]) # if tag[0]: # print("INFO INFO INFO") # print(tag[1]) # self.parent.jumpToDef(tag[1]) # if self.check_func(self.textUnderCursor(), True): # extraSelections = self.highlightCurrentLine() # selection = QTextEdit.ExtraSelection() # selection.format.setFontUnderline(True) # selection.format.setUnderlineColor(QColor("#00d2ff")) # selection.format.setForeground(QColor("#00d2ff")) # selection.format.setProperty(QTextFormat.FullWidthSelection, True) # selection.cursor = self.textCursor() # selection.cursor.clearSelection() # selection.cursor.select(QTextCursor.WordUnderCursor) # extraSelections.append(selection) # self.setExtraSelections(extraSelections) # self.text = self.textUnderCursor() # # # cursor = QCursor(Qt.PointingHandCursor) # # # QApplication.setOverrideCursor(cursor) # # QApplication.changeOverrideCursor(cursor) # # else: # self.text = None else: super().mousePressEvent(e) QApplication.restoreOverrideCursor() # super().mousePressEvent(e) def check_func(self, word, clicked=False): funcs = [ "abs", "all", "any", "ascii", "bin", "bool", "breakpoint", "bytearray", "bytes", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "exec", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "print", "property", "range", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "vars", "zip", "__import__", ] word_array = list(word) for wo in word_array: if wo in ["{", "}", "'", '"', "[", "]", "(", ")"]: word_array.remove(wo) for w in funcs: if w == "".join(word_array): if clicked: if self.info_process.state() == 0: self.info_process.start("pydoc {}".format(word)) else: pass return True def jump_to_def(self, word): # from Hydra.test import getTags return False # lol = getTags() # tag = lol.get(word, None) # if tag: # return True, tag # else: # return False, "" def get_pydoc_output(self): output = self.info_process.readAllStandardOutput().data().decode() self.parent.parent.open_documentation(output, self.info_process.arguments()[0]) def insertFromMimeData(self, source: QMimeData) -> None: print(source) self.insertPlainText(source.text()) print(self.textCursor().blockNumber()) def contextMenuEvent(self, event): menu = QMenu() """Initializing actions""" self.newFile() self.openFile() self.runFile() self.insert() self.insertClass() self.insertConcentricCircle() self.openBrowserFunction() menu.addAction(self.new_action) menu.addAction(self.open_action) menu.addAction(self.run_action) insertMenu = menu.addMenu("Insert") insertMenu.addAction(self.insertAction) insertMenu.addAction(self.insertClassAction) insertMenu.addAction(self.insertConcentricCircleAction) insertMenu.addAction(self.openBrowserAct) menu.setFont(self.menuFont) insertMenu.setFont(self.menuFont) menu.exec(event.globalPos()) del menu def mouseReleaseEvent(self, e): # self.check() super().mouseReleaseEvent(e) def openBrowser(self): widget = Browser("https://duckduckgo.com") word = "" index = self.parent.parent.tab.tabs.addTab(widget, "ylesanne2.py") self.parent.parent.tab.tabs.setCurrentIndex(index) def onlySpaces(self, word: str) -> bool: for char in word: if char != " ": return False return True def keyPressEvent(self, e): textCursor = self.textCursor() key = e.key() if (e.modifiers() == Qt.ControlModifier and key == 16777217): # that key code stands for tab self.parent.parent.switchTabs() isSearch = e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_F isDeleteLine = (e.modifiers() == Qt.ControlModifier and e.key() == 16777219 ) # This is for MacOS (CMD+Delete) if isSearch: try: currentWidget = self.parent currentFile = currentWidget.fileName currentEditor = currentWidget.editor textCursor = currentEditor.textCursor() textCursorPos = textCursor.position() if currentWidget is not None: text, okPressed = QInputDialog.getText( self, "Find", "Find what: ") if okPressed: if text == "": text = " " self.not_found = NoMatch(text) self.searchtext = text try: with open(currentFile, "r") as file: contents = file.read() self.indexes = list(find_all(contents, text)) if len(self.indexes) == 0: self.not_found = NoMatch(text) except FileNotFoundError as E: print(E, " on line 245 in the file Editor.py") except (AttributeError, UnboundLocalError) as E: print(E, " on line 228 in the file Editor.py") if isDeleteLine and platform.system( ) == "Darwin": # Check if the os is MacOS textCursor.select(QTextCursor.LineUnderCursor) textCursor.removeSelectedText() if key == Qt.Key_QuoteDbl: self.insertPlainText('"') self.moveCursorPosBack() if (e.modifiers() == Qt.ControlModifier and e.key() == 61): # Press Ctrl+Equal key to make font bigger self.font.setPointSize(self.size + 1) self.font.setFamily(editor["editorFont"]) self.setFont(self.font) self.size += 1 if e.modifiers() == Qt.ControlModifier and e.key() == 16777217: return if (e.modifiers() == Qt.ControlModifier and e.key() == 45): # Press Ctrl+Minus key to make font smaller self.font.setPointSize(self.size - 1) self.font.setFamily(editor["editorFont"]) self.setFont(self.font) self.size -= 1 if key == Qt.Key_F3: try: index = self.indexes[0 + self.l] currentWidget = self.parent currentFile = currentWidget.fileName currentEditor = currentWidget.editor textCursor.setPosition(index) textCursor.movePosition(textCursor.Right, textCursor.KeepAnchor, len(self.searchtext)) currentEditor.setTextCursor(textCursor) self.l += 1 except IndexError: self.l = 0 if key == 16777220: currentText: str = self.textCursor().block().text() space: str = " " if self.textUnderCursor().endswith(":"): spaces: int = 0 for index, i in enumerate(currentText): if i != " ": if index % 4 == 0: super().keyPressEvent(e) self.insertPlainText(space * (index + 4)) return if spaces == 0: super().keyPressEvent(e) self.insertPlainText(space * 4) return else: for index, i in enumerate(currentText): if i != " ": if index % 4 == 0 and index != 0: super().keyPressEvent(e) self.insertPlainText(space * index) return break else: if len(currentText) % 4 == 0 and self.onlySpaces( currentText): super().keyPressEvent(e) self.insertPlainText(space * len(currentText)) return super().keyPressEvent(e)