diff --git a/buffer/__init__.py b/buffer/__init__.py index 417f2d0..d8d7d1a 100644 --- a/buffer/__init__.py +++ b/buffer/__init__.py @@ -290,6 +290,14 @@ class Buffer(object): lines = data.split('\n') self.set_lines(lines, force) + # append into buffer + def append_lines(self, lines, act=ACT_NORM, force=False): + p = self.get_buffer_end() + self.insert_lines(p, lines, act, force) + def append_string(self, s, act=ACT_NORM, force=False): + lines = s.split("\n") + self.insert_lines(lines, act, force) + # insertion into buffer def insert_lines(self, p, lines, act=ACT_NORM, force=False): llen = len(lines) @@ -355,6 +363,48 @@ class Buffer(object): # should not happen raise Exception, "iiiijjjj" + # generic window functionality + def forward(self, p): + if p.x < len(self.lines[p.y]): + return Point(p.x + 1, p.y) + elif p.y < len(self.lines) - 1: + return Point(0, p.y + 1) + else: + return p + def backward(self, p): + if p.x > 0: + return Point(p.x - 1, p.y) + elif p.y > 0: + x = len(self.lines[p.y - 1]) + return Point(x, p.y - 1) + else: + return p + def end_of_line(self, p): + return Point(len(self.lines[p.y]), p.y) + def start_of_line(self, p): + return Point(0, p.y) + def previous_line(self, p): + if p.y > 0: + return Point(p.x, p.y - 1) + else: + return p + def next_line(self, p): + if p.y < len(self.lines) - 1: + return Point(p.x, p.y + 1) + else: + return p + def left_delete(self, p): + (x, y) = p.xy() + if x > 0: + self.delete_char(Point(x - 1, y)) + elif y > 0: + x = len(self.lines[y - 1]) + self.delete_char(Point(x, y - 1)) + def right_delete(self, p): + if (p.y < len(self.lines) - 1 or + p.x < len(self.lines[-1])): + self.delete_char(p) + class InterpreterPipeError(Exception): pass diff --git a/buffer/emul.py b/buffer/emul.py new file mode 100644 index 0000000..2c200f5 --- /dev/null +++ b/buffer/emul.py @@ -0,0 +1,124 @@ +import fcntl, os, select, pty, threading + +from buffer import Buffer, ACT_NORM, ACT_NONE +from term import XTerm +from point import Point + +# evil evil evil evil evil +class XTermBuffer(Buffer, XTerm): + btype = 'term' + modename = 'pipe' + def __init__(self, cmd, args, name=None): + XTerm.__init__(self) + Buffer.__init__(self) + self._name = name or '*XTerm*' + self._pid, self._pty = pty.fork() + if self._pid == 0: + # child process + os.execve(cmd, [cmd] + args, {'TERM': 'xterm'}) + + self._lock = threading.Lock() + self._towrite = '' + self._done = False + self._set_nonblock(self._pty) + self._thread = threading.Thread(target=self.pipe_read) + self._thread.setDaemon(True) + self._thread.start() + + + def _w(self): + return self.windows[0] + def _get_height(self): + return self._w().height + def _get_width(self): + return self._w().width + # TERM STUFF + def _term_insert(self, s): + w = self._w() + p = w.logical_cursor() + if p.x == len(self.lines[p.y]): + w.buffer.insert_string(p, s, act=ACT_NONE, force=True) + else: + w.buffer.overwrite_char(p, s, act=ACT_NONE, force=True) + def term_do_clear(self): + self.set_lines([''], force=True) + self._meta = [] + def term_do_backspace(self): + self._w().backward() + def term_do_tab(self): + self._term_insert(' ') + def term_do_newline(self): + w = self._w() + p = w.logical_cursor() + if p.y < len(self.lines) - 1: + w.start_of_line() + w.next_line() + else: + w.end_of_line() + w.buffer.insert_string(w.logical_cursor(), "\n", act=ACT_NONE, force=True) + def term_do_creturn(self): + self._w().start_of_line() + def term_do_delete(self): + self._w().delete_right() + def term_handle_print(self, c): + #self._term_insert('%d ' % ord(c)) + self._term_insert(c) + def term_handle_ctl(self, c): + n = ord(c) + if n == 8: + self.term_do_backspace() + elif n == 9: + self.term_do_tab() + elif n == 10: + self.term_do_newline() + elif n == 13: + self.term_do_creturn() + elif n == 27: + self.term_do_esc(c) + elif n == 127: + self.term_do_delete() + else: + self._term_insert('%x' % n) + + def term_receive(self, s): + for c in s: + self.term_handle(c) + + # BUFFER STUFF + def _set_nonblock(self, fd): + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NDELAY) + + def pipe_read(self): + fd = self._pty + try: + while not self._done: + if self._towrite: + ifd, ofd, efd = select.select([fd], [fd], [fd], 0.1) + else: + ifd, ofd, efd = select.select([fd], [], [fd], 0.1) + if ifd: + data = os.read(ifd[0], 1024) + self.term_receive(data) + if ofd: + self._lock.acquire() + n = os.write(ofd[0], self._towrite) + self._towrite = self._towrite[n:] + self._lock.release() + if efd: + raise Exception, "exception is ready: %s" % repr(efd) + except (OSError, TypeError): + pass + os.close(fd) + + def pipe_write(self, s): + self._lock.acquire() + self._towrite += s + self._lock.release() + + def name(self): return self._name + def changed(self): return False + def readonly(self): return True + def undo(self, move, act): raise Exception, "invalid" + def redo(self, move, act): raise Exception, "invalid" + def reload(self): raise Exception, "invalid" diff --git a/buffer/pipe.py b/buffer/pipe.py index f8bca59..512a4ef 100644 --- a/buffer/pipe.py +++ b/buffer/pipe.py @@ -75,7 +75,7 @@ class PipeBuffer(Buffer): if ifd: data = os.read(ifd[0], 1024) end = self.get_buffer_end() - data = self.term.filter(data) + data = self.term.term_filter(data) self.insert_string(end, data, force=True, act=ACT_NONE) if ofd: self._lock.acquire() diff --git a/method/shell.py b/method/shell.py index eb2cabc..09ddedb 100644 --- a/method/shell.py +++ b/method/shell.py @@ -78,7 +78,7 @@ class Man(Exec): errmsg = "man: ok" if output: xterm = term.XTerm() - output = xterm.filter(output) + output = xterm.term_filter(output) switch_to = err or self.show_success w.application.data_buffer('*Manpage*', output, switch_to=switch_to) w.set_error(errmsg) diff --git a/mode/shellmini.py b/mode/shellmini.py index 3a84553..1eb1eeb 100644 --- a/mode/shellmini.py +++ b/mode/shellmini.py @@ -1,5 +1,5 @@ import code, os, re, string, StringIO, sys, traceback -import buffer, buffer.pipe +import buffer, buffer.pipe, buffer.emul import color, completer, lex, method, mode, window from lex import Grammar, PatternRule, RegionRule from point import Point @@ -122,7 +122,8 @@ class OpenShell(Method): def execute(self, w, **vargs): a = w.application if not a.has_buffer_name('*Shell*'): - b = buffer.pipe.PipeBuffer('/bin/bash', [], name="*Shell*", term='xterm') + #b = buffer.pipe.PipeBuffer('/bin/bash', [], name="*Shell*", term='xterm') + b = buffer.emul.XTermBuffer('/bin/bash', [], name="*Shell*") a.add_buffer(b) window.Window(b, a) b = a.bufferlist.get_buffer_by_name('*Shell*') diff --git a/term.py b/term.py index 61c9f48..def53cf 100644 --- a/term.py +++ b/term.py @@ -1,6 +1,15 @@ +import os, re, string +from point import Point + +def show(c): + if c in string.printable: + return c + else: + return '\\%03o' % ord(c) + class Dumb: name = 'dumb' - def insert(self, s): + def _term_insert(self, s): assert self.i <= len(self.outc) if self.i == len(self.outc): self.outc.append(s) @@ -8,116 +17,134 @@ class Dumb: self.outc[self.i] = s self.i += 1 - def do_backspace(self): + def term_do_clear(self): + self.outs = '' + self.i = 0 + self.outc = [] + def term_do_backspace(self): self.i = max(0, self.i - 1) - def do_tab(self): - self.insert(' ') - def do_newline(self): + def term_do_tab(self): + self._term_insert(' ') + def term_do_newline(self): self.outs += ''.join(self.outc) + '\n' self.i = 0 self.outc = [] - def do_careturn(self): + def term_do_creturn(self): self.i = 0 - def do_esc(self, c): + def term_do_esc(self, c): pass - def do_delete(self): + def term_do_delete(self): if self.i < len(self.outc): del self.outc[self.i] - def handle_ctl(self, c): + def term_handle_ctl(self, c): n = ord(c) if n == 8: - self.do_backspace() + self.term_do_backspace() elif n == 9: - self.do_tab() + self.term_do_tab() elif n == 10: - self.do_newline() + self.term_do_newline() + elif n == 12: + self.term_do_clear() elif n == 13: - self.do_careturn() + self.term_do_creturn() elif n == 27: - self.do_esc(c) + self.term_do_esc(c) elif n == 127: - self.do_delete() - def handle_print(self, c): - self.insert(c) - def handle_8bit(self, c): + self.term_do_delete() + def term_handle_print(self, c): + self._term_insert(c) + def term_handle_8bit(self, c): pass - def handle(self, c): + def term_handle(self, c): n = ord(c) assert n >= 0 and n < 256 if n <= 27 or n == 127: - self.handle_ctl(c) + self.term_handle_ctl(c) elif n < 127: - self.handle_print(c) + self.term_handle_print(c) else: - self.handle_8bit(c) + self.term_handle_8bit(c) + def term_receive(self, s): + for c in s: + self.term_handle(c) - def filter(self, s): + def term_filter(self, s): self.i = 0 self.outc = [] self.outs = "" - for c in s: - self.handle(c) + self.term_receive(s) return self.outs + ''.join(self.outc) class XTerm(Dumb): name = 'xterm' - ansi_colors = { - '\033[30m': '[B:d]', - '\033[30;0m': '[B:d]', - '\033[30;1m': '[B:d:*]', - '\033[31m': '[r:d]', - '\033[31;0m': '[r:d]', - '\033[31;1m': '[r:d:*]', - '\033[32m': '[g:d]', - '\033[32;0m': '[g:d]', - '\033[32;1m': '[g:d:*]', - '\033[33m': '[y:d]', - '\033[33;0m': '[y:d]', - '\033[33;1m': '[y:d:*]', - '\033[34m': '[b:d]', - '\033[34;0m': '[b:d]', - '\033[34;1m': '[b:d:*]', - '\033[35m': '[m:d]', - '\033[35;0m': '[m:d]', - '\033[35;1m': '[m:d:*]', - '\033[36m': '[c:d]', - '\033[36;0m': '[c:d]', - '\033[36;1m': '[c:d:*]', - '\033[37m': '[w:d]', - '\033[37;0m': '[w:d]', - '\033[37;1m': '[w:d:*]', - '\033[39m': '[d:d]', - '\033[39;0m': '[d:d]', - '\033[39;1m': '[d:d:*]', + + comment_re = re.compile('^ *#') + header_re = re.compile('^[a-zA-Z0-9]+\|') + + bool_re = re.compile('^([a-zA-Z0-9]+)$') + num_re = re.compile('^([a-zA-Z0-9]+)#(.+)$') + str_re = re.compile('^([a-zA-Z0-9]+)=(.+)$') + + style_re = re.compile('^\033[[0-9;]+m') + + callbacks = { + 'clear': 'term_do_clear', + 'home': 'term_do_creturn', } - def filter(self, s): - self.meta = [] - return Dumb.filter(self, s) - - def do_esc(self, c): - self.meta.append(c) - - def handle(self, c): - if self.meta: - self.meta.append(c) - if c == 'm': - s = ''.join(self.meta) - if s in self.ansi_colors: - #self.insert(self.ansi_colors[s]) - pass - self.meta = [] - else: - Dumb.handle(self, c) - -class Auto(Dumb): - name = 'auto' def __init__(self): - try: - curses.setupterm() - except: - pass + self._meta = [] + f = os.popen('infocmp xterm', 'r') + #f = open('xterm.terminfo') + self.sequences = {} + for line in f: + if self.comment_re.match(line) or self.header_re.match(line): + continue + for field in [x.strip() for x in line.split(',')]: + if not field: + continue + elif self.bool_re.match(field) or self.num_re.match(field): + continue + m = self.str_re.match(field) + assert m, "huh?? %r" % field + name, val = m.groups() + if val.startswith('\\E'): + self.sequences[val.replace('\\E', '\033')] = name + f.close() + + def term_filter(self, s): + self._meta = [] + return Dumb.term_filter(self, s) + def term_do_esc(self, c): + self._meta.append(c) + + def term_handle(self, c): + if self._meta: + self._meta.append(c) + s = ''.join(self._meta) + + if s in self.sequences: + name = self.sequences[s] + if name in self.callbacks: + f = getattr(self, self.callbacks[name]) + f() + else: + self._term_insert('<%s>' % name) + # certain sequences shouldn't get removed immediately + if name in ('home',): + pass + else: + self._meta = [] + elif self.style_re.match(s): + self._meta = [] + elif len(s) > 20: + self._term_insert(''.join([show(c) for c in self._meta])) + self._meta = [] + else: + Dumb.term_handle(self, c) + # terminfo junk boolean_settings = [ diff --git a/window.py b/window.py index c72d83a..1ec3ff0 100644 --- a/window.py +++ b/window.py @@ -278,35 +278,22 @@ class Window(object): # moving in buffer def forward(self): - cursor = self.logical_cursor() - if cursor.x < len(self.buffer.lines[cursor.y]): - self.cursor = Point(cursor.x + 1, cursor.y) - elif cursor.y < len(self.buffer.lines) -1: - self.cursor = Point(0, cursor.y + 1) + self.cursor = self.buffer.forward(self.logical_cursor()) self.assure_visible_cursor() def backward(self): - cursor = self.logical_cursor() - if cursor.x > 0: - self.cursor = Point(cursor.x - 1, cursor.y) - elif cursor.y > 0: - x = len(self.buffer.lines[cursor.y - 1]) - self.cursor = Point(x, cursor.y - 1) + self.cursor = self.buffer.backward(self.logical_cursor()) self.assure_visible_cursor() def end_of_line(self): - cursor = self.logical_cursor() - self.cursor = Point(len(self.buffer.lines[cursor.y]), cursor.y) + self.cursor = self.buffer.end_of_line(self.logical_cursor()) self.assure_visible_cursor() def start_of_line(self): - cursor = self.logical_cursor() - self.cursor = Point(0, cursor.y) + self.cursor = self.buffer.start_of_line(self.logical_cursor()) self.assure_visible_cursor() def previous_line(self): - if self.cursor.y > 0: - self.cursor = Point(self.cursor.x, self.cursor.y - 1) + self.cursor = self.buffer.previous_line(self.cursor) self.assure_visible_cursor() def next_line(self): - if self.cursor.y < len(self.buffer.lines) - 1: - self.cursor = Point(self.cursor.x, self.cursor.y + 1) + self.cursor = self.buffer.next_line(self.cursor) self.assure_visible_cursor() # word handling @@ -511,18 +498,9 @@ class Window(object): # deletion def left_delete(self): - (x, y) = self.logical_cursor().xy() - if x > 0: - self.buffer.delete_char(Point(x - 1, y)) - elif y > 0: - x = len(self.buffer.lines[y - 1]) - self.buffer.delete_char(Point(x, y - 1)) + self.buffer.left_delete(self.logical_cursor()) def right_delete(self): - cursor = self.logical_cursor() - if cursor < self.last: - self.buffer.delete_char(cursor) - else: - pass + self.buffer.right_delete(self.logical_cursor()) def delete(self, p1, p2): self.buffer.delete(p1, p2)