Exemplo n.º 1
0
def calc_coords(text, layout, pos, clamp=1):
    """
    Calculate the coordinates closest to position pos in text with layout.

    text -- raw string or unicode string
    layout -- layout structure applied to text
    pos -- integer position into text
    clamp -- ignored right now
    """
    closest = None
    y = 0
    for line_layout in layout:
        x = 0
        for seg in line_layout:
            s = LayoutSegment(seg)
            if s.offs is None:
                x += s.sc
                continue
            if s.offs == pos:
                return x, y
            if s.end is not None and s.offs <= pos and s.end > pos:
                x += calc_width(text, s.offs, pos)
                return x, y
            distance = abs(s.offs - pos)
            if s.end is not None and s.end < pos:
                distance = pos - (s.end - 1)
            if closest is None or distance < closest[0]:
                closest = distance, (x, y)
            x += s.sc
        y += 1

    if closest:
        return closest[1]
    return 0, 0
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
def calc_coords( text, layout, pos, clamp=1 ):
    """
    Calculate the coordinates closest to position pos in text with layout.

    text -- raw string or unicode string
    layout -- layout structure applied to text
    pos -- integer position into text
    clamp -- ignored right now
    """
    closest = None
    y = 0
    for line_layout in layout:
        x = 0
        for seg in line_layout:
            s = LayoutSegment(seg)
            if s.offs is None:
                x += s.sc
                continue
            if s.offs == pos:
                return x,y
            if s.end is not None and s.offs<=pos and s.end>pos:
                x += calc_width( text, s.offs, pos )
                return x,y
            distance = abs(s.offs - pos)
            if s.end is not None and s.end<pos:
                distance = pos - (s.end-1)
            if closest is None or distance < closest[0]:
                closest = distance, (x,y)
            x += s.sc
        y += 1

    if closest:
        return closest[1]
    return 0,0
Exemplo n.º 5
0
def text_width(txt):
    """
    Return the width of the text in the terminal

    Use this instead of len() whenever txt could contain double- or zero-width
    Unicode characters.

    """
    return calc_width(txt, 0, len(txt))
Exemplo n.º 6
0
def text_width(txt):
    """
    Return the width of the text in the terminal

    Use this instead of len() whenever txt could contain double- or zero-width
    Unicode characters.

    """
    return calc_width(txt, 0, len(txt))
Exemplo n.º 7
0
    def _last_row(self, row):
        """On the last row we need to slide the bottom right character
        into place. Calculate the new line, attr and an insert sequence
        to do that.

        eg. last row:
        XXXXXXXXXXXXXXXXXXXXYZ

        Y will be drawn after Z, shifting Z into position.
        """

        new_row = row[:-1]
        z_attr, z_cs, last_text = row[-1]
        last_cols = util.calc_width(last_text, 0, len(last_text))
        last_offs, z_col = util.calc_text_pos(last_text, 0, 
            len(last_text), last_cols-1)
        if last_offs == 0:
            z_text = last_text
            del new_row[-1]
            # we need another segment
            y_attr, y_cs, nlast_text = row[-2]
            nlast_cols = util.calc_width(nlast_text, 0, 
                len(nlast_text))
            z_col += nlast_cols
            nlast_offs, y_col = util.calc_text_pos(nlast_text, 0,
                len(nlast_text), nlast_cols-1)
            y_text = nlast_text[nlast_offs:]
            if nlast_offs:
                new_row.append((y_attr, y_cs, 
                    nlast_text[:nlast_offs]))
        else:
            z_text = last_text[last_offs:]
            y_attr, y_cs = z_attr, z_cs
            nlast_cols = util.calc_width(last_text, 0,
                last_offs)
            nlast_offs, y_col = util.calc_text_pos(last_text, 0,
                last_offs, nlast_cols-1)
            y_text = last_text[nlast_offs:last_offs]
            if nlast_offs:
                new_row.append((y_attr, y_cs,
                    last_text[:nlast_offs]))
        
        new_row.append((z_attr, z_cs, z_text))
        return new_row, z_col-y_col, (y_attr, y_cs, y_text)
Exemplo n.º 8
0
    def _last_row(self, row):
        """On the last row we need to slide the bottom right character
        into place. Calculate the new line, attr and an insert sequence
        to do that.

        eg. last row:
        XXXXXXXXXXXXXXXXXXXXYZ

        Y will be drawn after Z, shifting Z into position.
        """

        new_row = row[:-1]
        z_attr, z_cs, last_text = row[-1]
        last_cols = util.calc_width(last_text, 0, len(last_text))
        last_offs, z_col = util.calc_text_pos(last_text, 0, 
            len(last_text), last_cols-1)
        if last_offs == 0:
            z_text = last_text
            del new_row[-1]
            # we need another segment
            y_attr, y_cs, nlast_text = row[-2]
            nlast_cols = util.calc_width(nlast_text, 0, 
                len(nlast_text))
            z_col += nlast_cols
            nlast_offs, y_col = util.calc_text_pos(nlast_text, 0,
                len(nlast_text), nlast_cols-1)
            y_text = nlast_text[nlast_offs:]
            if nlast_offs:
                new_row.append((y_attr, y_cs, 
                    nlast_text[:nlast_offs]))
        else:
            z_text = last_text[last_offs:]
            y_attr, y_cs = z_attr, z_cs
            nlast_cols = util.calc_width(last_text, 0,
                last_offs)
            nlast_offs, y_col = util.calc_text_pos(last_text, 0,
                last_offs, nlast_cols-1)
            y_text = last_text[nlast_offs:last_offs]
            if nlast_offs:
                new_row.append((y_attr, y_cs,
                    last_text[:nlast_offs]))
        
        new_row.append((z_attr, z_cs, z_text))
        return new_row, z_col-y_col, (y_attr, y_cs, y_text)
