import os, commands, re, tempfile
from subprocess import Popen, PIPE, STDOUT

import buffer, completer, default, dirutil, regex, util, window
from point import Point

class MethodError(Exception):
    pass

def arg(n, t=type(''), dt=None, p=None, h='', dv=default.none, ld=False):
    '''convenience function for arguments'''
    return Argument(n, type=t, datatype=dt, prompt=p, help=h, default=dv,
                    load_default=ld)
class Argument(object):
    def __init__(self, name, type=type(""), datatype=None, prompt=None, help='',
                 default=default.none, load_default=False):
        self.name = name
        self.type = type
        self.datatype = datatype
        if prompt is None:
            self.prompt = "%s: " % (name)
        else:
            self.prompt = prompt
        self.help = help

        self.load_default = load_default
        self.default = default

    def coerce_to_type(self, value):
        if self.type == type(0):
            try:
                return int(value, 0)
            except:
                raise Exception, "expected int; got %s" % (repr(value))
        else:
            return value

    def ask_for_value(self, method, w, **vargs):
        app = w.application
        assert app.mini_buffer_is_open() is False, "Recursive minibuffer antics"
        vargs2 = vargs.copy()
        assert callable(self.default), "default value func must be callable"
        if self.load_default:
            d = None
            starting_value = self.default(w)
        else:
            d = self.default(w)
            starting_value = None
        def return_value(v):
            if d is not None and v == "":
                v = d
            vargs2[self.name] = self.coerce_to_type(v)
            app.close_mini_buffer()
            method.execute(w, **vargs2)
        tabber = completer.get(self.datatype)
        if d is not None:
            p = self.prompt + "(%s) " % (d)
        else:
            p = self.prompt
        app.open_mini_buffer(p, return_value, method=method, tabber=tabber,
                             startvalue=starting_value)
        
class Method(object):
    _is_method = True
    args       = []
    help       = ""
    def __init__(self):
        self.name = self._name()
        if self.__doc__:
            self.help = self.__doc__

    def _name(cls):
        s = cls.__name__
        s2 = s[0].lower()
        for c in s[1:]:
            if c.isupper():
                s2 += '-' + c.lower()
            elif c == '_':
                s2 += '-'
            else:
                s2 += c
        return s2
    _name = classmethod(_name)

    def _pre_execute(self, w, **vargs):
        pass

    def execute(self, w, **vargs):
        try:
            self._pre_execute(w, **vargs)
        except MethodError, e:
            w.set_error(str(e))
            return
        for arg in self.args:
            if arg.name not in vargs:
                self.old_window = w
                arg.ask_for_value(self, w, **vargs)
                return
        self._execute(w, **vargs)
        w.buffer.undo_id += 1
    def _execute(self, w, **vargs):
        raise Exception, "Unimplemented Method: %s %r" % (self.name, vargs)

class RelexBuffer(Method):
    '''Relex the buffer; this resets syntax highlighting'''    
    def _execute(self, w, **vargs):
        h = w.get_highlighter()
        if h is None:
            w.set_error("No lexer for buffer.")
        else:
            h.highlight(w.buffer.lines)
            w.set_error("Buffer relexed.")

class ToggleWindow(Method):
    '''Move between visible windows'''
    def _execute(self, w, **vargs):
        w.application.toggle_window()

# complex text maniuplation
class TransposeWords(Method):
    '''Switch the place of the two words nearest the cursor'''
    pass

# you wanna quit right?
class Exit(Method):
    '''Exit the program, unless there are unsaved changes'''
    def _execute(self, w, **vargs):
        a = w.application
        assert a.mini_buffer_is_open() is False, "Recursive minibuffer antics"

        changed = False
        for b in w.application.bufferlist.buffers:
            changed = b.changed()
            if changed:
                break
        if not changed:
            w.application.exit()
            return
        else:
            self._old_window = w
            self._prompt = "There are buffers with unsaved changes; exit anyway? "
            a.open_mini_buffer(self._prompt, self._callback)

    def _callback(self, v):
        a = self._old_window.application
        if v == 'yes':
            a.exit()
        a.close_mini_buffer()
        if v == 'no':
            return
        a.open_mini_buffer(self._prompt, self._callback)
        a.set_error('Please type "yes" or "no"')

