Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

GTK3 update, plus a few minor new features. #1

Merged
merged 4 commits into from

2 participants

@killarny

I switched to logilab-astng as you suggested, and improved some of the gedit plugin code so that the pane hides when a non-python source file is being viewed, instead of showing an empty pane. I also added some error displays, so that when the source file has a SyntaxError that prevents the outline from being rendered, it'll show why the pane is blank, and let the user click on the error to go to the line that caused it.

In short, replicates the same functionality as your original, and adds a few very minor niceties that I've wanted for a while.

killarny added some commits
@killarny killarny Updated to GTK3.
Switched to logilab-astng for generating the code tree, since the compiler package was deprecated in python 2.6.
When the outline can't be generated due to invalid code in the source text, an error is displayed instead of the outline. Selecting the error will jump to the location in the source where the error was raised by astng.
Improved the handling of the outline pane; it will now completely hide when non-python source is in the text area, and reappear when needed.
Display of from-imports (ie, from foo import bar) and aliased imports (ie, import foo as bar) are different.
More attributes display, and look slightly different. For example, attributes on a class are no longer prefaced with "self.".
Attributes now have an icon.
Removed the "outline rendered in N seconds" label.
Changed the stock icon used for the outline pane.
d86dafb
@killarny killarny Added new plugin file for GTK3, and removed the old GTK2 gedit-plugin…
… file.
e39c22b
@killarny killarny Added error display in the outline when the logilab.astng module can'…
…t be imported.

Prevent scrolling when clicking on an outline row that has a line number of -1. Primarily for showing errors that are not related to a specific line of the source text.
499192e
@killarny killarny Improved output of parsing errors with long messages by making the er…
…ror display two lines.
a3ce312
@dieterv dieterv merged commit d5052b2 into dieterv:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 10, 2011
  1. @killarny

    Updated to GTK3.

    killarny authored
    Switched to logilab-astng for generating the code tree, since the compiler package was deprecated in python 2.6.
    When the outline can't be generated due to invalid code in the source text, an error is displayed instead of the outline. Selecting the error will jump to the location in the source where the error was raised by astng.
    Improved the handling of the outline pane; it will now completely hide when non-python source is in the text area, and reappear when needed.
    Display of from-imports (ie, from foo import bar) and aliased imports (ie, import foo as bar) are different.
    More attributes display, and look slightly different. For example, attributes on a class are no longer prefaced with "self.".
    Attributes now have an icon.
    Removed the "outline rendered in N seconds" label.
    Changed the stock icon used for the outline pane.
  2. @killarny
  3. @killarny

    Added error display in the outline when the logilab.astng module can'…

    killarny authored
    …t be imported.
    
    Prevent scrolling when clicking on an outline row that has a line number of -1. Primarily for showing errors that are not related to a specific line of the source text.
Commits on Nov 14, 2011
  1. @killarny
This page is out of date. Refresh to see the latest.
Showing with 188 additions and 206 deletions.
  1. +2 −2 pythonoutline.gedit-plugin → pythonoutline.plugin
  2. +186 −204 pythonoutline.py
