Exemplo n.º 1
0
    def draw_choice(self, y: int) -> None:
        if y + 3 <= self.screen_size.rows:
            self.draw_choice_boxes(y, *self.choices.values())
            return
        self.clickable_ranges.clear()
        current_line = ''
        current_ranges: Dict[str, int] = {}
        width = self.screen_size.cols - 2

        def commit_line(end: str = '\r\n') -> None:
            nonlocal current_line, y
            x = extra_for(wcswidth(current_line), width)
            self.print(' ' * x + current_line, end=end)
            for letter, sz in current_ranges.items():
                self.clickable_ranges[letter] = [Range(x, x + sz - 3, y)]
                x += sz
            current_ranges.clear()
            y += 1
            current_line = ''

        for letter, choice in self.choices.items():
            text = choice.text[:choice.idx]
            text += styled(choice.text[choice.idx], fg=choice.color or 'green', underline='straight' if letter == self.response_on_accept else None)
            text += choice.text[choice.idx + 1:]
            text += '  '
            sz = wcswidth(text)
            if sz + wcswidth(current_line) >= width:
                commit_line()
            current_line += text
            current_ranges[letter] = sz
        if current_line:
            commit_line(end='')
Exemplo n.º 2
0
 def write(self,
           write: Callable[[str], None],
           prompt: str = '',
           screen_cols: int = 0) -> None:
     if self.pending_bell:
         write('\a')
         self.pending_bell = False
     ci = self.current_input
     if self.is_password:
         ci = '*' * wcswidth(ci)
     text = prompt + ci
     cursor_pos = self.cursor_pos + wcswidth(prompt)
     if screen_cols:
         write(SAVE_CURSOR + text + RESTORE_CURSOR)
         used_lines, last_line_cursor_pos = divmod(cursor_pos, screen_cols)
         if used_lines == 0:
             if last_line_cursor_pos:
                 write(move_cursor_by(last_line_cursor_pos, 'right'))
         else:
             if used_lines:
                 write(move_cursor_by(used_lines, 'down'))
             if last_line_cursor_pos:
                 write(move_cursor_by(last_line_cursor_pos, 'right'))
     else:
         write(text)
         write('\r')
         if cursor_pos:
             write(move_cursor_by(cursor_pos, 'right'))
         write(set_cursor_shape('bar'))
Exemplo n.º 3
0
 def draw_status_line(self) -> None:
     if self.state < DIFFED:
         return
     self.enforce_cursor_state()
     self.cmd.set_cursor_position(0, self.num_lines)
     self.cmd.clear_to_eol()
     if self.state is COMMAND:
         self.line_edit.write(self.write)
     elif self.state is MESSAGE:
         self.cmd.styled(self.message, reverse=True)
     else:
         sp = '{:.0%}'.format(self.scroll_pos/self.max_scroll_pos) if self.scroll_pos and self.max_scroll_pos else '0%'
         scroll_frac = styled(sp, fg=self.opts.margin_fg)
         if self.current_search is None:
             counts = '{}{}{}'.format(
                     styled(str(self.added_count), fg=self.opts.highlight_added_bg),
                     styled(',', fg=self.opts.margin_fg),
                     styled(str(self.removed_count), fg=self.opts.highlight_removed_bg)
             )
         else:
             counts = styled('{} matches'.format(len(self.current_search)), fg=self.opts.margin_fg)
         suffix = counts + '  ' + scroll_frac
         prefix = styled(':', fg=self.opts.margin_fg)
         filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix)
         text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
         self.write(text)
Exemplo n.º 4
0
 def draw_status_line(self) -> None:
     if self.state.value < State.diffed.value:
         return
     self.enforce_cursor_state()
     self.cmd.set_cursor_position(0, self.num_lines)
     self.cmd.clear_to_eol()
     if self.state is State.command:
         self.line_edit.write(self.write)
     elif self.state is State.message:
         self.cmd.styled(self.message, reverse=True)
     else:
         sp = f'{self.scroll_pos/self.max_scroll_pos:.0%}' if self.scroll_pos and self.max_scroll_pos else '0%'
         scroll_frac = styled(sp, fg=self.opts.margin_fg)
         if self.current_search is None:
             counts = '{}{}{}'.format(
                 styled(str(self.added_count),
                        fg=self.opts.highlight_added_bg),
                 styled(',', fg=self.opts.margin_fg),
                 styled(str(self.removed_count),
                        fg=self.opts.highlight_removed_bg))
         else:
             counts = styled(f'{len(self.current_search)} matches',
                             fg=self.opts.margin_fg)
         suffix = f'{counts}  {scroll_frac}'
         prefix = styled(':', fg=self.opts.margin_fg)
         filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(
             suffix)
         text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
         self.write(text)
