Esempio n. 1
0
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)
Esempio n. 2
0
                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_()
Esempio n. 3
0
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()
Esempio n. 4
0
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)