# insert text
class InsertString(Method):
    _is_method = False
    def __init__(self, s):
        self.name = "insert-string-%s" % s
        self.args = []
        self.help = "Insert %r into the current buffer." % s
        self.string = s
    def _execute(self, w, **vargs):
        try:
            w.insert_string_at_cursor(self.string)
        except buffer.ReadOnlyError:
            w.set_error('Buffer is read-only')
class OverwriteChar(Method):
    _is_method = False
    def __init__(self, c):
        self.name = 'overwrite-char-%s' % c
        self.args = []
        self.help = "Overwrite %r into the current buffer." % c
        self.char = c
    def _execute(self, w, **vargs):
        w.overwrite_char_at_cursor(self.char)
class InsertText(Method):
    '''Insert literal text into the buffer'''
    args = [arg('text', t="string", p="Literal: ", h='Literal text to insert')]
    def _execute(self, w, **vargs):
        w.insert_string_at_cursor(vargs['text'])
class InsertText2(Method):
    '''Insert escaped text into the buffer'''
    args = [arg('text', t="string", p="Text: ", h='Text to insert')]
    def _execute(self, w, **vargs):
        text = vargs['text'].replace('\\n', '\n')
        text = text.replace('\\t', '    ')
        text = text.replace('\\\\', '\\')
        w.insert_string_at_cursor(text)
class InsertMultilineText(Method):
    '''Insert multiple lines into the buffer (M-RETURN to end; C-] to cancel)'''
    def _execute(self, w, **vargs):
        f = lambda s: w.insert_string_at_cursor(s)
        w.application.open_mini_buffer('Multi-Insert: ', f, self, None, 'insertmini')

# killing/copying/etc.
class Kill(Method):
    '''Kill the contents of the current line'''    
    def _execute(self, w, **vargs):
        w.kill_line()
class KillRegion(Method):
    '''Kill the region between the mark and the cursor'''    
    def _execute(self, w, **vargs):
        w.kill_region()
        w.set_error("Region killed by %s" % self.name)
class Copy(Method):
    '''Copy the contents of the current line'''
    def _execute(self, w, **vargs):
        result = w.copy_line()
        if result is None:
            w.set_error("Empty kill region")
class CopyRegion(Method):
    '''Copy the region between the mark and the cursor'''
    def _execute(self, w, **vargs):
        w.copy_region()
        w.set_active_point(w.mark)
        w.set_error("Region copied")
class Yank(Method):
    '''Paste the top item in the kill ring into the buffer'''
    def _execute(self, w, **vargs):
        if w.application.has_kill():
            w.yank()
        else:
            w.set_error("Kill ring is empty")
class ShowKill(Method):
    '''Display the top item in the kill ring'''
    def _execute(self, w, **vargs):
        if w.application.has_kill():
            s = w.application.get_kill()
            x = w.application.x
            if len(s) > x - 40:
                s = s[:x - 40] + "..."
            w.set_error("Kill ring contains %r" % s)
        else:
            w.set_error("Kill ring is empty")
class PopKill(Method):
    '''Pop the top item in the kill ring off'''
    def _execute(self, w, **vargs):
        if w.application.has_kill():
            s = w.pop_kill()
            x = w.application.x
            if len(s) > x - 40:
                s = s[:x - 40] + "..."
            w.set_error("Removed %r from Kill ring" % s)
        else:
            w.set_error("Kill ring is empty")

# delete
class DeleteLeft(Method):
    '''Delete the character to the left of the cursor'''
    def _execute(self, w, **vargs):
        (x, y) = w.logical_cursor().xy()
        line = w.buffer.lines[y]
        tabwidth = w.mode.tabwidth
        if x >= tabwidth and x % tabwidth == 0 and line[0:x].isspace():
            w.delete(Point(x - tabwidth, y), Point(x, y))
        else:
            w.left_delete()
class DeleteRight(Method):
    '''Delete the character under the cursor'''
    def _execute(self, w, **vargs):
        cursor = w.logical_cursor()
        line   = w.buffer.lines[cursor.y]
        lvl    = w.mode.tabwidth
        if len(line[cursor.x:]) >= lvl and line[:cursor.x + lvl].isspace():
            w.delete(Point(cursor.x, cursor.y), Point(cursor.x + lvl, cursor.y))
        else:
            w.right_delete()
class DeleteLeftWord(Method):
    '''Delete the from the cursor left to the end of the word'''
    def _execute(self, w, **vargs):
        w.delete_left_word()
class DeleteRightWord(Method):
    '''Delete the from under cursor right to the end of the word'''
    def _execute(self, w, **vargs):
        w.delete_right_word()
