#!/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
import mode2
import mode.mini, mode.search, mode.replace, mode.which
import mode.console, mode.consolemini
import mode.c, mode.python, mode.perl, mode.nasm, mode.sh, mode.sql, mode.java
import mode.lisp, mode.elisp, mode.scheme, mode.ocaml
import mode.blame, mode.diff, mode.dir
import mode.xml, mode.tt, mode.css, mode.javascript, mode.html
import mode.text, mode.text2, mode.mutt
import mode.bds, mode.life
import mode.rst

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
        # 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()

        # make sure the cursor is visible
        #curses.curs_set(1)

        # 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,
            'dir':         mode.dir.Dir,
            '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,
            'text2':       mode.text2.Text2,
            'which':       mode.which.Which,
            'xml':         mode.xml.XML,
            'html':        mode.html.HTML,
            'css':         mode.css.CSS,
            'life':        mode.life.Life,
            'mutt':        mode.mutt.Mutt,
            'javascript':  mode.javascript.Javascript,
            'sql':         mode.sql.Sql,
            'template':    mode.tt.Template,
            'bds':         mode.bds.BDS,
            'rst':         mode.rst.RST,
            'java':        mode.java.Java,

            'ocaml': mode.ocaml.Ocaml,

            # lisp dialects
            'lisp':   mode.lisp.Lisp,
            'scheme': mode.scheme.Scheme,
            'elisp':  mode.elisp.ELisp,
        }

        # 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',
            'components.xml': 'bds',
            '.emacs':         'elisp',
        }
        self.mode_extensions = {
            '.py':     'python',
            '.pl':     'perl',
            '.pm':     'perl',
            '.t':      'perl',
            '.c':      'c',
            #'.txt':   'text',
            '.txt':    'text2',
            '.s':      'nasm',
            '.sh':     'sh',
            '.bash':   'sh',
            '.xml':    'xml',
            '.xml.in': 'xml',
            '.html':   'html',
            '.htm':    'html',
            '.js':     'javascript',
            '.sql':    'sql',
            '.tt':     'template',
            '.css':    'css',
            '.rst':    'rst',
            '.java':   'java',
            '.el':     'elisp',
            '.scm':    'scheme',
            '.mli':    'ocaml',
            '.ml':     'ocaml',
        }
        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:
            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
        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['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(0)
        curses.meta(1)
        curses.cbreak()
        #curses.halfdelay(5)
        curses.noecho()
        curses.nonl()

    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 open_path(self, path, 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):
                    b = buffer2.FileBuffer(path, name=name)
                elif os.path.isdir(path):
                    b = buffer2.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 = buffer2.AesBuffer(path, password, name=name)
                else:
                    raise Exception, "not a file or dir: %r" % path
            b.open()
            window2.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:
            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
        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)

    # 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
    # each highlighted_range contains three things: [window, start_p, end_p]
    def add_highlighted_range(self, w, p1, p2, fg='default', bg='default'):
        self.highlighted_ranges.append([w, p1, p2, fg, bg])
    def clear_highlighted_ranges(self):
        self.highlighted_ranges = []

    # full screen drawer
    def draw(self, err=""):
        try:
            self.draw_slots() #XYZ
            self.draw_input_bar()
            self.draw_cursor()
            self.win.noutrefresh()
            curses.doupdate()
        except:
            # 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()
            if p.y >= len(b.lines):
                return
            (vy, vx) = (self.y - 1, min(p.x + len(self.mini_prompt), self.x - 2))
        else:
            slot = self.bufferlist.slots[self.active_slot]
            w = slot.window
            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()
            (vy, vx) = (None, None)
            while count < slot.height:
                if p.y == y and p.x >= x and p.x <= x + slot.width:
                    (vy, vx) = (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
        if vy is None or vx is None:
            return
        try:
            self.win.move(vy, vx)
        except:
            raise Exception, "(%d,%d==%r) was illegal (%d,%d)" % \
                (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 = junk & 255
        #attr = color.build(fg, bg, curses.A_REVERSE)
        attr = color.build(fg, bg)
        try:
            self.win.addch(sy, sx, char, attr)
        except:
            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 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, fg, bg) in self.highlighted_ranges:
            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.offset + count, px-x, p2.x-x, fg, bg)
                            break
                        else:
                            self.highlight_chars(slot.offset + count, px-x, slot.width, fg, bg)
                            px += slot.width - px + x
                    if x + slot.width >= len(w.buffer.lines[y]):
                        x = 0
                        y += 1
                    else:
                        x += slot.width
                    count += 1

        if w.margins_visible:
            for (limit, shade) in w.margins:
                #if limit <= self.x:
                if limit < self.x:
                    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)

    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]
            try:
                self.win.addstr(slot.offset + count, 0, s)
            except:
                self.set_error("addstr(%r + %r, %r, %r)" % (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 _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]
                    if token.string.endswith('\n'):
                        tstring = token.string[:-1]
                    else:
                        tstring = token.string
                        
                    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)

                    s          = tstring[s_offset:] 
                    token_done = x_offset + len(s) <= slot.width
                    token_wrap = x_offset + len(s) > slot.width
                    attr = color.build(*token.color)
                    self.win.addstr(slot.offset + count, x_offset, s[:slot.width - x_offset], attr)

                    if token_wrap:
                        self.win.addch(slot.offset + count, slot.width, '\\', redattr)
                        x += slot.width
                        count += 1
                    if token_done:
                        j += 1
                    if count >= slot.height:
                        break

                # we have finished this logical line of tokens
                j = 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  %-18s  (%s)--L%d--C%d--%s"
        status = format % (modflag, name, w.mode.name(), cursor.y+1, cursor.x+1, perc)
        #format = "%s  %-18s  (%s)--L%d--C%d--%s %s %s %s"
        #status = format % (modflag, name, w.mode.name(), cursor.y+1, cursor.x+1, perc, w.first, cursor, w.last)

        status = status.ljust(slot.width + 1)[:slot.width + 1]
        self.win.addstr(slot.height + slot.offset, 0, status, 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)

    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):
    if os.path.isfile(path) or not os.path.exists(path):
        p = getpass.getpass("Please enter the AES password: ")
        return buffer2.AesBuffer(path, p, nl, name)
    else:
        raise Exception, "can't open %r; unsupported file type" % path
def open_plain_file(path, nl, name=None):
    if os.path.isfile(path) or not os.path.exists(path):
        return buffer2.FileBuffer(path, nl, name)
    elif os.path.isdir(path):
        return buffer2.DirBuffer(path, nl, name)
    else:
        raise Exception, "can't open %r; unsupported file type" % path

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()
    if not args:
        args = ['.']
    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)