Exemplo n.º 9
0
    def draw_screen(self, size, r):
        """Create an html fragment from the render object.
        Append it to HtmlGenerator.fragments list.
        """
        # collect output in l
        l = []

        cols, rows = size

        assert r.rows() == rows

        if r.cursor is not None:
            cx, cy = r.cursor
        else:
            cx = cy = None

        y = -1
        for row in r.content():
            y += 1
            col = 0

            for a, cs, run in row:
                if not str is bytes:
                    run = run.decode()
                run = run.translate(_trans_table)
                if isinstance(a, AttrSpec):
                    aspec = a
                else:
                    aspec = self._palette[a][{
                        1: 1,
                        16: 0,
                        88: 2,
                        256: 3
                    }[self.colors]]

                if y == cy and col <= cx:
                    run_width = util.calc_width(run, 0, len(run))
                    if col + run_width > cx:
                        l.append(html_span(run, aspec, cx - col))
                    else:
                        l.append(html_span(run, aspec))
                    col += run_width
                else:
                    l.append(html_span(run, aspec))

            l.append("\n")

        # add the fragment to the list
        self.fragments.append("<pre>%s</pre>" % "".join(l))
Exemplo n.º 10
0
    def draw_screen(self, xxx_todo_changeme, r ):
        """Create an html fragment from the render object. 
        Append it to HtmlGenerator.fragments list.
        """
        (cols, rows) = xxx_todo_changeme
        l = []
        
        assert r.rows() == rows
    
        if r.cursor is not None:
            cx, cy = r.cursor
        else:
            cx = cy = None
        
        y = -1
        for row in r.content():
            y += 1
            col = 0
            
            for a, cs, run in row:
                run = run.translate(_trans_table)
                if isinstance(a, AttrSpec):
                    aspec = a
                else:
                    aspec = self._palette[a][
                        {1: 1, 16: 0, 88:2, 256:3}[self.colors]]

                if y == cy and col <= cx:
                    run_width = util.calc_width(run, 0,
                        len(run))
                    if col+run_width > cx:
                        l.append(html_span(run,
                            aspec, cx-col))
                    else:
                        l.append(html_span(run, aspec))
                    col += run_width
                else:
                    l.append(html_span(run, aspec))

            l.append("\n")
                        
        # add the fragment to the list
        self.fragments.append( "<pre>%s</pre>" % "".join(l) )
Exemplo n.º 11
0
def make_canvas(txt, attr, maxcol, fill_attr=None):
    processed_txt = []
    processed_attr = []
    processed_cs = []

    for line, line_attr in zip(txt, attr):
        # filter out zero-length attrs
        line_attr = [(aname, l) for aname, l in line_attr if l > 0]

        diff = maxcol - calc_width(line, 0, len(line))
        if diff > 0:
            line += " " * diff
            line_attr.append((fill_attr, diff))
        else:
            from urwid.util import rle_subseg
            line = line[:maxcol]
            line_attr = rle_subseg(line_attr, 0, maxcol)

        from urwid.util import apply_target_encoding
        encoded_line, line_cs = apply_target_encoding(line)

        # line_cs contains byte counts as requested by TextCanvas, but
        # line_attr still contains column counts at this point: let's fix this.
        def get_byte_line_attr(line, line_attr):
            i = 0
            for label, column_count in line_attr:
                byte_count = len(line[i:i +
                                      column_count].encode(_target_encoding))
                i += column_count
                yield label, byte_count

        line_attr = list(get_byte_line_attr(line, line_attr))

        processed_txt.append(encoded_line)
        processed_attr.append(line_attr)
        processed_cs.append(line_cs)

    return urwid.TextCanvas(processed_txt,
                            processed_attr,
                            processed_cs,
                            maxcol=maxcol)
Exemplo n.º 12
0
    def pack(self, size=None, focus=False):
        """
        Return the number of screen columns and rows required for
        this Text widget to be displayed without wrapping or 
        clipping, as a single element tuple.

        size -- None for unlimited screen columns or (maxcol,) to
                specify a maximum column size

        >>> Text(u"important things").pack()
        (16, 1)
        >>> Text(u"important things").pack((15,))
        (9, 2)
        >>> Text(u"important things").pack((8,))
        (8, 2)
        """
        text, attr = self.get_text()

        if size is not None:
            (maxcol, ) = size
            if not hasattr(self.layout, "pack"):
                return size
            trans = self.get_line_translation(maxcol, (text, attr))
            cols = self.layout.pack(maxcol, trans)
            return (cols, len(trans))

        i = 0
        cols = 0
        while i < len(text):
            j = text.find('\n', i)
            if j == -1:
                j = len(text)
            c = calc_width(text, i, j)
            if c > cols:
                cols = c
            i = j + 1
        return (cols, text.count('\n') + 1)