class DeleteLeftWhitespace(Method):
    '''Delete all contiguous of whitespace left of the cursor'''
    def _execute(self, w, **vargs):
        c = w.logical_cursor()
        p = c
        l = w.point_left(p)
        if l is None:
            return
        while l is not None and w.point_char(l) in (' ', '\n'):
            p = l
            l = w.point_left(p)
        if p < c:
            w.delete(p, c)
class DeleteRightWhitespace(Method):
    '''Delete all contiguous of whitespace under and right of the cursor'''
    def _execute(self, w, **vargs):
        c = w.logical_cursor()
        p = c
        while w.point_char(p) in (' ', '\n'):
            r = w.point_right(p)
            if r is None:
                break
            p = r
        if p > c:
            w.delete(c, p)
class DeleteLeftSpace(Method):
    '''Delete all contiguous spaces left of the cursor'''
    def _execute(self, w, **vargs):
        c = w.logical_cursor()
        p = c
        l = w.point_left(p)
        if l is None:
            return
        while l is not None and w.point_char(l) == ' ':
            p = l
            l = w.point_left(p)
        if p < c:
            w.delete(p, c)
class DeleteRightSpace(Method):
    '''Delete all contiguous spaces under and right of the cursor'''
    def _execute(self, w, **vargs):
        c = w.logical_cursor()
        p = c
        while w.point_char(p) == ' ':
            r = w.point_right(p)
            if r is None:
                break
            p = r
        if p > c:
            w.delete(c, p)

# errata
class LowercaseWord(Method):
    '''Lowercase all characters in word'''
    def _execute(self, w, **vargs):
        (p1, p2) = w.get_word_bounds()
        word = w.buffer.get_substring(p1, p2)
        w.delete(p1, p2)
        w.insert_string(p1, word.lower())
class UppercaseWord(Method):
    '''Uppercase all characters in word'''
    def _execute(self, w, **vargs):
        (p1, p2) = w.get_word_bounds()
        word = w.buffer.get_substring(p1, p2)
        w.delete(p1, p2)
        w.insert_string(p1, word.upper())

class MetaX(Method):
    '''Invoke commands by name'''
    args    = [arg('method', dt="method", p="M-x ", h='Method to execute')]
    name_re = re.compile(r'^ *([a-z0-9_-]+) *(?:\( *\))? *$')
    full_re = re.compile(r'^ *([a-z0-9_-]+) *\((.*)\) *$')
    arg_re  = re.compile(r' *(-?[0-9\.]+|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\\"])*") *')
    varg_re = re.compile(r' *([a-zA-Z0-9_]+) *= *(-?[0-9\.]+|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\\"])*") *')
    def _execute(self, w, **vargs):
        m = self.name_re.match(vargs['method'])
        if m:
            name2 = m.group(1)
            self._sub_execute(w, name2, {})
            return
        m = self.full_re.match(vargs['method'])
        if m:
            vargs2 = {}
            if '=' in m.group(2):
                name2, vargs_str = m.group(1), m.group(2)
                i = 0
                m = self.varg_re.search(vargs_str, i)
                while m:
                    i = m.end()
                    vargs2[m.group(1)] = eval(m.group(2))
                    if i == len(vargs_str):
                        break
                    elif vargs_str[i] != ',':
                        break
                    else:
                        i += 1
                        m = self.varg_re.search(vargs_str, i)
                if i == len(vargs_str):
                    self._sub_execute(w, name2, vargs2)
                    return
            else:
                name2, args_str = m.group(1), m.group(2)
                i = 0
                m = self.arg_re.search(args_str, i)
                args = []
                while m:
                    i = m.end()
                    args.append(eval(m.group(1)))
                    if i == len(args_str):
                        break
                    elif args_str[i] != ',':
                        break
                    else:
                        i += 1
                        m = self.arg_re.search(args_str, i)
                d = {}
                try:
                    meth = w.application.methods[name2]
                    for (arg, value) in zip(meth.args, args):
                        vargs2[arg.name] = value
                except:
                    pass
                if i == len(args_str):
                    self._sub_execute(w, name2, vargs2)
                    return
        w.set_error('could not parse argument %r' % vargs['method'])
    def _sub_execute(self, w, name, vargs):
        if name in w.application.methods:
            w.application.methods[name].execute(w, **vargs)
        else:
            w.set_error('no method named %r found' % name)
            
