From 93ca1881bf79de65c0b83303e71adbb685f58c21 Mon Sep 17 00:00:00 2001 From: moculus Date: Wed, 29 Oct 2008 04:02:50 +0000 Subject: [PATCH] buffer refactor --HG-- branch : pmacs2 --- application.py | 10 +- buffer.py => buffer/__init__.py | 248 -------------------------------- buffer/about.py | 25 ++++ buffer/color.py | 80 +++++++++++ buffer/pipe.py | 123 ++++++++++++++++ method/help.py | 5 +- mode/pipe.py | 27 ++++ mode/shellmini.py | 5 +- 8 files changed, 266 insertions(+), 257 deletions(-) rename buffer.py => buffer/__init__.py (75%) create mode 100644 buffer/about.py create mode 100644 buffer/color.py create mode 100644 buffer/pipe.py create mode 100644 mode/pipe.py diff --git a/application.py b/application.py index d8cc844..1aea537 100755 --- a/application.py +++ b/application.py @@ -3,7 +3,8 @@ import curses, curses.ascii, getpass, os, re, string, sys, termios, time import traceback from subprocess import Popen, PIPE, STDOUT -import buffer, bufferlist, color, completer, keyinput, method, minibuffer, mode +import buffer, buffer.about, buffer.color +import bufferlist, color, completer, keyinput, method, minibuffer, mode import util, window from point import Point @@ -134,7 +135,7 @@ class Application(object): 'latex', 'insertmini', 'conf', 'haskell', 'erlang', 'iperl', 'iperlmini', 'ipython', 'ipythonmini', 'awk', 'bds', #XYZ - 'shell', 'shellmini', 'fstab', 'yacc', + 'shell', 'shellmini', 'fstab', 'yacc', 'pipe', ) for name in names: exec("import mode.%s; mode.%s.install(self)" % (name, name)) @@ -161,8 +162,7 @@ class Application(object): # initialize our buffers # note that the first buffer in buffers will be initially visible - buffers.append(buffer.AboutBuffer()) - buffers.append(buffer.ScratchBuffer()) + buffers.append(buffer.about.AboutBuffer()) buffers.append(buffer.ConsoleBuffer()) if self.rcerror: @@ -478,7 +478,7 @@ class Application(object): if self.has_buffer_name(name): b = self.bufferlist.buffer_names[name] self.remove_buffer(b) - b = buffer.ColorDataBuffer(name, data) + b = buffer.color.ColorDataBuffer(name, data) if modename is not None: b.modename = modename window.Window(b, self, height=0, width=0) diff --git a/buffer.py b/buffer/__init__.py similarity index 75% rename from buffer.py rename to buffer/__init__.py index 3d361d6..167c01f 100644 --- a/buffer.py +++ b/buffer/__init__.py @@ -354,21 +354,6 @@ class Buffer(object): # should not happen raise Exception, "iiiijjjj" -# scratch is a singleton -scratch = None -class ScratchBuffer(Buffer): - btype = 'scratch' - def __new__(cls, *args, **kwargs): - global scratch - if scratch is None: - scratch = object.__new__(ScratchBuffer, *args, **kwargs) - return scratch - def name(self): - return "*Scratch*" - def close(self): - global scratch - scratch = None - class DataBuffer(Buffer): btype = 'data' def __init__(self, name, data): @@ -533,122 +518,6 @@ class IpythonBuffer(InterpreterBuffer): class BinaryDataException(Exception): pass -class PipeBuffer(Buffer): - btype = 'pipe' - terms = { - 'dumb': term.Dumb, - 'xterm': term.XTerm, - } - modename = 'colortext' - def __init__(self, cmd, args, name=None, term='dumb'): - Buffer.__init__(self) - self.cmd = cmd - if name: - self._name = name - else: - self._name = '*Pipe*' - - self.term = self.terms[term]() - self._pid, self._pty = pty.fork() - if self._pid == 0: - os.execve(cmd, [cmd] + args, {'TERM': self.term.name}) - - 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 _set_nonblock(self, fd): - flags = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NDELAY) - - def _filter_output(self, output): - output2 = [] - i = 0 - escaped = [] - for c in output: - if escaped: - escaped.append(c) - if c == 'm': - escaped = [] - elif c == '\x1b': - escaped.append(c) - elif c == '\n': - output2.append(c) - i = 0 - elif c == '\t': - j = i % 8 - output2.append(' ' * (8 - j)) - i += 8 - j - elif c == '\a': - pass - elif c == '\b': - if i > 0: - output2.pop(-1) - i -= 1 - else: - output2.append(c) - i += 1 - return ''.join(output2) - - def pipe_read(self): - fd = self._pty - 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) - end = self.get_buffer_end() - data = self.term.filter(data) - self.insert_string(end, data, force=True, act=ACT_NONE) - 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) - os.close(fd) - - def pipe_write(self, s): - self._lock.acquire() - self._towrite += s - self._lock.release() - - def insert_string(self, p, s, act=ACT_NORM, force=False): - lines = s.split("\n") - self.insert_lines(p, lines, act, force) - - def insert_lines(self, p, lines, act=ACT_NORM, force=False): - llen = len(lines) - assert llen > 0 - if not force and self.readonly(): - raise ReadOnlyError("buffer is read-only") - p2 = p.vadd(len(lines[-1]), llen - 1) - if llen > 1: - self.lines.insert(p.y + 1, []) - self.lines[p.y + 1] = lines[-1] + self.lines[p.y][p.x:] - self.lines[p.y] = self.lines[p.y][:p.x] + lines[0] - for i in range(1, llen - 1): - self.lines.insert(p.y + i, lines[i]) - else: - self.lines[p.y] = self.lines[p.y][:p.x] + lines[-1] + self.lines[p.y][p.x:] - self._region_add(p, p2, lines, act) - self.modified = True - - 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" - def close(self): raise Exception, "invalid" - class FileBuffer(Buffer): btype = 'file' def __init__(self, path, name=None): @@ -946,120 +815,3 @@ class PathListBuffer(DirBuffer): return name def name(self): return self._name - -# NOTE: this highlighter will not reprocess the data given. it is intended to -# be used with read-only buffers like DataBuffer and ColorBuffer -class ColorHighlighter(highlight.Highlighter): - color_re = re.compile(r'\[([a-zA-Z0-9\*:]+)\]') - color_map = { - 'B': 'black', - 'r': 'red', - 'g': 'green', - 'y': 'yellow', - 'b': 'blue', - 'm': 'magenta', - 'c': 'cyan', - 'w': 'white', - 'd': 'default', - '*': 'bold', - } - def __init__(self): - self.tokens = [] - def append_token(self, y, x, s, color): - s2 = s.replace('\\[', '[') - s2 = s2.replace('\\]', ']') - s2 = s2.replace('\\\\', '\\') - t = lex.Token('color_data', None, y, x, s2, color) - self.tokens[y].append(t) - return len(s) - len(s2) - def delete_token(self, y, i): - pass - def relex(self, lines, y1, x1, y2, x2, token=None): - pass - def relex_del(self, lines, y1, x1, y2, x2): - pass - def highlight(self, lines): - if self.tokens: - return - self.tokens = [[] for l in lines] - #self.tokens = [None] * len(lines) - for y in range(0, len(lines)): - self.tokens[y] = [] - line = lines[y] - c = ['default', 'default'] - i = 0 - offset = 0 - while i < len(line): - m = self.color_re.search(line, i) - if m: - (j, k) = (m.start(), m.end()) - if j > i: - offset += self.append_token(y, i - offset, line[i:j], c) - fields = m.group(1).split(':') - c = [self.color_map.get(x, x) for x in fields] - offset += k - j - i = k - else: - offset += self.append_token(y, i - offset, line[i:], c) - break - -class ColorDataBuffer(DataBuffer): - btype = 'colordata' - modename = 'colortext' - color_re = re.compile(r'\[([a-z:]+)\]') - def _highlight(self, data): - data2 = ColorHighlighter.color_re.sub('', data) - data2 = data2.replace('\\[', '[') - data2 = data2.replace('\\]', ']') - data2 = data2.replace('\\\\', '\\') - Buffer.set_data(self, data2, force=True) - lines = data.split(self.nl) - self.highlights = {'Colortext': ColorHighlighter()} - self.highlights['Colortext'].highlight(lines) - self.modified = False - def __init__(self, name, data): - DataBuffer.__init__(self, name, '') - self._highlight(data) - def set_data(self, data, force=True): - self._highlight(data) - -ABOUT_DATA = ''' -[r:d:*]=============================================================================== - -[y:d:*]************ ********** ****** ****** **** ******** ********* -[y:d:*]************** ****************** ************* ********** *********** -[y:d:*]******* ***** ****** ***** **** **** ****** **** **** ***** *** -[y:d:*] *** *** *** *** *** **** **** **** ******* -[y:d:*] *** *** *** *** *** **** **** **** ****** -[y:d:*] ***** ***** ***** ***** **** **** ****** **** **** **** **** -[y:d:*] ************ ***** ***** **** ************* ********** ********** -[y:d:*] ********** ***** ***** **** ****** **** ******** ******** -[y:d:*] *** -[y:d:*] *** [c:d:*]pmacs[d:d:*] is a python-based text editor by [c:d:*]Erik Osheim[d:d:*], [b:d:*](c) 2005-2008 -[y:d:*] *** [c:d:*]pmacs[d:d:*] is available to you under the [c:d:*]GNU General Public License v2 -[y:d:*]***** -[y:d:*]***** [d:d:*]to view available commands use [c:d:*]C-c M-h [b:d:*](show-bindings-buffer) -[y:d:*]***** [d:d:*]open files with [c:d:*]C-x C-f[d:d:*]; save with [c:d:*]C-x C-s[d:d:*]; quit with [c:d:*]C-x C-c - -[r:d:*]=============================================================================== -''' -class AboutBuffer(ColorDataBuffer): - def __init__(self): - ColorDataBuffer.__init__(self, '*About*', ABOUT_DATA) - -class ShellBuffer(Buffer): - btype = 'shell' - modename = 'shell' - def __init__(self): - Buffer.__init__(self) - self.clear() - def clear(self): - lines = ['=== Shell Console\n', - "=== Bash your brains out!\n"] - self.set_data(''.join(lines), force=True) - def name(self): - return '*Shell*' - def changed(self): - return False - def readonly(self): - return True diff --git a/buffer/about.py b/buffer/about.py new file mode 100644 index 0000000..14756c8 --- /dev/null +++ b/buffer/about.py @@ -0,0 +1,25 @@ +from buffer.color import ColorDataBuffer + +_data = ''' +[r:d:*]=============================================================================== + +[y:d:*]************ ********** ****** ****** **** ******** ********* +[y:d:*]************** ****************** ************* ********** *********** +[y:d:*]******* ***** ****** ***** **** **** ****** **** **** ***** *** +[y:d:*] *** *** *** *** *** **** **** **** ******* +[y:d:*] *** *** *** *** *** **** **** **** ****** +[y:d:*] ***** ***** ***** ***** **** **** ****** **** **** **** **** +[y:d:*] ************ ***** ***** **** ************* ********** ********** +[y:d:*] ********** ***** ***** **** ****** **** ******** ******** +[y:d:*] *** +[y:d:*] *** [c:d:*]pmacs[d:d:*] is a python-based text editor by [c:d:*]Erik Osheim[d:d:*], [b:d:*](c) 2005-2008 +[y:d:*] *** [c:d:*]pmacs[d:d:*] is available to you under the [c:d:*]GNU General Public License v2 +[y:d:*]***** +[y:d:*]***** [d:d:*]to view available commands use [c:d:*]C-c M-h [b:d:*](show-bindings-buffer) +[y:d:*]***** [d:d:*]open files with [c:d:*]C-x C-f[d:d:*]; save with [c:d:*]C-x C-s[d:d:*]; quit with [c:d:*]C-x C-c + +[r:d:*]=============================================================================== +''' +class AboutBuffer(ColorDataBuffer): + def __init__(self): + ColorDataBuffer.__init__(self, '*About*', _data) diff --git a/buffer/color.py b/buffer/color.py new file mode 100644 index 0000000..f3c77d2 --- /dev/null +++ b/buffer/color.py @@ -0,0 +1,80 @@ +import re +import lex +from buffer import Buffer, DataBuffer +from highlight import Highlighter + +# NOTE: this highlighter will not reprocess the data given. it is intended to +# be used with read-only buffers like DataBuffer and ColorBuffer +class ColorHighlighter(Highlighter): + color_re = re.compile(r'\[([a-zA-Z0-9\*:]+)\]') + color_map = { + 'B': 'black', + 'r': 'red', + 'g': 'green', + 'y': 'yellow', + 'b': 'blue', + 'm': 'magenta', + 'c': 'cyan', + 'w': 'white', + 'd': 'default', + '*': 'bold', + } + def __init__(self): + self.tokens = [] + def append_token(self, y, x, s, color): + s2 = s.replace('\\[', '[') + s2 = s2.replace('\\]', ']') + s2 = s2.replace('\\\\', '\\') + t = lex.Token('color_data', None, y, x, s2, color) + self.tokens[y].append(t) + return len(s) - len(s2) + def delete_token(self, y, i): + pass + def relex(self, lines, y1, x1, y2, x2, token=None): + pass + def relex_del(self, lines, y1, x1, y2, x2): + pass + def highlight(self, lines): + if self.tokens: + return + self.tokens = [[] for l in lines] + #self.tokens = [None] * len(lines) + for y in range(0, len(lines)): + self.tokens[y] = [] + line = lines[y] + c = ['default', 'default'] + i = 0 + offset = 0 + while i < len(line): + m = self.color_re.search(line, i) + if m: + (j, k) = (m.start(), m.end()) + if j > i: + offset += self.append_token(y, i - offset, line[i:j], c) + fields = m.group(1).split(':') + c = [self.color_map.get(x, x) for x in fields] + offset += k - j + i = k + else: + offset += self.append_token(y, i - offset, line[i:], c) + break + +class ColorDataBuffer(DataBuffer): + btype = 'colordata' + modename = 'colortext' + color_re = re.compile(r'\[([a-z:]+)\]') + def _highlight(self, data): + data2 = ColorHighlighter.color_re.sub('', data) + data2 = data2.replace('\\[', '[') + data2 = data2.replace('\\]', ']') + data2 = data2.replace('\\\\', '\\') + Buffer.set_data(self, data2, force=True) + lines = data.split(self.nl) + self.highlights = {'Colortext': ColorHighlighter()} + self.highlights['Colortext'].highlight(lines) + self.modified = False + def __init__(self, name, data): + DataBuffer.__init__(self, name, '') + self._highlight(data) + def set_data(self, data, force=True): + self._highlight(data) diff --git a/buffer/pipe.py b/buffer/pipe.py new file mode 100644 index 0000000..72a7e4a --- /dev/null +++ b/buffer/pipe.py @@ -0,0 +1,123 @@ +import fcntl, os, select, pty, threading + +from buffer import Buffer, ACT_NORM, ACT_NONE +import term + +class PipeBuffer(Buffer): + btype = 'pipe' + terms = { + 'dumb': term.Dumb, + 'xterm': term.XTerm, + } + modename = 'pipe' + def __init__(self, cmd, args, name=None, term='dumb'): + Buffer.__init__(self) + self.cmd = cmd + if name: + self._name = name + else: + self._name = '*Pipe*' + + self.term = self.terms[term]() + self._pid, self._pty = pty.fork() + if self._pid == 0: + os.execve(cmd, [cmd] + args, {'TERM': self.term.name}) + + 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 _set_nonblock(self, fd): + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NDELAY) + + def _filter_output(self, output): + output2 = [] + i = 0 + escaped = [] + for c in output: + if escaped: + escaped.append(c) + if c == 'm': + escaped = [] + elif c == '\x1b': + escaped.append(c) + elif c == '\n': + output2.append(c) + i = 0 + elif c == '\t': + j = i % 8 + output2.append(' ' * (8 - j)) + i += 8 - j + elif c == '\a': + pass + elif c == '\b': + if i > 0: + output2.pop(-1) + i -= 1 + else: + output2.append(c) + i += 1 + return ''.join(output2) + + 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) + end = self.get_buffer_end() + data = self.term.filter(data) + self.insert_string(end, data, force=True, act=ACT_NONE) + 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, e: + # read error means we're done + pass + os.close(fd) + + def pipe_write(self, s): + self._lock.acquire() + self._towrite += s + self._lock.release() + + def insert_string(self, p, s, act=ACT_NORM, force=False): + lines = s.split("\n") + self.insert_lines(p, lines, act, force) + + def insert_lines(self, p, lines, act=ACT_NORM, force=False): + llen = len(lines) + assert llen > 0 + if not force and self.readonly(): + raise ReadOnlyError("buffer is read-only") + p2 = p.vadd(len(lines[-1]), llen - 1) + if llen > 1: + self.lines.insert(p.y + 1, []) + self.lines[p.y + 1] = lines[-1] + self.lines[p.y][p.x:] + self.lines[p.y] = self.lines[p.y][:p.x] + lines[0] + for i in range(1, llen - 1): + self.lines.insert(p.y + i, lines[i]) + else: + self.lines[p.y] = self.lines[p.y][:p.x] + lines[-1] + self.lines[p.y][p.x:] + self._region_add(p, p2, lines, act) + self.modified = True + + 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/method/help.py b/method/help.py index 5d28a85..e10e2ca 100644 --- a/method/help.py +++ b/method/help.py @@ -1,7 +1,8 @@ import os, commands, re, sets, tempfile from subprocess import Popen, PIPE, STDOUT -import buffer, default, dirutil, regex, util, window +import buffer, buffer.about +import default, dirutil, regex, util, window from point import Point from method import DATATYPES, Method, Argument @@ -147,7 +148,7 @@ class AboutPmacs(Method): def _execute(self, w, **vargs): a = w.application if not a.has_buffer_name('*About*'): - b = buffer.AboutBuffer() + b = buffer.about.AboutBuffer() a.add_buffer(b) window.Window(b, a) b = a.bufferlist.get_buffer_by_name('*About*') diff --git a/mode/pipe.py b/mode/pipe.py new file mode 100644 index 0000000..77018de --- /dev/null +++ b/mode/pipe.py @@ -0,0 +1,27 @@ +import keyinput +from mode import Fundamental +from method import Method + +class PipeInsertChr(Method): + _is_method = False + def __init__(self, i): + self.name = "pipe-insert-chr-%s" % i + self.args = [] + self.help = "Insert chr(%d) into the current pipe-buffer." % i + self.string = chr(i) + def _execute(self, w, **vargs): + w.buffer.pipe_write(self.string) + +class Pipe(Fundamental): + modename = 'pipe' + def __init__(self, w): + Fundamental.__init__(self, w) + _codes = [1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14] + \ + [16, 18, 20, 23, 25] + list(range(32, 128)) + for i in _codes: + sym = keyinput.MAP.get(i, chr(i)) + obj = PipeInsertChr(i) + w.application.methods[obj.name] = obj + self.add_binding(obj.name, sym) + +install = Pipe.install diff --git a/mode/shellmini.py b/mode/shellmini.py index 0d3a29f..e83391b 100644 --- a/mode/shellmini.py +++ b/mode/shellmini.py @@ -1,5 +1,6 @@ import code, os, re, string, StringIO, sys, traceback -import buffer, color, completer, lex, method, mode, window +import buffer, buffer.pipe +import color, completer, lex, method, mode, window from lex import Grammar, PatternRule, RegionRule from point import Point from method import Method @@ -124,7 +125,7 @@ class OpenShell(Method): def execute(self, w, **vargs): a = w.application if not a.has_buffer_name('*Shell*'): - b = buffer.PipeBuffer('/bin/bash', [], name="*Shell*", term='xterm') + b = buffer.pipe.PipeBuffer('/bin/bash', [], name="*Shell*", term='xterm') a.add_buffer(b) window.Window(b, a) b = a.bufferlist.get_buffer_by_name('*Shell*')