Exemplo n.º 13
0
    def pack(self, size=None, focus=False):
        """
        Return the number of screen columns and rows required for
        this Text widget to be displayed without wrapping or 
        clipping, as a single element tuple.

        size -- None for unlimited screen columns or (maxcol,) to
                specify a maximum column size

        >>> Text(u"important things").pack()
        (16, 1)
        >>> Text(u"important things").pack((15,))
        (9, 2)
        >>> Text(u"important things").pack((8,))
        (8, 2)
        """
        text, attr = self.get_text()

        if size is not None:
            (maxcol,) = size
            if not hasattr(self.layout, "pack"):
                return size
            trans = self.get_line_translation( maxcol, (text,attr))
            cols = self.layout.pack( maxcol, trans )
            return (cols, len(trans))

        i = 0
        cols = 0
        while i < len(text):
            j = text.find('\n', i)
            if j == -1:
                j = len(text)
            c = calc_width(text, i, j)
            if c>cols:
                cols = c
            i = j+1
        return (cols, text.count('\n') + 1)
Exemplo n.º 14
0
    def calculate_text_segments(self, text, width, wrap):
        """
        Calculate the segments of text to display given width screen
        columns to display them.

        text - unicode text or byte string to display
        width - number of available screen columns
        wrap - wrapping mode used

        Returns a layout structure without alignment applied.
        """
        nl, nl_o, sp_o = "\n", "\n", " "
        if PYTHON3 and isinstance(text, bytes):
            nl = B(nl) # can only find bytes in python3 bytestrings
            nl_o = ord(nl_o) # + an item of a bytestring is the ordinal value
            sp_o = ord(sp_o)
        b = []
        p = 0
        if wrap == 'clip':
            # no wrapping to calculate, so it's easy.
            while p<=len(text):
                n_cr = text.find(nl, p)
                if n_cr == -1:
                    n_cr = len(text)
                sc = calc_width(text, p, n_cr)
                l = [(0,n_cr)]
                if p!=n_cr:
                    l = [(sc, p, n_cr)] + l
                b.append(l)
                p = n_cr+1
            return b


        while p<=len(text):
            # look for next eligible line break
            n_cr = text.find(nl, p)
            if n_cr == -1:
                n_cr = len(text)
            sc = calc_width(text, p, n_cr)
            if sc == 0:
                # removed character hint
                b.append([(0,n_cr)])
                p = n_cr+1
                continue
            if sc <= width:
                # this segment fits
                b.append([(sc,p,n_cr),
                    # removed character hint
                    (0,n_cr)])

                p = n_cr+1
                continue
            pos, sc = calc_text_pos( text, p, n_cr, width )
            if pos == p: # pathological width=1 double-byte case
                raise CanNotDisplayText(
                    "Wide character will not fit in 1-column width")
            if wrap == 'any':
                b.append([(sc,p,pos)])
                p = pos
                continue
            assert wrap == 'space'
            if text[pos] == sp_o:
                # perfect space wrap
                b.append([(sc,p,pos),
                    # removed character hint
                    (0,pos)])
                p = pos+1
                continue
            if is_wide_char(text, pos):
                # perfect next wide
                b.append([(sc,p,pos)])
                p = pos
                continue
            prev = pos
            while prev > p:
                prev = move_prev_char(text, p, prev)
                if text[prev] == sp_o:
                    sc = calc_width(text,p,prev)
                    l = [(0,prev)]
                    if p!=prev:
                        l = [(sc,p,prev)] + l
                    b.append(l)
                    p = prev+1
                    break
                if is_wide_char(text,prev):
                    # wrap after wide char
                    next = move_next_char(text, prev, pos)
                    sc = calc_width(text,p,next)
                    b.append([(sc,p,next)])
                    p = next
                    break
            else:
                # unwrap previous line space if possible to
                # fit more text (we're breaking a word anyway)
                if b and (len(b[-1]) == 2 or ( len(b[-1])==1
                        and len(b[-1][0])==2 )):
                    # look for removed space above
                    if len(b[-1]) == 1:
                        [(h_sc, h_off)] = b[-1]
                        p_sc = 0
                        p_off = p_end = h_off
                    else:
                        [(p_sc, p_off, p_end),
                               (h_sc, h_off)] = b[-1]
                    if (p_sc < width and h_sc==0 and
                        text[h_off] == sp_o):
                        # combine with previous line
                        del b[-1]
                        p = p_off
                        pos, sc = calc_text_pos(
                            text, p, n_cr, width )
                        b.append([(sc,p,pos)])
                        # check for trailing " " or "\n"
                        p = pos
                        if p < len(text) and (
                            text[p] in (sp_o, nl_o)):
                            # removed character hint
                            b[-1].append((0,p))
                            p += 1
                        continue


                # force any char wrap
                b.append([(sc,p,pos)])
                p = pos
        return b