Exemplo n.º 5
0
def fit_in(text, count):
    w = wcswidth(text)
    if w <= count:
        return text
    text = text[:count - 1]
    while wcswidth(text) > count - 1:
        text = text[:-1]
    return text + '…'
Exemplo n.º 6
0
    def test_utils(self):
        def w(x):
            return wcwidth(ord(x))
        self.ae(tuple(map(w, 'a1\0コニチ ✔')), (1, 1, 0, 2, 2, 2, 1, 1))
        self.ae(wcswidth('\u2716\u2716\ufe0f\U0001f337'), 5)
        self.ae(wcswidth('\033a\033[2mb'), 2)
        self.ae(wcswidth('\u25b6\ufe0f'), 2)
        self.ae(wcswidth('\U0001f610\ufe0e'), 1)
        # Regional indicator symbols (unicode flags) are defined as having
        # Emoji_Presentation so must have width 2
        self.ae(tuple(map(w, '\U0001f1ee\U0001f1f3')), (2, 2))
        tpl = truncate_point_for_length
        self.ae(tpl('abc', 4), 3)
        self.ae(tpl('abc', 2), 2)
        self.ae(tpl('abc', 0), 0)
        self.ae(tpl('a\U0001f337', 2), 1)
        self.ae(tpl('a\U0001f337', 3), 2)
        self.ae(tpl('a\U0001f337b', 4), 3)
        self.ae(sanitize_title('a\0\01 \t\n\f\rb'), 'a b')
        self.ae(tpl('a\x1b[31mbc', 2), 7)

        def tp(*data, leftover='', text='', csi='', apc='', ibp=False):
            text_r, csi_r, apc_r, rest = [], [], [], []
            left = ''
            in_bp = ibp

            def on_csi(x):
                nonlocal in_bp
                if x == '200~':
                    in_bp = True
                elif x == '201~':
                    in_bp = False
                csi_r.append(x)

            for d in data:
                left = parse_input_from_terminal(text_r.append, rest.append, on_csi, rest.append, rest.append, apc_r.append, left + d, in_bp)
            self.ae(left, leftover)
            self.ae(text, ' '.join(text_r))
            self.ae(csi, ' '.join(csi_r))
            self.ae(apc, ' '.join(apc_r))
            self.assertFalse(rest)

        tp('a\033[200~\033[32mxy\033[201~\033[33ma', text='a \033[32m xy a', csi='200~ 201~ 33m')
        tp('abc', text='abc')
        tp('a\033[38:5:12:32mb', text='a b', csi='38:5:12:32m')
        tp('a\033_x,;(\033\\b', text='a b', apc='x,;(')
        tp('a\033', '[', 'mb', text='a b', csi='m')
        tp('a\033[', 'mb', text='a b', csi='m')
        tp('a\033', '_', 'x\033', '\\b', text='a b', apc='x')
        tp('a\033_', 'x', '\033', '\\', 'b', text='a b', apc='x')

        for prefix in ('/tmp', tempfile.gettempdir()):
            for path in ('a.png', 'x/b.jpg', 'y/../c.jpg'):
                self.assertTrue(is_path_in_temp_dir(os.path.join(prefix, path)))
        for path in ('/home/xy/d.png', '/tmp/../home/x.jpg'):
            self.assertFalse(is_path_in_temp_dir(os.path.join(path)))
Exemplo n.º 7
0
 def _right(self) -> None:
     if not self.current_input:
         self.cursor_pos = 0
         return
     max_pos = wcswidth(self.current_input)
     if self.cursor_pos >= max_pos:
         self.cursor_pos = max_pos
         return
     before, after = self.split_at_cursor(1)
     self.cursor_pos += 1 + int(wcswidth(before) == self.cursor_pos)
