pmacs3/application.py

997 lines
36 KiB
Python
Executable File

#!/usr/bin/env python
import curses, curses.ascii, getpass, os, re, string, sys, termios, time
import traceback
from subprocess import Popen, PIPE, STDOUT
import buffer, buffer.about, buffer.color, buffer.console, buffer.data, buffer.fs
import bufferlist, color, completer, keyinput, method, minibuffer, mode
import util, window
from point import Point
class Application(object):
def __init__(self, stdscr, buffers=[], jump_to_line=None, init_mode=None):
# initalize curses primitives
self.stdscr = stdscr
self.y, self.x = self.stdscr.getmaxyx()
# initialize some basic stuff
# a highlighted_range contains three things: (window, start_p, end_p)
#self.state = defaultdict(lambda: {})
self.state = {}
self.config = {}
self.highlighted_ranges = []
self.mini_active = False
self.mini_buffer = None
self.mini_prompt = ""
self.error_string = ""
self.error_timestamp = None
self.need_draw = True
self.input = keyinput.Handler()
# some constants
self.error_timeout = -1
self.max_error_len = 192
self.max_num_kills = 64
self.def_word_letters = string.letters + string.digits
# 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'),
'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'),
'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'),
}
self.default_color = ('default', 'default',)
# xyz
self._load_config_defaults()
# initialize our colors
if curses.has_colors():
curses.start_color()
try:
curses.use_default_colors()
color.default_color = True
except:
# guess we weren't on 2.4
color.default_color = False
color.init()
# this is how we can change color settings
if curses.can_change_color():
#curses.init_color(curses.COLOR_BLUE, 750, 400, 0)
pass
else:
self.set_error("Dynamic color not available")
# initialize our modes
# 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 = {}
# initialize our methods
self.methods = {}
names = ('method', 'method.svn', 'method.cvs', 'method.search',
'method.buffers', 'method.move', 'method.shell',
'method.introspect', 'method.help')
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()
# ok, now let's load all the "standard" modes
mode.install(self)
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',
'bds', #XYZ
'shell', 'shellmini', 'fstab', 'yacc', 'pipe',
)
for name in names:
exec("import mode.%s; mode.%s.install(self)" % (name, name))
# create all the insert methods for the character ranges we like
for c in string.letters + string.digits + string.punctuation:
obj = method.InsertString(c)
self.methods[obj.name] = obj
obj = method.OverwriteChar(c)
self.methods[obj.name] = obj
# window/slot height/width
height = self.y - 1
width = self.x
# buffer list stuff
self.bufferlist = bufferlist.BufferList(height, width)
self.active_slot = 0
self.complete_slot = None
# run user custom code here
self.rcerror = None
self.loadrc()
# initialize our buffers
# note that only the first buffer will be initially visible
buffers.append(buffer.about.AboutBuffer())
#buffers.append(buffer.console.ConsoleBuffer())
if self.rcerror:
buffers.insert(0, buffer.data.DataBuffer('*RcError*', self.rcerror))
# build windows for our buffers
for b in buffers:
if b.modename:
window.Window(b, self)
else:
window.Window(b, self, mode_name=init_mode)
self.bufferlist.add_buffer(b)
self.bufferlist.set_slot(self.active_slot, buffers[0])
# see if the user has requested that we go to a particular line
if jump_to_line:
w = self.bufferlist.slots[0].window
self.methods['goto-line'].execute(w, lineno=jump_to_line)
# initialize our kill ring and last action
self.kill_ring = []
self.kill_commands = ['kill', 'kill-region']
self.last_action = None
self.last_search = None
self.last_replace_before = None
self.last_replace_after = None
self.registers = {}
# initialize tab handlers
method.DATATYPES['path'] = completer.FileCompleter(self)
method.DATATYPES['buffer'] = completer.BufferCompleter(self)
method.DATATYPES['command'] = completer.CommandCompleter(self)
method.DATATYPES['shell'] = completer.ShellCompleter(self)
method.DATATYPES['config'] = completer.ConfigCompleter(self)
method.DATATYPES['method'] = completer.MethodCompleter(self)
method.DATATYPES['register'] = completer.RegisterCompleter(self)
method.DATATYPES['mode'] = completer.ModeCompleter(self)
method.DATATYPES['token'] = completer.TokenCompleter(self)
# set up curses
self.win = curses.newwin(self.y, self.x, 0, 0)
self.win.leaveok(0)
curses.meta(1)
curses.cbreak()
curses.noecho()
curses.nonl()
# for non-blocking junk
curses.halfdelay(1)
curses.def_prog_mode()
def _load_config_defaults(self):
self.config['ignore-suffix'] = ['~', '-']
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
#lines = ["This is a completion buffer:", ""]
lines = []
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.
numcols = max(self.bufferlist.slots[n].width // (maxlen + 2), 1)
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, completer):
method.DATATYPES[datatype] = completer
# this sets up a mode, as well as optionally adding information on when to
# auto-load the mode
def setmode(self, name, cls, paths=[], basenames=[], extensions=[], detection=[]):
self.modes[name] = cls
for p in paths:
self.mode_paths[p] = name
for b in basenames:
self.mode_basenames[b] = name
for e in extensions:
self.mode_extensions[e] = name
for d in detection:
self.mode_detection[d] = name
def globals(self):
return globals()
def locals(self):
return locals()
def add_slot(self):
# XYZ
b = self.bufferlist.slots[self.active_slot].window.buffer
n = self.bufferlist.add_slot()
self.bufferlist.set_slot(n, b)
def remove_slot(self, n):
assert len(self.bufferlist.slots) > 1, "oh no you didn't!"
assert n >= 0 and n < len(self.bufferlist.slots), \
"invalid slot: %r (%r)" % (n, len(self.bufferlist.slots))
self.bufferlist.remove_slot(n)
if self.active_slot > n:
self.active_slot = max(0, self.active_slot - 1) #XYZ
def single_slot(self):
while len(self.bufferlist.slots) > 1:
if self.active_slot == 0:
self.remove_slot(1)
else:
self.remove_slot(0)
def get_window_height_width(self, i):
assert i >= 0 and i < len(self.bufferlist.slots), \
"invalid slot: %r" % slotname
slot = self.bufferlist.slots[i]
return (slot.height, slot.width)
# files and stuff
def close_buffer(self, b):
self.bufferlist.remove_buffer(b)
b.close()
active_slot = self.bufferlist.slots[self.active_slot]
for i in range(0, len(self.bufferlist.slots)):
if self.bufferlist.slots[i].is_empty():
if self.bufferlist.hidden_buffers:
self.bufferlist.set_slot(i, self.bufferlist.hidden_buffers[0])
else:
self.bufferlist.set_slot(i, active_slot.window.buffer)
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):
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'
window.Window(b, self, height=0, width=0, mode_name=mode_name)
self.add_buffer(b)
return b
# 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
def open_mini_buffer(self, prompt, callback, method=None, tabber=None,
modename=None, startvalue=None):
if self.mini_buffer_is_open():
self.close_mini_buffer()
self.mini_prompt = prompt
self.mini_buffer = minibuffer.MiniBuffer(callback, method, tabber, modename)
try:
w = self.x - 1 - len(self.mini_prompt) - 1
window.Window(self.mini_buffer, self, height=1, width=w)
if startvalue:
self.mini_buffer.set_data(startvalue)
self.mini_active = True
except minibuffer.MiniBufferError:
self.mini_buffer = None
self.mini_prompt = ''
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
if self.mini_buffer_is_open():
self.mini_buffer.close()
self.mini_buffer = None
self.mini_prompt = ""
assert not self.mini_active
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):
assert 0 <= self.active_slot and self.active_slot < len(self.bufferlist.slots)
self.active_slot = (self.active_slot + 1) % len(self.bufferlist.slots) #XYZ
def window(self):
return self.bufferlist.slots[self.active_slot].window
def active_window(self):
if self.mini_active:
return self.mini_buffer.windows[0]
else:
assert 0 <= self.active_slot and self.active_slot < len(self.bufferlist.slots), \
"0 <= %d < %d" % (self.active_slot, len(self.bufferlist.slots))
i = self.active_slot
return self.bufferlist.slots[i].window
# buffer handling
def file_buffer(self, path, data, switch_to=True):
assert not self.has_buffer_name(path), 'oh no! %r is already open' % path
assert not os.path.exists(path), 'oh no! %r already exists in fs' % path
f = open(path, 'w')
f.write(data)
f.close()
b = buffer.FileBuffer(path)
try:
b.open()
except buffer.BinaryDataException:
b = buffer.Binary32Buffer(path)
b.open()
b.modename = 'hex'
window.Window(b, self, height=0, width=0)
self.add_buffer(b)
if switch_to:
self.switch_buffer(b)
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)
b = buffer.data.DataBuffer(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)
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)
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)
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 b.name() is not "*Scratch*", "can't kill the scratch"
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)
#self.add_window_to_buffer(b, self.active_slot)
self.bufferlist.set_slot(self.active_slot, b)
def add_window_to_buffer(self, b, slotname):
# XYZ
if not b.has_window(slotname):
slot = self.bufferlist.slots[slotname]
window.Window(b, self, height=slot.height, width=slot.width)
# 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 resize_event(self):
(self.y, self.x) = self.stdscr.getmaxyx()
self.resize_slots()
def resize_slots(self):
n = len(self.bufferlist.slots)
assert n > 0
x = self.x - 1
y_sum = self.y - n
self.bufferlist.resize(y_sum, x)
# 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):
self.kill_ring[-1] = self.kill_ring[-1] + s
else:
self.kill_ring.append(s)
#if KILL_RING_LIMIT and len(self.kill_ring) > KILL_RING_LIMIT:
if self.max_num_kills and len(self.kill_ring) > self.max_num_kills:
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:
self.window().undo()
except Exception, e:
self.set_error("%s" % (e))
def redo(self):
try:
self.window().redo()
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)
# 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
# the mighty run-loop!
def run(self):
self.done = False
self.draw()
if os.getenv('PMC_EARLY_OUT'):
return
while not self.done:
i = self.win.getch()
#if not self.mini_active and self.completion_window_is_open():
# self.close_completion_buffer()
if i == curses.KEY_RESIZE:
while i == curses.KEY_RESIZE:
i = self.win.getch()
self.resize_event()
self.need_draw = True
err = ''
try:
self.input.parse(i)
except Exception, e:
err = str(e)
if self.input.tokens:
self.need_draw = True
while self.input.tokens:
t = self.input.tokens.pop(0)
self.active_window().mode.handle_token(t)
if self.need_draw:
self.draw(err)
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()
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
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()
# full screen drawer
def draw(self, err=""):
try:
n = len(self.get_minibuffer_lines())
assert n > 0
if n != self.bufferlist.mini_height:
self.bufferlist.resize_mini(n)
self.draw_slots()
self.draw_minibuffer()
self.draw_cursor()
self.win.refresh()
except Exception, e:
raise
# ok, so there was a problem...
# let's see if the screen changed sizes and if so, resize our slots
self.resize_event()
if err:
self.set_error(err)
if self.error_timestamp is not None and self.error_timeout > 0 and \
time.time() - self.error_timestamp > self.error_timeout:
self.clear_error()
(y, x) = self.stdscr.getmaxyx()
if y != self.y or x != self.x:
self.resize_event()
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)
count = 0
(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
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))
# 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)
def highlight_char(self, sy, sx, fg='default', bg='default'):
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)
for x in range(sx1, sx2):
self.highlight_char(sy, x, fg, bg)
def map_point(self, 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
#XYZ
#while w.ishidden(y) and y < len(w.buffer.lines):
# y += 1
else:
x += slot.width
count += 1
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))
slot = self.bufferlist.slots[i]
if slot.window is None:
return
w = slot.window
modename = w.mode.name()
self._draw_slot(i)
# highlighted regions
for hr in self.highlighted_ranges:
(high_w, p1, p2, fg, bg) = hr
if w is high_w and p2 >= w.first and p1 <= w.last:
count = 0
x, y = w.first.xy()
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:
self.highlight_chars(slot.y_offset + count, px-x + w.mode.lmargin, p2.x-x, fg, bg)
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]):
x = 0
y += 1
#XYZ
#while w.ishidden(y) and y < len(w.buffer.lines):
# y += 1
else:
x += slot.width - 1
count += 1
if w.margins_visible:
for (limit, shade) in w.margins:
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
count = 0
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)
def draw_status_bar(self, slotname):
slot = self.bufferlist.slots[slotname]
if slot.window is None:
return
status = slot.window.mode.get_status_bar()
status = status.ljust(slot.width)[:slot.width]
self.win.addstr(slot.height + slot.y_offset, 0, status, curses.A_REVERSE)
# input bar drawing
def draw_minibuffer(self):
lines = self.get_minibuffer_lines()
for i in range(0, len(lines)):
line = '%- *s' % (self.x - 1, lines[i])
try:
self.win.addstr(self.y - len(lines) + i, 0, line)
except:
pass
def get_minibuffer_lines(self):
lines, lines2 = [], []
if self.error_string:
if len(self.error_string) < self.max_error_len:
s = self.error_string
else:
s = self.error_string[:self.max_error_len] + '...'
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)]
i = 0
lines = []
while i < len(s):
lines.append(s[i:i + self.x - 1])
i += self.x - 1
lines.extend(lines2)
return lines
def open_aes_file(path, name=None, binary=False):
if os.path.isfile(path) or not os.path.exists(path):
p = getpass.getpass("Please enter the AES password: ")
return buffer.AesBuffer(path, p, name)
else:
raise Exception, "can't open %r; unsupported file type" % path
def open_plain_file(path, name=None, binary=False):
if os.path.isfile(path) or not os.path.exists(path):
if binary:
return buffer.Binary32Buffer(path, name)
else:
return buffer.FileBuffer(path, name)
elif os.path.isdir(path):
return buffer.fs.DirBuffer(path, name)
else:
raise Exception, "can't open %r; unsupported file type" % path
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()
if __name__ == "__main__":
ciphers = {
'none': open_plain_file,
'aes': open_aes_file,
}
# 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
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)
parser.add_option('-b', '--binary', dest='binary', action='store_true',
help='open file(s) in hex binary mode')
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)
# if debugging, disable error handling to produce backtraces
if opts.debug:
mode.DEBUG = True
# if -b but no -m, then use -m hex
if opts.binary and not opts.mode:
opts.mode = 'hex'
# 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
# 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()
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()
buffers.append(b)
paths.add(path)
names.add(name)
# save terminal state so we can restore it when the program exits
attr = termios.tcgetattr(sys.stdin)
keyinput.disable_control_chars()
# ok, now run our app
retval = 1
try:
retval = curses.wrapper(run_app, buffers, opts.goto, opts.mode)
except:
traceback.print_exc()
# restore terminal state
termios.tcsetattr(sys.stdin, termios.TCSANOW, attr)
sys.exit(retval)