Ejemplo n.º 1
0
    def reset(self, text, colours):
        """
        Reset the parser to analyze the supplied raw text.

        :param text: raw text to process.
        :param colours: colour tuple to initialise the colour map.
        """
        self._state = _DotDict()
        self._state.text = text
        # Force colours to be mutable (in case a tuple was passed in).
        self._state.attributes = [x for x in colours] if colours else None
Ejemplo n.º 2
0
    def reset(self, text, colours):
        """
        Reset the parser to analyze the supplied raw text.

        :param text: raw text to process.
        :param colours: colour tuple to initialise the colour map.
        """
        super(AnsiTerminalParser, self).reset(text, colours)
        state = _DotDict()
        state.attributes = [x for x in self._attributes
                            ] if self._attributes else [None, None, None]
        state.offset = 0
        state.last_offset = 0
        state.cursor = 0
        state.text = self._raw_text

        def _handle_escape(st):
            match = self._colour_sequence.match(str(st.text))
            if match is None:
                # Not a CSI sequence... Check for some other options.
                match = self._os_cmd.match(str(st.text))
                if match:
                    # OS command - just swallow it.
                    return len(match.group(1))
                elif st.text[1] == "M":
                    # Reverse Index - i.e. move up/scroll
                    self._result.append(
                        (st.last_offset, Parser.MOVE_RELATIVE, (0, -1)))
                    return 2

                # Unknown escape - guess how many characters to ignore - most likely just the next char...
                logger.debug("Ignoring: %s", st.text[0:2])
                return 2 if st.text[1] != "(" else 3
            else:
                # CSI sequence - look for the various options...
                if match.group(3) == "m":
                    # We have found a SGR escape sequence ( CSI ... m ).  These have zero or more
                    # embedded arguments, so create a simple FSM to process the parameter stream.
                    in_set_mode = False
                    in_index_mode = False
                    in_rgb_mode = False
                    skip_size = 0
                    attribute_index = 0
                    last_attributes = tuple(st.attributes)
                    for parameter in match.group(2).split(";"):
                        try:
                            parameter = int(parameter)
                        except ValueError:
                            parameter = 0
                        if in_set_mode:
                            # We are processing a set fore/background colour code
                            if parameter == 5:
                                in_index_mode = True
                            elif parameter == 2:
                                in_rgb_mode = True
                                skip_size = 3
                            else:
                                logger.info(
                                    ("Unexpected colour setting", parameter))
                                break
                            in_set_mode = False
                        elif in_index_mode:
                            # We are processing a 5;n sequence for colour indeces
                            st.attributes[attribute_index] = parameter
                            in_index_mode = False
                        elif in_rgb_mode:
                            # We are processing a 2;r;g;b sequence for RGB colours - just ignore.
                            skip_size -= 1
                            if skip_size <= 0:
                                in_rgb_mode = False
                        else:
                            # top-level stream processing
                            if parameter == 0:
                                # Reset
                                st.attributes = [
                                    constants.COLOUR_WHITE, constants.A_NORMAL,
                                    constants.COLOUR_BLACK
                                ]
                            elif parameter == 1:
                                # Bold
                                st.attributes[1] = constants.A_BOLD
                            elif parameter in (2, 22):
                                # Faint/normal - faint not supported so treat as normal
                                st.attributes[1] = constants.A_NORMAL
                            elif parameter == 7:
                                # Inverse
                                st.attributes[1] = constants.A_REVERSE
                            elif parameter == 27:
                                # Inverse off - assume that means normal
                                st.attributes[1] = constants.A_NORMAL
                            elif parameter in range(30, 38):
                                # Standard foreground colours
                                st.attributes[0] = parameter - 30
                            elif parameter in range(40, 48):
                                # Standard background colours
                                st.attributes[2] = parameter - 40
                            elif parameter == 38:
                                # Set foreground colour - next parameter is either 5 (index) or 2 (RGB color)
                                in_set_mode = True
                                attribute_index = 0
                            elif parameter == 48:
                                # Set background colour - next parameter is either 5 (index) or 2 (RGB color)
                                in_set_mode = True
                                attribute_index = 2
                            elif parameter in range(90, 98):
                                # Bright foreground colours
                                st.attributes[0] = parameter - 82
                            elif parameter in range(100, 108):
                                # Bright background colours
                                st.attributes[2] = parameter - 92
                            else:
                                logger.debug("Ignoring parameter: %s",
                                             parameter)
                    new_attributes = tuple(st.attributes)
                    if last_attributes != new_attributes:
                        self._result.append(
                            (st.last_offset, Parser.CHANGE_COLOURS,
                             new_attributes))
                elif match.group(3) == "K":
                    # This is a line delete sequence.  Parameter defines which parts to delete.
                    param = match.group(2)
                    if param in ("", "0"):
                        # Delete to end of line
                        self._result.append(
                            (state.last_offset, Parser.DELETE_LINE, 0))
                    elif param == "1":
                        # Delete from start of line
                        self._result.append(
                            (state.last_offset, Parser.DELETE_LINE, 1))
                    elif param == "2":
                        # Delete whole line
                        self._result.append(
                            (state.last_offset, Parser.DELETE_LINE, 2))
                elif match.group(3) == "P":
                    # This is a character delete sequence.  Parameter defines how many to delete.
                    param = 1 if match.group(2) == "" else int(match.group(2))
                    self._result.append(
                        (state.last_offset, Parser.DELETE_CHARS, param))
                elif match.group(3) == "A":
                    # Move cursor up.  Parameter defines how far to move..
                    param = 1 if match.group(2) == "" else int(match.group(2))
                    self._result.append(
                        (state.last_offset, Parser.MOVE_RELATIVE, (0, -param)))
                elif match.group(3) == "B":
                    # Move cursor down.  Parameter defines how far to move..
                    param = 1 if match.group(2) == "" else int(match.group(2))
                    self._result.append(
                        (state.last_offset, Parser.MOVE_RELATIVE, (0, param)))
                elif match.group(3) == "C":
                    # Move cursor forwards.  Parameter defines how far to move..
                    param = 1 if match.group(2) == "" else int(match.group(2))
                    self._result.append(
                        (state.last_offset, Parser.MOVE_RELATIVE, (param, 0)))
                elif match.group(3) == "D":
                    # Move cursor backwards.  Parameter defines how far to move..
                    param = 1 if match.group(2) == "" else int(match.group(2))
                    self._result.append(
                        (state.last_offset, Parser.MOVE_RELATIVE, (-param, 0)))
                elif match.group(3) == "H":
                    # Move cursor to specified position.
                    x, y = 0, 0
                    params = match.group(2).split(";")
                    y = int(params[0]) - 1 if params[0] != "" else 0
                    if len(params) > 1:
                        x = int(params[1]) - 1 if params[1] != "" else 0
                    self._result.append(
                        (state.last_offset, Parser.MOVE_ABSOLUTE, (x, y)))
                elif match.group(3) == "h" and match.group(2) == "?25":
                    # Various DEC private mode commands - look for cursor visibility, ignore others.
                    self._result.append(
                        (state.last_offset, Parser.SHOW_CURSOR, True))
                elif match.group(3) == "l" and match.group(2) == "?25":
                    # Various DEC private mode commands - look for cursor visibility, ignore others.
                    self._result.append(
                        (state.last_offset, Parser.SHOW_CURSOR, False))
                elif match.group(3) == "J" and match.group(2) == "2":
                    # Clear the screen.
                    self._result.append(
                        (state.last_offset, Parser.CLEAR_SCREEN, None))
                else:
                    logger.debug("Ignoring control: %s", match.group(1))
                return len(match.group(1))

        while len(state.text) > 0:
            char = ord(state.text[0])
            new_offset = 1
            if char > 31:
                self._result.append(
                    (state.last_offset, Parser.DISPLAY_TEXT, state.text[0]))
                state.last_offset = state.offset + 1
            elif char == 8:
                # Back space
                self._result.append(
                    (state.last_offset, Parser.MOVE_RELATIVE, (-1, 0)))
            elif char == 9:
                # Tab
                self._result.append((state.last_offset, Parser.NEXT_TAB, None))
            elif char == 13:
                # Carriage return
                self._result.append(
                    (state.last_offset, Parser.MOVE_ABSOLUTE, (0, None)))
            elif char == 27:
                new_offset = _handle_escape(state)
            else:
                logger.debug("Ignoring character: %d", char)
            state.offset += new_offset
            state.text = state.text[new_offset:]
