import re, sets, string, sys import color, commands, default, lex2, lex2_perl, method, mode2, regex, tab_perl from point2 import Point class Perl(mode2.Fundamental): def __init__(self, w): mode2.Fundamental.__init__(self, w) #self.tag_matching = True self.grammar = lex2_perl.PerlGrammar() self.lexer = lex2.Lexer(self.name(), 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 = { # basic stuff 'escaped': color.build('magenta', 'default'), 'null': color.build('default', 'default'), 'delimiter': color.build('default', 'default'), 'sub': color.build('cyan', 'default'), 'number': color.build('default', 'default'), 'operator': color.build('default', 'default'), 'endblock': color.build('red', 'default'), 'keyword': color.build('magenta', 'default'), 'scalar': color.build('yellow', 'default'), 'array': color.build('yellow', 'default'), 'deref': color.build('yellow', 'default'), 'hash': color.build('yellow', 'default'), 'hash_key': color.build('green', 'default'), 'comment': color.build('red', 'default'), 'function': color.build('cyan', 'default'), 'builtin': color.build('magenta', 'default'), 'method': color.build('cyan', 'default'), 'bareword': color.build('default', 'default'), 'label': color.build('cyan', 'default'), 'package': color.build('cyan', 'default'), 'class': color.build('cyan', 'default'), 'use': color.build('cyan', 'default'), 'method': color.build('cyan', 'default'), # heredoc 'heredoc1.start': color.build('green', 'default'), 'heredoc1.null': color.build('green', 'default'), 'heredoc1.end': color.build('green', 'default'), 'heredoc2.start': color.build('green', 'default'), 'heredoc2.null': color.build('green', 'default'), 'heredoc2.end': color.build('green', 'default'), 'eval_heredoc.start': color.build('cyan', 'default'), 'eval_heredoc.null': color.build('cyan', 'default'), 'eval_heredoc.end': color.build('cyan', 'default'), # pod 'pod.start': color.build('red', 'default'), 'pod.null': color.build('red', 'default'), 'pod.entry': color.build('magenta', 'default'), 'pod.end': color.build('red', 'default'), # "" strings 'string1.start': color.build('green', 'default'), 'string1.null': color.build('green', 'default'), 'string1.escaped': color.build('magenta', 'default'), 'string1.deref': color.build('yellow', 'default'), 'string1.end': color.build('green', 'default'), # '' strings 'string2.start': color.build('green', 'default'), 'string2.null': color.build('green', 'default'), 'string2.end': color.build('green', 'default'), # `` strings 'evalstring': color.build('cyan', 'default'), # quoted region 'quoted': color.build('cyan', 'default'), 'quoted.start': color.build('cyan', 'default'), 'quoted.null': color.build('cyan', 'default'), 'quoted.end': color.build('cyan', 'default'), # match regex 'match.start': color.build('cyan', 'default'), 'match.end': color.build('cyan', 'default'), 'match.null': color.build('cyan', 'default'), # replace regex 'replace.start': color.build('cyan', 'default'), 'replace.middle': color.build('cyan', 'default'), 'replace.end': color.build('cyan', 'default'), 'replace.null': color.build('cyan', 'default'), 'replace.escaped': color.build('magenta', 'default'), 'replace.deref': color.build('yellow', 'default'), 'replace.length': color.build('yellow', 'default'), 'replace.scalar': color.build('yellow', 'default'), 'replace.hash': color.build('yellow', 'default'), 'replace.cast': color.build('yellow', 'default'), # translate regex 'translate.start': color.build('magenta', 'default'), 'translate.middle': color.build('magenta', 'default'), 'translate.end': color.build('magenta', 'default'), 'translate.null': color.build('magenta', '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(0, start) end_p = 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(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(0, start) end_p = 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(0, start) end_p = Point(0, end + 1) w.kill(start_p, end_p) w.insert(start_p, data)