Exemplo n.º 8
0
def render_path_in_width(path: str, width: int) -> str:
    if os.altsep:
        path = path.replace(os.altsep, os.sep)
    if wcswidth(path) <= width:
        return path
    parts = path.split(os.sep)
    reduced = os.sep.join(map(reduce_to_single_grapheme, parts[:-1]))
    path = os.path.join(reduced, parts[-1])
    if wcswidth(path) <= width:
        return path
    x = truncate_point_for_length(path, width - 1)
    return path[:x] + '…'
Exemplo n.º 9
0
 def draw_yesno(self, y: int) -> None:
     yes = styled('Y', fg='green') + 'es'
     no = styled('N', fg='red') + 'o'
     if y + 3 <= self.screen_size.rows:
         self.draw_choice_boxes(y, Choice('Yes', 0, 'green', 'y'), Choice('No', 0, 'red', 'n'))
         return
     sep = ' ' * 3
     text = yes + sep + no
     w = wcswidth(text)
     x = extra_for(w, self.screen_size.cols - 2)
     nx = x + wcswidth(yes) + len(sep)
     self.clickable_ranges = {'y': [Range(x, x + wcswidth(yes) - 1, y)], 'n': [Range(nx, nx + wcswidth(no) - 1, y)]}
     self.print(' ' * x + text, end='')
Exemplo n.º 10
0
    def test_utils(self):
        def w(x):
            return wcwidth(ord(x))

        self.ae(tuple(map(w, 'a1\0コニチ ✔')), (1, 1, 0, 2, 2, 2, 1, 1))
        self.ae(wcswidth('\u2716\u2716\ufe0f\U0001f337'), 5)
        self.ae(wcswidth('\033a\033[2mb'), 2)
        tpl = truncate_point_for_length
        self.ae(tpl('abc', 4), 3)
        self.ae(tpl('abc', 2), 2)
        self.ae(tpl('abc', 0), 0)
        self.ae(tpl('a\U0001f337', 2), 1)
        self.ae(tpl('a\U0001f337', 3), 2)
        self.ae(tpl('a\U0001f337b', 4), 3)
        self.ae(sanitize_title('a\0\01 \t\n\f\rb'), 'a b')
        self.ae(tpl('a\x1b[31mbc', 2), 7)

        def tp(*data, leftover='', text='', csi='', apc='', ibp=False):
            text_r, csi_r, apc_r, rest = [], [], [], []
            left = ''
            in_bp = ibp

            def on_csi(x):
                nonlocal in_bp
                if x == '200~':
                    in_bp = True
                elif x == '201~':
                    in_bp = False
                csi_r.append(x)

            for d in data:
                left = parse_input_from_terminal(text_r.append, rest.append,
                                                 on_csi, rest.append,
                                                 rest.append, apc_r.append,
                                                 left + d, in_bp)
            self.ae(left, leftover)
            self.ae(text, ' '.join(text_r))
            self.ae(csi, ' '.join(csi_r))
            self.ae(apc, ' '.join(apc_r))
            self.assertFalse(rest)

        tp('a\033[200~\033[32mxy\033[201~\033[33ma',
           text='a \033[32m xy a',
           csi='200~ 201~ 33m')
        tp('abc', text='abc')
        tp('a\033[38:5:12:32mb', text='a b', csi='38:5:12:32m')
        tp('a\033_x,;(\033\\b', text='a b', apc='x,;(')
        tp('a\033', '[', 'mb', text='a b', csi='m')
        tp('a\033[', 'mb', text='a b', csi='m')
        tp('a\033', '_', 'x\033', '\\b', text='a b', apc='x')
        tp('a\033_', 'x', '\033', '\\', 'b', text='a b', apc='x')
Exemplo n.º 11
0
 def write(self, write: Callable[[str], None], prompt: str = '') -> None:
     if self.pending_bell:
         write('\a')
         self.pending_bell = False
     write(prompt)
     write(self.current_input)
     write('\r\x1b[{}C'.format(self.cursor_pos + wcswidth(prompt)))
