def subseg(self, text, start, end): """ Return a "sub-segment" list containing segment structures that make up a portion of this segment. A list is returned to handle cases where wide characters need to be replaced with a space character at either edge so two or three segments will be returned. """ if start < 0: start = 0 if end > self.sc: end = self.sc if start >= end: return [] # completely gone if self.text: # use text stored in segment (self.text) spos, epos, pad_left, pad_right = calc_trim_text( self.text, 0, len(self.text), start, end) return [(end - start, self.offs, bytes().ljust(pad_left) + self.text[spos:epos] + bytes().ljust(pad_right))] elif self.end: # use text passed as parameter (text) spos, epos, pad_left, pad_right = calc_trim_text( text, self.offs, self.end, start, end) l = [] if pad_left: l.append((1, spos - 1)) l.append((end - start - pad_left - pad_right, spos, epos)) if pad_right: l.append((1, epos)) return l else: # simple padding adjustment return [(end - start, self.offs)]
def subseg(self, text, start, end): """ Return a "sub-segment" list containing segment structures that make up a portion of this segment. A list is returned to handle cases where wide characters need to be replaced with a space character at either edge so two or three segments will be returned. """ if start < 0: start = 0 if end > self.sc: end = self.sc if start >= end: return [] # completely gone if self.text: # use text stored in segment (self.text) spos, epos, pad_left, pad_right = calc_trim_text( self.text, 0, len(self.text), start, end ) return [ (end-start, self.offs, bytes().ljust(pad_left) + self.text[spos:epos] + bytes().ljust(pad_right)) ] elif self.end: # use text passed as parameter (text) spos, epos, pad_left, pad_right = calc_trim_text( text, self.offs, self.end, start, end ) l = [] if pad_left: l.append((1,spos-1)) l.append((end-start-pad_left-pad_right, spos, epos)) if pad_right: l.append((1,epos)) return l else: # simple padding adjustment return [(end-start,self.offs)]
def apply_target_encoding( s ): """ Return (encoded byte string, character set rle). """ if _use_dec_special and type(s) == unicode: # first convert drawing characters try: s = s.translate( escape.DEC_SPECIAL_CHARMAP ) except NotImplementedError: # python < 2.4 needs to do this the hard way.. for c, alt in zip(escape.DEC_SPECIAL_CHARS, escape.ALT_DEC_SPECIAL_CHARS): s = s.replace( c, escape.SO+alt+escape.SI ) if type(s) == unicode: s = s.replace(escape.SI+escape.SO, u"") # remove redundant shifts s = codecs.encode(s, _target_encoding, 'replace') assert isinstance(s, bytes) SO = escape.SO.encode('ascii') SI = escape.SI.encode('ascii') sis = s.split(SO) assert isinstance(sis[0], bytes) sis0 = sis[0].replace(SI, bytes()) sout = [] cout = [] if sis0: sout.append( sis0 ) cout.append( (None,len(sis0)) ) if len(sis)==1: return sis0, cout for sn in sis[1:]: assert isinstance(sn, bytes) assert isinstance(SI, bytes) sl = sn.split(SI, 1) if len(sl) == 1: sin = sl[0] assert isinstance(sin, bytes) sout.append(sin) rle_append_modify(cout, (escape.DEC_TAG.encode('ascii'), len(sin))) continue sin, son = sl son = son.replace(SI, bytes()) if sin: sout.append(sin) rle_append_modify(cout, (escape.DEC_TAG, len(sin))) if son: sout.append(son) rle_append_modify(cout, (None, len(son))) outstr = bytes().join(sout) return outstr, cout
def apply_target_encoding(s): """ Return (encoded byte string, character set rle). """ if _use_dec_special and type(s) == str: # first convert drawing characters try: s = s.translate(escape.DEC_SPECIAL_CHARMAP) except NotImplementedError: # python < 2.4 needs to do this the hard way.. for c, alt in zip(escape.DEC_SPECIAL_CHARS, escape.ALT_DEC_SPECIAL_CHARS): s = s.replace(c, escape.SO + alt + escape.SI) if type(s) == str: s = s.replace(escape.SI + escape.SO, "") # remove redundant shifts s = codecs.encode(s, _target_encoding, 'replace') assert isinstance(s, bytes) SO = escape.SO.encode('ascii') SI = escape.SI.encode('ascii') sis = s.split(SO) assert isinstance(sis[0], bytes) sis0 = sis[0].replace(SI, bytes()) sout = [] cout = [] if sis0: sout.append(sis0) cout.append((None, len(sis0))) if len(sis) == 1: return sis0, cout for sn in sis[1:]: assert isinstance(sn, bytes) assert isinstance(SI, bytes) sl = sn.split(SI, 1) if len(sl) == 1: sin = sl[0] assert isinstance(sin, bytes) sout.append(sin) rle_append_modify(cout, (escape.DEC_TAG.encode('ascii'), len(sin))) continue sin, son = sl son = son.replace(SI, bytes()) if sin: sout.append(sin) rle_append_modify(cout, (escape.DEC_TAG, len(sin))) if son: sout.append(son) rle_append_modify(cout, (None, len(son))) outstr = bytes().join(sout) return outstr, cout
def parse_escape(self, char): if self.parsestate == 1: # within CSI if char in CSI_COMMANDS.keys(): self.parse_csi(char) self.parsestate = 0 elif char in B('0123456789;') or (not self.escbuf and char == B('?')): self.escbuf += char return elif self.parsestate == 0 and char == B(']'): # start of OSC self.escbuf = bytes() self.parsestate = 2 return elif self.parsestate == 2 and char == B("\x07"): # end of OSC self.parse_osc(self.escbuf.lstrip(B('0'))) elif self.parsestate == 2 and self.escbuf[-1:] + char == B(ESC + '\\'): # end of OSC self.parse_osc(self.escbuf[:-1].lstrip(B('0'))) elif self.parsestate == 2 and self.escbuf.startswith(B('P')) and \ len(self.escbuf) == 8: # set palette (ESC]Pnrrggbb) pass elif self.parsestate == 2 and not self.escbuf and char == B('R'): # reset palette pass elif self.parsestate == 2: self.escbuf += char return elif self.parsestate == 0 and char == B('['): # start of CSI self.escbuf = bytes() self.parsestate = 1 return elif self.parsestate == 0 and char in (B('%'), B('#'), B('('), B(')')): # non-CSI sequence self.escbuf = char self.parsestate = 3 return elif self.parsestate == 3: self.parse_noncsi(char, self.escbuf) elif char in (B('c'), B('D'), B('E'), B('H'), B('M'), B('Z'), B('7'), B('8'), B('>'), B('=')): self.parse_noncsi(char) self.leave_escape()
def _text_content(self): """ Return the text content of the canvas as a list of strings, one for each row. """ return [bytes().join([text for (attr, cs, text) in row]) for row in self.content()]
def reset(self): """ Reset the terminal. """ self.escbuf = bytes() self.within_escape = False self.parsestate = 0 self.attrspec = None self.charset = TermCharset() self.saved_cursor = None self.saved_attrs = None self.is_rotten_cursor = False self.reset_scroll() self.init_tabstops() # terminal modes self.modes.reset() # initialize self.term self.clear()
def __init__(self, text=None, attr=None, cs=None, cursor=None, maxcol=None, check_width=True): """ text -- list of strings, one for each line attr -- list of run length encoded attributes for text cs -- list of run length encoded character set for text cursor -- (x,y) of cursor or None maxcol -- screen columns taken by this canvas check_width -- check and fix width of all lines in text """ Canvas.__init__(self) if text == None: text = [] if check_width: widths = [] for t in text: if type(t) != bytes: raise CanvasError("Canvas text must be plain strings encoded in the screen's encoding", repr(text)) widths.append( calc_width( t, 0, len(t)) ) else: assert type(maxcol) == int widths = [maxcol] * len(text) if maxcol is None: if widths: # find maxcol ourselves maxcol = max(widths) else: maxcol = 0 if attr == None: attr = [[] for x in range(len(text))] if cs == None: cs = [[] for x in range(len(text))] # pad text and attr to maxcol for i in range(len(text)): w = widths[i] if w > maxcol: raise CanvasError("Canvas text is wider than the maxcol specified \n%r\n%r\n%r"%(maxcol,widths,text)) if w < maxcol: text[i] = text[i] + bytes().rjust(maxcol-w) a_gap = len(text[i]) - rle_len( attr[i] ) if a_gap < 0: raise CanvasError("Attribute extends beyond text \n%r\n%r" % (text[i],attr[i]) ) if a_gap: rle_append_modify( attr[i], (None, a_gap)) cs_gap = len(text[i]) - rle_len( cs[i] ) if cs_gap < 0: raise CanvasError("Character Set extends beyond text \n%r\n%r" % (text[i],cs[i]) ) if cs_gap: rle_append_modify( cs[i], (None, cs_gap)) self._attr = attr self._cs = cs self.cursor = cursor self._text = text self._maxcol = maxcol
def parse_escape(self, char): if self.parsestate == 1: # within CSI if char in CSI_COMMANDS.keys(): self.parse_csi(char) self.parsestate = 0 elif char in B("0123456789;") or (not self.escbuf and char == B("?")): self.escbuf += char return elif self.parsestate == 0 and char == B("]"): # start of OSC self.escbuf = bytes() self.parsestate = 2 return elif self.parsestate == 2 and char == B("\x07"): # end of OSC self.parse_osc(self.escbuf.lstrip(B("0"))) elif self.parsestate == 2 and self.escbuf[-1:] + char == B(ESC + "\\"): # end of OSC self.parse_osc(self.escbuf[:-1].lstrip(B("0"))) elif self.parsestate == 2 and self.escbuf.startswith(B("P")) and len(self.escbuf) == 8: # set palette (ESC]Pnrrggbb) pass elif self.parsestate == 2 and not self.escbuf and char == B("R"): # reset palette pass elif self.parsestate == 2: self.escbuf += char return elif self.parsestate == 0 and char == B("["): # start of CSI self.escbuf = bytes() self.parsestate = 1 return elif self.parsestate == 0 and char in (B("%"), B("#"), B("("), B(")")): # non-CSI sequence self.escbuf = char self.parsestate = 3 return elif self.parsestate == 3: self.parse_noncsi(char, self.escbuf) elif char in (B("c"), B("D"), B("E"), B("H"), B("M"), B("Z"), B("7"), B("8"), B(">"), B("=")): self.parse_noncsi(char) self.leave_escape()
def trim_text_attr_cs(text, attr, cs, start_col, end_col): """ Return ( trimmed text, trimmed attr, trimmed cs ). """ spos, epos, pad_left, pad_right = calc_trim_text(text, 0, len(text), start_col, end_col) attrtr = rle_subseg(attr, spos, epos) cstr = rle_subseg(cs, spos, epos) if pad_left: al = rle_get_at(attr, spos - 1) rle_append_beginning_modify(attrtr, (al, 1)) rle_append_beginning_modify(cstr, (None, 1)) if pad_right: al = rle_get_at(attr, epos) rle_append_modify(attrtr, (al, 1)) rle_append_modify(cstr, (None, 1)) return (bytes().rjust(pad_left) + text[spos:epos] + bytes().rjust(pad_right), attrtr, cstr)
def trim_text_attr_cs( text, attr, cs, start_col, end_col ): """ Return ( trimmed text, trimmed attr, trimmed cs ). """ spos, epos, pad_left, pad_right = calc_trim_text( text, 0, len(text), start_col, end_col ) attrtr = rle_subseg( attr, spos, epos ) cstr = rle_subseg( cs, spos, epos ) if pad_left: al = rle_get_at( attr, spos-1 ) rle_append_beginning_modify( attrtr, (al, 1) ) rle_append_beginning_modify( cstr, (None, 1) ) if pad_right: al = rle_get_at( attr, epos ) rle_append_modify( attrtr, (al, 1) ) rle_append_modify( cstr, (None, 1) ) return (bytes().rjust(pad_left) + text[spos:epos] + bytes().rjust(pad_right), attrtr, cstr)
def content(self, trim_left, trim_top, cols, rows, attr): """ return (cols, rows) of spaces with default attributes. """ def_attr = None if attr and None in attr: def_attr = attr[None] line = [(def_attr, None, bytes().rjust(cols))] for i in range(rows): yield line
def __init__(self, width, height, widget): Canvas.__init__(self) self.width, self.height = width, height self.widget = widget self.modes = widget.term_modes self.scrollback_buffer = TermScroller() self.scrolling_up = 0 self.utf8_eat_bytes = None self.utf8_buffer = bytes() self.coords["cursor"] = (0, 0, None) self.reset()
def process_char(self, char): """ Process a single character (single- and multi-byte). char -- a byte string """ x, y = self.term_cursor if isinstance(char, int): char = chr(char) dc = self.modes.display_ctrl if char == B("\x1b") and self.parsestate != 2: # escape self.within_escape = True elif not dc and char == B("\x0d"): # carriage return self.carriage_return() elif not dc and char == B("\x0f"): # activate G0 self.charset.activate(0) elif not dc and char == B("\x0e"): # activate G1 self.charset.activate(1) elif not dc and char in B("\x0a\x0b\x0c"): # line feed self.linefeed() if self.modes.lfnl: self.carriage_return() elif not dc and char == B("\x09"): # char tab self.tab() elif not dc and char == B("\x08"): # backspace if x > 0: self.set_term_cursor(x - 1, y) elif not dc and char == B("\x07") and self.parsestate != 2: # beep # we need to check if we're in parsestate 2, as an OSC can be # terminated by the BEL character! self.widget.beep() elif not dc and char in B("\x18\x1a"): # CAN/SUB self.leave_escape() elif not dc and char == B("\x7f"): # DEL pass # this is ignored elif self.within_escape: self.parse_escape(char) elif not dc and char == B("\x9b"): # CSI (equivalent to "ESC [") self.within_escape = True self.escbuf = bytes() self.parsestate = 1 else: self.push_cursor(char)
def apply_text_layout(text, attr, ls, maxcol): t = [] a = [] c = [] class AttrWalk: pass aw = AttrWalk aw.k = 0 # counter for moving through elements of a aw.off = 0 # current offset into text of attr[ak] def arange( start_offs, end_offs ): """Return an attribute list for the range of text specified.""" if start_offs < aw.off: aw.k = 0 aw.off = 0 o = [] while aw.off < end_offs: if len(attr)<=aw.k: # run out of attributes o.append((None,end_offs-max(start_offs,aw.off))) break at,run = attr[aw.k] if aw.off+run <= start_offs: # move forward through attr to find start_offs aw.k += 1 aw.off += run continue if end_offs <= aw.off+run: o.append((at, end_offs-max(start_offs,aw.off))) break o.append((at, aw.off+run-max(start_offs, aw.off))) aw.k += 1 aw.off += run return o for line_layout in ls: # trim the line to fit within maxcol line_layout = trim_line( line_layout, text, 0, maxcol ) line = [] linea = [] linec = [] def attrrange( start_offs, end_offs, destw ): """ Add attributes based on attributes between start_offs and end_offs. """ if start_offs == end_offs: [(at,run)] = arange(start_offs,end_offs) rle_append_modify( linea, ( at, destw )) return if destw == end_offs-start_offs: for at, run in arange(start_offs,end_offs): rle_append_modify( linea, ( at, run )) return # encoded version has different width o = start_offs for at, run in arange(start_offs, end_offs): if o+run == end_offs: rle_append_modify( linea, ( at, destw )) return tseg = text[o:o+run] tseg, cs = apply_target_encoding( tseg ) segw = rle_len(cs) rle_append_modify( linea, ( at, segw )) o += run destw -= segw for seg in line_layout: #if seg is None: assert 0, ls s = LayoutSegment(seg) if s.end: tseg, cs = apply_target_encoding( text[s.offs:s.end]) line.append(tseg) attrrange(s.offs, s.end, rle_len(cs)) rle_join_modify( linec, cs ) elif s.text: tseg, cs = apply_target_encoding( s.text ) line.append(tseg) attrrange( s.offs, s.offs, len(tseg) ) rle_join_modify( linec, cs ) elif s.offs: if s.sc: line.append(bytes().rjust(s.sc)) attrrange( s.offs, s.offs, s.sc ) else: line.append(bytes().rjust(s.sc)) linea.append((None, s.sc)) linec.append((None, s.sc)) t.append(bytes().join(line)) a.append(linea) c.append(linec) return TextCanvas(t, a, c, maxcol=maxcol)
def keypress(self, size, key): if self.terminated: return key if key == "window resize": width, height = size self.touch_term(width, height) return if (self.last_key == self.escape_sequence and key == self.escape_sequence): # escape sequence pressed twice... self.last_key = key self.keygrab = True # ... so pass it to the terminal elif self.keygrab: if self.escape_sequence == key: # stop grabbing the terminal self.keygrab = False self.last_key = key return else: if key == 'page up': self.term.scroll_buffer() self.last_key = key self._invalidate() return elif key == 'page down': self.term.scroll_buffer(up=False) self.last_key = key self._invalidate() return elif (self.last_key == self.escape_sequence and key != self.escape_sequence): # hand down keypress directly after ungrab. self.last_key = key return key elif self.escape_sequence == key: # start grabbing the terminal self.keygrab = True self.last_key = key return elif self._command_map[key] is None or key == 'enter': # printable character or escape sequence means: # lock in terminal... self.keygrab = True # ... and do key processing else: # hand down keypress self.last_key = key return key self.last_key = key self.term.scroll_buffer(reset=True) if key.startswith("ctrl "): if key[-1].islower(): key = chr(ord(key[-1]) - ord('a') + 1) else: key = chr(ord(key[-1]) - ord('A') + 1) else: if self.term_modes.keys_decckm and key in KEY_TRANSLATIONS_DECCKM: key = KEY_TRANSLATIONS_DECCKM.get(key) else: key = KEY_TRANSLATIONS.get(key, key) # ENTER transmits both a carriage return and linefeed in LF/NL mode. if self.term_modes.lfnl and key == "\x0d": key += "\x0a" if sys.version_info[0] >= 3: key = bytes(key, 'ascii') os.write(self.master, key)
def keypress(self, size, key): if self.terminated: return key if key == "window resize": width, height = size self.touch_term(width, height) return if self.last_key == self.escape_sequence and key == self.escape_sequence: # escape sequence pressed twice... self.last_key = key self.keygrab = True # ... so pass it to the terminal elif self.keygrab: if self.escape_sequence == key: # stop grabbing the terminal self.keygrab = False self.last_key = key return else: if key == "page up": self.term.scroll_buffer() self.last_key = key self._invalidate() return elif key == "page down": self.term.scroll_buffer(up=False) self.last_key = key self._invalidate() return elif self.last_key == self.escape_sequence and key != self.escape_sequence: # hand down keypress directly after ungrab. self.last_key = key return key elif self.escape_sequence == key: # start grabbing the terminal self.keygrab = True self.last_key = key return elif self._command_map[key] is None or key == "enter": # printable character or escape sequence means: # lock in terminal... self.keygrab = True # ... and do key processing else: # hand down keypress self.last_key = key return key self.last_key = key self.term.scroll_buffer(reset=True) if key.startswith("ctrl "): if key[-1].islower(): key = chr(ord(key[-1]) - ord("a") + 1) else: key = chr(ord(key[-1]) - ord("A") + 1) else: if self.term_modes.keys_decckm and key in KEY_TRANSLATIONS_DECCKM: key = KEY_TRANSLATIONS_DECCKM.get(key) else: key = KEY_TRANSLATIONS.get(key, key) # ENTER transmits both a carriage return and linefeed in LF/NL mode. if self.term_modes.lfnl and key == "\x0d": key += "\x0a" if sys.version_info[0] >= 3: key = bytes(key, "ascii") os.write(self.master, key)
def leave_escape(self): self.within_escape = False self.parsestate = 0 self.escbuf = bytes()