Пример #1
0
def calc_line_pos( text, line_layout, pref_col ):
    """
    Calculate the closest linear position to pref_col given a
    line layout structure.  Returns None if no position found.
    """
    closest_sc = None
    closest_pos = None
    current_sc = 0

    if pref_col == 'left':
        for seg in line_layout:
            s = LayoutSegment(seg)
            if s.offs is not None:
                return s.offs
        return
    elif pref_col == 'right':
        for seg in line_layout:
            s = LayoutSegment(seg)
            if s.offs is not None:
                closest_pos = s
        s = closest_pos
        if s is None:
            return
        if s.end is None:
            return s.offs
        return calc_text_pos( text, s.offs, s.end, s.sc-1)[0]

    for seg in line_layout:
        s = LayoutSegment(seg)
        if s.offs is not None:
            if s.end is not None:
                if (current_sc <= pref_col and
                    pref_col < current_sc + s.sc):
                    # exact match within this segment
                    return calc_text_pos( text,
                        s.offs, s.end,
                        pref_col - current_sc )[0]
                elif current_sc <= pref_col:
                    closest_sc = current_sc + s.sc - 1
                    closest_pos = s

            if closest_sc is None or ( abs(pref_col-current_sc)
                    < abs(pref_col-closest_sc) ):
                # this screen column is closer
                closest_sc = current_sc
                closest_pos = s.offs
            if current_sc > closest_sc:
                # we're moving past
                break
        current_sc += s.sc

    if closest_pos is None or type(closest_pos) == int:
        return closest_pos

    # return the last positions in the segment "closest_pos"
    s = closest_pos
    return calc_text_pos( text, s.offs, s.end, s.sc-1)[0]
Пример #2
0
def calc_line_pos( text, line_layout, pref_col ):
    """
    Calculate the closest linear position to pref_col given a
    line layout structure.  Returns None if no position found.
    """
    closest_sc = None
    closest_pos = None
    current_sc = 0

    if pref_col == 'left':
        for seg in line_layout:
            s = LayoutSegment(seg)
            if s.offs is not None:
                return s.offs
        return
    elif pref_col == 'right':
        for seg in line_layout:
            s = LayoutSegment(seg)
            if s.offs is not None:
                closest_pos = s
        s = closest_pos
        if s is None:
            return
        if s.end is None:
            return s.offs
        return calc_text_pos( text, s.offs, s.end, s.sc-1)[0]

    for seg in line_layout:
        s = LayoutSegment(seg)
        if s.offs is not None:
            if s.end is not None:
                if (current_sc <= pref_col and
                    pref_col < current_sc + s.sc):
                    # exact match within this segment
                    return calc_text_pos( text,
                        s.offs, s.end,
                        pref_col - current_sc )[0]
                elif current_sc <= pref_col:
                    closest_sc = current_sc + s.sc - 1
                    closest_pos = s

            if closest_sc is None or ( abs(pref_col-current_sc)
                    < abs(pref_col-closest_sc) ):
                # this screen column is closer
                closest_sc = current_sc
                closest_pos = s.offs
            if current_sc > closest_sc:
                # we're moving past
                break
        current_sc += s.sc

    if closest_pos is None or type(closest_pos) == int:
        return closest_pos

    # return the last positions in the segment "closest_pos"
    s = closest_pos
    return calc_text_pos( text, s.offs, s.end, s.sc-1)[0]
Пример #3
0
def html_span(s, aspec, cursor=-1):
    fg_r, fg_g, fg_b, bg_r, bg_g, bg_b = aspec.get_rgb_values()
    # use real colours instead of default fg/bg
    if fg_r is None:
        fg_r, fg_g, fg_b = _d_fg_r, _d_fg_g, _d_fg_b
    if bg_r is None:
        bg_r, bg_g, bg_b = _d_bg_r, _d_bg_g, _d_bg_b
    html_fg = "#%02x%02x%02x" % (fg_r, fg_g, fg_b)
    html_bg = "#%02x%02x%02x" % (bg_r, bg_g, bg_b)
    if aspec.standout:
        html_fg, html_bg = html_bg, html_fg
    extra = (";text-decoration:underline" * aspec.underline +
             ";font-weight:bold" * aspec.bold)

    def html_span(fg, bg, s):
        if not s: return ""
        return ('<span style="color:%s;'
                'background:%s%s">%s</span>' % (fg, bg, extra, html_escape(s)))

    if cursor >= 0:
        c_off, _ign = util.calc_text_pos(s, 0, len(s), cursor)
        c2_off = util.move_next_char(s, c_off, len(s))
        return (html_span(html_fg, html_bg, s[:c_off]) +
                html_span(html_bg, html_fg, s[c_off:c2_off]) +
                html_span(html_fg, html_bg, s[c2_off:]))
    else:
        return html_span(html_fg, html_bg, s)
