def get_completions(self, document, complete_event): if not document.current_line.strip(): return used, matches = self.ipy_completer.complete( line_buffer=document.current_line, cursor_pos=document.cursor_position_col ) start_pos = -len(used) for m in matches: m = unicodedata.normalize('NFC', m) # When the first character of the completion has a zero length, # then it's probably a decomposed unicode character. E.g. caused by # the "\dot" completion. Try to compose again with the previous # character. if wcwidth(m[0]) == 0: if document.cursor_position + start_pos > 0: char_before = document.text[document.cursor_position + start_pos - 1] m = unicodedata.normalize('NFC', char_before + m) # Yield the modified completion instead, if this worked. if wcwidth(m[0:1]) == 1: yield Completion(m, start_position=start_pos - 1) continue yield Completion(m, start_position=start_pos)
def get_completions(self, document, complete_event): if not document.current_line.strip(): return used, matches = self.ipy_completer.complete( line_buffer=document.current_line, cursor_pos=document.cursor_position_col ) start_pos = -len(used) for m in matches: if not m: # Guard against completion machinery giving us an empty string. continue m = unicodedata.normalize("NFC", m) # When the first character of the completion has a zero length, # then it's probably a decomposed unicode character. E.g. caused by # the "\dot" completion. Try to compose again with the previous # character. if wcwidth(m[0]) == 0: if document.cursor_position + start_pos > 0: char_before = document.text[document.cursor_position + start_pos - 1] m = unicodedata.normalize("NFC", char_before + m) # Yield the modified completion instead, if this worked. if wcwidth(m[0:1]) == 1: yield Completion(m, start_position=start_pos - 1) continue # TODO: Use Jedi to determine meta_text # (Jedi currently has a bug that results in incorrect information.) # meta_text = '' # yield Completion(m, start_position=start_pos, # display_meta=meta_text) yield Completion(m, start_position=start_pos)
def _get_completions(body, offset, cursor_position, ipyc): """ Private equivalent of get_completions() use only for unit_testing. """ debug = getattr(ipyc, 'debug', False) completions = _deduplicate_completions( body, ipyc.completions(body, offset)) for c in completions: if not c.text: # Guard against completion machinery giving us an empty string. continue text = unicodedata.normalize('NFC', c.text) # When the first character of the completion has a zero length, # then it's probably a decomposed unicode character. E.g. caused by # the "\dot" completion. Try to compose again with the previous # character. if wcwidth(text[0]) == 0: if cursor_position + c.start > 0: char_before = body[c.start - 1] fixed_text = unicodedata.normalize( 'NFC', char_before + text) # Yield the modified completion instead, if this worked. if wcwidth(text[0:1]) == 1: yield Completion(fixed_text, start_position=c.start - offset - 1) continue # TODO: Use Jedi to determine meta_text # (Jedi currently has a bug that results in incorrect information.) # meta_text = '' # yield Completion(m, start_position=start_pos, # display_meta=meta_text) yield Completion(c.text, start_position=c.start - offset, display_meta=c.type)
def draw(self): columns, lines = shutil.get_terminal_size((80, 20)) text = '\r' text += format_time(self.cur_pos) + '/' + format_time( self.duration) + ' | ' text += self.player_status.value text += ' current: ' text_columns = sum(wcwidth(c) for c in text) remain_columns = columns - text_columns - 5 if remain_columns > 0: msg = self._video_file_name if self._exception is not None: msg = str(self._exception) name_columns = sum(wcwidth(c) for c in msg) if name_columns <= remain_columns: text += msg text += ' ' * (remain_columns - name_columns) else: # 剩余空间太小就不显示 if remain_columns > 5: # 截取头部和尾部显示 part_len = int((remain_columns - 2) / 2) text += msg[:part_len - 1] + '..' + msg[-part_len + 1:] else: text += ' ' * (remain_columns - name_columns) stdout.write(text)
def get_completions(self, document, complete_event): if not document.current_line.strip(): return used, matches = self.ipy_completer.complete( line_buffer=document.current_line, cursor_pos=document.cursor_position_col) start_pos = -len(used) for m in matches: m = unicodedata.normalize('NFC', m) # When the first character of the completion has a zero length, # then it's probably a decomposed unicode character. E.g. caused by # the "\dot" completion. Try to compose again with the previous # character. if wcwidth(m[0]) == 0: if document.cursor_position + start_pos > 0: char_before = document.text[document.cursor_position + start_pos - 1] m = unicodedata.normalize('NFC', char_before + m) # Yield the modified completion instead, if this worked. if wcwidth(m[0:1]) == 1: yield Completion(m, start_position=start_pos - 1) continue # TODO: Use Jedi to determine meta_text # (Jedi currently has a bug that results in incorrect information.) # meta_text = '' # yield Completion(m, start_position=start_pos, # display_meta=meta_text) yield Completion(m, start_position=start_pos)
def __missing__(self, string): # Note: We use the `max(0, ...` because some non printable control # characters, like e.g. Ctrl-underscore get a -1 wcwidth value. # It can be possible that these characters end up in the input # text. if len(string) == 1: result = max(0, wcwidth(string)) else: result = sum(max(0, wcwidth(c)) for c in string) self[string] = result return result
def chop(line, width=None): if width is None: width = term.width nline = list(blessed.sequences.iter_parse(term, line)) length = sum(max(wcwidth(text), 0) for text, cap in nline if cap is None) endcaps = [] while length > width and nline: text, cap = nline.pop() if cap is None: length -= max(wcwidth(text), 0) else: endcaps.insert(0, text) return ''.join(text for text, _ in nline) + ''.join(endcaps)
def _get_completions(body, offset, cursor_position, ipyc): """ Private equivalent of get_completions() use only for unit_testing. """ debug = getattr(ipyc, "debug", False) completions = _deduplicate_completions(body, ipyc.completions(body, offset)) for c in completions: if not c.text: # Guard against completion machinery giving us an empty string. continue text = unicodedata.normalize("NFC", c.text) # When the first character of the completion has a zero length, # then it's probably a decomposed unicode character. E.g. caused by # the "\dot" completion. Try to compose again with the previous # character. if wcwidth(text[0]) == 0: if cursor_position + c.start > 0: char_before = body[c.start - 1] fixed_text = unicodedata.normalize("NFC", char_before + text) # Yield the modified completion instead, if this worked. if wcwidth(text[0:1]) == 1: yield Completion(fixed_text, start_position=c.start - offset - 1) continue # TODO: Use Jedi to determine meta_text # (Jedi currently has a bug that results in incorrect information.) # meta_text = '' # yield Completion(m, start_position=start_pos, # display_meta=meta_text) display_text = c.text adjusted_text = _adjust_completion_text_based_on_context( c.text, body, offset) if c.type == "function": yield Completion( adjusted_text, start_position=c.start - offset, display=_elide(display_text + "()"), display_meta=c.type + c.signature, ) else: yield Completion( adjusted_text, start_position=c.start - offset, display=_elide(display_text), display_meta=c.type, )
def make_widths_table() -> List[Tuple[int, int, int]]: table: List[Tuple[int, int, int]] = [] append = table.append make_table_task = progress.add_task("Calculating table...") widths = ((codepoint, wcwidth(chr(codepoint))) for codepoint in range(0, sys.maxunicode + 1)) _widths = [(codepoint, width) for codepoint, width in widths if width != 1] iter_widths = iter(_widths) endpoint, group_cell_size = next(iter_widths) start_codepoint = end_codepoint = endpoint for codepoint, cell_size in progress.track(iter_widths, task_id=make_table_task, total=len(_widths) - 1): if cell_size != group_cell_size or codepoint != end_codepoint + 1: append((start_codepoint, end_codepoint, group_cell_size)) start_codepoint = end_codepoint = codepoint group_cell_size = cell_size else: end_codepoint = codepoint append((start_codepoint, end_codepoint, group_cell_size)) return table
def on_config_changed(self, update): if "system_name" in update: self._all_keys = "".join(key.strip("-") for key in system.KEYS) self._all_keys_filler = [" " * wcwidth(k) for k in self._all_keys] self._numbers = set(system.NUMBERS.values()) # needs +2 to account for the Frame edges self.container.width = len(self._all_keys) + 2
def post(): if request.json['pwd'] != pwd: abort(403) channel = re.sub(' - Topic$', '', request.json['channel']) output = f"{channel} - {request.json['song']}" if request.json['chapter']: output += f" - {request.json['chapter']}" if wcswidth(output) > MAX_LENGTH: trimmed = [] count = 1 for c in reversed(output[-MAX_LENGTH:]): count += wcwidth(c) if count > MAX_LENGTH: break trimmed.append(c) output = f'…{"".join(reversed(trimmed))}' output = f'♫ {output}' with open('song.txt', 'w') as f: f.write(output) with open('url.txt', 'w') as f: f.write(f"{channel} - {request.json['song']} {request.json['url']}") return ''
def _enforce_width(text, width, unicode_aware=True): """ Enforce a displayed piece of text to be a certain number of cells wide. This takes into account double-width characters used in CJK languages. :param text: The text to be truncated :param width: The screen cell width to enforce :return: The resulting truncated text """ # Double-width strings cannot be more than twice the string length, so no need to try # expensive truncation if this upper bound isn't an issue. if (2 * len(text) < width) or (len(text) < width and not unicode_aware): return text # Can still optimize performance if we are not handling unicode characters. if unicode_aware: size = 0 for i, char in enumerate(str(text)): c_width = wcwidth(char) if ord(char) >= 256 else 1 if size + c_width > width: return text[0:i] size += c_width elif len(text) + 1 > width: return text[0:width] return text
def update(self, frame_no): for i in range(self._h): self._frame.canvas.print_at(" " * self.width, self._x, self._y + i, self._flush_brush.fg, self._flush_brush.att, self._flush_brush.bg) max_x = self._w + self._x - 1 max_y = self._h + self._y - 1 x = self._x y = self._y for l in self._value[self._scrl_offset:]: if y > max_y: break for c in l: w = wcwidth(c.ch) if x + w - 1 > max_x: break self._frame.canvas.print_at(c.ch, x, y, c.brush.fg, c.brush.att, c.brush.bg) x += w x = self._x y += 1
def ansilen_unicode(s): if isinstance(s, string_types): s_without_ansi = unicodedata.normalize('NFC', ANSIRE.sub('', s)) s_without_ansi = s_without_ansi.replace("\n", "_") return sum(map(lambda c: max(wcwidth.wcwidth(c), 0), s_without_ansi)) else: return len(s)
def length(self): r""" Return the printable length of string containing sequences. Strings containing ``term.left`` or ``\b`` will cause "overstrike", but a length less than 0 is not ever returned. So ``_\b+`` is a length of 1 (displays as ``+``), but ``\b`` alone is simply a length of 0. Some characters may consume more than one cell, mainly those CJK Unified Ideographs (Chinese, Japanese, Korean) defined by Unicode as half or full-width characters. For example: >>> from blessed import Terminal >>> from blessed.sequences import Sequence >>> term = Terminal() >>> msg = term.clear + term.red(u'コンニチハ') >>> Sequence(msg, term).length() 10 .. note:: Although accounted for, strings containing sequences such as ``term.clear`` will not give accurate returns, it is not considered lengthy (a length of 0). """ # because control characters may return -1, "clip" their length to 0. return sum(max(wcwidth(w_char), 0) for w_char in self.padd(strip=True))
def trunc(text, length): """ Truncates text to given length, taking into account wide characters. If truncated, the last char is replaced by an elipsis. """ if length < 1: raise ValueError("length should be 1 or larger") # Remove whitespace first so no unneccesary truncation is done. text = text.strip() text_length = wcswidth(text) if text_length <= length: return text # We cannot just remove n characters from the end since we don't know how # wide these characters are and how it will affect text length. # Use wcwidth to determine how many characters need to be truncated. chars_to_truncate = 0 trunc_length = 0 for char in reversed(text): chars_to_truncate += 1 trunc_length += wcwidth(char) if text_length - trunc_length <= length: break # Additional char to make room for elipsis n = chars_to_truncate + 1 return text[:-n].strip() + '…'
def __missing__(self, string: str) -> int: # Note: We use the `max(0, ...` because some non printable control # characters, like e.g. Ctrl-underscore get a -1 wcwidth value. # It can be possible that these characters end up in the input # text. result: int if len(string) == 1: result = max(0, wcwidth(string)) else: result = sum(self[c] for c in string) # Store in cache. self[string] = result # Rotate long strings. # (It's hard to tell what we can consider short...) if len(string) > self.LONG_STRING_MIN_LEN: long_strings = self._long_strings long_strings.append(string) if len(long_strings) > self.MAX_LONG_STRINGS: key_to_remove = long_strings.popleft() if key_to_remove in self: del self[key_to_remove] return result
def segment_buffer_line(buffer_line): """ segment a buffer line based on bg and fg colors """ is_wide_char = False text = "" start = 0 counter = 0 fg = "default" bg = "default" for i in buffer_line: if is_wide_char: is_wide_char = False continue char = buffer_line[i] is_wide_char = wcwidth(char.data) == 2 if counter == 0: counter = i text = " " * i if fg != char.fg or bg != char.bg: yield text, start, counter, fg, bg fg = char.fg bg = char.bg text = char.data start = counter else: text += char.data counter += 1 yield text, start, counter, fg, bg
def length(self): """S.length() -> int Returns printable length of unicode string ``S`` that may contain terminal sequences. Although accounted for, strings containing sequences such as ``term.clear`` will not give accurate returns, it is not considered lengthy (a length of 0). Combining characters, are also not considered lengthy. Strings containing ``term.left`` or ``\b`` will cause "overstrike", but a length less than 0 is not ever returned. So ``_\b+`` is a length of 1 (``+``), but ``\b`` is simply a length of 0. Some characters may consume more than one cell, mainly those CJK Unified Ideographs (Chinese, Japanese, Korean) defined by Unicode as half or full-width characters. For example: >>> from blessed import Terminal >>> from blessed.sequences import Sequence >>> term = Terminal() >>> Sequence(term.clear + term.red(u'コンニチハ')).length() 5 """ # because combining characters may return -1, "clip" their length to 0. clip = functools.partial(max, 0) return sum( clip(wcwidth.wcwidth(w_char)) for w_char in self.strip_seqs())
def width(self) -> float: # assume monospace glyph: fontforge.glyph for glyph in self.open().glyphs(): if glyph.unicode > 0: return glyph.width / wcwidth(chr(glyph.unicode)) raise "No valid glyph found in {}".format(self.uri)
def length(self): """S.length() -> int Returns printable length of unicode string ``S`` that may contain terminal sequences. Although accounted for, strings containing sequences such as ``term.clear`` will not give accurate returns, it is not considered lengthy (a length of 0). Combining characters, are also not considered lengthy. Strings containing ``term.left`` or ``\b`` will cause "overstrike", but a length less than 0 is not ever returned. So ``_\b+`` is a length of 1 (``+``), but ``\b`` is simply a length of 0. Some characters may consume more than one cell, mainly those CJK Unified Ideographs (Chinese, Japanese, Korean) defined by Unicode as half or full-width characters. For example: >>> from blessed import Terminal >>> from blessed.sequences import Sequence >>> term = Terminal() >>> Sequence(term.clear + term.red(u'コンニチハ')).length() 5 """ # because combining characters may return -1, "clip" their length to 0. clip = functools.partial(max, 0) return sum(clip(wcwidth.wcwidth(w_char)) for w_char in self.strip_seqs())
def __init__(self, width=2): """ ``cjk`` Treat ambiguous-width characters as double-width """ self.characters = (unichr(idx) for idx in xrange(LIMIT_UCS) if wcwidth(unichr(idx)) == width)
def __init__(self, string): self._string = [] self._state = [] self._width = [] self._termwidth = 0 state = set() for token in re.split(self.ANSI_REGEX, to_unicode(string)): if token: if re.match(self.ANSI_REGEX, token): if token == self.ANSI_RESET: state.clear() else: state.add(token) else: s_copy = set(state) for char in token: w = wcwidth(char) if w == -1: raise ValueError( ("Unsupported Literal {} in " "string {}").format(repr(char), repr(token))) self._termwidth += w self._string.append(char) self._width.append(w) self._state.append(s_copy)
def truncate(self, width): """ Truncate a string in a sequence-aware manner. Any printable characters beyond ``width`` are removed, while all sequences remain in place. Horizontal Sequences are first expanded by :meth:`padd`. :arg int width: The printable width to truncate the string to. :rtype: str :returns: String truncated to at most ``width`` printable characters. """ output = "" current_width = 0 target_width = width.__index__() parsed_seq = iter_parse(self._term, self.padd()) # Retain all text until non-cap width reaches desired width for text, cap in parsed_seq: if not cap: # use wcwidth clipped to 0 because it can sometimes return -1 current_width += max(wcwidth(text), 0) if current_width > target_width: break output += text # Return with remaining caps appended return output + ''.join(text for text, cap in parsed_seq if cap)
def wctrim(s, max_w): """Return pair s, w which is a string and the cell width of that string. The string is trimmed so that w <= max_w. Characters which wcwidth() reports as having negative width are removed. """ assert max_w >= 0 # Common case: string needs no trimming w = wcswidth(s) if w >= 0 and w <= max_w: return s, w # Otherwise, walk character by character w, chs = 0, [] for ch in s: ch_w = wcwidth(ch) if ch_w < 0: continue if w + ch_w > max_w: return ''.join(chs), w chs.append(ch) w += ch_w return ''.join(chs), w
def widthof(self, text): if isinstance(text, Markup): if isinstance(text, Text): return self.widthof(text.string) elif isinstance(text, (CSI, ControlCharacter)): return -1 elif isinstance(text, (Group, SGR)): width = 0 for child in text.children: w = self.widthof(child) if w == -1: return -1 width += w return width else: raise TypeError(f"unknown markup type: {type(text)}") else: width = 0 for ch in text: w = wcwidth.wcwidth(ch, self.unicode_version) if w == -1: return -1 width += w return width
def draw_regions(win, regions, y=None, x=None, max_w=None, starting_at=None): # pylint: disable=too-many-locals,too-many-branches,too-many-arguments # Get cursor position and window size cy, cx = win.getyx() nl, nc = win.getmaxyx() # Set default values if y is None: y = cy if x is None: x = cx if starting_at is None: starting_at = 0 if max_w is None: max_w = max(0, nc - x) # Use max_w to set nc nc = min(nc, x + max_w) # Abort if x or y outside of window if x < 0 or x >= nc or y < 0 or y >= nl: return # Otherwise, let's go for region in regions: text, style = region attr = style_attr(style) # Get width of region in cells and remaining space region_w = wcswidth(text) w_remaining = nc - x assert w_remaining >= 0 if region_w != -1 and w_remaining >= region_w: # If this text fits in the remaining space, just use addstr. Note # that we need to silently ignore any errors from addstr as per # https://bugs.python.org/issue8243 try: win.addstr(y, x, text, attr) except curses.error: pass x += region_w else: # The remaining space is too small, add character-by-character for c in text: c_w = wcwidth(c) if c_w == -1: continue if w_remaining >= c_w: try: win.addstr(y, x, c, attr) except curses.error: pass x += c_w w_remaining -= c_w # We're done if we're past the end of the line if x >= nc: break
def on_config_changed(self, config): if 'system_name' in config: self._strokes = [] self._all_keys = ''.join(key.strip('-') for key in system.KEYS) self._all_keys_filler = [' ' * wcwidth(k) for k in self._all_keys] self._numbers = set(system.NUMBERS.values()) self.header.setText(self._all_keys) self.on_style_changed(self._style)
def wctruncate(text, width=80): for i, c in enumerate(text): w = wcwidth(c) if w > 0: width -= w if width < 0: return text[:i] return text
def reset(self): self.modelAboutToBeReset.emit() self._all_keys = ''.join(key.strip('-') for key in system.KEYS) self._all_keys_filler = [' ' * wcwidth(k) for k in self._all_keys] self._numbers = set(system.NUMBERS.values()) self._stroke_list.clear() self.modelReset.emit() return self._all_keys
def _get_width(c): """ Return width of character. Wrapper around ``wcwidth``. """ try: return _CHAR_SIZES_CACHE[ord(c)] except IndexError: return wcwidth(c)
def wcfilter(ch): """Return x/X for non-printable characters.""" width = wcwidth.wcwidth(ch) if width == -1: return 'x' elif width == 0: return 'X' else: return ch
def which_char(text, cursor_position): w = 0 i = 0 # loop over to check for double width chars for i, c in enumerate(text): w += wcwidth(c) if w >= cursor_position: break return i
def __init__(self, width=2): """ ``cjk`` Treat ambiguous-width characters as double-width """ self.characters = (unichr(idx) for idx in xrange(LIMIT_UCS) if wcwidth(unichr(idx)) == width )
def test(widths_table): for codepoint in progress.track(range(0, sys.maxunicode + 1), description="Testing..."): character = chr(codepoint) width1 = get_cell_size(widths_table, character) width2 = wcwidth(character) if width1 != width2: print(f"{width1} != {width2}") break
def render(line): it = iter(line) while True: char = next(it).data assert sum(map(wcwidth, char[1:])) == 0 char_width = wcwidth(char[0]) if char_width == 1: yield char elif char_width == 2: yield char next(it) # Skip stub.
def __init__(self, width=2): """ Class constructor. :param width: generate characters of given width. :type width: int """ self.characters = (unichr(idx) for idx in xrange(LIMIT_UCS) if wcwidth(unichr(idx)) == width )
def on_config_changed(self, config): if 'system_name' in config: self._strokes = [] self._all_keys = ''.join(key.strip('-') for key in system.KEYS) self._all_keys_filler = [ ' ' * wcwidth(k) for k in self._all_keys ] self._numbers = set(system.NUMBERS.values()) self.header.setText(self._all_keys) self.on_style_changed(self._style)
def draw(self, data): """Displays decoded characters at the current cursor position and advances the cursor if :data:`~pyte.modes.DECAWM` is set. :param bytes data: bytes to display. .. versionchanged:: 0.5.0 Character width is taken into account. Specifically, zero-width and unprintable characters do not affect screen state. Full-width characters are rendered into two consecutive character containers. .. versionchanged:: 0.6.0 The input is now supposed to be in :func:`bytes`, which may encode multiple characters. """ for char in self._decode(data): char_width = wcwidth(char) if char_width <= 0: # Unprintable character or doesn't advance the cursor. return # If this was the last column in a line and auto wrap mode is # enabled, move the cursor to the beginning of the next line, # otherwise replace characters already displayed with newly # entered. if self.cursor.x == self.columns: if mo.DECAWM in self.mode: self.carriage_return() self.linefeed() else: self.cursor.x -= char_width # If Insert mode is set, new characters move old characters to # the right, otherwise terminal is in Replace mode and new # characters replace old characters at cursor position. if mo.IRM in self.mode: self.insert_characters(char_width) line = self.buffer[self.cursor.y] line[self.cursor.x] = self.cursor.attrs._replace(data=char) if char_width > 1: # Add a stub *after* a two-cell character. See issue #9 # on GitHub. line[self.cursor.x + 1] = self.cursor.attrs._replace(data=" ") # .. note:: We can't use :meth:`cursor_forward()`, because that # way, we'll never know when to linefeed. self.cursor.x += char_width
def _disp_width(self, pwcs, n=None): """ A wcswidth that never gives -1. Copying existing code is evil, but.. github.com/jquast/wcwidth/blob/07cea7f/wcwidth/wcwidth.py#L182-L204 """ # pylint: disable=C0103 # Invalid argument name "n" # TODO: Shall we consider things like ANSI escape seqs here? # We can implement some ignore-me segment like those wrapped by # \1 and \2 in readline too. end = len(pwcs) if n is None else n idx = slice(0, end) width = 0 for char in pwcs[idx]: width += max(0, wcwidth(char)) return width
def length(self): r""" Return the printable length of string containing sequences. Strings containing ``term.left`` or ``\b`` will cause "overstrike", but a length less than 0 is not ever returned. So ``_\b+`` is a length of 1 (displays as ``+``), but ``\b`` alone is simply a length of 0. Some characters may consume more than one cell, mainly those CJK Unified Ideographs (Chinese, Japanese, Korean) defined by Unicode as half or full-width characters. """ # because control characters may return -1, "clip" their length to 0. clip = functools.partial(max, 0) return sum(clip(wcwidth.wcwidth(w_char)) for w_char in self.strip_seqs())
def text_entry(self, ucs, name): """ Display a single column segment row describing ``(ucs, name)``. :param ucs: target unicode point character string. :param name: name of unicode point. :rtype: unicode """ style = self.screen.style if len(name) > style.name_len: idx = max(0, style.name_len - len(style.continuation)) name = u''.join((name[:idx], style.continuation if idx else u'')) if style.alignment == 'right': fmt = u' '.join(('0x{val:0>{ucs_printlen}x}', '{name:<{name_len}s}', '{delimiter}{ucs}{delimiter}' )) else: fmt = u' '.join(('{delimiter}{ucs}{delimiter}', '0x{val:0>{ucs_printlen}x}', '{name:<{name_len}s}')) delimiter = style.attr_minor(style.delimiter) if len(ucs) != 1: # determine display of combining characters val = ord(next((_ucs for _ucs in ucs if wcwidth(_ucs) == -1))) # a combining character displayed of any fg color # will reset the foreground character of the cell # combined with (iTerm2, OSX). disp_ucs = style.attr_major(ucs[0:2]) if len(ucs) > 2: disp_ucs += ucs[2] else: # non-combining val = ord(ucs) disp_ucs = style.attr_major(ucs) return fmt.format(name_len=style.name_len, ucs_printlen=UCS_PRINTLEN, delimiter=delimiter, name=name, ucs=disp_ucs, val=val)
def character_width(char): r""" Determine the width that a character is likely to be displayed as in a monospaced terminal. The width for a printable character will always be 0, 1, or 2. Nonprintable or control characters will return -1, a convention that comes from wcwidth. >>> character_width('車') 2 >>> character_width('A') 1 >>> character_width('\N{ZERO WIDTH JOINER}') 0 >>> character_width('\n') -1 """ return wcwidth(char)
def _is_equal_wcwidth(libc, ucs): w_libc = libc.wcwidth(ucs) w_local = wcwidth.wcwidth(ucs) assert w_libc == w_local, report_ucs_msg(ucs, w_libc, w_local)
import wcwidth for x in range(0, 0x10FFFF): print("%04x %s" % (x, wcwidth.wcwidth(chr(x))))
def draw(self, data): """Displays decoded characters at the current cursor position and advances the cursor if :data:`~pyte.modes.DECAWM` is set. :param bytes data: bytes to display. .. versionchanged:: 0.5.0 Character width is taken into account. Specifically, zero-width and unprintable characters do not affect screen state. Full-width characters are rendered into two consecutive character containers. .. versionchanged:: 0.6.0 The input is now supposed to be in :func:`bytes`, which may encode multiple characters. """ for char in self._decode(data): char_width = wcwidth(char) # If this was the last column in a line and auto wrap mode is # enabled, move the cursor to the beginning of the next line, # otherwise replace characters already displayed with newly # entered. if self.cursor.x == self.columns: if mo.DECAWM in self.mode: self.carriage_return() self.linefeed() elif char_width > 0: self.cursor.x -= char_width # If Insert mode is set, new characters move old characters to # the right, otherwise terminal is in Replace mode and new # characters replace old characters at cursor position. if mo.IRM in self.mode and char_width > 0: self.insert_characters(char_width) line = self.buffer[self.cursor.y] if char_width == 1: line[self.cursor.x] = self.cursor.attrs._replace(data=char) elif char_width == 2: # A two-cell character has a stub slot after it. line[self.cursor.x] = self.cursor.attrs._replace(data=char) if self.cursor.x + 1 < self.columns: line[self.cursor.x + 1] = self.cursor.attrs._replace(data=" ") elif char_width == 0 and unicodedata.combining(char): # A zero-cell character is combined with the previous # character either on this or preceeding line. if self.cursor.x: last = line[self.cursor.x - 1] normalized = unicodedata.normalize("NFC", last.data + char) line[self.cursor.x - 1] = last._replace(data=normalized) elif self.cursor.y: last = self.buffer[self.cursor.y - 1][self.columns - 1] normalized = unicodedata.normalize("NFC", last.data + char) self.buffer[self.cursor.y - 1][self.columns - 1] = \ last._replace(data=normalized) else: pass # Unprintable character or doesn't advance the cursor. # .. note:: We can't use :meth:`cursor_forward()`, because that # way, we'll never know when to linefeed. if char_width > 0: self.cursor.x = min(self.cursor.x + char_width, self.columns)
'Point', 'Renderer', 'Screen', ) Point = namedtuple('Point', 'y x') Size = namedtuple('Size', 'rows columns') #: If True: write the output of the renderer also to the following file. This #: is very useful for debugging. (e.g.: to see that we don't write more bytes #: than required.) _DEBUG_RENDER_OUTPUT = False _DEBUG_RENDER_OUTPUT_FILENAME = '/tmp/prompt-toolkit-render-output' #: Cache for wcwidth sizes. _CHAR_SIZES_CACHE = [wcwidth(six.unichr(i)) for i in range(0, 64000)] def _get_width(c): """ Return width of character. Wrapper around ``wcwidth``. """ try: return _CHAR_SIZES_CACHE[ord(c)] except IndexError: return wcwidth(c) class TerminalCodes: """ Escape codes for a VT100 terminal.