From: Simon Tatham Date: Sat, 9 Dec 2023 16:16:14 +0000 (+0000) Subject: Ability to prompt on the bottom screen line. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;h=84b03e2e1a449a70b5bf45f43fc8c62bf1a3a490;p=mastodonochrome.git Ability to prompt on the bottom screen line. --- diff --git a/cursesclient.py b/cursesclient.py index 64b6cf1..0dbf77c 100644 --- a/cursesclient.py +++ b/cursesclient.py @@ -48,7 +48,7 @@ class CursesUI(client.Client): default_fg = default_bg = None self.scr.keypad(1) - self.scr.scrollok(1) + self.scr.scrollok(0) self.scr.nodelay(1) # so we can use get_wch() via a select loop curses.noecho() self.scr_h, self.scr_w = self.scr.getmaxyx() @@ -98,9 +98,15 @@ class CursesUI(client.Client): def move_cursor_to(self, y, x): self.scr.move(y, x) + def clear_line(self, y): + try: + self.print_at(y, 0, text.ColouredString(' ' * self.scr_w)) + except curses.error: + pass # this can happen if clearing the bottom right screen corner + def clear(self): for y in range(self.scr_h): - self.print_at(y, 0, text.ColouredString(' ' * self.scr_w)) + self.clear_line(y) def get_wch(self): try: @@ -791,7 +797,99 @@ class StatusInfoFile(ObjectFile): super().__init__( cc, lambda x,cc:x, client.StatusInfoFeed(cc, self.data), title) -class Composer(Activity): +class EditorCommon: + def move_to(self, pos): + self.point = max(0, min(len(self.text), pos)) + + def handle_common_editing_keys(self, ch): + if ch in {ctrl('b'), curses.KEY_LEFT}: + self.move_to(self.point - 1) + elif ch in {ctrl('f'), curses.KEY_RIGHT}: + self.move_to(self.point + 1) + elif ch in {ctrl('h'), '\x7F', curses.KEY_BACKSPACE}: + if self.point > 0: + self.text = (self.text[:self.point - 1] + + self.text[self.point:]) + self.point -= 1 + elif ch in {ctrl('d'), curses.KEY_DC}: + if self.point < len(self.text): + self.text = (self.text[:self.point] + + self.text[self.point + 1:]) + elif ch in {ctrl('w')}: + if self.point > 0: + while True: + self.point -= 1 + if self.word_boundary(self.point): + break + elif ch in {ctrl('t')}: + if self.point < len(self.text): + while True: + self.point += 1 + if self.word_boundary(self.point): + break + elif ch in {ctrl('k')}: + if self.point < len(self.text): + if self.text[self.point] == '\n': + self.text = (self.text[:self.point] + + self.text[self.point+1:]) + else: + try: + endpos = self.text.index('\n', self.point) + except ValueError: + endpos = len(self.text) + self.ctrl_k_paste_buffer = self.text[self.point:endpos] + self.text = (self.text[:self.point] + + self.text[endpos:]) + elif ch in {ctrl('y')}: + self.text = ( + self.text[:self.point] + self.ctrl_k_paste_buffer + + self.text[self.point:]) + self.point += len(self.ctrl_k_paste_buffer) + elif isinstance(ch, str) and (' ' <= ch < '\x7F' or '\xA0' <= ch): + # TODO: overwrite mode + self.text = (self.text[:self.point] + ch + + self.text[self.point:]) + self.point += 1 + +class BottomLinePrompt(Activity, EditorCommon): + def __init__(self, cc, callback, prompt, text=""): + self.cc = cc + self.prompt = prompt + self.text = text + self.point = len(self.text) + self.callback = callback + + @property + def parent_activity(self): + assert len(self.cc.activity_stack) >= 2 + assert self.cc.activity_stack[-1] is self + return self.cc.activity_stack[-2] + + def render(self): + self.parent_activity.render() + + y = self.cc.scr_h-1 + self.cc.clear_line(y) + + prompt_string = text.ColouredString(self.prompt) + text_string = text.ColouredString(self.text) + # FIXME: prevent overflow + self.cc.print_at(y, 0, prompt_string + text_string) + self.cc.move_cursor_to( + y, prompt_string.width + text_string[:self.point].width) + + def handle_key(self, ch): + if ch in {ctrl('a'), curses.KEY_HOME}: + self.move_to(0) + elif ch in {ctrl('e'), curses.KEY_END}: + self.move_to(len(self.text)) + elif ch in {'\r', '\n'}: + self.callback(self.text) + self.chain_to(self.parent_activity) + else: + self.handle_common_editing_keys(ch) + +class Composer(Activity, EditorCommon): class DisplayText: def __init__(self, text): self.text = text @@ -970,9 +1068,6 @@ class Composer(Activity): self.cy, self.cx = self.dtext.yx[self.point] self.cc.move_cursor_to(self.cy + ytop, self.cx) - def move_to(self, pos): - self.point = max(0, min(len(self.text), pos)) - def word_boundary(self, pos): if pos == 0 or pos == len(self.text): return True @@ -998,11 +1093,7 @@ class Composer(Activity): # selection start/end points? Probably don't want to # replicate Mono's underpowered [^O][B] business. - if ch in {ctrl('b'), curses.KEY_LEFT}: - self.move_to(self.point - 1) - elif ch in {ctrl('f'), curses.KEY_RIGHT}: - self.move_to(self.point + 1) - elif ch in {ctrl('n'), curses.KEY_DOWN}: + if ch in {ctrl('n'), curses.KEY_DOWN}: try: new_point = util.last( (i for i, yx in enumerate(self.dtext.yx[self.point:], @@ -1040,45 +1131,6 @@ class Composer(Activity): self.point) if yx[0] == self.cy) self.move_to(new_point) - elif ch in {ctrl('h'), '\x7F', curses.KEY_BACKSPACE}: - if self.point > 0: - self.text = (self.text[:self.point - 1] + - self.text[self.point:]) - self.point -= 1 - elif ch in {ctrl('d'), curses.KEY_DC}: - if self.point < len(self.text): - self.text = (self.text[:self.point] + - self.text[self.point + 1:]) - elif ch in {ctrl('w')}: - if self.point > 0: - while True: - self.point -= 1 - if self.word_boundary(self.point): - break - elif ch in {ctrl('t')}: - if self.point < len(self.text): - while True: - self.point += 1 - if self.word_boundary(self.point): - break - elif ch in {ctrl('k')}: - if self.point < len(self.text): - if self.text[self.point] == '\n': - self.text = (self.text[:self.point] + - self.text[self.point+1:]) - else: - try: - endpos = self.text.index('\n', self.point) - except ValueError: - endpos = len(self.text) - self.ctrl_k_paste_buffer = self.text[self.point:endpos] - self.text = (self.text[:self.point] + - self.text[endpos:]) - elif ch in {ctrl('y')}: - self.text = ( - self.text[:self.point] + self.ctrl_k_paste_buffer + - self.text[self.point:]) - self.point += len(self.ctrl_k_paste_buffer) elif ch in {'\r', '\n'}: self.text = (self.text[:self.point] + '\n' + self.text[self.point:]) @@ -1090,11 +1142,8 @@ class Composer(Activity): self.chain_to(PostMenu(self)) elif ch in {ctrl('o')}: self.mode = 'ctrlo' - elif isinstance(ch, str) and (' ' <= ch < '\x7F' or '\xA0' <= ch): - # TODO: overwrite mode - self.text = (self.text[:self.point] + ch + - self.text[self.point:]) - self.point += 1 + else: + self.handle_common_editing_keys(ch) elif self.mode == 'ctrlo': if ch == ' ': self.chain_to(PostMenu(self))