View
4 pythonoutline.gedit-plugin → pythonoutline.plugin
@@ -1,7 +1,7 @@
-[Gedit Plugin]
+[Plugin]
Loader=python
Module=pythonoutline
-IAge=2
+IAge=3
Name=Python outline
Description=Python code structure outline
Authors=Dieter Verfaillie
View
390 pythonoutline.py
@@ -18,241 +18,223 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+from gi.repository import Gtk, GObject, Gedit, GdkPixbuf
+try:
+ from logilab.astng import builder
+except ImportError:
+ builder = None
-import os
+DEBUG = False
-import gtk
-import gedit
+def document_is_python(document):
+ if not document:
+ return False
+ uri = str(document.get_uri_for_display())
+ if document.get_mime_type() == 'text/x-python' or \
+ uri.endswith('.py') or uri.endswith('.pyw'):
+ return True
+ return False
-import compiler
-import gc
-import glob
-import time
-
-class TreeModelASTVisitor(compiler.visitor.ASTVisitor):
- defaulticon = None
-
- moduleicon = gtk.Window().render_icon(gtk.STOCK_COPY, gtk.ICON_SIZE_MENU)
- importicon = gtk.Window().render_icon(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
- classicon = gtk.Window().render_icon(gtk.STOCK_FILE, gtk.ICON_SIZE_MENU)
- functionicon = gtk.Window().render_icon(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
-
- def __init__(self, treemodel):
- compiler.visitor.ASTVisitor.__init__(self)
-
- self.treemodel = treemodel
-
- def walkChildren(self, node, parent=None):
- for child in node.getChildNodes():
- child.parent = node
- self.dispatch(child, parent)
-
- def default(self, node, parent=None):
- self.walkChildren(node, parent)
-
-
-class OutlineTreeModelASTVisitor(TreeModelASTVisitor):
- def __init__(self, treemodel):
- TreeModelASTVisitor.__init__(self, treemodel)
-
- def visitAssAttr(self, node, parent=None):
- if hasattr(node.expr, 'name'):
- if node.expr.name == 'self':
- iter = self.treemodel.append(parent, (self.defaulticon, 'self.' + node.attrname, node.__class__.__name__, node.lineno, None))
-
- def visitAssName(self, node, parent=None):
- if hasattr(node.parent, 'parent'):
- if not hasattr(node.parent.parent, 'parent'):
- iter = self.treemodel.append(parent, (self.defaulticon, node.name, node.__class__.__name__, node.lineno, None))
-
- def visitClass(self, node, parent=None):
- iter = self.treemodel.append(parent, (self.classicon, node.name, node.__class__.__name__, node.lineno, node.doc))
- self.walkChildren(node.code, iter)
-
- def visitDecorators(self, node, parent=None):
- iter = self.treemodel.append(parent, (self.defaulticon, None, node.__class__.__name__, node.lineno, None))
- self.walkChildren(node, iter)
-
- def visitFrom(self, node, parent=None):
- for name in node.names:
- if name[1] is None:
- self.treemodel.append(parent, (self.importicon, name[0] + ' (' + node.modname + ')', node.__class__.__name__, node.lineno, None))
- else:
- self.treemodel.append(parent, (self.importicon, name[1] + ' = ' + name[0] + ' (' + node.modname + ')', node.__class__.__name__, node.lineno, None))
-
- def visitFunction(self, node, parent=None):
- iter = self.treemodel.append(parent, (self.functionicon, node.name, node.__class__.__name__, node.lineno, node.doc))
- self.walkChildren(node, iter)
-
- def visitImport(self, node, parent=None):
- for name in node.names:
- if name[1] is None:
- self.treemodel.append(parent, (self.importicon, name[0], node.__class__.__name__, node.lineno, None))
- else:
- self.treemodel.append(parent, (self.importicon, name[1] + ' = ' + name[0], node.__class__.__name__, node.lineno, None))
-
- def visitName(self, node, parent=None):
- if node.parent.__class__.__name__ in ['Class', 'Function']:
- self.treemodel.append(parent, (self.defaulticon, node.name, node.__class__.__name__, node.lineno, None))
-
-
-class OutlineBox(gtk.VBox):
+class OutlineBox(Gtk.Grid):
def __init__(self):
- gtk.VBox.__init__(self)
-
- scrolledwindow = gtk.ScrolledWindow()
- scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- scrolledwindow.show()
- self.pack_start(scrolledwindow, True, True, 0)
-
- self.treeview = gtk.TreeView()
- self.treeview.set_rules_hint(True)
- self.treeview.set_headers_visible(False)
- self.treeview.set_enable_search(True)
- self.treeview.set_reorderable(False)
- self.treeselection = self.treeview.get_selection()
- self.treeselection.connect('changed', self.on_selection_changed)
- scrolledwindow.add(self.treeview)
-
- col = gtk.TreeViewColumn()
- col.set_title('name')
- col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
+ Gtk.Grid.__init__(self)
+ self.orientation = Gtk.Orientation.VERTICAL
+
+ scrolledwindow = Gtk.ScrolledWindow()
+ scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ scrolledwindow.set_property('expand', True)
+
+ self.treeview = treeview = Gtk.TreeView()
+ treeview.set_rules_hint(True)
+ treeview.set_headers_visible(False)
+ treeview.set_enable_search(True)
+ treeview.set_reorderable(False)
+ treeselection = treeview.get_selection()
+ treeselection.connect('changed', self.on_selection_changed)
+
+ col = Gtk.TreeViewColumn('Name')
+ col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
col.set_expand(True)
- render_pixbuf = gtk.CellRendererPixbuf()
+
+ # create renderer for the icon in each row
+ render_pixbuf = Gtk.CellRendererPixbuf()
render_pixbuf.set_property('xalign', 0.5)
render_pixbuf.set_property('yalign', 0.5)
render_pixbuf.set_property('xpad', 2)
render_pixbuf.set_property('ypad', 2)
- col.pack_start(render_pixbuf, expand=False)
+ col.pack_start(render_pixbuf, False)
col.add_attribute(render_pixbuf, 'pixbuf', 0)
- render_text = gtk.CellRendererText()
- render_text.set_property('xalign', 0)
- render_text.set_property('yalign', 0.5)
- col.pack_start(render_text, expand=True)
- col.add_attribute(render_text, 'text', 1)
- self.treeview.append_column(col)
- self.treeview.set_search_column(1)
- self.label = gtk.Label()
- self.pack_end(self.label, False)
+ # create renderer for the text in each row
+ render_text = Gtk.CellRendererText()
+ render_pixbuf.set_property('xalign', 0)
+ render_pixbuf.set_property('yalign', 0.5)
+ col.pack_start(render_text, True)
+ col.add_attribute(render_text, 'text', 1)
- self.expand_classes = False
- self.expand_functions = False
+ treeview.append_column(col)
+ treeview.set_search_column(1)
+ scrolledwindow.add(treeview)
+ self.add(scrolledwindow)
self.show_all()
- def on_toggle_expand_classes(self, action):
- self.expand_classes = action.get_active()
-
- def on_toggle_expand_functions(self, action):
- self.expand_functions = action.get_active()
-
- def on_row_has_child_toggled(self, treemodel, path, iter):
- if self.expand_classes and treemodel.get_value(iter, 2) == 'Class':
- self.treeview.expand_row(path, False)
- elif self.expand_functions and treemodel.get_value(iter, 2) == 'Function':
- self.treeview.expand_row(path, False)
-
def on_selection_changed(self, selection):
model, iter = selection.get_selected()
- if iter:
- lineno = model.get_value(iter, 3)
- if lineno:
- lineno = int(lineno) -1
- linestartiter = self.buffer.get_iter_at_line(lineno)
- lineenditer = self.buffer.get_iter_at_line(lineno)
- lineenditer.forward_line()
- line = self.buffer.get_text(linestartiter, lineenditer)
- name = model.get_value(iter, 1)
- start = line.find(name)
- if start > -1:
- end = start + len(name)
- self.buffer.select_range(
- self.buffer.get_iter_at_line_offset(lineno, start),
- self.buffer.get_iter_at_line_offset(lineno, end))
- self.view.scroll_to_cursor()
- else:
- #Todo: scroll view to lineno
- pass
-
- def create_treemodel(self):
- treemodel = gtk.TreeStore(gtk.gdk.Pixbuf, str, str, str, str)
- handler = treemodel.connect('row-has-child-toggled', self.on_row_has_child_toggled)
- return treemodel, handler
-
- def parse(self, view, buffer):
- self.view = view
- self.buffer = buffer
-
- startTime = time.time()
-
- treemodel, handler = self.create_treemodel()
- self.treeview.set_model(treemodel)
- self.treeview.freeze_child_notify()
-
- visitor = OutlineTreeModelASTVisitor(treemodel)
-
+ if not iter:
+ return
+ line_no = model.get_value(iter, 3)
+ if not line_no < 0:
+ model._document.goto_line(line_no)
+ model._view.scroll_to_cursor()
+
+
+class OutlineModel(Gtk.TreeStore):
+ moduleIcon = Gtk.Window().render_icon(Gtk.STOCK_COPY, Gtk.IconSize.MENU)
+ importIcon = Gtk.Window().render_icon(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU)
+ classIcon = Gtk.Window().render_icon(Gtk.STOCK_FILE, Gtk.IconSize.MENU)
+ functionIcon = Gtk.Window().render_icon(Gtk.STOCK_EXECUTE, Gtk.IconSize.MENU)
+ attributeIcon = Gtk.Window().render_icon(Gtk.STOCK_COPY, Gtk.IconSize.MENU)
+ errorIcon = Gtk.Window().render_icon(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.MENU)
+
+ def __init__(self, view, document):
+ self._view = view
+ self._document = document
+
+ # icon, name, class_name, line_no, docstring
+ Gtk.TreeStore.__init__(self, GdkPixbuf.Pixbuf, str, str, int, str)
+
+ if not builder:
+ self.append(None, [self.errorIcon, 'logilab.astng missing or invalid', None, -1, ''])
+ return
+
+ start, end = document.get_bounds()
+ text = document.get_text(start, end, False)
+
try:
- bounds = self.buffer.get_bounds()
- mod = compiler.parse(self.buffer.get_text(bounds[0], bounds[1]).replace('\r', '\n') + '\n')
- visitor.preorder(mod, visitor, None)
- del bounds, mod, visitor
- except SyntaxError:
- pass
- finally:
- gc.collect()
-
- treemodel.disconnect(handler)
- self.treeview.thaw_child_notify()
-
- stopTime = time.time()
- self.label.set_text('Outline created in ' + str(float(stopTime - startTime)) + ' s')
-
+ tree = builder.ASTNGBuilder().string_build(text)
+ except Exception, e:
+ self.append(None, [self.errorIcon, '%s\n\t%s' % (e.__class__.__name__, e.msg), None, e.lineno-1, e.text])
+ return
+
+ for n in tree.body:
+ self.append_member(n)
+
+ def append_member(self, member, parent=None):
+ classname = member.__class__.__name__
+ lineno = member.lineno - 1 # document is zero indexed
+ docstring = getattr(member, 'doc', '')
+
+ if classname == 'From':
+ for name, alias in member.names:
+ if alias:
+ item = self.append(parent, [self.importIcon, '%s [%s] from %s' % (alias, name, member.modname), classname, lineno, docstring])
+ else:
+ item = self.append(parent, [self.importIcon, '%s from %s' % (name, member.modname), classname, lineno, docstring])
+ elif classname == 'Import':
+ for name, alias in member.names:
+ if alias:
+ item = self.append(parent, [self.importIcon, '%s [%s]' % (alias, name), classname, lineno, docstring])
+ else:
+ item = self.append(parent, [self.importIcon, name, classname, lineno, docstring])
+ elif classname == 'Function':
+ item = self.append(parent, [self.functionIcon, member.name, classname, lineno, docstring])
+ elif classname == 'Class':
+ if getattr(member, 'basenames', None):
+ item = self.append(parent, [self.classIcon, '%s (%s)' % (member.name, ', '.join(member.basenames)), classname, lineno, docstring])
+ else:
+ item = self.append(parent, [self.classIcon, member.name, classname, lineno, docstring])
+ elif classname == 'Assign':
+ for target in member.targets:
+ self.append_member(target, parent=parent)
+ elif classname == 'AssAttr':
+ item = self.append(parent, [self.attributeIcon, member.attrname, classname, lineno, docstring])
+ elif classname == 'AssName':
+ item = self.append(parent, [self.attributeIcon, member.name, classname, lineno, docstring])
+ else:
+ if DEBUG:
+ print 'ERROR: unknown', classname, 'object:', getattr(member, 'name', str(member)), 'on line', lineno
+ return
+
+ for m in getattr(member, 'body', []):
+ self.append_member(m, parent=item)
+
+
+class PythonOutlineInstance(object):
+ _title = "Python Outline"
+ _name = "PythonOutlinePanel"
-class PythonOutlinePluginInstance(object):
def __init__(self, plugin, window):
self._window = window
self._plugin = plugin
-
- self._insert_panel()
+ # create the outline control
+ self.outlinebox = OutlineBox()
+ # add the outline control to the side panel tab
+ self.panel = self._window.get_side_panel()
+ self.panel.add_item(
+ self.outlinebox, self._name, self._title,
+ Gtk.Image.new_from_stock(Gtk.STOCK_INDEX, Gtk.IconSize.MENU)
+ )
+ self.panel.activate_item(self.outlinebox)
def deactivate(self):
- self._remove_panel
-
+ # remove the side panel tab that has our outline
+ self.panel.remove_item(self.outlinebox)
self._window = None
self._plugin = None
- def update_ui(self):
- document = self._window.get_active_document()
- if document:
- uri = str(document.get_uri())
- if document.get_mime_type() == 'text/x-python' or uri.endswith('.py') or uri.endswith('.pyw'):
- self.outlinebox.parse(self._window.get_active_view(), document)
- else:
- treemodel, handler = self.outlinebox.create_treemodel()
- self.outlinebox.treeview.set_model(treemodel)
-
- def _insert_panel(self):
- self.panel = self._window.get_side_panel()
- self.outlinebox = OutlineBox()
- self.panel.add_item(self.outlinebox, "Python Outline", gtk.STOCK_REFRESH)
+ def update_treeview(self):
+ model = OutlineModel(self._window.get_active_view(), self._window.get_active_document())
+ self.outlinebox.treeview.set_model(model)
- def _remove_panel(self):
- self.panel.destroy()
+class PythonOutlinePlugin(GObject.Object, Gedit.WindowActivatable):
+ __gtype_name__ = 'PythonOutline'
+ window = GObject.property(type=Gedit.Window)
-class PythonOutlinePlugin(gedit.Plugin):
def __init__(self):
- gedit.Plugin.__init__(self)
+ GObject.Object.__init__(self)
self._instances = {}
- def activate(self, window):
- self._instances[window] = PythonOutlinePluginInstance(self, window)
-
- def deactivate(self, window):
- self._instances[window].deactivate()
- del self._instances[window]
-
- def update_ui(self, window):
- self._instances[window].update_ui()
+ def do_activate(self):
+ self._handlers = []
+ self._handlers.append(
+ self.window.connect('tab-removed', self.on_tab_removed))
+ self._handlers.append(
+ self.window.connect('active-tab-changed', self.on_active_tab_changed))
+ self._handlers.append(
+ self.window.connect('active-tab-state-changed', self.on_active_tab_state_changed))
+
+ def do_deactivate(self):
+ for handler_id in self._handlers[:]:
+ self.window.disconnect(handler_id)
+ self._handlers.remove(handler_id)
+
+ def on_tab_removed(self, window, tab, data=None):
+ if window not in self._instances.keys():
+ return
+ if not window.get_active_tab():
+ self._instances[window].deactivate()
+ del self._instances[window]
+
+ def on_active_tab_changed(self, window, tab, data=None):
+ self.update_outline(window)
+
+ def on_active_tab_state_changed(self, window, data=None):
+ self.update_outline(window)
+
+ def update_outline(self, window):
+ document = window.get_active_document()
+ outline = self._instances.get(window)
+ if not outline:
+ if document_is_python(document):
+ outline = self._instances[window] = PythonOutlineInstance(
+ self, window)
+ elif not document_is_python(document):
+ self._instances[window].deactivate()
+ del self._instances[window]
+ return None
+ if outline:
+ outline.update_treeview()
+ return outline
Something went wrong with that request. Please try again.