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 = {} word_letters = None # config settings installed/modified by the mode config = {} dconfig = {} lconfig = {} sconfig = {} actions = [] completers = {} format = "%(flag)s %(bname)-18s (%(mname)s) %(indent)s %(cursor)s/%(mark)s %(perc)s" header_size = 3 header_fg = 'default' header_bg = 'blue' ## margins def _get_header(self): return self.get_setting('header') def _set_header(self, value): return self.set_setting('header', value) header = property(_get_header, _set_header) def _get_footer(self): return self.get_setting('footer') def _set_footer(self, value): return self.set_setting('footer', value) footer = property(_get_footer, _set_footer) def _get_lmargin(self): return self.get_setting('lmargin') def _set_lmargin(self, value): return self.set_setting('lmargin', value) lmargin = property(_get_lmargin, _set_lmargin) def _get_rmargin(self): return self.get_setting('rmargin') def _set_rmargin(self, value): return self.set_setting('rmargin', value) rmargin = property(_get_rmargin, _set_rmargin) def install(cls, app): app.setmode(cls.modename.lower(), cls, paths=cls.paths, bases=cls.basenames, exts=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 # install configuration stuff for (key, val) in cls.config.iteritems(): assert key not in app.config, "config conflict: %r" % key app.config[key] = val for (key, val) in cls.lconfig.iteritems(): app.config.setdefault(key, []) for val2 in val: app.config[key].append(val2) for (key, val) in cls.sconfig.iteritems(): app.config.setdefault(key, set()) app.config[key].add(val) for (key, d) in cls.dconfig.iteritems(): app.config.setdefault(key, {}) for (subkey, val) in d.iteritems(): app.config[key][subkey] = 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) #xyz self.init_setting('lmargin', 0) self.init_setting('rmargin', 1) self.init_setting('header', 0) self.init_setting('footer', 0) # 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-]', 'C-g',)) 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',)) self.add_bindings('increment', ('M-+',)) self.add_bindings('decrement', ('M--',)) self.add_bindings('uppercase-word', ('M-u',)) self.add_bindings('lowercase-word', ('M-l',)) # used for all word operations if not self.word_letters: self.word_letters = w.application.config['word_letters'] # 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) # buffer settings def get_setting(self, name): self.window.buffer.settings.setdefault(self.modename, {}) return self.window.buffer.settings[self.modename].get(name) def init_setting(self, name, value): self.window.buffer.settings.setdefault(self.modename, {}) self.window.buffer.settings[self.modename].setdefault(name, value) def set_setting(self, name, value): self.window.buffer.settings.setdefault(self.modename, {}) self.window.buffer.settings[self.modename][name] = value # header def showing_header(self): return self.header != 0 def _enable_header(self): self.header = self.header_size def _disable_header(self): self.header = 0 def enable_header(self): self._enable_header() self._slot_reset() def disable_header(self): self._disable_header() self._slot_reset() def _slot_reset(self): w = self.window slot = w.application.bufferlist.find_window_slot(w) assert slot is not None slot.reset() # line numbers def showing_line_numbers(self): return self.lmargin != 0 def enable_line_numbers(self): l = len(self.window.buffer.lines) self.lmargin = int(math.log(l, 10)) + 3 def disable_line_numbers(self): self.lmargin = 0 # headers and margins def get_header(self): fg, bg = self.header_fg, self.header_bg if self.tabber is None: s = "Header support is not available for this mode" hs = [RenderString(s=s, attrs=color.build(fg, bg))] while len(hs) < 3: hs.insert(0, RenderString(s='', attrs=color.build(fg, bg))) return hs w = self.window y = self.window.first.y if self.window.first.x > 0: y += 1 lvl = self.tabber.get_level(y) markers = self.tabber.record[y] if w.buffer.is_whitespace(y): ws = None else: ws = w.buffer.count_leading_whitespace(y) hs = [] i = len(markers) - 1 while i >= 0 and len(hs) < 3: marker = markers[i] i -= 1 if marker.y == y: continue if ws and marker.level > ws: continue s = w.buffer.lines[marker.y][:w.width - 1] hs.insert(0, RenderString(s=s, attrs=color.build(fg, bg))) while len(hs) < 3: hs.insert(0, RenderString(s='', attrs=color.build(fg, bg))) return hs def get_footer(self): fg, bg = "default", "red" return [RenderString(s='footer', attrs=color.build(fg, bg))] def get_rmargin(self, w, y, x, ended=False, cont=False): c, fg, bg = " ", "red", "default" if cont: c = "\\" 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_indent(self): b = self.window.buffer if b.writetabs: t = 't' else: t = 's' return '%d%s' % (b.indentlvl, t) 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() d = { 'bname': w.buffer.name(), 'mname': self.name(), 'flag': self._get_flag(), 'perc': self._get_perc(), 'indent': self._get_indent(), 'cursor': '(%d,%d)' % (c.y + 1, c.x + 1), 'mark': self._get_mark(), } return d 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: #XYZ: HACK--should fix if t in ('C-]', 'C-g'): self.window.set_error('Cancelled') else: self.window.set_error(str(e)) 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): mname = self.name() 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) if self.lmargin != 0: self.enable_line_numbers() 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) if self.lmargin != 0: self.enable_line_numbers() install = Fundamental.install