class ToggleMargins(Method):
    '''Show or hide column margins'''
    def _execute(self, w, **vargs):
        w.margins_visible = not w.margins_visible
class CenterView(Method):
    '''Move view to center on cursor'''
    def _execute(self, w, **vargs):
        w.center_view(force=True)
class SetMark(Method):
    '''Set the mark to the current cursor location'''
    def _execute(self, w, **vargs):
        w.set_mark()
class SwitchMark(Method):
    '''Switch the mark and the cursor locations'''
    def _execute(self, w, **vargs):
        w.switch_mark()

# insertion methods
class InsertNewline(Method):
    '''Insert newline into buffer at the cursor'''
    def _execute(self, w, **vargs):
        w.insert_string_at_cursor('\n')
class InsertSpace(Method):
    '''Insert space into buffer at the cursor'''
    def _execute(self, w, **vargs):
        w.insert_string_at_cursor(' ')

class InsertSquotes(Method):
    '''Insert a pair of single-quotes into the buffer'''
    def _execute(self, w, **vargs):
        w.insert_string_at_cursor("''")
        w.backward()
class InsertDquotes(Method):
    '''Insert a pair of double-quotes into the buffer'''
    def _execute(self, w, **vargs):
        w.insert_string_at_cursor('""')
        w.backward()

class InsertTab(Method):
    '''Insert tab into buffer, or tabbify line, depending on mode'''
    def _execute(self, w, **vargs):
        cursor = w.logical_cursor()
        if w.mode.tabber:
            i = w.mode.tabber.get_level(cursor.y)
        else:
            i = None

        if i is None:
            w.insert_string_at_cursor(' ' * w.mode.tabwidth)
        else:
            j = w.buffer.count_leading_whitespace(cursor.y)
            if i != j:
                KillWhitespace().execute(w)
                w.insert_string(Point(0, cursor.y), ' ' * i)
            else:
                w.goto(Point(j, cursor.y))

class KillWhitespace(Method):
    '''Delete leading whitespace on current line'''
    def _execute(self, w, **vargs):
        cursor = w.logical_cursor()
        i = w.buffer.count_leading_whitespace(cursor.y)
        if i > 0:
            w.delete(Point(0, cursor.y), Point(i, cursor.y))

# tabification
class TabBuffer(Method):
    '''Tabbify every line in the current buffer'''
    def _execute(self, w, **vargs):
        y = w.logical_cursor().y
        it = InsertTab()
        for i in range(0, len(w.buffer.lines)):
            w.goto_line(i)
            it.execute(w)
        w.goto_line(y)
class GetIndentionLevel(Method):
    '''Calculate the indention level for this line'''
    def _execute(self, w, **vargs):
        cursor = w.logical_cursor()
        if not w.mode.tabber:
            w.set_error('No tabber available')
            return
        else:
            i = w.mode.tabber.get_level(cursor.y)
            w.set_error('Indention level: %r' % i)

# commenting
class CommentRegion(Method):
    '''Prepend a comment to every line in the current buffer'''
    def _execute(self, w, **vargs):
        cursor = w.logical_cursor()
        if cursor < w.mark:
            p1 = cursor
            p2 = w.mark
        elif w.mark < cursor:
            p1 = w.mark
            p2 = cursor
        else:
            w.input_line = "Empty kill region"
            return

        commentc = w.mode.commentc or '#'

        x = w.buffer.detect_indent_level(p1.y, p2.y) or 0
        for y in range(p1.y, p2.y):
            c = commentc
            if len(w.buffer.lines[y]) < x:
                c += ' ' * (x - len(w.buffer.lines[y]))
            w.buffer.insert_string(Point(x, y), c)

class UncommentRegion(Method):
    '''Remove a comment from every line in the current buffer'''
    def _execute(self, w, **vargs):
        cursor = w.logical_cursor()
        if cursor < w.mark:
            p1 = cursor
            p2 = w.mark
        elif w.mark < cursor:
            p1 = w.mark
            p2 = cursor
        else:
            w.input_line = "Empty kill region"
            return

        commentc  = w.mode.commentc or '#'
        commentre = re.compile('^( *)((?:%s)+)' % commentc)

        for y in range(p1.y, p2.y):
            line = w.buffer.lines[y]
            m = commentre.match(line)
            if not m:
                continue
            s1, s2 = m.groups()
            x1, x2 = len(s1), len(s1) + len(s2)
            w.buffer.delete(Point(x1, y), Point(x2, y))

