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

import buffer, completer, default, dirutil, regex, util, window
import buffer.colors
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_completer(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       = ""
    metadata   = {}
    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 in ('yes', 'y'):
            a.exit()
        a.close_mini_buffer()
        if v in ('no', 'n'):
            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):
    '''Call pmacs functions by name (with or without arguments)'''
    args        = [arg('method', dt="method", p="M-x ", h='Method to execute')]
    name_re     = re.compile(r'[a-z0-9_-]+')

    py_empty_re = re.compile(r'^\( *\)$')
    py_delim_re = re.compile(r', *')
    py_end_re   = re.compile(r' *\)')

    pythonstyle = {
        'arg_re':  re.compile(r'("(?:[^\\"]|\\.)"|[^=),]+)(?= *,| *\))'),
        'varg_re': re.compile(r'([a-z0-9_]+)=("(?:[^\\"]|\\.)"|[^=),]+)'),
    }
    shellstyle = {
        'arg_re':  re.compile(r'("(?:[^\\"]|\\.)"|[^= ]+)(?= +|$)'),
        'varg_re': re.compile(r'([a-z0-9_]+)=("(?:[^\\"]|\\.)"|[^= ]+)'),
    }
    def _parse_arg(self, w, style, other, i):
        m1 = style['arg_re'].match(other, i)
        m2 = style['varg_re'].match(other, i)
        if not (m1 or m2):
            w.set_error("1couldn't parse %r:%d -> %r" % (other, i, other[i:]))
            return (None, None, 0)
        elif m1 and m2:
            w.set_error("3couldn't parse %r:%d -> %r" % (other, i, other[i:]))
            return (None, None, 0)

        if m1:
            name, value = None, m1.group(1)
            if value.startswith('"'): value = eval(value)
            return (None, value, m1.end())
        elif m2:
            name, value = m2.group(1), m2.group(2)
            if value.startswith('"'): value = eval(value)
            return (name, value, m2.end())
        
    def _execute(self, w, **vargs):
        s = vargs['method'].strip()
        m = self.name_re.match(s)
        assert m, "invalid cmd %r" % s

        func  = m.group(0)
        args  = []
        vargs = {}
        other = s[m.end():].strip()
        if not other or self.py_empty_re.match(other):
            # no arguments
            pass
        elif other.startswith('('):
            # python type call
            i = 1
            while other[i] == ' ':
                i += 1
            while i < len(other):
                name, value, i = self._parse_arg(w, self.pythonstyle, other, i)
                if not value: return
                elif name:
                    vargs[name] = value
                else:
                    args.append(value)
                if self.py_end_re.match(other, i): break
                m = self.py_delim_re.match(other, i)
                if not m:
                    w.set_error("2couldn't parse %r" % s[i:])
                    return
                i = m.end()
        else:
            # shell type call
            i = 0
            while i < len(other):
                if other[i] == ' ':
                    i += 1
                    continue
                name, value, i = self._parse_arg(w, self.shellstyle, other, i)
                if not value: return
                elif name:
                    vargs[name] = value
                else:
                    args.append(value)

        meth = w.application.methods.get(func)
        if meth is None:
            w.set_error("method %r not found" % func)
            return
        try:
            for (arg, value) in zip(meth.args, args): vargs[arg.name] = value
        except:
            w.set_error("4fail")
            return
        meth.execute(w, **vargs)
            
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):
        if w.application.last_action == self.name:
            w.application.highlight_mark = True
            w.set_error("Highlighting enabled: %r" % w.application.highlight_mark)
        else:
            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 + 1)
            it.execute(w)
        w.goto_line(y + 1)
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

        c   = w.mode.commentc or '#'
        lvl = w.buffer.detect_indent_level(p1.y, p2.y) or 0

        for y in range(p1.y, p2.y):
            if len(w.buffer.lines[y]) < lvl:
                pad = lvl - len(w.buffer.lines[y])
                x   = lvl - pad
            else:
                pad = 0
                x   = lvl
            w.buffer.insert_string(Point(x, y), ' ' * pad + 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):
    '''Wrap a line of text based on a predefined margin'''
    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):
    '''Wrap contiguous lines of text based on a predefined margin'''
    limit    = 80
    valid_re = re.compile('^()( *)([^ ].*)$')
    empty_re = re.compile('^ ')
    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

        if self.empty_re.match(w.buffer.lines[p1.y]):
            return

        m = self.valid_re.match(w.buffer.lines[p1.y])
        if not m:
            # the line was empty
            return
        elif not m.group(1) and m.group(2):
            # 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 self.empty_re.match(w.buffer.lines[i - 1])):
                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 self.empty_re.match(w.buffer.lines[i])):
            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)

        # 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

        # 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):
        w.undo()
