450 lines
16 KiB
Python
450 lines
16 KiB
Python
|
import re, sets, string, sys
|
||
|
import color, commands, default, lex, lex_perl, method, mode, point, regex, tab_perl
|
||
|
|
||
|
class Perl(mode.Fundamental):
|
||
|
def __init__(self, w):
|
||
|
mode.Fundamental.__init__(self, w)
|
||
|
|
||
|
self.tag_matching = True
|
||
|
self.grammar = lex_perl.PerlGrammar()
|
||
|
self.lexer = lex.Lexer(self.grammar)
|
||
|
|
||
|
self.add_action_and_bindings(PerlCheckSyntax(), ('C-c s',))
|
||
|
self.add_action_and_bindings(PerlHashCleanup(), ('C-c h',))
|
||
|
#self.add_action_and_bindings(PerlHashCleanup2(), ('C-c h',))
|
||
|
self.add_action_and_bindings(PerlViewModulePerldoc(), ('C-c v',))
|
||
|
self.add_action_and_bindings(PerlViewWordPerldoc(), ('C-c p',))
|
||
|
self.add_action_and_bindings(PerlWrapLine(), ('M-q',))
|
||
|
self.add_action_and_bindings(PerlGotoFunction(), ('C-c M-g',))
|
||
|
self.add_action_and_bindings(PerlWhichFunction(), ('C-c w',))
|
||
|
self.add_action_and_bindings(PerlListFunctions(), ('C-c W',))
|
||
|
|
||
|
self.add_bindings('close-paren', (')',))
|
||
|
self.add_bindings('close-brace', ('}',))
|
||
|
self.add_bindings('close-bracket', (']',))
|
||
|
|
||
|
self.default_color = color.build('default', 'default')
|
||
|
self.colors = {
|
||
|
'heredoc': color.build('green', 'default'),
|
||
|
'endblock': color.build('red', 'default'),
|
||
|
'pod': color.build('red', 'default'),
|
||
|
'comment': color.build('red', 'default'),
|
||
|
'string1': color.build('green', 'default'),
|
||
|
'string2': color.build('green', 'default'),
|
||
|
'evalstring': color.build('cyan', 'default'),
|
||
|
'default string': color.build('green', 'default'),
|
||
|
'keyword': color.build('magenta', 'default'),
|
||
|
'length scalar': color.build('yellow', 'default'),
|
||
|
'system scalar': color.build('yellow', 'default'),
|
||
|
'system array': color.build('yellow', 'default'),
|
||
|
'scalar': color.build('yellow', 'default'),
|
||
|
'dereference': color.build('yellow', 'default'),
|
||
|
'array': color.build('yellow', 'default'),
|
||
|
'hash': color.build('yellow', 'default'),
|
||
|
'hash bareword index': color.build('green', 'default'),
|
||
|
'quoted region': color.build('cyan', 'default'),
|
||
|
'match regex': color.build('cyan', 'default'),
|
||
|
'replace regex': color.build('cyan', 'default'),
|
||
|
'literal hash bareword index': color.build('green', 'default'),
|
||
|
'interpolated scalar': color.build('yellow', 'default'),
|
||
|
'interpolated system scalar': color.build('yellow', 'default'),
|
||
|
'interpolated array': color.build('yellow', 'default'),
|
||
|
'interpolated system array': color.build('yellow', 'default'),
|
||
|
'interpolated hash': color.build('yellow', 'default'),
|
||
|
'label': color.build('cyan', 'default'),
|
||
|
'package': color.build('cyan', 'default'),
|
||
|
'use': color.build('cyan', 'default'),
|
||
|
'method': color.build('cyan', 'default'),
|
||
|
'methodref': color.build('cyan', 'default'),
|
||
|
'method declaration': color.build('cyan', 'default'),
|
||
|
'instance method': color.build('cyan', 'default'),
|
||
|
'static method': color.build('cyan', 'default'),
|
||
|
'built-in method': color.build('magenta', 'default'),
|
||
|
'bareword method': color.build('cyan', 'default'),
|
||
|
#'bareword': color.build('yellow', 'magenta'),
|
||
|
'bizzaro': color.build('magenta', 'green')
|
||
|
}
|
||
|
|
||
|
#self.highlighter.lex_buffer()
|
||
|
#self.get_regions()
|
||
|
self.tabber = tab_perl.PerlTabber(self)
|
||
|
self.functions = None
|
||
|
|
||
|
def name(self):
|
||
|
return "Perl"
|
||
|
|
||
|
def build_function_map(self):
|
||
|
b = self.window.buffer
|
||
|
self.functions = {}
|
||
|
for i in range(0, len(b.lines)):
|
||
|
m = regex.perl_function.match(b.lines[i])
|
||
|
if m:
|
||
|
self.functions[m.group(1)] = i
|
||
|
|
||
|
def get_functions(self):
|
||
|
if self.functions is None:
|
||
|
self.build_function_map()
|
||
|
return self.functions
|
||
|
|
||
|
def get_function_names(self):
|
||
|
functions = self.get_functions()
|
||
|
pairs = [[functions[key], key] for key in functions]
|
||
|
pairs.sort()
|
||
|
names = [x[1] for x in pairs]
|
||
|
return names
|
||
|
|
||
|
class PerlWrapLine(method.Method):
|
||
|
'''Wrap lines, comments, POD'''
|
||
|
margin = 80
|
||
|
comment_re = re.compile('^( *)(#+)( *)([^ ].*)$')
|
||
|
def _execute(self, w, **vargs):
|
||
|
pcursor = w.physical_cursor()
|
||
|
r = w.get_region(pcursor)
|
||
|
if r is None:
|
||
|
return
|
||
|
|
||
|
t = r[4]
|
||
|
if t == 'pod':
|
||
|
assert False, 'POD: %s' % repr(r)
|
||
|
elif t == 'comment':
|
||
|
self._wrap_comment(w)
|
||
|
else:
|
||
|
return
|
||
|
|
||
|
def _wrap_comment(self, w):
|
||
|
l = w.logical_cursor()
|
||
|
m = self.comment_re.match(w.buffer.lines[l.y])
|
||
|
if not m:
|
||
|
assert False, 'no match oh geez'
|
||
|
|
||
|
pad = m.group(1) + m.group(2) + m.group(3)
|
||
|
data = m.group(4) + ' '
|
||
|
|
||
|
start = l.y
|
||
|
end = l.y + 1
|
||
|
|
||
|
while end < len(w.buffer.lines):
|
||
|
m = self.comment_re.match(w.buffer.lines[end])
|
||
|
if m:
|
||
|
data += m.group(4) + ' '
|
||
|
end += 1
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
words = [word for word in data.split() if word]
|
||
|
|
||
|
lines = [pad]
|
||
|
for word in words:
|
||
|
if len(lines[-1]) == len(pad):
|
||
|
lines[-1] += word
|
||
|
elif len(lines[-1]) + 1 + len(word) <= self.margin:
|
||
|
lines[-1] += ' ' + word
|
||
|
else:
|
||
|
lines.append(pad + word)
|
||
|
|
||
|
# remove the old text and add the new
|
||
|
start_p = point.Point(0, start)
|
||
|
end_p = point.Point(len(w.buffer.lines[end-1]), end-1)
|
||
|
w.kill(start_p, end_p)
|
||
|
w.insert(start_p, '\n'.join(lines))
|
||
|
|
||
|
class PerlCheckSyntax(method.Method):
|
||
|
'''Check the syntax of a perl file'''
|
||
|
def _args(self):
|
||
|
return [method.Argument("lib", type=type(""), prompt="Location of lib: ",
|
||
|
default=default.build_constant("."))]
|
||
|
def _execute(self, window, **vargs):
|
||
|
a = vargs['lib']
|
||
|
cmd = "perl -c -I '%s' '%s'" % (a, window.buffer.path)
|
||
|
(status, output) = commands.getstatusoutput(cmd)
|
||
|
if status == 0:
|
||
|
window.application.set_error("Syntax OK")
|
||
|
window.application.data_buffer("*Perl-Check-Syntax*", output, switch_to=False)
|
||
|
else:
|
||
|
window.application.data_buffer("*Perl-Check-Syntax*", output)
|
||
|
|
||
|
class PerlViewModulePerldoc(method.Method):
|
||
|
'''View documentation about this file using perldoc'''
|
||
|
def _execute(self, w, **vargs):
|
||
|
cmd = "perldoc -t -T '%s'" % w.buffer.path
|
||
|
(status, output) = commands.getstatusoutput(cmd)
|
||
|
w.application.data_buffer("*Perldoc*", output, switch_to=True)
|
||
|
|
||
|
class PerlViewWordPerldoc(method.Method):
|
||
|
'''View documentation about a package or function using perldoc'''
|
||
|
def _execute(self, w, **vargs):
|
||
|
cursor = w.logical_cursor()
|
||
|
line = w.buffer.lines[cursor.y]
|
||
|
|
||
|
word_chars = string.letters + string.digits + '_:'
|
||
|
|
||
|
if line[cursor.x] not in word_chars:
|
||
|
w.application.set_error('error: no word selected')
|
||
|
return
|
||
|
|
||
|
start = cursor.x
|
||
|
while start > 0 and line[start - 1] in word_chars:
|
||
|
start -= 1
|
||
|
|
||
|
end = cursor.x + 1
|
||
|
while end < len(line) - 1 and line[end] in word_chars:
|
||
|
end += 1
|
||
|
|
||
|
word = line[start:end]
|
||
|
w.application.set_error('the current word is: %r' % word)
|
||
|
|
||
|
ok = False
|
||
|
data = ''
|
||
|
|
||
|
perl_word_re = re.compile('^[a-zA-Z_][a-zA-Z_0-9]*(?:::[a-zA-Z_][a-zA-Z0-9]*)*$')
|
||
|
if not perl_word_re.match(word):
|
||
|
w.application.set_error('invalid word: %r' % word)
|
||
|
return
|
||
|
|
||
|
if '::' in word:
|
||
|
# we are probably dealing with a package
|
||
|
parts = word.split('::')
|
||
|
while len(parts) > 0:
|
||
|
newword = '::'.join(parts)
|
||
|
cmd = "perldoc -t -T '%s'" % newword
|
||
|
(status, data) = commands.getstatusoutput(cmd)
|
||
|
if status == 0:
|
||
|
word = newword
|
||
|
ok = True
|
||
|
break
|
||
|
parts.pop(-1)
|
||
|
elif ':' in word:
|
||
|
w.application.set_error('invalid word2222: %r' % word)
|
||
|
return
|
||
|
else:
|
||
|
cmd = "perldoc -t -T -f '%s'" % word
|
||
|
(status, data) = commands.getstatusoutput(cmd)
|
||
|
if status == 0:
|
||
|
ok = True
|
||
|
else:
|
||
|
cmd = "perldoc -t -T -f '%s'" % word
|
||
|
(status, data) = commands.getstatusoutput(cmd)
|
||
|
ok = status == 0
|
||
|
|
||
|
if not ok:
|
||
|
w.application.set_error('nothing found for %r' % word)
|
||
|
else:
|
||
|
w.application.data_buffer("*Perldoc*", data, switch_to=True)
|
||
|
w.application.set_error('displaying documentation for %r' % word)
|
||
|
|
||
|
class PerlGotoFunction(method.Method):
|
||
|
'''Jump to a function defined in this module'''
|
||
|
def _args(self):
|
||
|
return [method.Argument("name", type=type(""), datatype="perlfunction",
|
||
|
prompt="Goto Function: ")]
|
||
|
def _execute(self, w, **vargs):
|
||
|
name = vargs['name']
|
||
|
functions = w.mode.get_functions()
|
||
|
if name in functions:
|
||
|
number = functions[name]
|
||
|
p = point.Point(0, number)
|
||
|
w.goto(p)
|
||
|
else:
|
||
|
w.application.set_error("Function %r was not found" % name)
|
||
|
|
||
|
class PerlListFunctions(method.Method):
|
||
|
'''Show the user all functions defined in this module'''
|
||
|
def _execute(self, w, **vargs):
|
||
|
names = w.mode.get_function_names()
|
||
|
output = "\n".join(names) + "\n"
|
||
|
w.application.data_buffer("*Perl-List-Functions*", output, switch_to=True)
|
||
|
|
||
|
class PerlWhichFunction(method.Method):
|
||
|
'''Show the user what function they are in'''
|
||
|
def _execute(self, w, **vargs):
|
||
|
cursor = w.logical_cursor()
|
||
|
i = cursor.y
|
||
|
name = None
|
||
|
while i >= 0 and name is None:
|
||
|
line = w.buffer.lines[i]
|
||
|
m = regex.perl_function.match(line)
|
||
|
if m:
|
||
|
name = m.group(1)
|
||
|
else:
|
||
|
i -= 1
|
||
|
if name is None:
|
||
|
w.application.set_error("None");
|
||
|
else:
|
||
|
w.application.set_error("line %d: %s" % (i, name))
|
||
|
|
||
|
class PerlHashCleanup(method.Method):
|
||
|
'''Correctly align assignment blocks and literal hashes'''
|
||
|
def _execute(self, window, **vargs):
|
||
|
cursor = window.logical_cursor()
|
||
|
b = window.buffer
|
||
|
|
||
|
# so this is where we will store the groups that we find
|
||
|
groups_by_line = {}
|
||
|
|
||
|
# the regex we will try
|
||
|
regexes = [regex.perl_hash_cleanup,
|
||
|
regex.perl_assign_cleanup]
|
||
|
|
||
|
# if we aren't in a hash, inform the user and exit
|
||
|
line = b.lines[cursor.y]
|
||
|
myregex = None
|
||
|
for r in regexes:
|
||
|
if r.match(line):
|
||
|
myregex = r
|
||
|
|
||
|
if myregex is None:
|
||
|
raise Exception, "Not a perl hash line"
|
||
|
|
||
|
groups_by_line[cursor.y] = myregex.match(line).groups()
|
||
|
|
||
|
# find the beginning of this hash block
|
||
|
start = 0
|
||
|
i = cursor.y - 1
|
||
|
while i >= 0:
|
||
|
line = b.lines[i]
|
||
|
m = myregex.match(line)
|
||
|
if not m:
|
||
|
start = i + 1
|
||
|
break
|
||
|
else:
|
||
|
groups_by_line[i] = m.groups()
|
||
|
i -= 1
|
||
|
|
||
|
# find the end of this hash block
|
||
|
end = len(b.lines) - 1
|
||
|
i = cursor.y + 1
|
||
|
while i < len(b.lines):
|
||
|
line = b.lines[i]
|
||
|
m = myregex.match(line)
|
||
|
if not m:
|
||
|
end = i - 1
|
||
|
break
|
||
|
else:
|
||
|
groups_by_line[i] = m.groups()
|
||
|
i += 1
|
||
|
|
||
|
# assume that the least indented line is correct
|
||
|
indent_w = min([len(groups_by_line[k][0]) for k in groups_by_line])
|
||
|
|
||
|
# find the longest hash key to base all the other padding on
|
||
|
key_w = max([len(groups_by_line[k][1]) for k in groups_by_line])
|
||
|
|
||
|
# for each line, format it correctly
|
||
|
keys = groups_by_line.keys()
|
||
|
keys.sort()
|
||
|
data = ''
|
||
|
for i in keys:
|
||
|
indent_pad = ' ' * indent_w
|
||
|
key = groups_by_line[i][1]
|
||
|
sep = groups_by_line[i][3]
|
||
|
value = groups_by_line[i][5]
|
||
|
key_pad = ' ' * (key_w - len(key))
|
||
|
data += indent_pad + key + key_pad + ' ' + sep + ' ' + value + '\n'
|
||
|
|
||
|
# remove the old text and add the new
|
||
|
start_p = point.Point(0, start)
|
||
|
end_p = point.Point(0, end + 1)
|
||
|
window.kill(start_p, end_p)
|
||
|
window.insert(start_p, data)
|
||
|
|
||
|
class PerlHashCleanup2(method.Method):
|
||
|
'''Correctly align assignment blocks and literal hashes'''
|
||
|
def process_line2(self, line_regions, sep=None, indent=None):
|
||
|
(pre_toks, sep_tok, post_toks) = ([], None, [])
|
||
|
ok = False
|
||
|
before = True
|
||
|
for r in line_regions:
|
||
|
(start, end, attr, s, name) = r
|
||
|
if name == "":
|
||
|
continue
|
||
|
elif before:
|
||
|
if len(pre_toks) == 0:
|
||
|
pre_toks.append(r)
|
||
|
elif (name == "delimiter" and s == sep or
|
||
|
(sep is None and (s == "=" or s == "=>"))):
|
||
|
sep_tok = r
|
||
|
before = False
|
||
|
else:
|
||
|
pre_toks.append(r)
|
||
|
else:
|
||
|
post_toks.append(r)
|
||
|
ok = True
|
||
|
|
||
|
if ok:
|
||
|
return (True, sep_tok[3], (pre_toks, sep_tok, post_toks))
|
||
|
else:
|
||
|
return (False, "", ([], None, []))
|
||
|
|
||
|
def _execute(self, w, **vargs):
|
||
|
cursor = w.logical_cursor()
|
||
|
b = w.buffer
|
||
|
|
||
|
# so this is where we will store the groups that we find
|
||
|
groups_by_line = {}
|
||
|
|
||
|
all_regions = w.mode.get_regions()
|
||
|
line_regions = all_regions[cursor.y]
|
||
|
(ok, sep, group) = self.process_line2(line_regions)
|
||
|
|
||
|
if not ok:
|
||
|
raise Exception, "Not a perl hash line"
|
||
|
groups_by_line[cursor.y] = group
|
||
|
|
||
|
# find the beginning of this hash block
|
||
|
start = cursor.y
|
||
|
while start >= 0:
|
||
|
(ok2, sep2, group2) = self.process_line2(all_regions[start - 1], sep)
|
||
|
if not ok2:
|
||
|
break
|
||
|
start -= 1
|
||
|
groups_by_line[start] = group2
|
||
|
|
||
|
# find the end of this hash block
|
||
|
end = cursor.y
|
||
|
while end < len(b.lines) - 1:
|
||
|
(ok2, sep2, group2) = self.process_line2(all_regions[end + 1], sep)
|
||
|
if not ok2:
|
||
|
break
|
||
|
end += 1
|
||
|
groups_by_line[end] = group2
|
||
|
|
||
|
# find the minimum indented line
|
||
|
indent_w = None
|
||
|
for k in groups_by_line:
|
||
|
x = groups_by_line[k][0][0].start
|
||
|
if indent_w is None or x < indent_w:
|
||
|
indent_w = x
|
||
|
|
||
|
# find the max key length
|
||
|
key_w = None
|
||
|
for k in groups_by_line:
|
||
|
x = groups_by_line[k][0][-1].end - groups_by_line[k][0][0].start
|
||
|
if key_w is None or x > key_w:
|
||
|
key_w = x
|
||
|
|
||
|
# for each line, format it correctly
|
||
|
keys = groups_by_line.keys()
|
||
|
keys.sort()
|
||
|
data = ''
|
||
|
for i in keys:
|
||
|
line = ' ' * indent_w
|
||
|
l = groups_by_line[i][0][0].start
|
||
|
for t in groups_by_line[i][0]:
|
||
|
line += ' ' * max(0, t.start - l)
|
||
|
line += t.value
|
||
|
l = t.end
|
||
|
line += ' ' * max(0, key_w - l + groups_by_line[i][0][0].start)
|
||
|
line += ' ' + groups_by_line[i][1].value + ' '
|
||
|
l = groups_by_line[i][2][0].start
|
||
|
for t in groups_by_line[i][2]:
|
||
|
line += ' ' * max(0, t.start - l)
|
||
|
line += t.value
|
||
|
l = t.end
|
||
|
data += line + '\n'
|
||
|
|
||
|
# remove the old text and add the new
|
||
|
start_p = point.Point(0, start)
|
||
|
end_p = point.Point(0, end + 1)
|
||
|
w.kill(start_p, end_p)
|
||
|
w.insert(start_p, data)
|