Exemplo n.º 15
0
    def calculate_text_segments( self, text, width, wrap ):
        """
        Calculate the segments of text to display given width screen 
        columns to display them.  
        
        text - text to display
        width - number of available screen columns
        wrap - wrapping mode used
        
        Returns a layout structure without aligmnent applied.
        """
        b = []
        p = 0
        if wrap == 'clip':
            # no wrapping to calculate, so it's easy.
            while p<=len(text):
                n_cr = text.find("\n", p)
                if n_cr == -1: 
                    n_cr = len(text)
                sc = calc_width(text, p, n_cr)
                l = [(0,n_cr)]
                if p!=n_cr:
                    l = [(sc, p, n_cr)] + l
                b.append(l)
                p = n_cr+1
            return b

        
        while p<=len(text):
            # look for next eligible line break
            n_cr = text.find("\n", p)
            if n_cr == -1: 
                n_cr = len(text)
            sc = calc_width(text, p, n_cr)
            if sc == 0:
                # removed character hint
                b.append([(0,n_cr)])
                p = n_cr+1
                continue
            if sc <= width:
                # this segment fits
                b.append([(sc,p,n_cr),
                    # removed character hint
                    (0,n_cr)])
                
                p = n_cr+1
                continue
            pos, sc = calc_text_pos( text, p, n_cr, width )
            # FIXME: handle pathological width=1 double-byte case
            if wrap == 'any':
                b.append([(sc,p,pos)])
                p = pos
                continue
            assert wrap == 'space'
            if text[pos] == " ":
                # perfect space wrap
                b.append([(sc,p,pos),
                    # removed character hint
                    (0,pos)])
                p = pos+1
                continue
            if is_wide_char(text, pos):
                # perfect next wide
                b.append([(sc,p,pos)])
                p = pos
                continue
            prev = pos    
            while prev > p:
                prev = move_prev_char(text, p, prev)
                if text[prev] == " ":
                    sc = calc_width(text,p,prev)
                    l = [(0,prev)]
                    if p!=prev:
                        l = [(sc,p,prev)] + l
                    b.append(l)
                    p = prev+1 
                    break
                if is_wide_char(text,prev):
                    # wrap after wide char
                    next = move_next_char(text, prev, pos)
                    sc = calc_width(text,p,next)
                    b.append([(sc,p,next)])
                    p = next
                    break
            else:
                # unwrap previous line space if possible to
                # fit more text (we're breaking a word anyway)
                if b and (len(b[-1]) == 2 or ( len(b[-1])==1 
                        and len(b[-1][0])==2 )):
                    # look for removed space above
                    if len(b[-1]) == 1:
                        [(h_sc, h_off)] = b[-1]
                        p_sc = 0
                        p_off = p_end = h_off
                    else:
                        [(p_sc, p_off, p_end),
                               (h_sc, h_off)] = b[-1]
                    if (p_sc < width and h_sc==0 and
                        text[h_off] == " "):
                        # combine with previous line
                        del b[-1]
                        p = p_off
                        pos, sc = calc_text_pos( 
                            text, p, n_cr, width )
                        b.append([(sc,p,pos)])
                        # check for trailing " " or "\n"
                        p = pos
                        if p < len(text) and (
                            text[p] in (" ","\n")):
                            # removed character hint
                            b[-1].append((0,p))
                            p += 1
                        continue
                        
                        
                # force any char wrap
                b.append([(sc,p,pos)])
                p = pos
        return b
