pmacs3/method/__init__.py

1146 lines
40 KiB
Python

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, q='default'):
'''convenience function for arguments'''
return Argument(n, type=t, datatype=dt, prompt=p, help=h, default=dv,
load_default=ld, queue=q)
class Argument(object):
def __init__(self, name, type=type(""), datatype=None, prompt=None,
help='', default=default.none, load_default=False,
queue='default'):
self.name = name
self.type = type
self.datatype = datatype
self.prompt = prompt or name + ': '
self.help = help
self.load_default = load_default
self.default = default
self.queue = queue
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, queue=self.queue)
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',
q='metax')]
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 RenderColorData(Method):
def _execute(self, w, **vargs):
data = w.buffer.make_string()
w.application.color_data_buffer('*Rendered-Colortext*', data)
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))