Exemplo n.º 12
0
 def _left(self) -> None:
     if not self.current_input:
         self.cursor_pos = 0
         return
     if self.cursor_pos:
         before, after = self.split_at_cursor(-1)
         self.cursor_pos = wcswidth(before)
Exemplo n.º 13
0
 def write(self, write, prompt=''):
     if self.pending_bell:
         write('\a')
         self.pending_bell = False
     write(prompt)
     write(self.current_input)
     write('\r\x1b[{}C'.format(self.cursor_pos + wcswidth(prompt)))
Exemplo n.º 14
0
 def cell(i: int, idx: str, c: str,
          desc: str) -> Generator[str, None, None]:
     yield colored(idx, 'green') + ' '
     yield colored(c, 'gray', True)
     w = wcswidth(c)
     if w < 2:
         yield ' ' * (2 - w)
Exemplo n.º 15
0
    def _draw_line(self, current_line: AbsoluteLine) -> None:
        y = current_line - self.point.top_line  # type: ScreenLine
        line = self.lines[current_line - 1]
        clear_eol = '\x1b[m\x1b[K'
        sgr0 = '\x1b[m'

        plain = unstyled(line)
        selection_sgr = '\x1b[38{};48{}m'.format(
            color_as_sgr(self.opts.selection_foreground),
            color_as_sgr(self.opts.selection_background))
        start, end = self._start_end()

        # anti-flicker optimization
        if self.mark_type.line_inside_region(current_line, start, end):
            self.cmd.set_cursor_position(0, y)
            self.print('{}{}'.format(selection_sgr, plain), end=clear_eol)
            return

        self.cmd.set_cursor_position(0, y)
        self.print('{}{}'.format(sgr0, line), end=clear_eol)

        if self.mark_type.line_outside_region(current_line, start, end):
            return

        start_x, end_x = self.mark_type.selection_in_line(
            current_line, start, end, wcswidth(plain))
        if start_x is None or end_x is None:
            return

        line_slice, half = string_slice(plain, start_x, end_x)
        self.cmd.set_cursor_position(start_x - (1 if half else 0), y)
        self.print('{}{}'.format(selection_sgr, line_slice), end='')
Exemplo n.º 16
0
    def format_completions(self, substitution: str, matches: Sequence[str],
                           longest_match_length: int) -> None:
        import readline
        print()
        files, dirs = [], []
        for m in matches:
            if m.endswith('/'):
                if len(m) > 1:
                    m = m[:-1]
                dirs.append(m)
            else:
                files.append(m)

        ss = screen_size_function()()
        if dirs:
            print(styled('Directories', bold=True, fg_intense=True))
            print_table(dirs, ss, self.dircolors)
        if files:
            print(styled('Files', bold=True, fg_intense=True))
            print_table(files, ss, self.dircolors)

        buf = readline.get_line_buffer()
        x = readline.get_endidx()
        buflen = wcswidth(buf)
        print(self.prompt, buf, sep='', end='')
        if x < buflen:
            pos = x + self.prompt_len
            print(f"\r\033[{pos}C", end='')
        print(sep='', end='', flush=True)
def mark(text, args, Mark, extra_cli_args, *a):
    if extra_cli_args and extra_cli_args[0] not in button_map:
        print(f"The key `{extra_cli_args[0]}` is unknown.")
        print(f"You must specify one of: {', '.join(button_map.keys())}")
        return
    if args.type == "emoji" or args.type == "emoji_char_and_name":
        import demoji

        if demoji.last_downloaded_timestamp() is None:
            demoji.download_codes()
        demoji.set_emoji_pattern()
        if args.type == "emoji":
            regex = demoji._EMOJI_PAT
        else:
            emoji_name_pattern = r":[a-z0-9_+-]+:"
            regex = re.compile(r"{}|{}".format(demoji._EMOJI_PAT.pattern,
                                               emoji_name_pattern))
        args.minimum_match_length = 1
    else:
        pattern, _ = functions_for(args)
        regex = re.compile(pattern)
    for idx, (s, e, _) in enumerate(
            regex_finditer(regex, args.minimum_match_length, text)):
        lines = text[:s].split("\n")
        y = len(lines) - 1
        x = wcswidth(lines[-1])
        mark_text = text[s:e].replace("\n", "").replace("\0", "")
        yield Mark(idx, s, e, mark_text, {"x": x, "y": y})