Exemplo n.º 16
0
    def calculate_text_segments(self, text, width, wrap):
        """
        Calculate the segments of text to display given width screen
        columns to display them.

        text - unicode text or byte string to display
        width - number of available screen columns
        wrap - wrapping mode used

        Returns a layout structure without alignment applied.
        """

        # TODO: This function is a horror and a mess, and really hard to
        # understand. It's based on urwids StandardLayout, which by itself
        # is overly complex, and I added tab handling, which made it worse.
        # It's a prime candidate for refacturing, making easier to understand
        # and as it is heavily used, profiling would be nice too.

        nl, nl_o, sp_o, tab_o = "\n", "\n", " ", "\t"
        if PYTHON3 and isinstance(text, bytes):
            nl = B(nl)  # can only find bytes in python3 bytestrings
            nl_o = ord(nl_o)  # + an item of a bytestring is the ordinal value
            sp_o = ord(sp_o)
            tab_o = ord(tab_o)
        b = []
        p = 0
        if wrap == 'clip':
            # no wrapping to calculate, so it's easy.
            l = []
            while p <= len(text):
                n_cr = find_newline(text, p)
                if p != n_cr:
                    line = text[p:n_cr]
                    pt = 0
                    while pt < len(line):
                        n_tab = line.find(tab_o, pt)
                        if n_tab == -1:
                            end = len(line)
                        else:
                            end = n_tab

                        sc = calc_width(line, pt, end)
                        if sc != 0:
                            l.append((sc, p + pt, p + end))

                        if end == n_tab:  # A tab was found
                            extra_space = (self.tab_width - (
                                sc % self.tab_width))
                            l.append((extra_space, p + n_tab))

                        pt = end + 1

                l.append((0, n_cr))
                b.append(l)
                l = []
                if text[n_cr:n_cr+2] in TWOCHAR_NEWLINES:
                    # Two char newline:
                    p = n_cr + 2
                else:
                    p = n_cr + 1
            return b

        while p <= len(text):
            # look for next eligible line break
            n_cr = find_newline(text, p)

            line = text[p:n_cr]
            l = []
            pt = 0
            lc = 0
            while pt < len(line):
                n_tab = line.find(tab_o, pt)
                if n_tab == -1:
                    end = len(line)
                else:
                    end = n_tab

                sc = calc_width(line, pt, end)

                if lc + sc <= width:
                    # this segment fits
                    if sc:
                        l.append((sc, p + pt, p + end))
                    if end == n_tab:  # A tab was found
                        extra_space = self.tab_width - (sc % self.tab_width)
                        l.append((extra_space, p + n_tab))
                        lc += extra_space
                    else:
                        # removed character hint
                        l.append((0, p + end))

                    pt = end + 1
                    lc += sc

                    if lc >= width:
                        # The tab can sometimes push line length to width, and
                        # then we adjust the line length and make a new line.
                        overshoot = lc - width
                        spaces, pos = l[-1]
                        l[-1] = (spaces - overshoot, pos)
                        b.append(l)
                        l = []
                        lc = 0
                    continue

                # This segment does not fit. Let's fit it.
                pos, sc = calc_text_pos(line, pt, end, width - lc)
                if pos == pt:  # pathological width=1 double-byte case
                    raise CanNotDisplayText(
                        "Wide character will not fit in 1-column width")

                if wrap == 'any':
                    l.append((sc, p + pt, p + pos))
                    l.append((0, p + pos))
                    b.append(l)
                    l = []
                    lc = 0
                    pt = pos
                    continue

                assert wrap == 'space'
                if line[pos] == sp_o:
                    # perfect space wrap
                    l.append((sc, p + pt, p + pos))
                    # removed character hint
                    l.append((0, p + pos))
                    b.append(l)
                    l = []
                    lc = 0
                    pt = pos + 1
                    continue

                if is_wide_char(line, pos):
                    # perfect next wide
                    l.append((sc, p + pt, p + pos))
                    b.append(l)
                    l = []
                    lc = 0
                    pt = pos
                    continue

                prev = pos
                while prev > pt:
                    prev = move_prev_char(line, pt, prev)
                    if line[prev] == sp_o:
                        sc = calc_width(line, pt, prev)
                        if prev != pt:
                            l.append((sc, p + pt, p + prev))
                        l.append((0, p + prev))
                        b.append(l)
                        l = []
                        lc = 0
                        pt = prev + 1
                        break

                    if is_wide_char(line, prev):
                        # wrap after wide char
                        nextc = move_next_char(line, prev, pos)
                        sc = calc_width(line, pt, nextc)
                        l.append((sc, p + pt, p + nextc))
                        b.append(l)
                        l = []
                        lc = 0
                        pt = nextc
                        break
                else:
                    if lc == 0:
                        # unwrap previous line space if possible to
                        # fit more text (we're breaking a word anyway)
                        if b and (len(b[-1]) == 2 or (len(b[-1]) == 1 and
                                                      len(b[-1][0]) == 2)):
                            # look for removed space above
                            if len(b[-1]) == 1:
                                [(h_sc, h_off)] = b[-1]
                                p_sc = 0
                                p_off = p_end = h_off
                            else:
                                [(p_sc, p_off, p_end),
                                 (h_sc, h_off)] = b[-1][-2:]
                            if (p_sc < width and h_sc == 0 and
                               text[h_off] == sp_o):
                                # combine with previous line
                                old_line = b[-1][:-2]
                                del b[-1]
                                pt = p_off - p
                                pos, sc = calc_text_pos(
                                    line, pt, end, width)
                                old_line.append((sc, p + pt, p + pos))
                                b.append(old_line)
                                # check for trailing " " or "\n"
                                pt = pos
                                if pt < len(text) and (
                                   text[pt] in (sp_o, nl_o)):
                                    # removed character hint
                                    b[-1].append((0, p + pt))
                                    pt += 1
                                continue
                    # Break on previous tab, and try again.
                    if l:
                        b.append(l)
                        l = []
                        lc = 0
                        continue

                    # There is no space to break the line on, unwrapping the
                    # previous line doesn't help, I guess we just break on a
                    # character.
                    b.append([(sc, p + pt, p + pos)])
                    l = []
                    lc = 0
                    pt = pos

            # force any char wrap
            if l:
                b.append(l)
            elif not line:
                # An empty line.
                b.append([(0, n_cr)])
                pt = 1

            if text[pt-1:pt+1] in TWOCHAR_NEWLINES:
                # Two char newline:
                pt += 1
            p += pt
        return b
