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()
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:
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
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
# 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:],
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:])
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))