# wrapping/justifying/etc
class WrapLine(Method):
    limit = 80
    space_re = re.compile(' +')
    def _token_len(self, tokens):
        l = 0
        for t in tokens:
            l += len(t)
        return l
    def _find_line_bounds(self, limit, tokens, x, y):
        if len(tokens[0]) > limit:
            i = 1
        else:
            i = 0
            l = self._token_len(tokens[:i+1])
            while i < len(tokens) and l <= limit:
                i += 1
                l = self._token_len(tokens[:i+1])
            while i > 1 and tokens and tokens[i-1].isspace():
                token = tokens.pop(i-1)
                l -= len(token)
                if x > l:
                    x -= len(token)
                i -= 1
        return i, x, y
    def _clear_preceeding_spaces(self, tokens, x, y):
        while tokens and self.space_re.match(tokens[0]):
            if x > 0:
                x = max(0, x - len(tokens[0]))
            del tokens[0]
        return x, y

    def _wrap_line(self, limit, line, x, y):
        tokens = re.findall('[^ ]+| +', line)
        if self._token_len(tokens) <= limit:
            return None, None, None

        lines = []
        while tokens and self._token_len(tokens) > limit:
            i, x, y = self._find_line_bounds(limit, tokens, x, y)
            s = ''.join(tokens[:i])
            lines.append(s)
            if x > len(s):
                y += 1
                x -= len(s)
            del tokens[:i]
            x, y = self._clear_preceeding_spaces(tokens, x, y)
        if tokens:
            lines.append(''.join(tokens) + ' ')

        return lines, x, y
    def _execute(self, w, **vargs):
        limit = util.get_margin_limit(w, self.limit)
        cursor = w.logical_cursor()
        x, y = cursor.xy()
        lines, x, y = self._wrap_line(limit, w.buffer.lines[y], x, y)
        if lines is None:
            return
        p1 = Point(0, cursor.y)
        p2 = Point(len(w.buffer.lines[cursor.y]), cursor.y)
        w.buffer.delete(p1, p2)
        p3 = Point(0, cursor.y)
        w.buffer.insert_lines(p3, lines)
        w.goto(Point(x, y))

class WrapParagraph(Method):
    limit    = 80
    valid_re = re.compile('^( *)([^ ].*)$')
    empty_re = regex.whitespace
    def _execute(self, w, **vargs):
        limit = util.get_margin_limit(w, self.limit)

        # we will store the start of our paragaph in p1, and also the original
        # cursor position.
        p1 = oldc = w.logical_cursor()
        cur_offset = 0

        m = self.valid_re.match(w.buffer.lines[p1.y])
        if not m:
            # the line was empty
            return
        elif m.group(1):
            # the line had leading whitespace
            return

        # see if we are starting in the middle of the paragraph; if so, then
        # let's find the actual begining, and update p1 accordingly.
        i = p1.y
        if i > 1 and w.buffer.lines[i] and not w.buffer.lines[i].startswith(' '):
            while i > 1 and w.buffer.lines[i - 1] and not w.buffer.lines[i - 1].startswith(' '):
                i -= 1
        p1 = Point(0, i)

        # get the first line; strip it, and put it in our new lines list.
        s1 = w.buffer.lines[p1.y][p1.x:]
        s2 = s1.rstrip()
        if p1.y <= oldc.y:
            cur_offset += len(s1) - len(s2)
        lines = [s2]

        # ok, so now let's move forward and find the end of the paragraph.
        i = p1.y + 1
        while i < len(w.buffer.lines) and w.buffer.lines[i] and not w.buffer.lines[i].startswith(' '):
            s1 = w.buffer.lines[i]
            s2 = s1.rstrip()
            if oldc.y == i:
                # once we've adjusted all our previous lines, adjust our
                # stored cursor to keep it's X and Y in sync (set Y to the line
                # the paragraph started on, increase X by the previous lines
                # plus added spaces minus removed whitespace.
                x = p1.x + oldc.x + sum([len(x) + 1 for x in lines]) - cur_offset
                oldc = Point(x, p1.y)
            elif i < oldc.y:
                cur_offset += len(s1) - len(s2)
            lines.append(s2)
            i += 1

        # stringify our paragraph
        s = " ".join(lines)

        #raise Exception, '%r %r' % (limit, s)

        # ok, so now we need to find the line breaks
        newlines = []
        while s:
            # if we have less than the limit left, add it and we're done!
            if len(s) < limit:
                newlines.append(s)
                break

            # look for the rightmost space within our bounds
            j = s.rfind(' ', 0, limit)
            # if we failed to find one, look for the leftmost space
            if j == -1:
                j = s.find(' ')
            # if we failed to find any, use the whole rest of the paragraph
            if j == -1:
                j = len(s)
            # add the next chunk we found and adjust the paragraph
            newlines.append(s[:j])
            s = s[j + 1:]

        # translate our cursor according to the line breaks we just did.
        (x, y) = oldc.xy()
        k = 0
        while k < len(newlines) - 1 and x > len(newlines[k]):
            x = x - len(newlines[k]) - 1
            y += 1
            k += 1

        #assert len(newlines), 'fooga: %r %r' % (limit, s)

        # kill the old paragraph region, insert the new, and goto the new cursor
        w.delete(p1, Point(len(w.buffer.lines[i-1]), i-1))
        w.insert_lines(p1, newlines)
        w.goto(Point(x, y))