Exemplo n.º 17
0
    def calculate_text_segments(self, text, width, wrap):
        """
        Calculate the segments of text to display given width screen
        columns to display them.

        text - unicode text or byte string to display
        width - number of available screen columns
        wrap - wrapping mode used

        Returns a layout structure without aligmnent applied.
        """
        nl, nl_o, sp_o = "\n", "\n", " "
        if PYTHON3 and isinstance(text, bytes):
            nl = B(nl)  # can only find bytes in python3 bytestrings
            nl_o = ord(nl_o)  # + an item of a bytestring is the ordinal value
            sp_o = ord(sp_o)
        b = []
        p = 0
        if wrap == 'clip':
            # no wrapping to calculate, so it's easy.
            while p <= len(text):
                n_cr = text.find(nl, p)
                if n_cr == -1:
                    n_cr = len(text)
                sc = calc_width(text, p, n_cr)
                l = [(0, n_cr)]
                if p != n_cr:
                    l = [(sc, p, n_cr)] + l
                b.append(l)
                p = n_cr + 1
            return b

        while p <= len(text):
            # look for next eligible line break
            n_cr = text.find(nl, p)
            if n_cr == -1:
                n_cr = len(text)
            sc = calc_width(text, p, n_cr)
            if sc == 0:
                # removed character hint
                b.append([(0, n_cr)])
                p = n_cr + 1
                continue
            if sc <= width:
                # this segment fits
                b.append([
                    (sc, p, n_cr),
                    # removed character hint
                    (0, n_cr)
                ])

                p = n_cr + 1
                continue
            pos, sc = calc_text_pos(text, p, n_cr, width)
            if pos == p:  # pathological width=1 double-byte case
                raise CanNotDisplayText(
                    "Wide character will not fit in 1-column width")
            if wrap == 'any':
                b.append([(sc, p, pos)])
                p = pos
                continue
            assert wrap == 'space'
            if text[pos] == sp_o:
                # perfect space wrap
                b.append([
                    (sc, p, pos),
                    # removed character hint
                    (0, pos)
                ])
                p = pos + 1
                continue
            if is_wide_char(text, pos):
                # perfect next wide
                b.append([(sc, p, pos)])
                p = pos
                continue
            prev = pos
            while prev > p:
                prev = move_prev_char(text, p, prev)
                if text[prev] == sp_o:
                    sc = calc_width(text, p, prev)
                    l = [(0, prev)]
                    if p != prev:
                        l = [(sc, p, prev)] + l
                    b.append(l)
                    p = prev + 1
                    break
                if is_wide_char(text, prev):
                    # wrap after wide char
                    next = move_next_char(text, prev, pos)
                    sc = calc_width(text, p, next)
                    b.append([(sc, p, next)])
                    p = next
                    break
            else:
                # unwrap previous line space if possible to
                # fit more text (we're breaking a word anyway)
                if b and (len(b[-1]) == 2 or
                          (len(b[-1]) == 1 and len(b[-1][0]) == 2)):
                    # look for removed space above
                    if len(b[-1]) == 1:
                        [(h_sc, h_off)] = b[-1]
                        p_sc = 0
                        p_off = p_end = h_off
                    else:
                        [(p_sc, p_off, p_end), (h_sc, h_off)] = b[-1]
                    if (p_sc < width and h_sc == 0 and text[h_off] == sp_o):
                        # combine with previous line
                        del b[-1]
                        p = p_off
                        pos, sc = calc_text_pos(text, p, n_cr, width)
                        b.append([(sc, p, pos)])
                        # check for trailing " " or "\n"
                        p = pos
                        if p < len(text) and (text[p] in (sp_o, nl_o)):
                            # removed character hint
                            b[-1].append((0, p))
                            p += 1
                        continue

                # force any char wrap
                b.append([(sc, p, pos)])
                p = pos
        return b
