import math, os, string import color, method from lex import Lexer from point import Point from render import RenderString DEBUG = False class ActionError(Exception): pass class Handler(object): def __init__(self): self.prefixes = set(["C-x", "C-c", "C-u"]) self.last_sequence = '' self.curr_tokens = [] self.bindings = {} # handle adding and removing actions def add_action(self, action): if self.window is None: return elif action.name in self.window.application.methods: return else: self.window.application.methods[action.name] = action def del_action(self, name): if self.window is None: return for binding in self.bindings.keys(): if self.bindings[binding] == name: del self.bindings[binding] def add_binding(self, name, sequence): if self.window is None: return elif not hasattr(self.window, 'application'): raise Exception, "argh %r %r" % (self, self.window) elif name not in self.window.application.methods: raise Exception, "No action called %r found" % name else: self.bindings[sequence] = name def add_bindings(self, name, sequences): if self.window is None: return for sequence in sequences: self.add_binding(name, sequence) def del_binding(self, sequence): if self.window is None: return del self.bindings[sequence] def add_action_and_bindings(self, action, sequences): if self.window is None: return self.add_action(action) for sequence in sequences: self.add_binding(action.name, sequence) def handle_token(self, t): '''self.handle_token(token): returns None, or the action to take. raises an exception on unknown input''' self.curr_tokens.append(t) sequence = " ".join(self.curr_tokens) if sequence in self.bindings: act = self.window.application.methods[self.bindings[sequence]] self.last_sequence = sequence self.curr_tokens = [] return act elif t in self.prefixes: for binding in self.bindings: if binding.startswith(sequence): return None self.curr_tokens = [] self.last_sequence = sequence raise ActionError("no action defined for %r" % (sequence)) class Fundamental(Handler): '''This is the default mode''' modename = "Fundamental" paths = [] basenames = [] extensions = [] detection = [] savetabs = False tabwidth = 4 tabbercls = None grammar = None lexer = None tabber = None context = None colors = {} config = {} actions = [] completers = {} format = "%(flag)s %(bname)-18s (%(mname)s) %(cursor)s/%(mark)s %(perc)s" # margin/line numbering show_line_numbers = False lmargin = 0 rmargin = 1 def install(cls, app): app.setmode(cls.modename.lower(), cls, paths=cls.paths, basenames=cls.basenames, extensions=cls.extensions, detection=cls.detection) for (key, val) in cls.colors.iteritems(): if key in app.token_colors: s = 'token_colors: name %r conflict in %r' % (key, cls.modename) raise Exception, s else: app.token_colors[key] = val for (key, val) in cls.config.iteritems(): app.config[key] = val for mcls in cls.actions: m = mcls() if m.name in app.methods: return else: app.methods[m.name] = m for (datatype, completer) in cls.completers.iteritems(): app.set_completer(datatype, completer) install = classmethod(install) def __init__(self, w): assert w is not None self.window = w Handler.__init__(self) # first let's add all the "default" actions self.add_bindings('start-of-line', ('C-a', 'HOME',)) self.add_bindings('end-of-line', ('C-e', 'END',)) self.add_bindings('backward', ('C-b', 'L_ARROW',)) self.add_bindings('forward', ('C-f', 'R_ARROW',)) self.add_bindings('center-view', ('C-l',)) self.add_bindings('next-line', ('C-n', 'D_ARROW',)) self.add_bindings('previous-line', ('C-p', 'U_ARROW',)) self.add_bindings('next-section', ('M-n', 'M-D_ARROW',)) self.add_bindings('previous-section', ('M-p', 'M-U_ARROW',)) self.add_bindings('page-down', ('C-v', 'PG_DN',)) self.add_bindings('page-up', ('M-v', 'PG_UP',)) self.add_bindings('goto-beginning', ('M-<',)) self.add_bindings('goto-end', ('M->',)) self.add_bindings('delete-left', ('DELETE', 'BACKSPACE',)) self.add_bindings('delete-left-word', ('M-DELETE', 'M-BACKSPACE',)) self.add_bindings('delete-right', ('C-d',)) self.add_bindings('delete-right-word', ('M-d',)) self.add_bindings('kill-region', ('C-w',)) self.add_bindings('copy-region', ('M-w',)) self.add_bindings('kill', ('C-k',)) self.add_bindings('copy', ('M-k',)) self.add_bindings('yank', ('C-y',)) self.add_bindings('pop-kill', ('M-y',)) self.add_bindings('right-word', ('M-f',)) self.add_bindings('left-word', ('M-b',)) self.add_bindings('set-mark', ('C-@',)) self.add_bindings('switch-buffer', ('C-x b',)) self.add_bindings('switch-mark', ('C-x C-x',)) self.add_bindings('undo', ('C-/', 'C-x u',)) self.add_bindings('redo', ('M-/', 'M-_', 'C-x r',)) self.add_bindings('goto-line', ('M-g',)) self.add_bindings('forward-chars', ('C-x M-c',)) self.add_bindings('forward-lines', ('C-x M-n',)) self.add_bindings('search', ('C-s',)) self.add_bindings('reverse-search', ('C-r',)) self.add_bindings('regex-search', ('M-C-s',)) self.add_bindings('regex-reverse-search', ('M-C-r',)) self.add_bindings('toggle-margins', ('M-m',)) self.add_bindings('replace', ('M-%',)) self.add_bindings('regex-replace', ('M-$',)) self.add_bindings('open-file', ('C-x C-f',)) self.add_bindings('kill-buffer', ('C-x k',)) self.add_bindings('list-buffers', ('C-x C-b',)) self.add_bindings('meta-x', ('M-x',)) self.add_bindings('wrap-line', ('M-q',)) self.add_bindings('transpose-words', ('M-t',)) self.add_bindings('save-buffer', ('C-x C-s',)) self.add_bindings('save-buffer-as', ('C-x C-w',)) self.add_bindings('relex-buffer', ('M-r',)) self.add_bindings('exit', ('C-x C-c',)) self.add_bindings('split-window', ('C-x s', 'C-x 2',)) self.add_bindings('unsplit-window', ('C-u s', 'C-x 1',)) self.add_bindings('toggle-window', ('C-x o',)) self.add_bindings('delete-left-whitespace', ('C-c DELETE', 'C-c BACKSPACE',)) self.add_bindings('delete-right-whitespace', ('C-c d',)) self.add_bindings('insert-space', ('SPACE',)) self.add_bindings('insert-tab', ('TAB',)) self.add_bindings('insert-newline', ('RETURN',)) self.add_bindings('comment-region', ('C-c #',)) self.add_bindings('uncomment-region', ('C-u C-c #',)) self.add_bindings('justify-right', ('C-c f',)) self.add_bindings('justify-left', ('C-c b',)) self.add_bindings('indent-block', ('C-c >',)) self.add_bindings('unindent-block', ('C-c <',)) self.add_bindings('token-complete', ('C-x TAB',)) self.add_bindings('open-aes-file', ('C-c a',)) self.add_bindings('open-console', ('M-e',)) self.add_bindings('show-bindings-buffer', ('C-c M-h',)) self.add_bindings('show-functions-buffer', ('C-c M-?',)) self.add_bindings('which-command', ('M-h',)) self.add_bindings('cmd-help-buffer', ('M-?',)) self.add_bindings('set-mode', ('C-x m',)) self.add_bindings('cancel', ('C-]',)) self.add_bindings('exec', ('C-c e', 'C-c !')) self.add_bindings('grep', ('C-c g',)) self.add_bindings('pipe', ('C-c p', 'C-c |')) self.add_bindings('view-buffer-parent', ('C-c .',)) self.add_bindings('insert-squotes', ('M-\'',)) self.add_bindings('insert-dquotes', ('M-"',)) self.add_bindings('get-token', ('C-c t',)) self.add_bindings('insert-text', ('C-c i',)) self.add_bindings('insert-text2', ('C-c M-i',)) self.add_bindings('insert-multiline-text', ('C-c m',)) # create all the insert actions for the basic text input for c in string.letters + string.digits + string.punctuation: self.add_binding('insert-string-%s' % c, c) # lexing for highlighting, etc. if self.grammar: self.lexer = Lexer(self, self.grammar) else: self.lexer = None self.gstack = {} self.ghist = {} # tab handling if self.tabbercls: self.tabber = self.tabbercls(self) def enable_line_numbers(self): self.show_line_numbers = True l = len(self.window.buffer.lines) self.lmargin = int(math.log(l, 10)) + 3 def disable_line_numbers(self): self.show_line_numbers = False self.lmargin = 0 def get_rmargin(self, w, y, x, ended=False, cont=False): c, fg, bg = " ", "red", "default" if cont: c = "\\" if w.hiddenindicator(y): bg = "green" return [RenderString(s=c, attrs=color.build(fg, bg))] def get_lmargin(self, w, y, x, ended=False, cont=False): lm = self.lmargin if ended: i = int(math.log(y, 10)) + 1 s = ('% *s' % (lm - 1, '-' * i))[-lm:] + ' ' elif not cont: s = ('% *d' % (lm - 1, y + 1))[-lm:] + ' ' else: s = ' ' * lm return [RenderString(s=s, attrs=color.build('default', 'default', 'bold'))] # get mode name def name(self): return self.modename def _get_flag(self): b = self.window.buffer if b.readonly(): if b.changed(): return '%*' else: return '%%' else: if b.changed(): return '**' else: return '--' def _get_perc(self): w = self.window if w.first_is_visible(): return "Top" elif w.last_is_visible(): return "Bot" else: return "%2d%%" % (w.first.y*100 / len(w.buffer.lines)) def _get_mark(self): w = self.window if w.mark: return '(%d,%d)' % (w.mark.y + 1, w.mark.x + 1) else: return '(None)' def get_status_names(self): w = self.window c = w.logical_cursor() return { 'bname': w.buffer.name(), 'mname': self.name(), 'flag': self._get_flag(), 'perc': self._get_perc(), 'cursor': '(%d,%d)' % (c.y + 1, c.x + 1), 'mark': self._get_mark(), } def get_status_bar(self): names = self.get_status_names() return self.format % names # handle input tokens def handle_token(self, t): '''self.handle_token(token): handles input "token"''' self.window.active_point = None self.window.clear_error() try: act = Handler.handle_token(self, t) if act is None: self.window.set_error(' '.join(self.curr_tokens)) return else: act.execute(self.window) self.window.application.last_action = act.name except ActionError, e: if t != 'C-]': self.window.set_error(str(e)) else: self.window.set_error('Cancelled') except Exception, e: if DEBUG: raise else: err = "%s in mode '%s'" % (e, self.name()) self.window.set_error(err) def region_added(self, p, newlines): if self.lexer is not None: ydelta = len(newlines) - 1 xdelta = len(newlines[-1]) ghist = {} for name in self.ghist: for gp in self.ghist[name]: if gp < p: newp = gp elif ydelta == 0: if p.y == gp.y: newp = Point(gp.x + xdelta, gp.y) else: newp = gp else: if gp.y == p.y: newp = Point(gp.x + xdelta, gp.y + ydelta) else: newp = Point(gp.x, gp.y + ydelta) ghist.setdefault(name, {}) ghist[name][newp] = self.ghist[name][gp] self.ghist = ghist if self.tabber is not None: self.tabber.region_added(p, newlines) if self.context is not None: self.context.region_added(p, newlines) def region_removed(self, p1, p2): if self.lexer is not None: ydelta = p2.y - p1.y xdelta = p2.x - p1.x ghist = {} for name in self.ghist: for gp in self.ghist[name]: if gp < p1: newp = gp elif p1 <= gp and gp < p2: continue elif ydelta == 0: if gp.y == p2.y: newp = Point(gp.x - xdelta, gp.y) else: newp = gp else: if gp.y == p2.y: newp = Point(gp.x - xdelta, gp.y - ydelta) else: newp = Point(gp.x, gp.y - ydelta) ghist.setdefault(name, {}) ghist[name][newp] = self.ghist[name][gp] self.ghist = ghist if self.tabber is not None: self.tabber.region_removed(p1, p2) if self.context is not None: self.context.region_removed(p1, p2) install = Fundamental.install