pmacs3/application.py

1046 lines
38 KiB
Python
Raw Normal View History

2007-03-06 10:05:38 -05:00
#!/usr/bin/env python
import curses, curses.ascii, getpass, os, re, string, sys, termios, time
2007-03-06 10:05:38 -05:00
import traceback
from subprocess import Popen, PIPE, STDOUT
2007-03-06 10:05:38 -05:00
import buffer, buffer.about, buffer.color, buffer.console, buffer.data
import buffer.fs
import bufferlist, color, completer, ispell, keyinput, method, minibuffer
import mode, util, window
from point import Point
2007-03-06 10:05:38 -05:00
2007-06-13 22:18:41 -04:00
class Application(object):
2007-03-06 10:05:38 -05:00
def __init__(self, stdscr, buffers=[], jump_to_line=None, init_mode=None):
# initalize curses primitives
self.stdscr = stdscr
2008-11-08 10:30:04 -05:00
self.y, self.x = self.stdscr.getmaxyx()
2007-03-06 10:05:38 -05:00
# initialize some basic stuff
self.config = {}
2007-03-06 10:05:38 -05:00
self.highlighted_ranges = []
2007-07-03 12:53:14 -04:00
self.mini_active = False
self.mini_buffer = None
self.mini_prompt = ""
self.error_string = ""
self.error_timestamp = None
2008-11-08 10:30:04 -05:00
self.need_draw = True
2007-07-03 12:53:14 -04:00
self.input = keyinput.Handler()
2008-11-08 10:30:04 -05:00
2008-03-16 02:16:41 -04:00
# let's prepopulate some default token colors
self.token_colors = {
'comment': ('red', 'default', 'bold'),
'comment.start': ('red', 'default', 'bold'),
'comment.data': ('red', 'default', 'bold'),
'comment.null': ('red', 'default', 'bold'),
'comment.end': ('red', 'default', 'bold'),
'continuation': ('red', 'default', 'bold'),
'string.start': ('green', 'default', 'bold'),
2008-09-29 21:02:23 -04:00
'string.data': ('green', 'default', 'bold'),
'string.null': ('green', 'default', 'bold'),
'string.hex': ('magenta', 'default', 'bold'),
'string.octal': ('magenta', 'default', 'bold'),
'string.escaped': ('magenta', 'default', 'bold'),
'string.end': ('green', 'default', 'bold'),
2008-07-19 16:00:11 -04:00
'char': ('green', 'default', 'bold'),
'integer': ('default', 'default', 'bold'),
'float': ('default', 'default', 'bold'),
'number': ('default', 'default', 'bold'),
'label': ('magenta', 'default', 'bold'),
'keyword': ('cyan', 'default', 'bold'),
'function': ('blue', 'default', 'bold'),
'builtin': ('magenta', 'default', 'bold'),
'method': ('cyan', 'default', 'bold'),
'bareword': ('default', 'default', 'bold'),
'delimiter': ('default', 'default', 'bold'),
2008-03-16 02:16:41 -04:00
}
self.default_color = ('default', 'default',)
2007-03-06 10:05:38 -05:00
2008-11-08 12:44:59 -05:00
# initialize configuration defaults
self._load_config_defaults()
2007-03-06 10:05:38 -05:00
# initialize our colors
if curses.has_colors():
curses.start_color()
try:
curses.use_default_colors()
color.default_color = True
except:
color.default_color = False
# this is how we can change color settings
if curses.can_change_color():
2009-01-15 19:07:53 -05:00
#curses.init_color(curses.COLOR_YELLOW, 222, 400, 900)
#curses.init_color(curses.COLOR_GREEN, 222, 400, 900)
#curses.init_color(curses.COLOR_RED, 222, 400, 222)
2007-03-06 10:05:38 -05:00
pass
else:
2009-01-19 13:55:54 -05:00
#raise Exception("urk")
2007-03-06 10:05:38 -05:00
self.set_error("Dynamic color not available")
2009-01-15 19:07:53 -05:00
color.init()
2007-03-06 10:05:38 -05:00
# initialize our modes
2007-10-18 17:07:35 -04:00
# the first dict stores our modes by (lowercase) name, and the other
# dicts all store various ways to "auto-detecting" the correct mode,
# in the precedence with which they are used.
self.modes = {}
self.mode_paths = {}
self.mode_basenames = {}
self.mode_extensions = {}
self.mode_detection = {}
2007-03-06 10:05:38 -05:00
# initialize our methods
self.methods = {}
2008-11-12 10:52:01 -05:00
names = (
'method', 'method.svn', 'method.cvs', 'method.search',
'method.buffers', 'method.move', 'method.shell',
'method.introspect', 'method.help', 'method.numbers',
'method.spell',
)
for name in names:
exec("import %s" % name)
mod = eval(name)
for mname in dir(mod):
cls = eval("%s.%s" % (name, mname))
if hasattr(cls, '_is_method') and cls._is_method:
self.methods[cls._name()] = cls()
2007-03-06 10:05:38 -05:00
# ok, now let's load all the "standard" modes
mode.install(self)
2008-10-04 10:12:12 -04:00
names = ('blame', 'c', 'console', 'consolemini', 'css',
'diff', 'dir', 'elisp', 'hex', 'html', 'java', 'javascript',
'lisp', 'make', 'mini', 'mutt', 'nasm', 'ocaml', 'perl',
'python', 'replace', 'rst', 'scheme', 'search', 'sh', 'sql',
'tt', 'text', 'text2', 'which', 'xml', 'cheetah', 'colortext',
'latex', 'insertmini', 'conf', 'haskell', 'erlang',
'iperl', 'iperlmini', 'ipython', 'ipythonmini', 'awk',
2008-10-29 00:02:50 -04:00
'shell', 'shellmini', 'fstab', 'yacc', 'pipe',
2008-12-07 00:02:28 -05:00
'mbox', 'error', 'lua',
)
for name in names:
exec("import mode.%s; mode.%s.install(self)" % (name, name))
2007-03-06 10:05:38 -05:00
# create all the insert methods for the character ranges we like
for c in string.letters + string.digits + string.punctuation:
2008-11-08 19:06:22 -05:00
obj = method.InsertString(c)
self.methods[obj.name] = obj
obj = method.OverwriteChar(c)
self.methods[obj.name] = obj
2007-03-06 10:05:38 -05:00
# buffer list stuff
2008-11-08 13:32:32 -05:00
height = self.y - 1
width = self.x
self.bufferlist = bufferlist.BufferList(height, width)
self.active_slot = 0
self.complete_slot = None
2007-10-18 11:28:55 -04:00
# run user custom code here
self.rcerror = None
2007-10-18 11:28:55 -04:00
self.loadrc()
2007-03-06 10:05:38 -05:00
# initialize our buffers
2008-10-29 13:17:16 -04:00
# note that only the first buffer will be initially visible
2008-10-29 00:02:50 -04:00
buffers.append(buffer.about.AboutBuffer())
if self.rcerror:
2008-10-29 10:58:06 -04:00
buffers.insert(0, buffer.data.DataBuffer('*RcError*', self.rcerror))
2007-03-06 10:05:38 -05:00
# build windows for our buffers
for b in buffers:
2008-10-29 13:17:16 -04:00
if b.modename:
window.Window(b, self)
2007-06-24 09:51:43 -04:00
else:
window.Window(b, self, mode_name=init_mode)
2007-03-06 10:05:38 -05:00
self.bufferlist.add_buffer(b)
self.bufferlist.set_slot(self.active_slot, buffers[0])
2007-03-06 10:05:38 -05:00
# see if the user has requested that we go to a particular line
if not self.rcerror and jump_to_line:
2007-06-04 23:05:33 -04:00
w = self.bufferlist.slots[0].window
2008-03-18 11:03:11 -04:00
self.methods['goto-line'].execute(w, lineno=jump_to_line)
2007-03-06 10:05:38 -05:00
# initialize our kill ring and last action
2007-07-02 20:29:27 -04:00
self.kill_ring = []
self.kill_commands = ['kill', 'kill-region']
self.last_action = None
self.last_search = None
2007-03-06 10:05:38 -05:00
self.last_replace_before = None
2007-07-02 20:29:27 -04:00
self.last_replace_after = None
self.registers = {}
2007-03-06 10:05:38 -05:00
# initialize tab handlers
completer.set('path', completer.FileCompleter(self))
completer.set('buffer', completer.BufferCompleter(self))
completer.set('command', completer.CommandCompleter(self))
completer.set('shell', completer.ShellCompleter(self))
completer.set('config', completer.ConfigCompleter(self))
completer.set('method', completer.MethodCompleter(self))
completer.set('register', completer.RegisterCompleter(self))
completer.set('mode', completer.ModeCompleter(self))
completer.set('token', completer.TokenCompleter(self))
2007-03-06 10:05:38 -05:00
# set up curses
self.win = curses.newwin(self.y, self.x, 0, 0)
self.win.leaveok(0)
2007-03-06 10:05:38 -05:00
curses.meta(1)
curses.cbreak()
2007-08-12 18:04:00 -04:00
curses.noecho()
curses.nonl()
curses.halfdelay(1)
curses.def_prog_mode()
2007-03-06 10:05:38 -05:00
def _load_config_defaults(self):
2008-12-05 00:41:02 -05:00
self.config['ignore_suffix'] = ['~', '-']
self.config['error_timeout'] = -1
self.config['max_error_len'] = 192
self.config['max_num_kills'] = 64
self.config['word_letters'] = string.letters + string.digits
self.config['default_color'] = ('default', 'default',)
self.config['margin'] = 80
self.config['margin_color'] = 'blue'
self.config['use_last_replace'] = False
def completion_window_is_open(self):
n = self.complete_slot
if n is None:
pass
elif n >= len(self.bufferlist.slots):
self.complete_slot = None
elif self.bufferlist.slots[n].window is None:
self.complete_slot = None
elif not hasattr(self.bufferlist.slots[n].window.buffer, '_completion'):
self.complete_slot = None
else:
return True
return False
def get_completion_window(self):
return self.bufferlist.slots[self.complete_slot].window
def open_completion_buffer(self, s, candidates):
opened = False
previous = None
if len(self.bufferlist.slots) == 1:
self.add_slot()
opened = True
n = len(self.bufferlist.slots) - 1
if self.active_slot == n:
n -= 1
if not opened:
previous = self.bufferlist.slots[n].window.buffer
2008-05-05 09:21:00 -04:00
lines = []
2008-05-05 13:47:12 -04:00
clen = len(candidates)
if clen > self.bufferlist.slots[n].height:
maxlen = 0
for c in candidates:
maxlen = max(maxlen, len(c))
# NOTE: this is not an optimal packing, but it's fast and easy to
# understand. i encourage someone else to write something better.
2008-05-30 14:13:26 -04:00
numcols = max(self.bufferlist.slots[n].width // (maxlen + 2), 1)
2008-05-05 13:47:12 -04:00
numrows = clen - ((clen // numcols) * (numcols - 1))
for i in range(0, numrows):
names = []
index = i * numcols
for j in range(0, numcols):
if index + j < clen:
names.append('%-*s' % (maxlen, candidates[index + j]))
else:
break
lines.append(' '.join(names))
else:
lines = list(candidates)
data = '\n'.join(lines)
b = self.data_buffer("*Completions*", data, switch_to=False)
b._completion = s
b._opened = opened
b._previous = previous
self.bufferlist.set_slot(n, b)
self.complete_slot = n
def close_completion_buffer(self):
w = self.get_completion_window()
opened = w.buffer._opened
if opened:
self.bufferlist.remove_slot(self.complete_slot)
else:
self.bufferlist.set_slot(self.complete_slot, w.buffer._previous)
self.close_buffer(w.buffer)
self.complete_slot = None
def set_completer(self, datatype, comp):
completer.set(datatype, comp)
# this sets up a mode, as well as optionally adding information on when to
# auto-load the mode
2008-11-08 12:44:59 -05:00
def setmode(self, name, cls, paths=[], bases=[], exts=[], detection=[]):
2007-10-18 12:05:21 -04:00
self.modes[name] = cls
for p in paths:
self.mode_paths[p] = name
2008-11-08 12:44:59 -05:00
for b in bases:
2007-10-18 12:05:21 -04:00
self.mode_basenames[b] = name
2008-11-08 12:44:59 -05:00
for e in exts:
2007-10-18 12:05:21 -04:00
self.mode_extensions[e] = name
for d in detection:
self.mode_detection[d] = name
2007-03-06 10:05:38 -05:00
def globals(self):
return globals()
def locals(self):
return locals()
def add_slot(self):
2007-06-04 23:05:33 -04:00
b = self.bufferlist.slots[self.active_slot].window.buffer
2007-06-04 03:29:37 -04:00
n = self.bufferlist.add_slot()
self.bufferlist.set_slot(n, b)
def remove_slot(self, n):
2008-12-05 00:41:02 -05:00
slots = self.bufferlist.slots
assert len(slots) > 1, "oh no you didn't!"
assert n >= 0 and n < len(slots), "invalid slot %r %r" % (n, len(slots))
2007-06-04 03:29:37 -04:00
self.bufferlist.remove_slot(n)
if self.active_slot > n:
self.active_slot = max(0, self.active_slot - 1)
2007-03-06 10:05:38 -05:00
def single_slot(self):
while len(self.bufferlist.slots) > 1:
if self.active_slot == 0:
self.remove_slot(1)
else:
self.remove_slot(0)
2007-06-04 03:29:37 -04:00
def get_window_height_width(self, i):
2008-12-05 00:41:02 -05:00
slots = self.bufferlist.slots
assert i >= 0 and i < len(slots), "invalid slot: %r" % slotname
slot = slots[i]
2007-03-06 10:05:38 -05:00
return (slot.height, slot.width)
# files and stuff
def close_buffer(self, b):
2008-11-08 12:44:59 -05:00
blist = self.bufferlist
blist.remove_buffer(b)
b.close()
2008-11-08 12:44:59 -05:00
active_slot = blist.slots[self.active_slot]
for i in range(0, len(blist.slots)):
if blist.slots[i].is_empty():
if blist.hidden_buffers:
blist.set_slot(i, blist.hidden_buffers[0])
else:
2008-11-08 12:44:59 -05:00
blist.set_slot(i, active_slot.window.buffer)
2009-01-28 16:28:33 -05:00
assert blist.slots[i].window is not None
def open_path(self, path, binary=False, cipher=None, password=None):
path = os.path.abspath(os.path.realpath(util.expand_tilde(path)))
b = self.get_buffer_by_path(path)
if b is None:
name = os.path.basename(path)
if self.has_buffer_name(name):
i = 1
auxname = '%s/%d' % (name, i)
while self.has_buffer_name(auxname):
i += 1
auxname = '%s/%d' % (name, i)
name = auxname
mode_name = None
if cipher is None:
if not os.path.exists(path) or os.path.isfile(path):
if binary:
b = buffer.Binary32Buffer(path, name=name)
else:
b = buffer.FileBuffer(path, name=name)
elif os.path.isdir(path):
2008-10-29 10:58:06 -04:00
b = buffer.fs.DirBuffer(path, name=name)
mode_name = 'dir'
else:
raise Exception, "not a file or dir: %r" % path
elif cipher == 'aes':
if not password:
raise Exception, "password is required"
if not os.path.exists(path) or os.path.isfile(path):
b = buffer.AesBuffer(path, password, name=name)
else:
raise Exception, "not a file or dir: %r" % path
try:
b.open()
except buffer.BinaryDataException:
if binary:
raise
else:
binary = True
b = buffer.Binary32Buffer(path, name=name)
b.open()
if mode_name is None:
mode_name = 'hex'
2007-10-21 20:55:29 -04:00
window.Window(b, self, height=0, width=0, mode_name=mode_name)
self.add_buffer(b)
return b
2007-03-06 10:05:38 -05:00
# mini buffer handling
def get_mini_buffer(self):
return self.mini_buffer
def mini_buffer_is_open(self):
return self.mini_buffer is not None
2008-11-08 12:44:59 -05:00
def open_mini_buffer(self, prompt, cb, method=None, tabber=None,
2008-05-30 14:13:26 -04:00
modename=None, startvalue=None):
2007-03-06 10:05:38 -05:00
if self.mini_buffer_is_open():
self.close_mini_buffer()
self.mini_prompt = prompt
2008-11-08 12:44:59 -05:00
self.mini_buffer = minibuffer.MiniBuffer(cb, method, tabber, modename)
try:
w = self.x - 1 - len(self.mini_prompt) - 1
window.Window(self.mini_buffer, self, height=1, width=w)
2008-05-30 14:13:26 -04:00
if startvalue:
self.mini_buffer.set_data(startvalue)
self.mini_active = True
except minibuffer.MiniBufferError:
self.mini_buffer = None
self.mini_prompt = ''
2007-03-06 10:05:38 -05:00
def exec_mini_buffer(self):
self.mini_buffer.callback(self.mini_buffer.make_string())
self.close_mini_buffer()
def close_mini_buffer(self):
self.mini_active = False
2007-03-06 10:05:38 -05:00
if self.mini_buffer_is_open():
self.mini_buffer.close()
self.mini_buffer = None
self.mini_prompt = ""
assert not self.mini_active
2007-03-06 10:05:38 -05:00
def get_mini_buffer_prompt(self):
return self.mini_prompt
def set_mini_buffer_prompt(self, p):
self.mini_prompt = p
# window handling
def toggle_window(self):
2008-11-08 12:44:59 -05:00
blist = self.bufferlist
assert 0 <= self.active_slot and self.active_slot < len(blist.slots)
self.active_slot = (self.active_slot + 1) % len(blist.slots)
2007-03-06 10:05:38 -05:00
def window(self):
2007-06-04 23:05:33 -04:00
return self.bufferlist.slots[self.active_slot].window
2007-03-06 10:05:38 -05:00
def active_window(self):
if self.mini_active:
2007-06-04 23:05:33 -04:00
return self.mini_buffer.windows[0]
2007-03-06 10:05:38 -05:00
else:
2008-11-08 12:44:59 -05:00
slot = self.active_slot
assert 0 <= slot and slot < len(self.bufferlist.slots), \
"0 <= %d < %d" % (slot, len(self.bufferlist.slots))
2007-06-04 03:29:37 -04:00
i = self.active_slot
return self.bufferlist.slots[i].window
2007-03-06 10:05:38 -05:00
# buffer handling
2008-11-08 12:44:59 -05:00
def new_file_buffer(self, path, data, switch_to=True):
assert not self.has_buffer_name(path), '%r is already open' % path
assert not os.path.exists(path), '%r already exists' % path
# touch the file
2007-03-06 10:05:38 -05:00
f = open(path, 'w')
f.write(data)
f.close()
2008-11-08 12:44:59 -05:00
# create the buffer
b = buffer.FileBuffer(path)
try:
b.open()
except buffer.BinaryDataException:
b = buffer.Binary32Buffer(path)
b.open()
b.modename = 'hex'
2007-10-21 20:55:29 -04:00
window.Window(b, self, height=0, width=0)
2007-03-06 10:05:38 -05:00
self.add_buffer(b)
if switch_to:
self.switch_buffer(b)
return b
2007-03-06 10:05:38 -05:00
def data_buffer(self, name, data, switch_to=True, modename=None):
if self.has_buffer_name(name):
b = self.bufferlist.buffer_names[name]
self.remove_buffer(b)
2008-10-29 10:58:06 -04:00
b = buffer.data.DataBuffer(name, data)
2007-03-06 10:05:38 -05:00
if modename is not None:
b.modename = modename
2007-10-21 20:55:29 -04:00
window.Window(b, self, height=0, width=0)
2007-03-06 10:05:38 -05:00
self.add_buffer(b)
if switch_to:
self.switch_buffer(b)
return b
def color_data_buffer(self, name, data, switch_to=True, modename='colortext'):
if self.has_buffer_name(name):
b = self.bufferlist.buffer_names[name]
self.remove_buffer(b)
2008-10-29 00:02:50 -04:00
b = buffer.color.ColorDataBuffer(name, data)
if modename is not None:
b.modename = modename
window.Window(b, self, height=0, width=0)
self.add_buffer(b)
if switch_to:
self.switch_buffer(b)
2007-03-06 10:05:38 -05:00
def get_buffer_by_path(self, path):
return self.bufferlist.get_buffer_by_path(path)
def has_buffer_name(self, name):
return self.bufferlist.has_buffer_name(name)
def get_buffer_by_name(self, name):
return self.bufferlist.get_buffer_by_name(name)
def has_buffer(self, b):
return self.bufferlist.has_buffer(b)
def add_buffer(self, b):
self.bufferlist.add_buffer(b)
def remove_buffer(self, b):
assert self.bufferlist.has_buffer(b), "can't kill what's not there"
assert len(self.bufferlist.buffers) > 1, "can't kill with no other buffers"
self.bufferlist.remove_buffer(b)
b.close()
if self.bufferlist.empty_slot(self.active_slot):
b2 = self.bufferlist.hidden_buffers[0]
self.bufferlist.set_slot(self.active_slot, b2)
def switch_buffer(self, b):
assert self.has_buffer_name(b.name()), "buffer %s does not exist" % (b.name())
assert 0 <= self.active_slot and self.active_slot < len(self.bufferlist.slots)
2007-06-05 00:49:24 -04:00
#self.add_window_to_buffer(b, self.active_slot)
2007-03-06 10:05:38 -05:00
self.bufferlist.set_slot(self.active_slot, b)
def add_window_to_buffer(self, b, slotname):
if not b.has_window(slotname):
slot = self.bufferlist.slots[slotname]
2007-10-21 20:55:29 -04:00
window.Window(b, self, height=slot.height, width=slot.width)
2007-03-06 10:05:38 -05:00
# error string handling
def set_error(self, s):
self.error_string = s
self.error_timestamp = time.time()
def clear_error(self):
self.error_string = ""
self.error_timestamp = None
def try_manual_resize(self):
y, x = self.stdscr.getmaxyx()
if y != self.y or x != self.x:
self.y, self.x = y, x
self.resize_slots()
2007-03-06 10:05:38 -05:00
def resize_event(self):
2007-07-28 23:08:42 -04:00
(self.y, self.x) = self.stdscr.getmaxyx()
2007-03-06 10:05:38 -05:00
self.resize_slots()
def resize_slots(self):
n = len(self.bufferlist.slots)
2007-06-04 10:06:04 -04:00
assert n > 0
2007-03-06 10:05:38 -05:00
x = self.x - 1
y_sum = self.y - n
2007-07-28 23:08:42 -04:00
self.bufferlist.resize(y_sum, x)
2007-03-06 10:05:38 -05:00
# exit
def exit(self):
self.done = True
# kill stack manipulation
def push_kill(self, s):
if s is not None:
if self.last_action in self.kill_commands and \
len(self.kill_ring):
2007-03-06 10:05:38 -05:00
self.kill_ring[-1] = self.kill_ring[-1] + s
else:
self.kill_ring.append(s)
maxnum = self.config.get('max_num_kills')
2008-11-08 12:44:59 -05:00
if maxnum and len(self.kill_ring) > maxnum:
2007-03-06 10:05:38 -05:00
self.kill_ring.pop(0)
def pop_kill(self):
return self.kill_ring.pop(-1)
def has_kill(self, i=-1):
return len(self.kill_ring) >= abs(i)
def get_kill(self, i=-1):
return self.kill_ring[i]
# undo/redo
def undo(self):
try:
2007-06-04 03:29:37 -04:00
self.window().undo()
2007-03-06 10:05:38 -05:00
except Exception, e:
self.set_error("%s" % (e))
def redo(self):
try:
2007-06-04 03:29:37 -04:00
self.window().redo()
2007-03-06 10:05:38 -05:00
except Exception, e:
self.set_error("%s" % (e))
# action creating methods
def make_insert_action(self, c):
return lambda: self.window().insert_string(c)
def make_window_action(self, methodname):
f = getattr(self.window(), methodname)
f()
# we are evil
def eval(self, s):
return eval(s)
2007-10-18 11:28:55 -04:00
# load user configuration NOW
def loadrc(self):
path = os.path.join(os.getenv('HOME'), '.pmc', 'conf')
if os.path.exists(path):
try:
f = open(path, 'r')
#execfile(path)
exec(f)
f.close()
except Exception, e:
s = traceback.format_exc()
self.rcerror = 'There was an error during startup:\n\n%s' % s
2007-10-18 11:28:55 -04:00
# the mighty run-loop!
2007-03-06 10:05:38 -05:00
def run(self):
self.done = False
self.draw()
if os.getenv('PMC_EARLY_OUT'):
return
2007-03-06 10:05:38 -05:00
while not self.done:
i = self.win.getch()
2008-05-29 16:35:21 -04:00
#if not self.mini_active and self.completion_window_is_open():
# self.close_completion_buffer()
2007-03-06 10:05:38 -05:00
if i == curses.KEY_RESIZE:
while i == curses.KEY_RESIZE:
i = self.win.getch()
self.resize_event()
2008-11-08 10:30:04 -05:00
self.need_draw = True
2007-03-06 10:05:38 -05:00
try:
self.input.parse(i)
except Exception, e:
self.set_error(str(e))
2008-11-08 10:30:04 -05:00
if self.input.tokens:
self.need_draw = True
while self.input.tokens:
2007-03-06 10:05:38 -05:00
t = self.input.tokens.pop(0)
self.active_window().mode.handle_token(t)
2008-05-29 16:35:21 -04:00
2008-11-08 10:30:04 -05:00
if self.need_draw:
self.draw()
2008-11-08 10:30:04 -05:00
self.need_draw = False
# clear the error line; it might look confusing to the user
try:
self.win.addstr(self.y-1, 0, ' ' * self.x)
except:
pass
self.win.refresh()
2007-03-06 10:05:38 -05:00
return
# highlighting
def add_highlighted_range(self, hr):
self.highlighted_ranges.append(hr)
def clear_highlighted_ranges(self, name=None):
if name is None:
self.highlighted_ranges = []
else:
i = 0
while i < len(self.highlighted_ranges):
if self.highlighted_ranges[i].name == name:
del self.highlighted_ranges[i]
else:
i += 1
2007-03-06 10:05:38 -05:00
# running external programs
def run_pipe(self, args, b, name='*Output*', switch='yes', modename=None):
pipe = Popen(args=args, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
pipe.stdin.write(b.make_string())
pipe.stdin.close()
output = pipe.stdout.read()
status = pipe.wait()
if callable(switch):
switch_to = switch(status)
else:
switch_to = bool(switch)
2008-12-03 15:30:33 -05:00
self.data_buffer(name, output, switch_to=switch_to, modename=modename)
return status
def run_external(self, *args):
curses.reset_shell_mode()
try:
pipe = Popen(args)
pipe.wait()
except OSError, e:
self.set_error("%s: %s" % (args[0], e))
curses.reset_prog_mode()
self.win.redrawwin()
self.draw()
2007-03-06 10:05:38 -05:00
# full screen drawer
def draw(self):
2007-07-28 23:08:42 -04:00
try:
n = len(self.get_minibuffer_lines())
assert n > 0
if n != self.bufferlist.mini_height:
self.bufferlist.resize_mini(n)
2007-09-26 11:56:07 -04:00
self.draw_slots()
self.draw_minibuffer()
2007-07-28 23:08:42 -04:00
self.draw_cursor()
2008-04-06 22:31:13 -04:00
self.win.refresh()
except Exception, e:
raise
2007-07-28 23:08:42 -04:00
# ok, so there was a problem...
# let's see if the screen changed sizes and if so, resize our slots
self.resize_event()
# clear the error message if appropriate
tout = self.config.get('error_timeout', 0)
tstamp = self.error_timestamp
if tstamp and tout and time.time() - tstamp > tout:
self.clear_error()
self.try_manual_resize()
2007-03-06 10:05:38 -05:00
def draw_cursor(self):
if self.mini_active:
b = self.mini_buffer
w = b.windows[0]
p = w.logical_cursor()
x = p.x + len(self.mini_prompt)
y = p.y
if y >= len(b.lines):
return
lines = self.get_minibuffer_lines()
while x > self.x - 1:
y += 1
x -= self.x - 1
vy, vx = self.y - len(lines) + y, x
else:
slot = self.bufferlist.slots[self.active_slot]
w = slot.window
swidth = slot.width - w.mode.lmargin - w.mode.rmargin
if w.active_point is not None and w.point_is_visible(w.active_point):
p = w.active_point
else:
p = w.logical_cursor()
blen = len(w.buffer.lines)
2009-01-28 16:28:33 -05:00
#count = 0
count = w.mode.header #XYZ
(x, y) = w.first.xy()
(vy, vx) = (None, None)
while count < slot.height:
if p.y == y and p.x >= x and p.x <= x + swidth:
vy, vx = slot.y_offset + count, p.x - x + w.mode.lmargin
break
2008-04-10 11:01:48 -04:00
if y >= blen or x + swidth >= len(w.buffer.lines[y]):
x = 0
y += 1
else:
x += swidth
count += 1
if vy is None or vx is None:
return
try:
self.win.move(vy, vx)
except:
raise Exception, "(%r=%r) no (%r)" % ((vx, vy), p, (self.x, self.y))
2007-03-06 10:05:38 -05:00
# sub-drawing methods
def draw_slots(self):
self.win.erase()
for i in range(0, len(self.bufferlist.slots)):
slot = self.bufferlist.slots[i]
self.draw_slot(i)
self.draw_status_bar(i)
2007-07-28 23:08:42 -04:00
def highlight_char(self, sy, sx, fg='default', bg='default'):
2007-06-14 06:10:20 -04:00
junk = self.win.inch(sy, sx)
char = chr(junk & 255)
attr = color.build(fg, bg)
try:
self.win.addstr(sy, sx, char, attr)
except Exception, e:
raise Exception, "(%d, %d, %r, %r) v. (%d, %d)" % \
(sy, sx, fg, bg, self.y, self.x)
def highlight_chars(self, sy, sx1, sx2, fg='default', bg='default'):
assert sx2 < self.x, "%d < %d" % (sx2, self.x)
2007-06-14 06:10:20 -04:00
for x in range(sx1, sx2):
self.highlight_char(sy, x, fg, bg)
2007-06-14 06:10:20 -04:00
2009-01-28 16:28:33 -05:00
# NOTE: this is totally broken
def map_point(self, w, p):
count = 0
x, y = w.first.xy()
while count < slot.height:
if p1.y == y and p1.x >= x and p1.x - x < slot.width:
return (count, x)
if x + slot.width > len(w.buffer.lines[y]):
x = 0
y += 1
else:
x += slot.width
count += 1
2007-06-04 03:29:37 -04:00
def draw_slot(self, i):
assert self.active_slot < len(self.bufferlist.slots), \
"strange: %d < %d" % (self.active_slot, len(self.bufferlist.slots))
assert i < len(self.bufferlist.slots), \
"puzzling: %d < %d" % (i, len(self.bufferlist.slots))
2007-06-04 03:29:37 -04:00
slot = self.bufferlist.slots[i]
if slot.window is None:
2007-03-06 10:05:38 -05:00
return
2007-06-04 03:29:37 -04:00
w = slot.window
2007-06-05 00:49:24 -04:00
modename = w.mode.name()
2007-06-04 03:29:37 -04:00
2009-01-28 16:28:33 -05:00
# XYZ
rstrs = w.mode.get_header(w)
assert len(rstrs) >= w.mode.header
for i in range(0, w.mode.header):
rstrs[i].draw(self.win, slot.y_offset + i, slot.x_offset)
# XYZ
self._draw_slot(i)
2007-03-06 10:05:38 -05:00
2007-06-14 06:10:20 -04:00
# highlighted regions
for hr in self.highlighted_ranges:
(high_w, p1, p2, fg, bg) = hr
2007-07-03 12:53:14 -04:00
if w is high_w and p2 >= w.first and p1 <= w.last:
2009-01-28 16:28:33 -05:00
#count = 0
count = w.mode.header #XYZ
x, y = w.first.xy()
2007-06-14 06:10:20 -04:00
px = p1.x
while count < slot.height:
if p1.y == y and px >= x and px - x < slot.width:
if slot.width > p2.x - x:
2008-04-06 22:31:13 -04:00
self.highlight_chars(slot.y_offset + count, px-x + w.mode.lmargin, p2.x-x, fg, bg)
2007-06-14 06:10:20 -04:00
break
else:
self.highlight_chars(slot.y_offset + count, px-x + w.mode.lmargin, slot.width - 1, fg, bg)
px += slot.width - px + x - 1
if x + slot.width > len(w.buffer.lines[y]):
2007-06-14 06:10:20 -04:00
x = 0
y += 1
else:
x += slot.width - 1
2007-06-14 06:10:20 -04:00
count += 1
2007-07-03 12:53:14 -04:00
if w.margins_visible:
shade = util.get_margin_color(w, 'blue')
limit = util.get_margin_limit(w, 80)
if limit < self.x:
for j in range(0, slot.height):
char = chr(self.win.inch(j + slot.y_offset, limit) & 255)
attr = color.build('default', shade, 'bold')
self.win.addstr(j + slot.y_offset, limit + w.mode.lmargin,
char, attr)
def _draw_slot(self, i):
slot = self.bufferlist.slots[i]
w = slot.window
redattr = color.build_attr(color.pairs('red', 'default'))
x, y = w.first.xy()
lm, rm = w.mode.lmargin, w.mode.rmargin
lines = w.buffer.lines
2009-01-28 16:28:33 -05:00
#count = 0
count = w.mode.header #XYZ
k = x // (slot.width - lm - rm)
modename = w.mode.name()
lit = w.mode.name() in w.buffer.highlights
ended = False
while count < slot.height:
if lit:
rlines = w.render_line_lit(y, slot.width - lm - rm)
else:
rlines = w.render_line_raw(y, slot.width - lm - rm)
for j in range(k, len(rlines)):
if lm:
lcont = j > 0
for rstr in slot.window.mode.get_lmargin(w, y, x, ended, lcont):
rstr.draw(self.win, slot.y_offset + count, 0)
for rstr in rlines[j]:
rstr.draw(self.win, slot.y_offset + count, 0 + lm)
if rm:
rcont = j < len(rlines) - 1
for rstr in slot.window.mode.get_rmargin(w, y, x, ended, rcont):
rstr.draw(self.win, slot.y_offset + count, slot.width - rm)
count += 1
if count >= slot.height:
break
k = 0
y += 1
ended = ended or y >= len(w.buffer.lines)
2007-03-06 10:05:38 -05:00
def draw_status_bar(self, slotname):
slot = self.bufferlist.slots[slotname]
2007-06-04 03:29:37 -04:00
if slot.window is None:
2007-03-06 10:05:38 -05:00
return
status = slot.window.mode.get_status_bar()
status = status.ljust(slot.width)[:slot.width]
2008-04-06 22:31:13 -04:00
self.win.addstr(slot.height + slot.y_offset, 0, status, curses.A_REVERSE)
2007-03-06 10:05:38 -05:00
# input bar drawing
def draw_minibuffer(self):
lines = self.get_minibuffer_lines()
attr = color.build('default', 'default')
if self.error_string:
attr = color.build('default', 'default')
2007-09-26 11:56:07 -04:00
for i in range(0, len(lines)):
line = lines[i]
2008-04-23 17:21:50 -04:00
try:
self.win.addstr(self.y - len(lines) + i, 0, line, attr)
2008-04-23 17:21:50 -04:00
except:
pass
if self.error_string or not self.mini_buffer_is_open():
return
pattr = color.build('cyan', 'default', 'bold')
plines = self.get_minibuffer_x_lines(self.mini_prompt)
for i in range(0, len(plines)):
pline = plines[i]
try:
self.win.addstr(self.y - len(lines) + i, 0, pline, pattr)
except:
pass
def get_minibuffer_x_lines(self, s):
i = 0
lines = []
while i < len(s):
lines.append(s[i:i + self.x - 1])
i += self.x - 1
return lines
2007-09-26 11:56:07 -04:00
def get_minibuffer_lines(self):
lines2 = []
if self.error_string:
2008-11-08 12:44:59 -05:00
maxlen = self.config['max_error_len']
if len(self.error_string) < maxlen:
s = self.error_string
else:
2008-11-08 12:44:59 -05:00
s = self.error_string[:maxlen] + '...'
elif self.mini_buffer_is_open():
s = self.mini_prompt + self.mini_buffer.lines[0]
lines2 = self.mini_buffer.lines[1:]
else:
return [' ' * (self.x - 1)]
lines = self.get_minibuffer_x_lines(s)
lines.extend(lines2)
return lines
2007-03-06 10:05:38 -05:00
def open_aes_file(path, name=None, binary=False):
2007-07-20 10:51:36 -04:00
if os.path.isfile(path) or not os.path.exists(path):
2007-07-19 14:37:39 -04:00
p = getpass.getpass("Please enter the AES password: ")
return buffer.AesBuffer(path, p, name)
2007-07-19 14:37:39 -04:00
else:
raise Exception, "can't open %r; unsupported file type" % path
def open_plain_file(path, name=None, binary=False):
2007-07-20 10:51:36 -04:00
if os.path.isfile(path) or not os.path.exists(path):
if binary:
return buffer.Binary32Buffer(path, name)
else:
return buffer.FileBuffer(path, name)
2007-07-19 14:37:39 -04:00
elif os.path.isdir(path):
2008-10-29 10:58:06 -04:00
return buffer.fs.DirBuffer(path, name)
2007-07-19 14:37:39 -04:00
else:
raise Exception, "can't open %r; unsupported file type" % path
2007-03-06 10:05:38 -05:00
2008-11-08 10:30:04 -05:00
def run_app(stdscr, buffers, jump_to_line=None, init_mode=None):
curses.def_shell_mode()
a = Application(stdscr, buffers, jump_to_line, init_mode)
a.run()
return 0
2008-11-08 10:30:04 -05:00
2007-03-06 10:05:38 -05:00
if __name__ == "__main__":
ciphers = {
'none': open_plain_file,
'aes': open_aes_file,
}
2007-03-06 10:05:38 -05:00
# preprocess args
argv = list(sys.argv[1:])
goto_line = None
i = 0
while i < len(argv):
if argv[i] == '-nw':
del argv[i]
elif argv[i].startswith('+'):
goto_line = int(argv.pop(i))
else:
i += 1
2007-03-06 10:05:38 -05:00
import optparse
parser = optparse.OptionParser()
parser.set_defaults(debug=False)
parser.set_defaults(goto=None)
parser.set_defaults(mode=None)
parser.set_defaults(cipher='none')
parser.set_defaults(linetype='unix')
parser.set_defaults(binary=False)
2007-03-06 10:05:38 -05:00
parser.add_option('-b', '--binary', dest='binary', action='store_true',
help='open file(s) in hex binary mode')
2007-03-06 10:05:38 -05:00
parser.add_option('-d', '--debug', dest='debug', action='store_true',
help='run in debug mode')
parser.add_option('-e', '--encrypt', dest='cipher', metavar='CIPHER',
help='decrypt and encrypt with CIPHER (default: none)')
parser.add_option('-g', '--goto', dest='goto', metavar='NUM', type='int',
help='jump to line NUM of the first argument')
parser.add_option('-m', '--mode', dest='mode', metavar='MODE',
help='open arguments in MODE')
(opts, args) = parser.parse_args(argv)
2007-03-06 10:05:38 -05:00
# if debugging, disable error handling to produce backtraces
if opts.debug:
2007-10-21 20:55:29 -04:00
mode.DEBUG = True
2007-03-06 10:05:38 -05:00
# if -b but no -m, then use -m hex
if opts.binary and not opts.mode:
opts.mode = 'hex'
2007-03-06 10:05:38 -05:00
# we will support using +19 as the first argument to indicate opening the
# first file on line 19 (same as -g 19 or --goto 19)
if goto_line:
opts.goto = goto_line
2007-06-04 23:05:33 -04:00
2007-03-06 10:05:38 -05:00
# figure out what kind of file open function to use
if opts.cipher not in ciphers:
sys.stderr.write('invalid cipher: %r' % opts.cipher)
sys.exit(2)
f = ciphers[opts.cipher]
# open each path using our callback to get a buffer, open that buffer, etc.
buffers = []
names = set()
paths = set()
2008-04-06 22:31:13 -04:00
2007-03-06 10:05:38 -05:00
for path in args:
path = os.path.abspath(os.path.realpath(util.expand_tilde(path)))
if path in paths:
continue
name = os.path.basename(path)
if name in names:
i = 1
auxname = '%s/%d' % (name, i)
while auxname in names:
i += 1
auxname = '%s/%d' % (name, i)
name = auxname
try:
b = f(path, name, opts.binary)
b.open()
except buffer.BinaryDataException, e:
if not opts.mode:
opts.mode = 'hex'
b = f(path, name, True)
b.open()
2007-03-06 10:05:38 -05:00
buffers.append(b)
paths.add(path)
names.add(name)
2008-11-08 10:30:04 -05:00
# save terminal state so we can restore it when the program exits
attr = termios.tcgetattr(sys.stdin)
keyinput.disable_control_chars()
2007-03-06 10:05:38 -05:00
# ok, now run our app
2008-11-08 10:30:04 -05:00
try:
curses.wrapper(run_app, buffers, opts.goto, opts.mode)
err = 0
2008-11-08 10:30:04 -05:00
except:
traceback.print_exc()
err = 1
2008-11-08 10:30:04 -05:00
# restore terminal state
termios.tcsetattr(sys.stdin, termios.TCSANOW, attr)
ispell.free()
sys.exit(err)