class CountWords(Method):
    '''Count the number of words in the document'''
    def _execute(self, w, **vargs):
        wcount = 0
        pcount = 0
        inp = False
        name = w.buffer.name()
        for line in w.buffer.lines:
            c = len(line.split())
            if c and not inp:
                inp = True
                pcount += 1
            elif not c and inp:
                inp = False
            wcount += c
        w.set_error("%d words (%d paragraphs) found in %r" % (wcount, pcount, name))

class JustifyRight(Method):
    '''Justify text with the previous line right from the cursor by whitespace'''
    def _execute(self, w, **vargs):
        DeleteLeftSpace().execute(w)
        cursor = w.logical_cursor()
        prev_line = w.buffer.lines[cursor.y-1]
        this_line = w.buffer.lines[cursor.y]
        if cursor.y <= 0:
            return
        if cursor.x >= len(prev_line):
            return
        i = cursor.x
        while prev_line[i] != ' ':
            i += 1
            if i >= len(prev_line):
                return
        while prev_line[i] == ' ':
            i += 1
            if i >= len(prev_line):
                return
        s = ' ' * (i - cursor.x)
        w.insert_string_at_cursor(s)
class JustifyLeft(Method):
    '''Justify text with the previous line left from the cursor by whitespace'''
    def _execute(self, w, **vargs):
        DeleteRightWhitespace().execute(w)
        cursor = w.logical_cursor()
        prev_line = w.buffer.lines[cursor.y-1]
        this_line = w.buffer.lines[cursor.y]
        if cursor.y <= 0:
            return
        if cursor.x <= 0:
            return
        i = cursor.x
        while i >= len(prev_line):
            i -= 1
            if i <= 0:
                return
            if this_line[i] != ' ':
                return
        while prev_line[i] != ' ':
            i -= 1
            if i <= 0:
                return
            if this_line[i] != ' ':
                return
        while prev_line[i] == ' ':
            i -= 1
            if i >= len(prev_line):
                return
            if this_line[i] != ' ':
                return
        w.buffer.delete(Point(i, cursor.y), cursor)

# undo/redo
class Undo(Method):
    '''Undo last action'''
    def _execute(self, w, **vargs):
        try:
            w.undo()
        except Exception, e:
            w.set_error("%s" % (e))
class Redo(Method):
    '''Redo last undone action'''
    def _execute(self, w, **vargs):
        try:
            w.redo()
        except Exception, e:
            w.set_error("%s" % (e))

class UnindentBlock(Method):
    '''Prepend a tab of space to each line in region'''
    def _execute(self, w, **vargs):
        lvl    = w.mode.tabwidth
        cursor = w.logical_cursor()
        if cursor < w.mark:
            p1 = cursor
            p2 = w.mark
        elif w.mark < cursor:
            p1 = w.mark
            p2 = cursor
        else:
            w.input_line = "Empty kill region"
            return
        lines = w.buffer.lines[p1.y:p2.y]
        for i in range(0, len(lines)):
            if lines[i].startswith('    '):
                lines[i] = lines[i][lvl:]
        w.buffer.delete(Point(0, p1.y), Point(0, p2.y))
        w.buffer.insert_string(Point(0, p1.y), '\n'.join(lines) + '\n')
