From e2e6e28c0755379931a13ef83503e8c9ca8e37f7 Mon Sep 17 00:00:00 2001 From: moculus Date: Sun, 11 May 2008 23:40:06 +0000 Subject: [PATCH] python-mode improvements; context --HG-- branch : pmacs2 --- buffer.py | 8 +- context.py | 78 +++++++++++++ edb.py | 104 ++++++++---------- mode/__init__.py | 19 +++- mode/dir.py | 28 ++--- mode/python.py | 278 ++++++++++++++++++++++++++--------------------- 6 files changed, 311 insertions(+), 204 deletions(-) create mode 100644 context.py diff --git a/buffer.py b/buffer.py index 30980e5..c573117 100644 --- a/buffer.py +++ b/buffer.py @@ -161,17 +161,17 @@ class Buffer(object): def _region_add(self, p1, p2, lines, act): move = DelMove(self, p1, p2) self.add_to_stack(move, act) - for w in self.windows: - w.region_added(p1, lines) for name in self.highlights: self.highlights[name].relex_add(self.lines, p1.y, p1.x, lines) + for w in self.windows: + w.region_added(p1, lines) def _region_del(self, p1, p2, lines, act): move = AddMove(self, p1, lines) self.add_to_stack(move, act) - for w in self.windows: - w.region_removed(p1, p2) for name in self.highlights: self.highlights[name].relex_del(self.lines, p1.y, p1.x, p2.y, p2.x) + for w in self.windows: + w.region_removed(p1, p2) # internal validation def _validate_point(self, p): diff --git a/context.py b/context.py new file mode 100644 index 0000000..eefb178 --- /dev/null +++ b/context.py @@ -0,0 +1,78 @@ +class Context(object): + """This object stores and updates contextual metadata about the buffer.""" + def __init__(self, mode): + self.mode = mode + self.names = None + self.namelines = None + + def region_added(self, p, newlines): + self.adjust_name_map(p, len(newlines) - 1) + self.rebuild_name_map(p.y, p.y + len(newlines)) + def region_removed(self, p1, p2): + self.adjust_name_map(p2, p1.y - p2.y) + self.rebuild_name_map(p1.y, p1.y + 1) + + def adjust_name_map(self, p, delta): + if delta == 0: + return + elif delta > 0: + self.namelines.extend([None] * delta) + for i in reversed(range(p.y + 1, len(self.mode.window.buffer.lines))): + self.namelines[i] = self.namelines[i - delta] + for i in range(p.y, p.y + delta): + self.namelines[i] = None + else: + for i in range(p.y + 1, len(self.mode.window.buffer.lines)): + self.namelines[i + delta] = self.namelines[i] + + def _init_name_map(self): + self.names = {} + self.namelines = [None] * len(self.mode.window.buffer.lines) + + def build_name_map(self): + self._init_name_map() + self._build_name_map(0, len(self.mode.window.buffer.lines), None, None, []) + + def rebuild_name_map(self, y1, y2): + for y in range(y1, y2): + name = self.namelines[y] + if name: + if name in self.names: + del self.names[name] + if name in self.classes: + del self.classes[name] + if name in self.functions: + del self.functions[name] + self.namelines[y] = None + + if y1 == 0: + self._build_name_map(y1, y2, None, None, []) + else: + last, curr, stack = None, self.namelines[y1 - 1], [] + count = 0 + if curr: + for part in curr.split('.'): + stack.append([count, part]) + count += self.mode.tabwidth + self._build_name_map(y1, y2, last, curr, stack) + + def _build_name_map(self, y1, y2, last, curr, stack): + raise Exception, 'unimplemented' + + def get_line_name(self, y): + if self.namelines is None: + self.build_name_map() + if y < len(self.namelines): + return self.namelines[y] + else: + return None + def get_names(self): + if self.names is None: + self.build_name_map() + return self.names + def get_name_list(self): + return self._ordered_dict(self.get_names()) + def _ordered_dict(self, d): + pairs = [[d[key], key] for key in d] + pairs.sort() + return [x[1] for x in pairs] diff --git a/edb.py b/edb.py index 4630b67..6feed2c 100644 --- a/edb.py +++ b/edb.py @@ -1,66 +1,59 @@ -import bdb, os, re, sys, time, traceback +#! /usr/bin/env python -class Edb(bdb.Bdb): - run_ = 0 - def interaction(self, frame, t): +"""Another Python debugger.""" + +import os, pdb, sys, time, traceback + +__all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace", + "post_mortem", "help"] + +class Edb(pdb.Pdb): + def cmdloop(self, intro=None): + stop = False + while not stop: + sys.stdout.write(self.prompt) + sys.stdout.flush() + line = sys.stdin.readline() + line = self.precmd(line) + stop = self.onecmd(line) + stop = self.postcmd(stop, line) + self.postloop() + + def columnize(self, list, displaywidth=80): + pass + def do_help(self, arg): + pass + def print_topics(self, header, cmds, cmdlen, maxcol): pass - def user_call(self, frame, args): - name = frame.f_code.co_name or "" - print "call", name, args - sys.stdin.readline() - self.set_continue() # continue - def user_line(self, frame): - if self.run_: - self.run_ = 0 - self.set_trace() # start tracing - else: - # arrived at breakpoint - name = frame.f_code.co_name or "" - filename = self.canonic(frame.f_code.co_filename) - print "break at", filename, frame.f_lineno, "in", name - print "continue..." - sys.stdin.readline() - self.set_continue() # continue to next breakpoint +# Simplified interface +def run(statement, globals=None, locals=None): + Edb().run(statement, globals, locals) +def runeval(expression, globals=None, locals=None): + return Edb().runeval(expression, globals, locals) +def runctx(statement, globals, locals): + run(statement, globals, locals) +def runcall(*args, **kwds): + return Edb().runcall(*args, **kwds) +def set_trace(): + Edb().set_trace(sys._getframe().f_back) - def user_return(self, frame, value): - name = frame.f_code.co_name or "" - print "return from", name, value - print "continue..." - sys.stdin.readline() - self.set_continue() # continue - - def user_exception(self, frame, exception): - name = frame.f_code.co_name or "" - print "exception in", name, exception - print "continue..." - sys.stdin.readline() - self.set_continue() # continue - - def _runscript(self, filename): - # Start with fresh empty copy of globals and locals and tell the script - # that it's being run as __main__ to avoid scripts being able to access - # the pdb.py namespace. - globals_ = {"__name__" : "__main__"} - locals_ = globals_ - - # When bdb sets tracing, a number of call and line events happens - # BEFORE debugger even reaches user's code (and the exact sequence of - # events depends on python version). So we take special measures to - # avoid stopping before we reach the main script (see user_line and - # user_call for details). - self._wait_for_mainpyfile = 1 - self.mainpyfile = self.canonic(filename) - self._user_requested_quit = 0 - statement = 'execfile( "%s")' % filename - self.run(statement, globals=globals_, locals=locals_) +# Post-Mortem interface +def post_mortem(t): + p = Edb() + p.reset() + while t.tb_next is not None: + t = t.tb_next + p.interaction(t.tb_frame, t) +def pm(): + post_mortem(sys.last_traceback) def main(): if not sys.argv[1:]: print "usage: edb.py scriptfile [arg] ..." sys.exit(2) - mainpyfile = sys.argv[1] # Get script filename + mainpyfile = sys.argv[1] if not os.path.exists(mainpyfile): print 'Error:', mainpyfile, 'does not exist' sys.exit(1) @@ -79,18 +72,17 @@ def main(): print "The program finished and will be restarted" except SystemExit: # In most cases SystemExit does not warrant a post-mortem session. - print "The program exited via sys.exit(). Exit status:", + print "The program exited via sys.exit(). Exit status: ", print sys.exc_info()[1] except: traceback.print_exc() print "Uncaught exception. Entering post mortem debugging" - #print "Running 'cont' or 'step' will restart the program" + print "Running 'cont' or 'step' will restart the program" t = sys.exc_info()[2] while t.tb_next is not None: t = t.tb_next edb.interaction(t.tb_frame,t) print "Post mortem debugger finished. The "+mainpyfile+" will be restarted" -# When invoked as main program, invoke the debugger on a script if __name__=='__main__': main() diff --git a/mode/__init__.py b/mode/__init__.py index 7aca4f4..c915399 100644 --- a/mode/__init__.py +++ b/mode/__init__.py @@ -86,6 +86,7 @@ class Fundamental(Handler): grammar = None lexer = None tabber = None + context = None colors = {} config = {} actions = [] @@ -322,9 +323,7 @@ class Fundamental(Handler): self.window.set_error(err) def region_added(self, p, newlines): - if self.tabber is not None: - self.tabber.region_added(p, newlines) - if self.lexer: + if self.lexer is not None: ydelta = len(newlines) - 1 xdelta = len(newlines[-1]) ghist = {} @@ -345,10 +344,13 @@ class Fundamental(Handler): ghist.setdefault(name, {}) ghist[name][newp] = self.ghist[name][gp] self.ghist = ghist - def region_removed(self, p1, p2): if self.tabber is not None: - self.tabber.region_removed(p1, p2) - if self.lexer: + self.tabber.region_added(p, newlines) + if self.context is not None: + self.context.region_added(p, newlines) + + def region_removed(self, p1, p2): + if self.lexer is not None: ydelta = p2.y - p1.y xdelta = p2.x - p1.x ghist = {} @@ -371,4 +373,9 @@ class Fundamental(Handler): ghist.setdefault(name, {}) ghist[name][newp] = self.ghist[name][gp] self.ghist = ghist + if self.tabber is not None: + self.tabber.region_removed(p1, p2) + if self.context is not None: + self.context.region_removed(p1, p2) + install = Fundamental.install diff --git a/mode/dir.py b/mode/dir.py index 52bbc1a..d0e758b 100644 --- a/mode/dir.py +++ b/mode/dir.py @@ -165,20 +165,20 @@ class Dir(mode.Fundamental): modename = 'Dir' grammar = DirGrammar() colors = { - 'dir_blk.start': ('cyan', 'default', 'bold'), - 'dir_blk.name': ('cyan', 'default', 'bold'), - 'dir_chr.start': ('yellow', 'default', 'bold'), - 'dir_chr.name': ('yellow', 'default', 'bold'), - 'dir_dir.start': ('blue', 'default', 'bold'), - 'dir_dir.dir_name': ('blue', 'default', 'bold'), - 'dir_lnk.start': ('green', 'default', 'bold'), - 'dir_lnk.name': ('green', 'default', 'bold'), - 'dir_fifo.start': ('red', 'default', 'bold'), - 'dir_fifo.name': ('red', 'default', 'bold'), - 'dir_sock.start': ('red', 'default', 'bold'), - 'dir_sock.name': ('red', 'default', 'bold'), - 'dir_unk.start': ('magenta', 'default', 'bold'), - 'dir_unk.name': ('magenta', 'default', 'bold'), + 'dir_blk.start': ('cyan', 'default', 'bold'), + 'dir_blk.dir_name': ('cyan', 'default', 'bold'), + 'dir_chr.start': ('yellow', 'default', 'bold'), + 'dir_chr.dir_name': ('yellow', 'default', 'bold'), + 'dir_dir.start': ('blue', 'default', 'bold'), + 'dir_dir.dir_name': ('blue', 'default', 'bold'), + 'dir_lnk.start': ('green', 'default', 'bold'), + 'dir_lnk.dir_name': ('green', 'default', 'bold'), + 'dir_fifo.start': ('red', 'default', 'bold'), + 'dir_fifo.dir_name': ('red', 'default', 'bold'), + 'dir_sock.start': ('red', 'default', 'bold'), + 'dir_sock.dir_name': ('red', 'default', 'bold'), + 'dir_unk.start': ('magenta', 'default', 'bold'), + 'dir_unk.dir_name': ('magenta', 'default', 'bold'), 'dir_perm.perm_setid': ('yellow', 'default', 'bold'), 'dir_perm.perm_sticky': ('yellow', 'default', 'bold'), diff --git a/mode/python.py b/mode/python.py index 909c515..062a23b 100644 --- a/mode/python.py +++ b/mode/python.py @@ -1,5 +1,5 @@ import commands, os.path, sets, string, sys, traceback -import color, completer, default, mode, method, regex, tab +import color, completer, context, default, mode, method, regex, tab from point import Point from lex import Grammar, PatternRule, RegionRule, OverridePatternRule @@ -173,53 +173,6 @@ class PythonTabber(tab.StackTabber): self._append(token.string, currlvl + w) return currlvl -class PythonInitNames(method.Method): - '''Jump to a function defined in this module''' - def _execute(self, w, **vargs): - w.mode.build_name_map() - w.application.set_error("Initialized name maps") - -class PythonGotoName(method.Method): - '''Jump to a class or function defined in this module''' - args = [method.Argument("name", type(""), "pythonname", "Goto Name: ")] - def _execute(self, w, **vargs): - name = vargs['name'] - d = {} - d.update(w.mode.get_classes()) - d.update(w.mode.get_functions()) - if name in d: - w.goto(Point(0, d[name])) - else: - w.application.set_error("Function %r was not found" % name) -class PythonGotoFunction(method.Method): - '''Jump to a function defined in this module''' - args = [method.Argument("name", type(""), "pythonfunction", "Goto Function: ")] - def _execute(self, w, **vargs): - name = vargs['name'] - functions = w.mode.get_functions() - if name in functions: - w.goto(Point(0, functions[name])) - else: - w.application.set_error("Function %r was not found" % name) -class PythonGotoClass(method.Method): - '''Jump to a class defined in this module''' - args = [method.Argument("name", type(""), "pythonclass", "Goto Class: ")] - def _execute(self, w, **vargs): - name = vargs['name'] - classes = w.mode.get_classes() - if name in classes: - w.goto(Point(0, classes[name])) - else: - w.application.set_error("Class %r was not found" % name) - -class PythonListNames(method.Method): - '''Show the user all functions defined in this module''' - def _execute(self, w, **vargs): - names = w.mode.get_function_names() - names.extend(w.mode.get_class_names()) - output = '\n'.join(sorted(names)) + "\n" - w.application.data_buffer("*Python-List-Names*", output, switch_to=True) - class PythonCheckSyntax(method.Method): '''Check the syntax of the current python file''' def _execute(self, w, **vargs): @@ -318,35 +271,151 @@ class PythonDictCleanup(method.Method): class PythonInsertTripleSquotes(method.Method): '''Insert a triple-quoted string using single-quotes''' + _q = "'''" def _execute(self, w, **vargs): - w.insert_string_at_cursor("''''''") + w.insert_string_at_cursor('%s%s' % (_q, _q)) for i in range(0, 3): w.backward() - -class PythonInsertTripleDquotes(method.Method): +class PythonInsertTripleDquotes(PythonInsertTripleSquotes): '''Insert a triple-quoted string using double-quotes''' - def _execute(self, w, **vargs): - w.insert_string_at_cursor('""""""') - for i in range(0, 3): - w.backward() + _q = '"""' + +class PythonInitNames(method.Method): + '''Jump to a function defined in this module''' + def _execute(self, w, **vargs): + w.mode.context.build_name_map() + w.application.set_error("Initialized name maps") + +class PythonGotoName(method.Method): + '''Jump to a class or function defined in this module''' + args = [method.Argument("name", type(""), "pythonname", "Goto Name: ")] + title = 'Name' + def _get_dict(self, w): + return w.mode.context.get_names() + def _execute(self, w, **vargs): + name = vargs['name'] + d = self._get_dict(w) + if name in d: + w.goto(Point(0, d[name])) + else: + w.application.set_error("%r %r was not found" % (title, name)) +class PythonGotoFunction(PythonGotoName): + '''Jump to a function defined in this module''' + args = [method.Argument("name", type(""), "pythonfunction", "Goto Function: ")] + title = 'Function' + def _get_dict(self, w): + return w.mode.context.get_functions() +class PythonGotoClass(method.Method): + '''Jump to a class defined in this module''' + args = [method.Argument("name", type(""), "pythonclass", "Goto Class: ")] + title = 'Class' + def _get_dict(self, w): + return w.mode.context.get_classes() + +class PythonListNames(method.Method): + '''Show the user all functions defined in this module''' + def _execute(self, w, **vargs): + names = w.mode.context.get_names() + output = '\n'.join(sorted(names)) + "\n" + w.application.data_buffer("*Python-List-Names*", output, switch_to=True) -class PythonFunctionCompleter(completer.Completer): - def get_candidates(self, s, w=None): - old_window = w.buffer.method.old_window - functions = old_window.mode.get_functions() - return [n for n in functions if n.startswith(s)] -class PythonClassCompleter(completer.Completer): - def get_candidates(self, s, w=None): - old_window = w.buffer.method.old_window - classes = old_window.mode.get_classes() - return [n for n in classes if n.startswith(s)] class PythonNameCompleter(completer.Completer): + def _get_dict(self, w): + return w.buffer.method.old_window.mode.context.get_names() def get_candidates(self, s, w=None): - old_window = w.buffer.method.old_window - names = [] - names.extend(old_window.mode.get_classes()) - names.extend(old_window.mode.get_functions()) - return [n for n in names if n.startswith(s)] + return [n for n in self._get_dict(w) if n.startswith(s)] +class PythonFunctionCompleter(PythonNameCompleter): + def _get_dict(self, w): + return w.buffer.method.old_window.mode.context.get_functions() +class PythonClassCompleter(completer.Completer): + def _get_dict(self, w): + return w.buffer.method.old_window.mode.context.get_classes() + +class PythonContext(context.Context): + def __init__(self, mode): + self.mode = mode + self.names = None + self.namelines = None + self.classes = None + self.functions = None + + # new object methods + def get_functions(self): + if self.functions is None: + self.build_name_map() + return self.functions + def get_classes(self): + if self.classes is None: + self.build_name_map() + return self.classes + def get_function_list(self): + return self._ordered_dict(self.get_functions()) + def get_class_list(self): + return self._ordered_dict(self.get_classes()) + + # overridden object methods + def _init_name_map(self): + self.names = {} + self.classes = {} + self.functions = {} + self.namelines = [None] * len(self.mode.window.buffer.lines) + + def _build_name_map(self, y1, y2, last, curr, stack): + blen = len(self.mode.window.buffer.lines) + highlights = self.mode.window.get_highlighter() + i = y1 + while i < y2: + g = highlights.tokens[i] + if (len(g) == 1 and g[0].name == 'eol' or + len(g) == 2 and g[0].name == 'null' and g[1].name == 'eol'): + if last is None: + last = i + i += 1 + if i == y2 and y2 < blen: + y2 += 1 + continue + + if g[0].name == 'null': + j, lvl = 1, len(g[0].string) + else: + j, lvl = 0, 0 + while stack and lvl <= stack[-1][0]: + stack.pop(-1) + + if last is not None: + curr = '.'.join([x[1] for x in stack]) + if curr: + for k in range(last, i): + self.namelines[k] = curr + last = None + + if len(g[j:]) > 3: + d, found = None, False + if g[j].name == 'python_keyword' and g[j].string == 'class': + d, found = self.classes, True + elif g[j].name == 'python_keyword' and g[j].string == 'def': + d, found = self.functions, True + if found: + stack.append([lvl, g[j+2].string]) + curr = '.'.join([x[1] for x in stack]) + d[curr] = i + self.names[curr] = i + else: + curr = '.'.join([x[1] for x in stack]) + + if i == y2 - 1 and curr != self.namelines[i] and y2 < blen: + y2 += 1 + if curr: + self.namelines[i] = curr + i += 1 + + if last is not None and y2 < len(self.namelines): + if self.namelines[y2]: + n = len(self.namelines[y2].split('.')) + curr = '.'.join([x[1] for x in stack[:n]]) + if curr: + for k in range(last, y2): + self.namelines[k] = curr class Python(mode.Fundamental): modename = 'Python' @@ -382,6 +451,20 @@ class Python(mode.Fundamental): "pythonfunction": PythonFunctionCompleter(), "pythonclass": PythonClassCompleter(), } + format = "%(flag)s %(bname)-18s (%(mname)s) %(cursor)s/%(mark)s %(perc)s [%(name)s]" + def get_status_names(self): + w = self.window + c = w.logical_cursor() + names = { + 'bname': w.buffer.name(), + 'mname': self.name(), + 'flag': self._get_flag(), + 'perc': self._get_perc(), + 'cursor': '(%d,%d)' % (c.y + 1, c.x + 1), + 'mark': self._get_mark(), + 'name': self.context.get_line_name(c.y), + } + return names def __init__(self, w): mode.Fundamental.__init__(self, w) self.add_bindings('close-paren', (')',)) @@ -394,59 +477,6 @@ class Python(mode.Fundamental): self.add_bindings('python-dict-cleanup', ('C-c h',)) self.add_bindings('python-insert-triple-squotes', ('C-c M-\'',)) self.add_bindings('python-insert-triple-dquotes', ('C-c M-"',)) - - self.classes = None - self.functions = None - def build_name_map(self): - b = self.window.buffer - scope_stack = [] - self.classes = {} - self.functions = {} - for i in range(0, len(b.lines)): - if regex.whitespace.match(b.lines[i]): - continue - m = regex.python_indent.match(b.lines[i]) - assert m - lvl = len(m.group(1)) - while scope_stack: - if lvl <= scope_stack[-1][0]: - scope_stack.pop(-1) - else: - break - m = regex.python_scope.match(b.lines[i]) - if m: - (ws, typ, name) = m.groups() - lvl = len(ws) - if typ == 'class': - #raise Exception, repr(m.groups()) - d = self.classes - else: - d = self.functions - if scope_stack: - prefix = '.'.join([x[1] for x in scope_stack]) - d['%s.%s' % (prefix, name)] = i - else: - d[name] = i - scope_stack.append((len(ws), name)) - def get_functions(self): - if self.functions is None: - self.build_name_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 - def get_classes(self): - if self.classes is None: - self.build_name_map() - return self.classes - def get_class_names(self): - classes = self.get_classes() - pairs = [[classes[key], key] for key in classes] - pairs.sort() - names = [x[1] for x in pairs] - return names + self.context = PythonContext(self) install = Python.install