class Redo(Method):
    '''Redo last undone action'''
    def _execute(self, w, **vargs):
        w.redo()

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 Diff(Method):
    '''diff the buffer's contents with the given file'''
    args = [arg("path1", t=type(""), p="Filename: ", dt='path', h="left path to diff"),
            arg("path2", t=type(""), p="Filename: ", dt='path', h="right path to diff")]
    def _get_cmd(self, w, **vargs):
        return ("/usr/bin/diff", '-u', vargs['path1'], vargs['path2'])
    def _pipe_write(self, pipe, w, **vargs):
        pass
    def _execute(self, w, **vargs):
        cmd  = self._get_cmd(w, **vargs)
        pipe = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
        self._pipe_write(pipe, w, **vargs)
        outdata = pipe.stdout.read()
        errdata = pipe.stderr.read()
        status  = pipe.wait()
        if status == 0:
            w.set_error("No difference found" + str(status))
        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" % (pipe.pid, status))

class FileDiff(Diff):
    '''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 _get_cmd(self, w, **vargs):
        return ("/usr/bin/diff", '-u', '-', vargs['path'])
    def _pipe_write(self, pipe, w, **vargs):
        indata = w.buffer.make_string()
        pipe.stdin.write(indata)
        pipe.stdin.close()

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_msg('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():
            w.center_view(force=True)
        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 CloseParen(Method):
    '''Insert ), matching if applicable'''
    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 CloseBrace(CloseParen):
    '''Insert }, matching if applicable'''
    mytag = '}'
class CloseBracket(CloseParen):
    '''Insert ], matching if applicable'''
    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="Register name 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 of the named register onto the kill stack'''
    MAX_TXT = 30
    MAX_REG = 18
    args = [arg('name', dt="register", p="Register name: ", h="Register name 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):
    '''View the value of a particular config variables'''
    args = [arg('name', dt='config', p="Config variable: ", h='Config variable name')]
    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):
    '''View the value of all config variables'''
    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):
    '''Set a particular config variable to a value'''
    args = [arg('name', dt='config', p="Variable name: ", h='Config variable name'),
            arg('value', t=type(''), p="Variable value: ", h='Config variable value')]
    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):
    '''Toggle the visibility of the buffer header'''
    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')

# TODO: rename to left-margin
class ToggleLineNumbers(Method):
    '''Toggle the visibility of the left margin'''
    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):
    '''Set the tab-width for the current buffer'''
    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):
    '''Set the default tab-width for the current mode'''
    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)

class ViewTokenColors(Method):
    args = []
    def _execute(self, w, **vargs):
        a = w.application

        keys  = sorted([x for x in a.token_colors.keys()])
        l = 0
        for key in keys:
            l = max(l, len(key))
        lines = ['Color information for %d tokens:\n' % len(keys), '\n']
        for key in keys:
            c = buffer.colors.get_cbuf_code(*a.token_colors[key])
            lines.append('%s%-*s  %r\n' % (c, l, key, a.token_colors[key]))
        a.color_data_buffer("*Token-Colors*", ''.join(lines), switch_to=True)

class SetTokenColors(Method):
    args = [arg('name', p='Token Name: ', h=''),
            arg('colors', p='Colors: ', h='')]
    def _execute(self, w, **vargs):
        name = vargs['name']
        colors = tuple([x.strip() for x in vargs['colors'].split(',')])
        a = w.application
        if '*' in name:
            a.cached_colors = {}
            nstr = name.replace('.', '\\.').replace('*', '.*')
            r     = re.compile(nstr)
            count = 0
            for name2 in a.token_colors:
                if r.match(name2):
                    a.token_colors[name2] = colors
                    count += 1
            msg = 'Color for %d tokens matching %s set to %r'
            w.set_error(msg % (count, name, colors))
        elif name in a.token_colors:
            a.cached_colors = {}
            a.token_colors[name] = colors
            w.set_error('Color for %s set to %r' % (name, colors))