#!/usr/bin/env python import curses, curses.ascii, getpass, os, re, string, sys, termios, time import traceback from subprocess import Popen, PIPE, STDOUT #from collections import defaultdict import buffer, bufferlist, color, completer, keyinput, method, minibuffer, mode import util, window from point import Point 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): curses.def_shell_mode() a = Application(stdscr, buffers, jump_to_line, init_mode) a.run() MAX_ERROR_DISPLAY = 128 KILL_RING_LIMIT = 128 WORD_LETTERS = list(string.letters + string.digits) ERROR_TIMEOUT = -1 #ERROR_TIMEOUT = 2 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.input = keyinput.Handler() # let's prepopulate some default token colors self.token_colors = { 'comment': ('red', 'default', 'bold'), 'comment.start': ('red', 'default', 'bold'), 'comment.null': ('red', 'default', 'bold'), 'comment.end': ('red', 'default', 'bold'), 'continuation': ('red', 'default', 'bold'), 'string.start': ('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': ('default', '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',) # 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 = ('about', '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', ) 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 the first buffer in buffers will be initially visible buffers.append(buffer.AboutBuffer()) buffers.append(buffer.ScratchBuffer()) buffers.append(buffer.ConsoleBuffer()) if self.rcerror: buffers.insert(0, buffer.DataBuffer('*RcError*', self.rcerror)) # build windows for our buffers for b in buffers: if b.name() == '*Console*': 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() method.DATATYPES['buffer'] = completer.BufferCompleter(self) method.DATATYPES['command'] = completer.CommandCompleter() method.DATATYPES['shell'] = completer.ShellCompleter() method.DATATYPES['config'] = completer.ConfigCompleter() method.DATATYPES['method'] = completer.MethodCompleter() method.DATATYPES['register'] = completer.RegisterCompleter() method.DATATYPES['mode'] = completer.ModeCompleter() # 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() curses.def_prog_mode() 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 = self.bufferlist.slots[n].width // (maxlen + 2) 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.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): 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) 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.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.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: 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() while not self.done: i = self.win.getch() 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(err) # 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 ERROR_TIMEOUT > 0 and \ time.time() - self.error_timestamp > 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) < MAX_ERROR_DISPLAY: s = self.error_string else: s = self.error_string[:MAX_ERROR_DISPLAY] + '...' 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.DirBuffer(path, name) else: raise Exception, "can't open %r; unsupported file type" % path 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) # ok, now run our app run(buffers, opts.goto, opts.mode)