Exemplo n.º 18
0
class Screen:
    def __init__(self):
        self.palette = {}
        self.has_color = True
        self._started = False

    started = property(lambda self: self._started)

    def register_palette(self, l):
        """Register a list of palette entries.

        l -- list of (name, foreground, background) or
             (name, same_as_other_name) palette entries.

        calls self.register_palette_entry for each item in l
        """

        for item in l:
            if len(item) in (3, 4):
                self.register_palette_entry(*item)
                continue
            assert len(item) == 2, "Invalid register_palette usage"
            name, like_name = item
            if not self.palette.has_key(like_name):
                raise Exception("palette entry '%s' doesn't exist" % like_name)
            self.palette[name] = self.palette[like_name]

    def register_palette_entry(self, name, foreground, background, mono=None):
        """Register a single palette entry.

        name -- new entry/attribute name
        foreground -- foreground colour
        background -- background colour
        mono -- monochrome terminal attribute

        See curses_display.register_palette_entry for more info.
        """
        if foreground == "default":
            foreground = "black"
        if background == "default":
            background = "light gray"
        self.palette[name] = (foreground, background, mono)

    def set_mouse_tracking(self, enable=True):
        """Not yet implemented"""
        pass

    def tty_signal_keys(self, *args, **vargs):
        """Do nothing."""
        pass

    def start(self):
        """
        This function reads the initial screen size, generates a
        unique id and handles cleanup when fn exits.

        web_display.set_preferences(..) must be called before calling
        this function for the preferences to take effect
        """
        global _prefs

        assert not self._started

        client_init = sys.stdin.read(50)
        assert client_init.startswith("window resize "), client_init
        ignore1, ignore2, x, y = client_init.split(" ", 3)
        x = int(x)
        y = int(y)
        self._set_screen_size(x, y)
        self.last_screen = {}
        self.last_screen_width = 0

        self.update_method = os.environ["HTTP_X_URWID_METHOD"]
        assert self.update_method in ("multipart", "polling")

        if self.update_method == "polling" and not _prefs.allow_polling:
            sys.stdout.write("Status: 403 Forbidden\r\n\r\n")
            sys.exit(0)

        clients = glob.glob(os.path.join(_prefs.pipe_dir, "urwid*.in"))
        if len(clients) >= _prefs.max_clients:
            sys.stdout.write("Status: 503 Sever Busy\r\n\r\n")
            sys.exit(0)

        urwid_id = "%09d%09d" % (random.randrange(10**
                                                  9), random.randrange(10**9))
        self.pipe_name = os.path.join(_prefs.pipe_dir, "urwid" + urwid_id)
        os.mkfifo(self.pipe_name + ".in", 0600)
        signal.signal(signal.SIGTERM, self._cleanup_pipe)

        self.input_fd = os.open(self.pipe_name + ".in",
                                os.O_NONBLOCK | os.O_RDONLY)
        self.input_tail = ""
        self.content_head = ("Content-type: "
                             "multipart/x-mixed-replace;boundary=ZZ\r\n"
                             "X-Urwid-ID: " + urwid_id + "\r\n"
                             "\r\n\r\n"
                             "--ZZ\r\n")
        if self.update_method == "polling":
            self.content_head = ("Content-type: text/plain\r\n"
                                 "X-Urwid-ID: " + urwid_id + "\r\n"
                                 "\r\n\r\n")

        signal.signal(signal.SIGALRM, self._handle_alarm)
        signal.alarm(ALARM_DELAY)
        self._started = True

    def stop(self):
        """
        Restore settings and clean up.
        """
        assert self._started
        # XXX which exceptions does this actually raise? EnvironmentError?
        try:
            self._close_connection()
        except Exception:
            pass
        signal.signal(signal.SIGTERM, signal.SIG_DFL)
        self._cleanup_pipe()
        self._started = False

    def set_input_timeouts(self, *args):
        pass

    def run_wrapper(self, fn):
        """
        Run the application main loop, calling start() first
        and stop() on exit.
        """
        try:
            self.start()
            return fn()
        finally:
            self.stop()

    def _close_connection(self):
        if self.update_method == "polling child":
            self.server_socket.settimeout(0)
            sock, addr = self.server_socket.accept()
            sock.sendall("Z")
            sock.close()

        if self.update_method == "multipart":
            sys.stdout.write("\r\nZ" "\r\n--ZZ--\r\n")
            sys.stdout.flush()

    def _cleanup_pipe(self, *args):
        if not self.pipe_name: return
        # XXX which exceptions does this actually raise? EnvironmentError?
        try:
            os.remove(self.pipe_name + ".in")
            os.remove(self.pipe_name + ".update")
        except Exception:
            pass

    def _set_screen_size(self, cols, rows):
        """Set the screen size (within max size)."""

        if cols > MAX_COLS:
            cols = MAX_COLS
        if rows > MAX_ROWS:
            rows = MAX_ROWS
        self.screen_size = cols, rows

    def draw_screen(self, (cols, rows), r):
        """Send a screen update to the client."""

        if cols != self.last_screen_width:
            self.last_screen = {}

        sendq = [self.content_head]

        if self.update_method == "polling":
            send = sendq.append
        elif self.update_method == "polling child":
            signal.alarm(0)
            try:
                s, addr = self.server_socket.accept()
            except socket.timeout:
                sys.exit(0)
            send = s.sendall
        else:
            signal.alarm(0)
            send = sendq.append
            send("\r\n")
            self.content_head = ""

        assert r.rows() == rows

        if r.cursor is not None:
            cx, cy = r.cursor
        else:
            cx = cy = None

        new_screen = {}

        y = -1
        for row in r.content():
            y += 1
            row = list(row)

            l = []

            sig = tuple(row)
            if y == cy: sig = sig + (cx, )
            new_screen[sig] = new_screen.get(sig, []) + [y]
            old_line_numbers = self.last_screen.get(sig, None)
            if old_line_numbers is not None:
                if y in old_line_numbers:
                    old_line = y
                else:
                    old_line = old_line_numbers[0]
                send("<%d\n" % old_line)
                continue

            col = 0
            for (a, cs, run) in row:
                run = run.translate(_trans_table)
                if a is None:
                    fg, bg, mono = "black", "light gray", None
                else:
                    fg, bg, mono = self.palette[a]
                if y == cy and col <= cx:
                    run_width = util.calc_width(run, 0, len(run))
                    if col + run_width > cx:
                        l.append(code_span(run, fg, bg, cx - col))
                    else:
                        l.append(code_span(run, fg, bg))
                    col += run_width
                else:
                    l.append(code_span(run, fg, bg))

            send("".join(l) + "\n")
        self.last_screen = new_screen
        self.last_screen_width = cols

        if self.update_method == "polling":
            sys.stdout.write("".join(sendq))
            sys.stdout.flush()
            sys.stdout.close()
            self._fork_child()
        elif self.update_method == "polling child":
            s.close()
        else:  # update_method == "multipart"
            send("\r\n--ZZ\r\n")
            sys.stdout.write("".join(sendq))
            sys.stdout.flush()

        signal.alarm(ALARM_DELAY)
Exemplo n.º 19
0
 def wtest(self, desc, s, exp):
     s = B(s)
     result = util.calc_width(s, 0, len(s))
     assert result == exp, "%s got:%r expected:%r" % (desc, result, exp)
Exemplo n.º 20
0
class HtmlGenerator(BaseScreen):
    # class variables
    fragments = []
    sizes = []
    keys = []
    started = True

    def __init__(self):
        super(HtmlGenerator, self).__init__()
        self.colors = 16
        self.bright_is_bold = False  # ignored
        self.has_underline = True  # ignored
        self.register_palette_entry(None, _default_foreground,
                                    _default_background)

    def set_terminal_properties(self,
                                colors=None,
                                bright_is_bold=None,
                                has_underline=None):

        if colors is None:
            colors = self.colors
        if bright_is_bold is None:
            bright_is_bold = self.bright_is_bold
        if has_underline is None:
            has_underline = self.has_underline

        self.colors = colors
        self.bright_is_bold = bright_is_bold
        self.has_underline = has_underline

    def set_mouse_tracking(self):
        """Not yet implemented"""
        pass

    def start(self):
        pass

    def stop(self):
        pass

    def set_input_timeouts(self, *args):
        pass

    def reset_default_terminal_palette(self, *args):
        pass

    def run_wrapper(self, fn):
        """Call fn."""
        return fn()

    def draw_screen(self, (cols, rows), r):
        """Create an html fragment from the render object. 
        Append it to HtmlGenerator.fragments list.
        """
        # collect output in l
        l = []

        assert r.rows() == rows

        if r.cursor is not None:
            cx, cy = r.cursor
        else:
            cx = cy = None

        y = -1
        for row in r.content():
            y += 1
            col = 0

            for a, cs, run in row:
                run = run.translate(_trans_table)
                if isinstance(a, AttrSpec):
                    aspec = a
                else:
                    aspec = self._palette[a][{
                        1: 1,
                        16: 0,
                        88: 2,
                        256: 3
                    }[self.colors]]

                if y == cy and col <= cx:
                    run_width = util.calc_width(run, 0, len(run))
                    if col + run_width > cx:
                        l.append(html_span(run, aspec, cx - col))
                    else:
                        l.append(html_span(run, aspec))
                    col += run_width
                else:
                    l.append(html_span(run, aspec))

            l.append("\n")

        # add the fragment to the list
        self.fragments.append("<pre>%s</pre>" % "".join(l))