Exemplo n.º 18
0
 def draw_status_line(self):
     if self.state < DIFFED:
         return
     self.cmd.set_cursor_position(0, self.num_lines)
     self.cmd.clear_to_eol()
     scroll_frac = styled('{:.0%}'.format(self.scroll_pos /
                                          (self.max_scroll_pos or 1)),
                          fg=self.opts.margin_fg)
     counts = '{}{}{}'.format(
         styled(str(self.added_count), fg=self.opts.highlight_added_bg),
         styled(',', fg=self.opts.margin_fg),
         styled(str(self.removed_count), fg=self.opts.highlight_removed_bg))
     suffix = counts + '  ' + scroll_frac
     prefix = styled(':', fg=self.opts.margin_fg)
     filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix)
     text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
     self.write(text)
Exemplo n.º 19
0
 def update_prompt(self):
     self.update_current_char()
     if self.current_char is None:
         c, color = '??', 'red'
     else:
         c, color = self.current_char, 'green'
     w = wcswidth(c)
     self.prompt = self.prompt_template.format(colored(c, color))
     self.promt_len = w + len(self.prompt_template) - 2
Exemplo n.º 20
0
 def backspace(self, num: int = 1) -> bool:
     before, after = self.split_at_cursor()
     nbefore = before[:-num]
     if nbefore != before:
         self.current_input = nbefore + after
         self.cursor_pos = wcswidth(nbefore)
         return True
     self.pending_bell = True
     return False
Exemplo n.º 21
0
 def delete(self, num: int = 1) -> bool:
     before, after = self.split_at_cursor()
     nafter = after[num:]
     if nafter != after:
         self.current_input = before + nafter
         self.cursor_pos = wcswidth(before)
         return True
     self.pending_bell = True
     return False
Exemplo n.º 22
0
 def word_left(self) -> Position:
     if self.point.x > 0:
         line = unstyled(self.lines[self.point.line - 1])
         pos = truncate_point_for_length(line, self.point.x)
         pred = (self._is_word_char if self._is_word_char(line[pos - 1])
                 else self._is_word_separator)
         new_pos = pos - len(''.join(takewhile(pred, reversed(line[:pos]))))
         return Position(wcswidth(line[:new_pos]), self.point.y,
                         self.point.top_line)
     if self.point.y > 0:
         return Position(
             wcswidth(unstyled(self.lines[self.point.line - 2])),
             self.point.y - 1, self.point.top_line)
     if self.point.top_line > 1:
         return Position(
             wcswidth(unstyled(self.lines[self.point.line - 2])),
             self.point.y, self.point.top_line - 1)
     return self.point
Exemplo n.º 23
0
 def draw_long_text(self, text: str) -> int:
     y = 0
     width = self.screen_size.cols - 2
     while text:
         t, text = truncate_at_space(text, width)
         t = t.strip()
         self.print(' ' * extra_for(wcswidth(t), width), styled(t, bold=True), sep='')
         y += 1
     return y
Exemplo n.º 24
0
def render_progress_in_width(
    path: str,
    max_path_length: int = 80,
    spinner_char: str = '⠋',
    bytes_per_sec: float = 1024,
    secs_so_far: float = 100.,
    bytes_so_far: int = 33070,
    total_bytes: int = 50000,
    width: int = 80,
    is_complete: bool = False,
) -> str:
    unit_style = styled('|', dim=True)
    sep, trail = unit_style.split('|')
    if is_complete or bytes_so_far >= total_bytes:
        ratio = human_size(total_bytes, sep=sep)
        rate = human_size(int(safe_divide(total_bytes, secs_so_far)),
                          sep=sep) + '/s'
        eta = styled(render_seconds(secs_so_far), fg='green')
    else:
        tb = human_size(total_bytes, sep=' ', max_num_of_decimals=1)
        val = float(tb.split(' ', 1)[0])
        ratio = format_number(val * safe_divide(bytes_so_far, total_bytes),
                              max_num_of_decimals=1) + '/' + tb.replace(
                                  ' ', sep)
        rate = human_size(int(bytes_per_sec), sep=sep) + '/s'
        bytes_left = total_bytes - bytes_so_far
        eta_seconds = safe_divide(bytes_left, bytes_per_sec)
        eta = render_seconds(eta_seconds)
    lft = f'{spinner_char} '
    max_space_for_path = width // 2 - wcswidth(lft)
    w = min(max_path_length, max_space_for_path)
    p = lft + render_path_in_width(path, w)
    w += wcswidth(lft)
    p = ljust(p, w)
    q = f'{ratio}{trail}{styled(" @ ", fg="yellow")}{rate}{trail}'
    q = rjust(q, 25) + ' '
    eta = ' ' + eta
    extra = width - w - wcswidth(q) - wcswidth(eta)
    if extra > 4:
        q += render_progress_bar(safe_divide(bytes_so_far, total_bytes),
                                 extra) + eta
    else:
        q += eta.strip()
    return p + q