Пример #4
0
def html_span(s, aspec, cursor = -1):
    fg_r, fg_g, fg_b, bg_r, bg_g, bg_b = aspec.get_rgb_values()
    # use real colours instead of default fg/bg
    if fg_r is None:
        fg_r, fg_g, fg_b = _d_fg_r, _d_fg_g, _d_fg_b
    if bg_r is None:
        bg_r, bg_g, bg_b = _d_bg_r, _d_bg_g, _d_bg_b
    html_fg = "#%02x%02x%02x" % (fg_r, fg_g, fg_b)
    html_bg = "#%02x%02x%02x" % (bg_r, bg_g, bg_b)
    if aspec.standout:
        html_fg, html_bg = html_bg, html_fg
    extra = (";text-decoration:underline" * aspec.underline +
        ";font-weight:bold" * aspec.bold)
    def html_span(fg, bg, s):
        if not s: return ""
        return ('<span style="color:%s;'
            'background:%s%s">%s</span>' %
            (fg, bg, extra, html_escape(s)))

    if cursor >= 0:
        c_off, _ign = util.calc_text_pos(s, 0, len(s), cursor)
        c2_off = util.move_next_char(s, c_off, len(s))
        return (html_span(html_fg, html_bg, s[:c_off]) +
            html_span(html_bg, html_fg, s[c_off:c2_off]) +
            html_span(html_fg, html_bg, s[c2_off:]))
    else:
        return html_span(html_fg, html_bg, s)
Пример #5
0
 def __init__(self, fill_char, cols, rows):
     Canvas.__init__(self)
     end, col = calc_text_pos(fill_char, 0, len(fill_char), 1)
     assert col == 1, "Invalid fill_char: %r" % fill_char
     self._text, cs = apply_target_encoding(fill_char[:end])
     self._cs = cs[0][0]
     self.size = cols, rows
     self.cursor = None
Пример #6
0
 def __init__(self, fill_char, cols, rows):
     Canvas.__init__(self)
     end, col = calc_text_pos(fill_char, 0, len(fill_char), 1)
     assert col == 1, "Invalid fill_char: %r" % fill_char
     self._text, cs = apply_target_encoding(fill_char[:end])
     self._cs = cs[0][0]
     self.size = cols, rows
     self.cursor = None
Пример #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)
Пример #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)
Пример #9
0
def code_span(s, fg, bg, cursor=-1):
    code_fg = _code_colours[fg]
    code_bg = _code_colours[bg]

    if cursor >= 0:
        c_off, _ign = util.calc_text_pos(s, 0, len(s), cursor)
        c2_off = util.move_next_char(s, c_off, len(s))

        return (code_fg + code_bg + s[:c_off] + "\n" + code_bg + code_fg +
                s[c_off:c2_off] + "\n" + code_fg + code_bg + s[c2_off:] + "\n")
    else:
        return code_fg + code_bg + s + "\n"
Пример #10
0
def code_span( s, fg, bg, cursor = -1):
    code_fg = _code_colours[ fg ]
    code_bg = _code_colours[ bg ]
    
    if cursor >= 0:
        c_off, _ign = util.calc_text_pos(s, 0, len(s), cursor)
        c2_off = util.move_next_char(s, c_off, len(s))

        return ( code_fg + code_bg + s[:c_off] + "\n" +
             code_bg + code_fg + s[c_off:c2_off] + "\n" +
             code_fg + code_bg + s[c2_off:] + "\n")
    else:
        return code_fg + code_bg + s + "\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 - text_width(line)
        if diff > 0:
            line += " " * diff
            line_attr.append((fill_attr, diff))
        else:
            from urwid.util import rle_subseg
            line = line[:calc_text_pos(line, 0, len(line), maxcol)[0]]
            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)
Пример #12
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 - text_width(line)
        if diff > 0:
            line += " "*diff
            line_attr.append((fill_attr, diff))
        else:
            from urwid.util import rle_subseg
            line = line[:calc_text_pos(line, 0, len(line), maxcol)[0]]
            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)
Пример #13
0
 def ctptest(self, text, tests):
     text = B(text)
     for s,e,p, expected in tests:
         got = util.calc_text_pos( text, s, e, p )
         assert got == expected, "%r got:%r expected:%r" % ((s,e,p),
                                                            got, expected)
Пример #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
Пример #15
0
 def ctptest(self, text, tests):
     text = B(text)
     for s, e, p, expected in tests:
         got = util.calc_text_pos(text, s, e, p)
         assert got == expected, "%r got:%r expected:%r" % (
             (s, e, p), got, expected)
Пример #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 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
Пример #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 - 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
Пример #18
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