pmacs3/mode/__init__.py

529 lines
19 KiB
Python

import math
import os
import string
import sys
import traceback
from util import defaultdict
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 list(self.bindings.keys()):
if self.bindings[binding] == name:
del self.bindings[binding]
def add_binding(self, name, sequence):
if self.window is None:
#return
raise Exception("No window available")
elif not hasattr(self.window, 'application'):
raise Exception("No application available")
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'''
name = "Fundamental"
paths = []
basenames = []
extensions = []
detection = []
savetabs = False
tabwidth = 4
tabbercls = None
grammar = None
lexer = None
tabber = None
context = None
colors = {}
word_letters = None
commentc = None
# config settings installed/modified by the mode
config = {}
dconfig = {}
lconfig = {}
sconfig = {}
actions = []
_bindings = {}
completers = {}
format = "%(flag)s %(bname)s (%(mname)s) %(indent)s %(cursor)s %(perc)s %(vc-info)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.name.lower(), cls, paths=cls.paths,
bases=cls.basenames, exts=cls.extensions,
detection=cls.detection)
for (key, val) in cls.colors.items():
if key in app.token_colors:
s = 'token_colors: name %r conflict in %r' % (key, cls.name)
raise Exception(s)
else:
app.token_colors[key] = val
# install configuration stuff
for (key, val) in cls.config.items():
assert key not in app.config, "config conflict: %r" % key
app.config[key] = val
for (key, val) in cls.lconfig.items():
app.config.setdefault(key, [])
for val2 in val:
app.config[key].append(val2)
for (key, val) in cls.sconfig.items():
app.config.setdefault(key, set())
app.config[key].add(val)
for (key, d) in cls.dconfig.items():
app.config.setdefault(key, {})
for (subkey, val) in d.items():
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.items():
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-+', 'M-='))
self.add_bindings('decrement', ('M--',))
self.add_bindings('uppercase-word', ('M-u',))
self.add_bindings('lowercase-word', ('M-l',))
self.add_bindings('find-tag', ('M-.',))
# 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.ascii_letters + string.digits + string.punctuation:
self.add_binding('insert-string-%s' % c, c)
# per-mode bindings
for (name, sequences) in self._bindings.items():
self.add_bindings(name, sequences)
# 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.name, {})
return self.window.buffer.settings[self.name].get(name)
def init_setting(self, name, value):
self.window.buffer.settings.setdefault(self.name, {})
self.window.buffer.settings[self.name].setdefault(name, value)
def set_setting(self, name, value):
self.window.buffer.settings.setdefault(self.name, {})
self.window.buffer.settings[self.name][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'))]
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 = defaultdict(str)
d2 = {
'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),
'first': '(%d,%d)' % (w.first.y + 1, w.first.x + 1),
'mark': self._get_mark(),
}
d.update(d2)
d.update(dict(w.buffer.metadata))
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_msg(' '.join(self.curr_tokens))
return
else:
act.execute(self.window)
self.window.application.post_action_hook(act)
#self.window.application.last_action = act.name
except ActionError as e:
#XYZ: HACK--should fix
if t in ('C-]', 'C-g'):
self.window.set_msg('Cancelled')
else:
self.window.set_error(str(e))
except Exception as e:
if DEBUG:
raise
exc_type, exc_value, exc_traceback = sys.exc_info()
data = ''.join(traceback.format_tb(exc_traceback))
self.window.application.data_buffer("*Exception*", data,
switch_to=False)
self.window.set_error(str(e))
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