Exemplo n.º 25
0
 def add_text(self, text: str) -> None:
     if self.current_input:
         x = truncate_point_for_length(
             self.current_input, self.cursor_pos) if self.cursor_pos else 0
         self.current_input = self.current_input[:
                                                 x] + text + self.current_input[
                                                     x:]
     else:
         self.current_input = text
     self.cursor_pos += wcswidth(text)
Exemplo n.º 26
0
 def commit_line(end: str = '\r\n') -> None:
     nonlocal current_line, y
     x = extra_for(wcswidth(current_line), width)
     self.print(' ' * x + current_line, end=end)
     for letter, sz in current_ranges.items():
         self.clickable_ranges[letter] = [Range(x, x + sz - 3, y)]
         x += sz
     current_ranges.clear()
     y += 1
     current_line = ''
Exemplo n.º 27
0
 def draw_screen(self) -> None:
     self.cmd.clear_screen()
     y = 1
     if self.cli_opts.message:
         self.cmd.styled(self.cli_opts.message, bold=True)
         y += wcswidth(self.cli_opts.message) // self.screen_size.cols
     self.print()
     if self.cli_opts.type == 'yesno':
         self.draw_yesno(y)
     else:
         self.draw_choice(y)
Exemplo n.º 28
0
 def print_line(add_borders: Callable[[str], str], *items: Tuple[str, str], is_last: bool = False) -> None:
     nonlocal y
     texts = []
     positions = []
     x = 0
     for (letter, text) in items:
         positions.append((letter, x, wcswidth(text) + 2))
         text = add_borders(text)
         if letter == self.response_on_accept:
             text = highlight(text, only_edges=add_borders is middle)
         text += sep
         x += wcswidth(text)
         texts.append(text)
     line = ''.join(texts).rstrip()
     offset = extra_for(wcswidth(line), width)
     for (letter, x, sz) in positions:
         x += offset
         self.clickable_ranges[letter].append(Range(x, x + sz - 1, y))
     self.print(' ' * offset, line, sep='', end='' if is_last else '\r\n')
     y += 1
Exemplo n.º 29
0
 def draw_title_bar(self) -> None:
     entries = []
     for name, key, mode in all_modes:
         entry = ' {} ({}) '.format(name, key)
         if mode is self.mode:
             entry = styled(entry, reverse=False, bold=True)
         entries.append(entry)
     text = _('Search by:{}').format(' '.join(entries))
     extra = self.screen_size.cols - wcswidth(text)
     if extra > 0:
         text += ' ' * extra
     self.print(styled(text, reverse=True))
Exemplo n.º 30
0
 def word_right(self) -> Position:
     line = unstyled(self.lines[self.point.line - 1])
     pos = truncate_point_for_length(line, self.point.x)
     if pos < len(line):
         pred = (self._is_word_char if self._is_word_char(line[pos]) else
                 self._is_word_separator)
         new_pos = pos + len(''.join(takewhile(pred, line[pos:])))
         return Position(wcswidth(line[:new_pos]), self.point.y,
                         self.point.top_line)
     if self.point.y < self.screen_size.rows - 1:
         return Position(0, self.point.y + 1, self.point.top_line)
     if self.point.top_line + self.point.y < len(self.lines):
         return Position(0, self.point.y, self.point.top_line + 1)
     return self.point