class IndentBlock(Method):
    '''Prepend a tab of space to each line in region'''
    def _execute(self, w, **vargs):
        cursor = w.logical_cursor()
        if cursor < w.mark:
            p1 = cursor
            p2 = w.mark
        elif w.mark < cursor:
            p1 = w.mark
            p2 = cursor
        else:
            w.input_line = "Empty kill region"
            return
        lines = w.buffer.lines[p1.y:p2.y]
        tstr = ' ' * w.mode.tabwidth
        for i in range(0, len(lines)):
            lines[i] = tstr + lines[i]
        w.buffer.delete(Point(0, p1.y), Point(0, p2.y))
        w.buffer.insert_string(Point(0, p1.y), '\n'.join(lines) + '\n')

class FileDiff(Method):
    '''diff the buffer's contents with the given file'''
    args = [arg("path", t=type(""), p="Filename: ", dt='path',
                h="path to diff against current buffer's contents")]
    def _execute(self, w, **vargs):
        cmd = ("/usr/bin/diff", '-u', '-', vargs['path'])
        pipe = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
        pid = pipe.pid

        indata = w.buffer.make_string()
        pipe.stdin.write(indata)
        pipe.stdin.close()

        outdata = pipe.stdout.read()
        errdata = pipe.stderr.read()
        status = pipe.wait() >> 8

        if status == 0:
            w.set_error("No difference found")
        elif status == 1:
            w.application.data_buffer("*Diff*", outdata, switch_to=True, modename='diff')
            w.set_error("Differences were found")
        else:
            w.application.data_buffer("*Diff*", errdata, switch_to=True)
            w.set_error("There was an error: %d exited with status %s" % (pid, status))

class SetMode(Method):
    '''Set the mode of the current buffer'''
    args = [arg('mode', dt='mode', p="Enter new mode: ")]
    def _execute(self, w, **vargs):
        mode_name = vargs['mode']
        m = w.application.modes[mode_name](w)
        w.set_mode(m)
        w.set_error('Set mode to %r' % (mode_name))

class Cancel(Method):
    '''Cancel command in-progress, and return to the main buffer'''
    def execute(self, w, **vargs):
        w.application.close_mini_buffer()
        if w.application.completion_window_is_open():
            w.application.close_completion_buffer()
        w.set_error('Cancel')

class SplitWindow(Method):
    '''Split the main window horizontally into upper and lower windows'''
    def execute(self, w, **vargs):
        a = w.application
        a.add_slot()
        if not w.cursor_is_visible():
            p = w.first
            w.goto(p)
        n = len(a.bufferlist.slots)
        a.set_error('Window has been split into %d windows!' % n)

class UnsplitWindow(Method):
    '''Maximize the current window to fill the screen'''
    def execute(self, w, **vargs):
        w.application.single_slot()
        w.set_error('Window has been unsplit back to one window!')

class CloseTag(Method):
    mytag     = ')'
    def _execute(self, w, **vargs):
        # first, de-reference some variables and actually do the insertion
        # NOTE: we derence the cursor *before* inserting the character, so it is
        # expecected that the cursor variable should be the point the new
        # character is on.
        (x, y) = w.logical_cursor().xy()
        w.insert_string_at_cursor(self.mytag)
        app         = w.application
        buffer      = w.buffer
        highlighter = buffer.highlights[w.mode.name()]
        tokens      = highlighter.tokens

        # REFACTOR: we have methods in window to do this now
        i = 0
        while i < len(tokens[y]):
            token = tokens[y][i]
            if token.x == x and token.string == self.mytag:
                break
            elif token.x <= x and token.end_x() > x:
                return
            i += 1
        if i >= len(tokens[y]):
            return

        tag_stack = []
        while y >= 0:
            while i >= 0 and i < len(tokens[y]):
                token = tokens[y][i]
                n = token.name
                s = token.string
                if n in w.mode.closetokens and s in w.mode.closetags:
                    tag_stack.append(s)
                elif n in w.mode.opentokens and s in w.mode.opentags:
                    if tag_stack[-1] == w.mode.opentags[s]:
                        del tag_stack[-1]
                    else:
                        app.set_error("tag mismatch; got %r expected %r" %
                                      (s, w.mode.closetags[tag_stack[-1]]))
                        return
                if len(tag_stack) == 0:
                    p = Point(token.x, y)
                    s = w.buffer.lines[p.y][:p.x+1]
                    if len(s) > 60:
                        s = "..." + s[-60:]
                    msg = 'matches %r' % s
                    w.set_active_point(p, msg)
                    return
                i -= 1
            y -= 1
            i = len(tokens[y]) - 1

