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
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:]
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
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])