797 lines
28 KiB
Python
Executable File
797 lines
28 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import curses, curses.ascii, getpass, os, re, string, sets, sys, termios, time
|
|
import traceback
|
|
|
|
import buffer2, bufferlist, color, completer, keyinput, method, minibuffer
|
|
import util, window2
|
|
from point2 import Point
|
|
|
|
# modes
|
|
# TODO: mode_c mode_nasm mode_sh mode_sql mode_javascript mode_diff mode_tt
|
|
import mode2
|
|
import mode_mini, mode_search, mode_replace, mode_which
|
|
import mode_console, mode_consolemini
|
|
import mode_blame, mode_diff
|
|
import mode_python, mode_perl, mode_xml
|
|
import mode_life, mode_text, mode_mutt
|
|
|
|
def run(buffers, jump_to_line=None, init_mode=None):
|
|
# save terminal state so we can restore it when the program exits
|
|
attr = termios.tcgetattr(sys.stdin)
|
|
keyinput.disable_control_chars()
|
|
|
|
retval = 1
|
|
try:
|
|
retval = curses.wrapper(run_app, buffers, jump_to_line, init_mode)
|
|
except:
|
|
traceback.print_exc()
|
|
|
|
# restore terminal state
|
|
termios.tcsetattr(sys.stdin, termios.TCSANOW, attr)
|
|
return retval
|
|
|
|
def run_app(stdscr, buffers, jump_to_line=None, init_mode=None):
|
|
a = Application(stdscr, buffers, jump_to_line, init_mode)
|
|
a.run()
|
|
|
|
KILL_RING_LIMIT = 128
|
|
WORD_LETTERS = list(string.letters + string.digits)
|
|
ERROR_TIMEOUT = -1
|
|
#ERROR_TIMEOUT = 2
|
|
|
|
#DARK_BACKGROUND = False
|
|
DARK_BACKGROUND = True
|
|
|
|
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
|
|
self.margins_visible = False
|
|
#self.margins = [(80, 'blue'), (90, 'red')]
|
|
self.margins = [(80, 'blue'), ]
|
|
# each highlighted_range contains three things: [window, start_p, end_p]
|
|
self.highlighted_ranges = []
|
|
self.mini_active = False
|
|
self.mini_buffer = None
|
|
self.mini_prompt = ""
|
|
self.error_string = ""
|
|
self.error_timestamp = None
|
|
self.input = keyinput.Handler()
|
|
|
|
# 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
|
|
self.modes = {
|
|
'blame': mode_blame.Blame,
|
|
# 'c': mode_c.C,
|
|
'console': mode_console.Console,
|
|
'consolemini': mode_consolemini.Console,
|
|
'diff': mode_diff.Diff,
|
|
'fundamental': mode2.Fundamental,
|
|
'mini': mode_mini.Mini,
|
|
# 'nasm': mode_nasm.Nasm,
|
|
'perl': mode_perl.Perl,
|
|
'python': mode_python.Python,
|
|
'replace': mode_replace.Replace,
|
|
'search': mode_search.Search,
|
|
# 'sh': mode_sh.Sh,
|
|
'text': mode_text.Text,
|
|
'which': mode_which.Which,
|
|
'xml': mode_xml.XML,
|
|
'life': mode_life.Life,
|
|
'mutt': mode_mutt.Mutt,
|
|
# 'sql': mode_sql.Sql,
|
|
# 'javascript': mode_javascript.Javascript,
|
|
# 'template': mode_tt.Template,
|
|
}
|
|
|
|
# these are used in this order to determine which mode to open certain
|
|
# kinds of files
|
|
self.mode_paths = {
|
|
#'/etc/profile': 'sh',
|
|
}
|
|
self.mode_basenames = {
|
|
#'.bashrc': 'sh',
|
|
#'.bash_profile': 'sh',
|
|
#'.profile': 'sh',
|
|
}
|
|
self.mode_extensions = {
|
|
'.py': 'python',
|
|
'.pl': 'perl',
|
|
'.pm': 'perl',
|
|
'.t': 'perl',
|
|
# '.c': 'c',
|
|
'.txt': 'text',
|
|
# '.s': 'nasm',
|
|
# '.sh': 'sh',
|
|
# '.bash': 'sh',
|
|
'.xml': 'xml',
|
|
'.xml.in': 'xml',
|
|
# '.html': 'xml',
|
|
# '.htm': 'xml',
|
|
# '.sql': 'sql',
|
|
# '.js': 'javascript',
|
|
# '.tt': 'template'
|
|
}
|
|
self.mode_detection = {
|
|
'python': 'python',
|
|
'perl': 'perl',
|
|
# 'sh': 'sh',
|
|
# 'bash': 'sh',
|
|
}
|
|
|
|
# initialize our methods
|
|
self.methods = {}
|
|
for name in dir(method):
|
|
cls = eval("method.%s" % name)
|
|
if hasattr(cls, '_is_method') and cls._is_method:
|
|
self.methods[cls._name()] = cls()
|
|
|
|
# create all the insert methods for the character ranges we like
|
|
for c in string.letters + string.digits + string.punctuation:
|
|
## closing tags are handled differently
|
|
#if c == ')' or c == ']' or c == '}':
|
|
# continue
|
|
obj = method.InsertString(c)
|
|
self.methods[obj.name] = obj
|
|
|
|
# window/slot height/width
|
|
height = self.y - 2
|
|
width = self.x - 1
|
|
|
|
# initialize our buffers
|
|
# note that the first buffer in buffers will be initially visible
|
|
buffers.append(buffer2.ScratchBuffer())
|
|
buffers.append(buffer2.ConsoleBuffer())
|
|
self.bufferlist = bufferlist.BufferList(height, width)
|
|
self.active_slot = 0
|
|
|
|
# build windows for our buffers
|
|
for b in buffers:
|
|
if b.name() == '*Console*':
|
|
window2.Window(b, self, height, width, mode_name='console')
|
|
else:
|
|
window2.Window(b, self, height, width, mode_name=init_mode)
|
|
self.bufferlist.add_buffer(b)
|
|
self.bufferlist.set_slot(0, 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
|
|
method.GotoLine().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
|
|
|
|
# initialize tab handlers
|
|
method.DATATYPES['path'] = completer.FileCompleter()
|
|
method.DATATYPES['buffer'] = completer.BufferCompleter(self)
|
|
method.DATATYPES['command'] = completer.CommandCompleter()
|
|
method.DATATYPES['shell'] = completer.ShellCompleter()
|
|
method.DATATYPES['method'] = completer.MethodCompleter()
|
|
method.DATATYPES['mode'] = completer.ModeCompleter()
|
|
method.DATATYPES['perlfunction'] = completer.PerlFunctionCompleter()
|
|
|
|
# set up curses
|
|
self.win = curses.newwin(self.y, self.x, 0, 0)
|
|
self.win.leaveok(1)
|
|
curses.meta(1)
|
|
curses.halfdelay(1)
|
|
self.hide_cursor()
|
|
|
|
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)
|
|
|
|
# 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):
|
|
if self.mini_buffer_is_open():
|
|
self.close_mini_buffer()
|
|
self.mini_prompt = prompt
|
|
self.mini_buffer = minibuffer.MiniBuffer(callback, method, tabber, modename)
|
|
try:
|
|
window2.Window(self.mini_buffer, self, height=1,
|
|
width=self.x-1-len(self.mini_prompt)-1)
|
|
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 = buffer2.FileBuffer(path)
|
|
b.open()
|
|
window2.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 = buffer2.DataBuffer(name, data)
|
|
if modename is not None:
|
|
b.modename = modename
|
|
window2.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]
|
|
window2.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 - 1 - n
|
|
# XYZ this method seems broken
|
|
self.bufferlist.resize(x, y_sum)
|
|
|
|
# hide the curses cursor
|
|
def hide_cursor(self):
|
|
self.win.move(self.y-2, 0)
|
|
try:
|
|
curses.curs_set(0)
|
|
except:
|
|
pass
|
|
|
|
# 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 len(self.kill_ring) > KILL_RING_LIMIT:
|
|
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)
|
|
|
|
# the might run-loop!
|
|
def run(self):
|
|
self.done = False
|
|
#keycodes = []
|
|
while not self.done:
|
|
i = self.win.getch()
|
|
#if i > 0:
|
|
# if len(keycodes) >= 6:
|
|
# keycodes.pop(0)
|
|
# keycodes.append(str(i))
|
|
#self.set_error('keycodes: %s' % repr(keycodes))
|
|
if i == curses.KEY_RESIZE:
|
|
while i == curses.KEY_RESIZE:
|
|
i = self.win.getch()
|
|
self.resize_event()
|
|
err = ''
|
|
try:
|
|
self.input.parse(i)
|
|
except Exception, e:
|
|
err = str(e)
|
|
while len(self.input.tokens):
|
|
t = self.input.tokens.pop(0)
|
|
self.active_window().mode.handle_token(t)
|
|
self.draw()
|
|
if err:
|
|
self.set_error(err)
|
|
if self.error_timestamp is not None and \
|
|
ERROR_TIMEOUT > 0 and \
|
|
time.time() - self.error_timestamp > ERROR_TIMEOUT:
|
|
self.clear_error()
|
|
return
|
|
|
|
# highlighting
|
|
# each highlighted_range contains three things: [window, start_p, end_p]
|
|
def add_highlighted_range(self, w, start_p, end_p):
|
|
self.highlighted_ranges.append([w, start_p, end_p])
|
|
def clear_highlighted_ranges(self):
|
|
self.highlighted_ranges = []
|
|
|
|
# full screen drawer
|
|
def draw(self):
|
|
self.hide_cursor()
|
|
self.draw_slots()
|
|
self.draw_input_bar()
|
|
self.hide_cursor()
|
|
self.win.noutrefresh()
|
|
self.hide_cursor()
|
|
curses.doupdate()
|
|
|
|
# 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):
|
|
junk = self.win.inch(sy, sx)
|
|
c = junk & 255
|
|
attr = (junk & (curses.A_COLOR|curses.A_ATTRIBUTES)) | curses.A_REVERSE
|
|
self.win.addch(sy, sx, c, attr)
|
|
def highlight_chars(self, sy, sx1, sx2):
|
|
for x in range(sx1, sx2):
|
|
self.highlight_char(sy, x)
|
|
|
|
def draw_slot(self, i):
|
|
assert self.active_slot < len(self.bufferlist.slots), "only two"
|
|
assert i < len(self.bufferlist.slots), "only three"
|
|
slot = self.bufferlist.slots[i]
|
|
if slot.window is None:
|
|
return
|
|
w = slot.window
|
|
modename = w.mode.name()
|
|
|
|
if modename in w.buffer.highlights:
|
|
self._draw_slot_lit(i)
|
|
else:
|
|
self._draw_slot_raw(i)
|
|
|
|
# highlighted regions
|
|
for (high_w, p1, p2) in self.highlighted_ranges:
|
|
if w is not high_w:
|
|
# this region isn't in the current window so skip it
|
|
pass
|
|
elif p2 < w.first or p1 > w.last:
|
|
# this region is not visible, so skip it
|
|
pass
|
|
else:
|
|
# ok, so now we need to do some highlighting
|
|
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 px + slot.width > p2.x:
|
|
self.highlight_chars(slot.offset + count, px, p2.x)
|
|
break
|
|
else:
|
|
self.highlight_chars(slot.offset + count, px, px + slot.width)
|
|
px += slot.width
|
|
if x + slot.width >= len(w.buffer.lines[y]):
|
|
x = 0
|
|
y += 1
|
|
else:
|
|
x += slot.width
|
|
count += 1
|
|
|
|
if self.margins_visible:
|
|
for (limit, shade) in self.margins:
|
|
if self.x > limit:
|
|
for j in range(0, slot.height):
|
|
char = self.win.inch(j + slot.offset, limit) & 255
|
|
attr = color.build('default', shade, 'bold')
|
|
self.win.addch(j + slot.offset, limit, char, attr)
|
|
|
|
if self.mini_active is False and self.active_slot == i:
|
|
if w.active_point is not None and w.point_is_visible(w.active_point):
|
|
p = w.active_point
|
|
else:
|
|
p = w.logical_cursor()
|
|
count = 0
|
|
(x, y) = w.first.xy()
|
|
while count < slot.height:
|
|
if p.y == y and p.x >= x and p.x <= x + slot.width:
|
|
self.highlight_char(slot.offset + count, p.x - x)
|
|
break
|
|
if x + slot.width > len(w.buffer.lines[y]):
|
|
x = 0
|
|
y += 1
|
|
else:
|
|
x += slot.width
|
|
count += 1
|
|
|
|
def _draw_slot_raw(self, i):
|
|
slot = self.bufferlist.slots[i]
|
|
w = slot.window
|
|
modename = w.mode.name()
|
|
redattr = color.build_attr(color.pairs('red', 'default'))
|
|
|
|
(x, y) = w.first.xy()
|
|
lines = w.buffer.lines
|
|
count = 0
|
|
while count < slot.height:
|
|
if y >= len(lines):
|
|
self.win.addstr(slot.offset + count, 0, '~', redattr)
|
|
count += 1
|
|
continue
|
|
|
|
line = lines[y]
|
|
s = line[x:x + slot.width]
|
|
self.win.addstr(slot.offset + count, 0, s)
|
|
if x + slot.width >= len(line):
|
|
x = 0
|
|
y += 1
|
|
else:
|
|
self.win.addch(slot.offset + count, slot.width, '\\', redattr)
|
|
x += slot.width
|
|
count += 1
|
|
|
|
def _get_token_color(self, w, token):
|
|
fqlist = token.fqlist()
|
|
c = w.mode.default_color
|
|
for j in range(0, len(fqlist)):
|
|
name = '.'.join(fqlist[j:])
|
|
if name in w.mode.colors:
|
|
c = w.mode.colors[name]
|
|
break
|
|
if DARK_BACKGROUND:
|
|
c |= curses.A_BOLD
|
|
return c
|
|
|
|
def _draw_slot_lit(self, i):
|
|
slot = self.bufferlist.slots[i]
|
|
w = slot.window
|
|
modename = w.mode.name()
|
|
redattr = color.build_attr(color.pairs('red', 'default'))
|
|
highlighter = w.buffer.highlights[modename]
|
|
|
|
(x, y) = w.first.xy()
|
|
j = 0
|
|
count = 0
|
|
assert len(w.buffer.lines) == len(highlighter.tokens)
|
|
while count < slot.height:
|
|
if y < len(w.buffer.lines):
|
|
while j < len(highlighter.tokens[y]):
|
|
token = highlighter.tokens[y][j]
|
|
assert token.y == y, '%d == %d' % (token.y, y)
|
|
|
|
s_offset = max(x - token.x, 0)
|
|
x_offset = max(token.x - x, 0)
|
|
#assert x_offset < slot.width, '%d < %d' % (x_offset, slot.width)
|
|
assert x_offset <= slot.width, '%d <= %d' % (x_offset, slot.width)
|
|
|
|
c = self._get_token_color(w, token)
|
|
s = token.string[s_offset:]
|
|
token_done = x_offset + len(s) <= slot.width
|
|
token_wrap = x_offset + len(s) > slot.width
|
|
self.win.addstr(slot.offset + count, x_offset, s[:slot.width], c)
|
|
|
|
if token_wrap:
|
|
self.win.addch(slot.offset + count, slot.width, '\\', redattr)
|
|
x += slot.width
|
|
count += 1
|
|
if token_done:
|
|
j += 1
|
|
|
|
# we have finished this logical line of tokens
|
|
j = 0
|
|
x = 0
|
|
y += 1
|
|
count += 1
|
|
else:
|
|
self.win.addstr(slot.offset + count, 0, '~', redattr)
|
|
count += 1
|
|
|
|
def draw_status_bar(self, slotname):
|
|
slot = self.bufferlist.slots[slotname]
|
|
if slot.window is None:
|
|
return
|
|
|
|
w = slot.window
|
|
b = w.buffer
|
|
cursor = w.logical_cursor()
|
|
first = w.first
|
|
last = w.last
|
|
|
|
if b.readonly():
|
|
if b.changed():
|
|
modflag = '%*'
|
|
else:
|
|
modflag = '%%'
|
|
else:
|
|
if b.changed():
|
|
modflag = '**'
|
|
else:
|
|
modflag = '--'
|
|
|
|
if w.mark:
|
|
mark = w.mark
|
|
else:
|
|
mark = Point(-1, -1)
|
|
name = b.name()
|
|
|
|
if w.first_is_visible():
|
|
perc = "Top"
|
|
elif w.last_is_visible():
|
|
perc = "Bot"
|
|
else:
|
|
perc = "%2d%%" % (first.y*100 / len(b.lines))
|
|
|
|
# XYZ: we should actually use more of the 'state' variables
|
|
format = "----:%s-Fl %-18s (%s)--L%d--C%d--%s"
|
|
status = format % (modflag, name, w.mode.name(), cursor.y+1, cursor.x+1, perc)
|
|
#format = "----:%s-Fl %-18s (%s)--L%d--C%d--%s--%s--%s"
|
|
#status = format % (modflag, name, w.mode.name(), cursor.y+1, cursor.x+1, w.first, w.last, perc)
|
|
status = status[:slot.width + 1]
|
|
status += "-" * (slot.width - len(status) + 1)
|
|
self.win.addnstr(slot.height + slot.offset, 0, status, slot.width + 1,
|
|
curses.A_REVERSE)
|
|
|
|
# input bar drawing
|
|
def draw_input_bar(self):
|
|
if self.error_string:
|
|
self.draw_error()
|
|
elif self.mini_buffer_is_open():
|
|
self.draw_mini_buffer()
|
|
else:
|
|
self.draw_nothing()
|
|
try:
|
|
# fucking python, fucking curses, fucking fuck
|
|
self.win.addch(self.y-1, self.x-1, ' ')
|
|
except:
|
|
pass
|
|
def draw_error(self):
|
|
l = self.x - 1
|
|
s1 = self.error_string
|
|
s2 = util.cleanse(util.padtrunc(s1, l))
|
|
self.win.addnstr(self.y-1, 0, s2, l)
|
|
def draw_mini_buffer(self):
|
|
l = self.x - 1
|
|
b = self.mini_buffer
|
|
s1 = self.mini_prompt + b.lines[0]
|
|
s2 = util.padtrunc(s1, l)
|
|
self.win.addnstr(self.y-1, 0, s2, l)
|
|
|
|
if self.mini_active:
|
|
w = b.windows[0]
|
|
cursor = w.logical_cursor()
|
|
(cx, cy) = cursor.xy()
|
|
if cy >= len(b.lines):
|
|
return
|
|
elif cx == len(b.lines[cy]):
|
|
c = ' '
|
|
else:
|
|
c = b.lines[cy][cx]
|
|
self.win.addch(self.y-1, cx + len(self.mini_prompt), c, curses.A_REVERSE)
|
|
def draw_nothing(self):
|
|
l = self.x - 1
|
|
self.win.addnstr(self.y-1, 0, util.pad('', l), l)
|
|
|
|
def open_aes_file(path, nl, name=None):
|
|
p = getpass.getpass("Please enter the AES password: ")
|
|
b = buffer2.AesBuffer(path, p, nl, name)
|
|
return b
|
|
|
|
def open_plain_file(path, nl, name=None):
|
|
b = buffer2.FileBuffer(path, nl, name)
|
|
return b
|
|
|
|
if __name__ == "__main__":
|
|
ciphers = { 'none': open_plain_file,
|
|
'aes': open_aes_file }
|
|
|
|
linetypes = { 'win': '\r\n',
|
|
'mac': '\r',
|
|
'unix': '\n' }
|
|
|
|
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.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('-l', '--line-end', dest='linetype', metavar='TYPE',
|
|
help='use TYPE (win,mac,unix) line endings (default: unix)')
|
|
parser.add_option('-m', '--mode', dest='mode', metavar='MODE',
|
|
help='open arguments in MODE')
|
|
|
|
(opts, args) = parser.parse_args()
|
|
|
|
# if debugging, disable error handling to produce backtraces
|
|
if opts.debug:
|
|
mode2.DEBUG = True
|
|
|
|
# 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 len(args) > 0 and args[0].startswith('+'):
|
|
opts.goto = int(args[0][1:])
|
|
args = args[1:]
|
|
|
|
if opts.goto is not None:
|
|
opts.goto += 1
|
|
|
|
# figure out which kind of line types we're using
|
|
if opts.linetype not in linetypes:
|
|
sys.stderr.write('invalid linetype: %r' % opts.linetype)
|
|
sys.exit(1)
|
|
nl = linetypes[opts.linetype]
|
|
|
|
# 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 = sets.Set()
|
|
paths = sets.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
|
|
b = f(path, nl, name)
|
|
b.open()
|
|
buffers.append(b)
|
|
paths.add(path)
|
|
names.add(name)
|
|
|
|
# ok, now run our app
|
|
run(buffers, opts.goto, opts.mode)
|