Ejemplo n.º 3
0
    def fix(self, start_x, start_y, max_width, max_height):
        """
        Fix the location and size of all the Widgets in this Layout.

        :param start_x: The start column for the Layout.
        :param start_y: The start line for the Layout.
        :param max_width: Max width to allow this layout.
        :param max_height: Max height to allow this layout.
        :returns: The next line to be used for any further Layouts.
        """
        x = start_x
        width = max_width
        y = w = 0
        max_y = start_y
        string_len = wcswidth if self._frame.canvas.unicode_aware else len
        dimensions = []
        for i, column in enumerate(self._columns):
            # For each column determine if we need a tab offset for labels.
            # Only allow labels to take up 1/3 of the column.
            if len(column) > 0:
                offset = max([0 if c.label is None else string_len(c.label) + 1 for c in column])
            else:
                offset = 0
            offset = int(min(offset,
                         width * self._column_sizes[i] // 3))

            # Start tracking new column
            dimensions.append(_DotDict())
            dimensions[i].parameters = []
            dimensions[i].offset = offset

            # Do first pass to figure out the gaps for widgets that want to fill remaining space.
            fill_layout = None
            fill_column = None
            y = start_y
            w = int(width * self._column_sizes[i])
            for widget in column:
                h = widget.required_height(offset, w)
                if h == Widget.FILL_FRAME:
                    if fill_layout is None and fill_column is None:
                        dimensions[i].parameters.append([widget, x, w, h])
                        fill_layout = widget
                    else:
                        # Two filling widgets in one column - this is a bug.
                        raise Highlander("Too many Widgets filling Layout")
                elif h == Widget.FILL_COLUMN:
                    if fill_layout is None and fill_column is None:
                        dimensions[i].parameters.append([widget, x, w, h])
                        fill_column = widget
                    else:
                        # Two filling widgets in one column - this is a bug.
                        raise Highlander("Too many Widgets filling Layout")
                else:
                    dimensions[i].parameters.append([widget, x, w, h])
                    y += h

            # Note space used by this column.
            dimensions[i].height = y

            # Update tracking variables fpr the next column.
            max_y = max(max_y, y)
            x += w

        # Finally check whether the Layout is allowed to expand.
        if self.fill_frame:
            max_y = max(max_y, start_y + max_height)

        # Now apply calculated sizes, updating any widgets that need to fill space.
        for column in dimensions:
            y = start_y
            for widget, x, w, h in column.parameters:
                if h == Widget.FILL_FRAME:
                    h = max(1, start_y + max_height - column.height)
                elif h == Widget.FILL_COLUMN:
                    h = max_y - column.height
                widget.set_layout(x, y, column.offset, w, h)
                y += h

        return max_y
Ejemplo n.º 4
0
    def reset(self, text, colours):
        """
        Reset the parser to analyze the supplied raw text.

        :param text: raw text to process.
        :param colours: colour tuple to initialise the colour map.
        """
        super(AnsiTerminalParser, self).reset(text, colours)
        state = _DotDict()
        state.result = []
        state.attributes = [x for x in self._attributes] if self._attributes else [None, None, None]
        state.offset = 0
        state.last_offset = 0
        state.cursor = 0
        state.text = self._raw_text

        def _handle_escape(st):
            match = self._colour_sequence.match(str(st.text))
            if match is None:
                # Escape - ignore next char as a minimal way to handle many sequences
                return 2
            else:
                if match.group(3) == "m":
                    # We have found a SGR escape sequence ( CSI ... m ).  These have zero or more
                    # embedded arguments, so create a simple FSM to process the parameter stream.
                    in_set_mode = False
                    in_index_mode = False
                    in_rgb_mode = False
                    skip_size = 0
                    attribute_index = 0
                    for parameter in match.group(2).split(";"):
                        try:
                            parameter = int(parameter)
                        except ValueError:
                            parameter = 0
                        if in_set_mode:
                            # We are processing a set fore/background colour code
                            if parameter == 5:
                                in_index_mode = True
                            elif parameter == 2:
                                in_rgb_mode = True
                                skip_size = 3
                            else:
                                logger.info(("Unexpected colour setting", parameter))
                            in_set_mode = False
                        elif in_index_mode:
                            # We are processing a 5;n sequence for colour indeces
                            st.attributes[attribute_index] = parameter
                            in_index_mode = False
                        elif in_rgb_mode:
                            # We are processing a 2;r;g;b sequence for RGB colours - just ignore.
                            skip_size -= 1
                            if skip_size <= 0:
                                in_rgb_mode = False
                        else:
                            # top-level stream processing
                            if parameter == 0:
                                # Reset
                                st.attributes = [constants.COLOUR_WHITE,
                                                 constants.A_NORMAL,
                                                 constants.COLOUR_BLACK]
                            elif parameter == 1:
                                # Bold
                                st.attributes[1] = constants.A_BOLD
                            elif parameter in (2, 22):
                                # Faint/normal - faint not supported so treat as normal
                                st.attributes[1] = constants.A_NORMAL
                            elif parameter == 7:
                                # Inverse
                                st.attributes[1] = constants.A_REVERSE
                            elif parameter == 27:
                                # Inverse off - assume that means normal
                                st.attributes[1] = constants.A_NORMAL
                            elif parameter in range(30, 38):
                                # Standard foreground colours
                                st.attributes[0] = parameter - 30
                            elif parameter in range(40, 48):
                                # Standard background colours
                                st.attributes[2] = parameter - 40
                            elif parameter == 38:
                                # Set foreground colour - next parameter is either 5 (index) or 2 (RGB color)
                                in_set_mode = True
                                attribute_index = 0
                            elif parameter == 48:
                                # Set background colour - next parameter is either 5 (index) or 2 (RGB color)
                                in_set_mode = True
                                attribute_index = 2
                            else:
                                logger.debug("Ignoring parameter: %s", parameter)
                elif match.group(3) == "K":
                    # This is a line delete sequence.  Parameter defines which parts to delete.
                    param = match.group(2)
                    if param in ("", "0"):
                        st.result = st.result[:st.cursor]
                    elif param == "1":
                        st.result = [[" ", tuple(st.attributes), st.offset] for _ in range(st.cursor)] + \
                                     st.result[st.cursor:]
                    elif param == "2":
                        st.result = [[" ", tuple(st.attributes), st.offset] for _ in range(st.cursor)]
                elif match.group(3) == "P":
                    # This is a character delete sequence.  Parameter defines how many to delete.
                    param = 1 if match.group(2) == "" else int(match.group(2))
                    st.result = st.result[:st.cursor] + st.result[st.cursor + param:]
                elif match.group(3) == "C":
                    # Move cursor forwards.  Parameter defines how far to move..
                    param = 1 if match.group(2) == "" else int(match.group(2))
                    st.cursor += param
                elif match.group(3) == "D":
                    # Move cursor backwards.  Parameter defines how far to move..
                    param = 1 if match.group(2) == "" else int(match.group(2))
                    st.cursor -= param
                else:
                    logger.debug("Ignoring control: %s", match.group(3))
                return len(match.group(1))

        while len(state.text) > 0:
            char = ord(state.text[0])
            new_offset = 1
            if char > 31:
                state.result[state.cursor:state.cursor + 1] = [
                    [state.text[0], tuple(state.attributes), state.last_offset]]
                state.cursor += 1
                state.last_offset = state.offset + 1
            elif char == 8:
                # Back space
                state.cursor = max(state.cursor - 1, 0)
            elif char == 13:
                # Carriage return
                state.cursor = 0
            elif char == 27:
                new_offset = _handle_escape(state)
            else:
                logger.debug("Ignoring character: %d", char)
            state.offset += new_offset
            state.text = state.text[new_offset:]

        self._result = state.result
        self._cursor = len(state.result) - state.cursor
        if state.last_offset != state.offset:
            self._result.append([None, tuple(state.attributes), state.last_offset])