Exemplo n.º 21
0
 def wtest(self, desc, s, exp):
     s = B(s)
     result = util.calc_width( s, 0, len(s))
     assert result==exp, "%s got:%r expected:%r" % (desc, result, exp)
Exemplo n.º 22
0
    def draw_screen(self, size, r):
        """Send a screen update to the client."""

        (cols, rows) = size

        if cols != self.last_screen_width:
            self.last_screen = {}

        sendq = [self.content_head]

        if self.update_method == "polling":
            send = sendq.append
        elif self.update_method == "polling child":
            signal.alarm(0)
            try:
                s, addr = self.server_socket.accept()
            except socket.timeout:
                sys.exit(0)
            send = s.sendall
        else:
            signal.alarm(0)
            send = sendq.append
            send("\r\n")
            self.content_head = ""

        assert r.rows() == rows

        if r.cursor is not None:
            cx, cy = r.cursor
        else:
            cx = cy = None

        new_screen = {}

        y = -1
        for row in r.content():
            y += 1
            row = list(row)

            l = []

            sig = tuple(row)
            if y == cy: sig = sig + (cx, )
            new_screen[sig] = new_screen.get(sig, []) + [y]
            old_line_numbers = self.last_screen.get(sig, None)
            if old_line_numbers is not None:
                if y in old_line_numbers:
                    old_line = y
                else:
                    old_line = old_line_numbers[0]
                send("<%d\n" % old_line)
                continue

            col = 0
            for (a, cs, run) in row:
                run = run.translate(_trans_table)
                if a is None:
                    fg, bg, mono = "black", "light gray", None
                else:
                    fg, bg, mono = self.palette[a]
                if y == cy and col <= cx:
                    run_width = util.calc_width(run, 0, len(run))
                    if col + run_width > cx:
                        l.append(code_span(run, fg, bg, cx - col))
                    else:
                        l.append(code_span(run, fg, bg))
                    col += run_width
                else:
                    l.append(code_span(run, fg, bg))

            send("".join(l) + "\n")
        self.last_screen = new_screen
        self.last_screen_width = cols

        if self.update_method == "polling":
            sys.stdout.write("".join(sendq))
            sys.stdout.flush()
            sys.stdout.close()
            self._fork_child()
        elif self.update_method == "polling child":
            s.close()
        else:  # update_method == "multipart"
            send("\r\n--ZZ\r\n")
            sys.stdout.write("".join(sendq))
            sys.stdout.flush()

        signal.alarm(ALARM_DELAY)
Exemplo n.º 23
0
    def draw_screen(self, xxx_todo_changeme, r ):
        """Send a screen update to the client."""
        (cols, rows) = xxx_todo_changeme
        if cols != self.last_screen_width:
            self.last_screen = {}

        sendq = [self.content_head]

        if self.update_method == "polling":
            send = sendq.append
        elif self.update_method == "polling child":
            signal.alarm( 0 )
            try:
                s, addr = self.server_socket.accept()
            except socket.timeout:
                sys.exit(0)
            send = s.sendall
        else:
            signal.alarm( 0 )
            send = sendq.append
            send("\r\n")
            self.content_head = ""

        assert r.rows() == rows

        if r.cursor is not None:
            cx, cy = r.cursor
        else:
            cx = cy = None

        new_screen = {}

        y = -1
        for row in r.content():
            y += 1
            row = list(row)

            l = []

            sig = tuple(row)
            if y == cy: sig = sig + (cx,)
            new_screen[sig] = new_screen.get(sig,[]) + [y]
            old_line_numbers = self.last_screen.get(sig, None)
            if old_line_numbers is not None:
                if y in old_line_numbers:
                    old_line = y
                else:
                    old_line = old_line_numbers[0]
                send( "<%d\n"%old_line )
                continue

            col = 0
            for (a, cs, run) in row:
                run = run.translate(_trans_table)
                if a is None:
                    fg,bg,mono = "black", "light gray", None
                else:
                    fg,bg,mono = self.palette[a]
                if y == cy and col <= cx:
                    run_width = util.calc_width(run, 0,
                        len(run))
                    if col+run_width > cx:
                        l.append(code_span(run, fg, bg,
                            cx-col))
                    else:
                        l.append(code_span(run, fg, bg))
                    col += run_width
                else:
                    l.append(code_span(run, fg, bg))

            send("".join(l)+"\n")
        self.last_screen = new_screen
        self.last_screen_width = cols

        if self.update_method == "polling":
            sys.stdout.write("".join(sendq))
            sys.stdout.flush()
            sys.stdout.close()
            self._fork_child()
        elif self.update_method == "polling child":
            s.close()
        else: # update_method == "multipart"
            send("\r\n--ZZ\r\n")
            sys.stdout.write("".join(sendq))
            sys.stdout.flush()

        signal.alarm( ALARM_DELAY )