class CloseParen(CloseTag):
    mytag = ')'
class CloseBrace(CloseTag):
    mytag = '}'
class CloseBracket(CloseTag):
    mytag = ']'

class RegisterSave(Method):
    '''Save the top item of the kill stack into the named register'''
    MAX_TXT = 30
    MAX_REG = 18
    args = [arg('name', dt="register", p="Register name: ",
                h="Name of the register to use")]
    def _pre_execute(self, w, **vargs):
        if not w.has_kill():
            raise MethodError("No text on the kill stack")
    def _execute(self, w, **vargs):
        name                          = vargs['name']
        text                          = w.get_kill()
        w.application.registers[name] = text
        if len(name) > self.MAX_REG:
            name = name[:self.MAX_REG] + '...'
        if len(text) > self.MAX_TXT:
            text = text[:self.MAX_TXT] + '...'
        w.set_error('Saved %r into register %r' % (text, name))

class RegisterRestore(Method):
    '''Push the value saved in the named register onto the kill stack'''
    MAX_TXT = 30
    MAX_REG = 18
    args = [arg('name', dt="register", p="Register name: ",
                h="Name of the register to use")]
    def _execute(self, w, **vargs):
        name = vargs['name']
        if name not in w.application.registers:
            w.set_error('Register %r does not exist' % name)
            return
        app = w.application
        text = app.registers[name]
        w.push_kill(text)
        if len(text) > self.MAX_TXT:
            text = text[0:self.MAX_TXT] + '...'
        if len(name) > self.MAX_REG:
            name = name[0:self.MAX_REG] + '...'
        w.set_error('Restored %r from register %r' % (text, name))

class GetConfigVariable(Method):
    args = [arg('name', dt='config', p="Variable name: ",
                h='Name of the configuration parameter')]
    def _execute(self, w, **vargs):
        name = vargs['name']
        if name in w.application.config:
            value = w.application.config[name]
            w.set_error("param %r set to %r" % (name, value))
        else:
            w.set_error("param %r is not set" % (name,))

class ViewConfigVariables(Method):
    def _execute(self, w, **vargs):
        lines = ["APPLICATION CONFIGURATION VARIABLES\n"]
        for name in w.application.config:
            lines.append("    %-20s  %r\n" % (name, w.application.config[name]))
        data = ''.join(lines)
        w.application.data_buffer('*Config*', data, switch_to=True)

class SetConfigVariable(Method):
    args = [arg('name', dt='config', p="Variable name: ",
                h='Name of the configuration parameter'),
            arg('value', t=type(''), p="Variable value: ",
                h='Configuration parameter value to use')]
    def _execute(self, w, **vargs):
        name  = vargs['name']
        found = name in w.application.config
        try:
            value = eval(vargs['value'])
        except:
            value = vargs['value']
        w.application.config[name] = value
        if found:
            w.set_error("param %r set to %r" % (name, value))
        else:
            w.set_error("previously unset param %r set to %r" % (name, value))

class ToggleHeader(Method):
    def _execute(self, w, **vargs):
        if w.mode.showing_header():
            w.mode.disable_header()
            w.set_error('Header hidden')
        else:
            w.mode.enable_header()
            w.set_error('Header visible')

class ToggleLineNumbers(Method):
    def _execute(self, w, **vargs):
        if w.mode.showing_line_numbers():
            w.mode.disable_line_numbers()
            w.set_error('Line numbers hidden')
        else:
            w.mode.enable_line_numbers()
            w.set_error('Line numbers visible')

class SetTabWidth(Method):
    args = [arg('width', t=type(0), p="Tab Width: ", h='New tab width for buffer')]
    def _execute(self, w, **vargs):
        w.mode.tabwidth = vargs['width']
        w.set_error('Tab width set to %d' % w.mode.tabwidth)

class SetModeTabWidth(Method):
    args = [arg('mode', dt='mode', p="Mode: ", h=''),
            arg('width', t=type(0), p="Default Tab Width: ", h='New default tab width for mode')]
    def _execute(self, w, **vargs):
        app, mode  = w.application, vargs['mode']
        if mode not in app.modes:
            w.set_error('Mode %r not found' % mode)
            return
        app.modes[mode].tabwidth = vargs['width']
        w.set_error('Default tab width set to %d' % app.modes[mode].tabwidth)