diff --git a/README.markdown b/README.markdown index 4137cac..ebb1363 100644 --- a/README.markdown +++ b/README.markdown @@ -49,11 +49,13 @@ Installation 5. (Optional) If you want to enable the configuration dialog you need to compile the settings schema. You must do this as root. - cd /home/<YOUR USER NAME>/.local/share/gedit/plugins/sourcecodebrowser/data/ + ``` + cd /home//.local/share/gedit/plugins/sourcecodebrowser/data/ cp org.gnome.gedit.plugins.sourcecodebrowser.gschema.xml /usr/share/glib-2.0/schemas/ glib-compile-schemas /usr/share/glib-2.0/schemas/ + ``` Screenshots ----------- diff --git a/sourcecodebrowser.plugin b/sourcecodebrowser.plugin index c929aca..f3945c1 100644 --- a/sourcecodebrowser.plugin +++ b/sourcecodebrowser.plugin @@ -1,5 +1,5 @@ [Plugin] -Loader=python +Loader=python3 Module=sourcecodebrowser IAge=3 Name=Source Code Browser diff --git a/sourcecodebrowser/__init__.py b/sourcecodebrowser/__init__.py index b302f1f..c8c4e2c 100644 --- a/sourcecodebrowser/__init__.py +++ b/sourcecodebrowser/__init__.py @@ -1,3 +1,2 @@ -import plugin -from plugin import SourceCodeBrowserPlugin - +from . import plugin +from . plugin import SourceCodeBrowserPlugin diff --git a/sourcecodebrowser/ctags.py b/sourcecodebrowser/ctags.py index 7e978c6..a581f6a 100644 --- a/sourcecodebrowser/ctags.py +++ b/sourcecodebrowser/ctags.py @@ -88,8 +88,8 @@ def parse(self, command, executable=None): #args = [arg.replace('%20', ' ') for arg in shlex.split(command)] args = shlex.split(command) p = subprocess.Popen(args, 0, shell=False, stdout=subprocess.PIPE, executable=executable) - symbols = self._parse_text(p.communicate()[0]) - + symbols = self._parse_text(p.communicate()[0].decode('utf8')) + def _parse_text(self, text): """ Parses ctags text which may have come from a TAG file or from raw output diff --git a/sourcecodebrowser/plugin.py b/sourcecodebrowser/plugin.py index 19123fc..3fa0bc3 100644 --- a/sourcecodebrowser/plugin.py +++ b/sourcecodebrowser/plugin.py @@ -2,7 +2,7 @@ import sys import logging import tempfile -import ctags +from . import ctags from gi.repository import GObject, GdkPixbuf, Gedit, Gtk, PeasGtk, Gio logging.basicConfig() @@ -10,26 +10,27 @@ SETTINGS_SCHEMA = "org.gnome.gedit.plugins.sourcecodebrowser" DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') ICON_DIR = os.path.join(DATA_DIR, 'icons', '16x16') - -class SourceTree(Gtk.VBox): + +class SourceTree(Gtk.Box): """ Source Tree Widget - + A treeview storing the heirarchy of source code symbols within a particular document. Requries exhuberant-ctags. """ __gsignals__ = { "tag-activated": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_PYOBJECT,)), - } - + } + def __init__(self): - Gtk.VBox.__init__(self) + Gtk.Box.__init__(self) + self.set_orientation(Gtk.Orientation.VERTICAL) self._log = logging.getLogger(self.__class__.__name__) self._log.setLevel(LOG_LEVEL) self._pixbufs = {} self._current_uri = None self.expanded_rows = {} - + # preferences (should be set by plugin) self.show_line_numbers = True self.ctags_executable = 'ctags' @@ -37,52 +38,52 @@ def __init__(self): self.sort_list = True self.create_ui() self.show_all() - + def get_missing_pixbuf(self): """ Used for symbols that do not have a known image. """ if not 'missing' in self._pixbufs: filename = os.path.join(ICON_DIR, "missing-image.png") self._pixbufs['missing'] = GdkPixbuf.Pixbuf.new_from_file(filename) - + return self._pixbufs['missing'] - + def get_pixbuf(self, icon_name): - """ + """ Get the pixbuf for a specific icon name fron an internal dictionary of pixbufs. If the icon is not already in the dictionary, it will be loaded - from an external file. + from an external file. """ - if icon_name not in self._pixbufs: + if icon_name not in self._pixbufs: filename = os.path.join(ICON_DIR, icon_name + ".png") if os.path.exists(filename): try: self._pixbufs[icon_name] = GdkPixbuf.Pixbuf.new_from_file(filename) except Exception as e: - self._log.warn("Could not load pixbuf for icon '%s': %s", - icon_name, + self._log.warn("Could not load pixbuf for icon '%s': %s", + icon_name, str(e)) self._pixbufs[icon_name] = self.get_missing_pixbuf() else: self._pixbufs[icon_name] = self.get_missing_pixbuf() - + return self._pixbufs[icon_name] def clear(self): """ Clear the tree view so that new data can be loaded. """ - if self.expand_rows: + if self.expand_rows: self._save_expanded_rows() self._store.clear() - + def create_ui(self): """ Craete the main user interface and pack into box. """ self._store = Gtk.TreeStore(GdkPixbuf.Pixbuf, # icon GObject.TYPE_STRING, # name GObject.TYPE_STRING, # kind - GObject.TYPE_STRING, # uri - GObject.TYPE_STRING, # line - GObject.TYPE_STRING) # markup + GObject.TYPE_STRING, # uri + GObject.TYPE_STRING, # line + GObject.TYPE_STRING) # markup self._treeview = Gtk.TreeView.new_with_model(self._store) - self._treeview.set_headers_visible(False) + self._treeview.set_headers_visible(False) column = Gtk.TreeViewColumn("Symbol") cell = Gtk.CellRendererPixbuf() column.pack_start(cell, False) @@ -91,14 +92,14 @@ def create_ui(self): column.pack_start(cell, True) column.add_attribute(cell, 'markup', 5) self._treeview.append_column(column) - + self._treeview.connect("row-activated", self.on_row_activated) - + sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(self._treeview) self.pack_start(sw, True, True, 0) - + def _get_tag_iter(self, tag, parent_iter=None): """ Get the tree iter for the specified tag, or None if the tag cannot @@ -109,9 +110,9 @@ def _get_tag_iter(self, tag, parent_iter=None): if self._store.get_value(tag_iter, 1) == tag.name: return tag_iter tag_iter = self._store.iter_next(tag_iter) - + return None - + def _get_kind_iter(self, kind, uri, parent_iter=None): """ Get the iter for the specified kind. Creates a new node if the iter @@ -122,27 +123,27 @@ def _get_kind_iter(self, kind, uri, parent_iter=None): if self._store.get_value(kind_iter, 2) == kind.name: return kind_iter kind_iter = self._store.iter_next(kind_iter) - + # Kind node not found, so we'll create it. pixbuf = self.get_pixbuf(kind.icon_name()) markup = "%s" % kind.group_name() - kind_iter = self._store.append(parent_iter, (pixbuf, - kind.group_name(), - kind.name, - uri, - None, + kind_iter = self._store.append(parent_iter, (pixbuf, + kind.group_name(), + kind.name, + uri, + None, markup)) return kind_iter - + def load(self, kinds, tags, uri): """ - Load the tags into the treeview and restore the expanded rows if + Load the tags into the treeview and restore the expanded rows if applicable. """ self._current_uri = uri # load root-level tags first for i, tag in enumerate(tags): - if "class" not in tag.fields: + if "class" not in tag.fields: parent_iter = None pixbuf = self.get_pixbuf(tag.kind.icon_name()) if 'line' in tag.fields and self.show_line_numbers: @@ -150,13 +151,13 @@ def load(self, kinds, tags, uri): else: markup = tag.name kind_iter = self._get_kind_iter(tag.kind, uri, parent_iter) - new_iter = self._store.append(kind_iter, (pixbuf, - tag.name, - tag.kind.name, - uri, - tag.fields['line'], + new_iter = self._store.append(kind_iter, (pixbuf, + tag.name, + tag.kind.name, + uri, + tag.fields['line'], markup)) - # second level tags + # second level tags for tag in tags: if "class" in tag.fields and "." not in tag.fields['class']: pixbuf = self.get_pixbuf(tag.kind.icon_name()) @@ -170,20 +171,20 @@ def load(self, kinds, tags, uri): kind_iter = self._get_kind_iter(parent_tag.kind, uri, None) parent_iter = self._get_tag_iter(parent_tag, kind_iter) kind_iter = self._get_kind_iter(tag.kind, uri, parent_iter) # for sub-kind nodes - new_iter = self._store.append(kind_iter, (pixbuf, - tag.name, - tag.kind.name, - uri, - tag.fields['line'], + new_iter = self._store.append(kind_iter, (pixbuf, + tag.name, + tag.kind.name, + uri, + tag.fields['line'], markup)) - # TODO: We need to go at least one more level to deal with the inline + # TODO: We need to go at least one more level to deal with the inline # classes used in many python projects (eg. Models in Django) # Recursion would be even better. - - # sort - if self.sort_list: + + # sort + if self.sort_list: self._store.set_sort_column_id(1, Gtk.SortType.ASCENDING) - + # expand if uri in self.expanded_rows: for strpath in self.expanded_rows[uri]: @@ -225,25 +226,25 @@ def parse_file(self, path, uri): parser.parse(command, self.ctags_executable) except Exception as e: self._log.warn("Could not execute ctags: %s (executable=%s)", - str(e), + str(e), self.ctags_executable) self.load(parser.kinds, parser.tags, uri) - - + + def _save_expanded_rows(self): self.expanded_rows[self._current_uri] = [] - self._treeview.map_expanded_rows(self._save_expanded_rows_mapping_func, + self._treeview.map_expanded_rows(self._save_expanded_rows_mapping_func, self._current_uri) - + def _save_expanded_rows_mapping_func(self, treeview, path, uri): self.expanded_rows[uri].append(str(path)) - - + + class Config(object): def __init__(self): self._log = logging.getLogger(self.__class__.__name__) self._log.setLevel(LOG_LEVEL) - + def get_widget(self, has_schema): filename = os.path.join(DATA_DIR, 'configure_dialog.ui') builder = Gtk.Builder() @@ -255,7 +256,7 @@ def get_widget(self, has_schema): return None widget = builder.get_object("configure_widget") widget.set_border_width(12) - + if not has_schema: widget.set_sensitive(False) else: @@ -277,30 +278,30 @@ def get_widget(self, has_schema): ) builder.connect_signals(self) return widget - + def on_show_line_numbers_toggled(self, button, data=None): self._settings.set_boolean('show-line-numbers', button.get_active()) - + def on_expand_rows_toggled(self, button, data=None): self._settings.set_boolean('expand-rows', button.get_active()) - + def on_load_remote_files_toggled(self, button, data=None): self._settings.set_boolean('load-remote-files', button.get_active()) - + def on_sort_list_toggled(self, button, data=None): self._settings.set_boolean('sort-list', button.get_active()) - + def on_ctags_executable_changed(self, editable, data=None): self._settings.set_string('ctags-executable', editable.get_text()) - - + + class SourceCodeBrowserPlugin(GObject.Object, Gedit.WindowActivatable, PeasGtk.Configurable): """ Source Code Browser Plugin for Gedit 3.x - + Adds a tree view to the side panel of a Gedit window which provides a list of programming symbols (functions, classes, variables, etc.). - + https://live.gnome.org/Gedit/PythonPluginHowTo """ __gtype_name__ = "SourceCodeBrowserPlugin" @@ -315,10 +316,10 @@ def __init__(self): filename = os.path.join(ICON_DIR, "source-code-browser.png") self.icon = Gtk.Image.new_from_file(filename) - + def do_create_configure_widget(self): return Config().get_widget(self._has_settings_schema()) - + def do_activate(self): """ Activate plugin """ self._log.debug("Activating plugin") @@ -330,9 +331,12 @@ def do_activate(self): self._sourcetree.expand_rows = self.expand_rows self._sourcetree.sort_list = self.sort_list panel = self.window.get_side_panel() - panel.add_item(self._sourcetree, "SymbolBrowserPlugin", "Source Code", self.icon) + if hasattr(panel,"add_titled"): + panel.add_titled(self._sourcetree, "SymbolBrowserPlugin", "Source Code") + else: + panel.add_item(self._sourcetree, "SymbolBrowserPlugin", "Source Code") self._handlers = [] - hid = self._sourcetree.connect("focus", self.on_sourcetree_focus) + hid = self._sourcetree.connect("draw", self.on_sourcetree_draw) self._handlers.append((self._sourcetree, hid)) if self.ctags_version is not None: hid = self._sourcetree.connect('tag-activated', self.on_tag_activated) @@ -345,7 +349,7 @@ def do_activate(self): self._handlers.append((self.window, hid)) else: self._sourcetree.set_sensitive(False) - + def do_deactivate(self): """ Deactivate the plugin """ self._log.debug("Deactivating plugin") @@ -353,16 +357,16 @@ def do_deactivate(self): obj.disconnect(hid) self._handlers = None pane = self.window.get_side_panel() - pane.remove_item(self._sourcetree) + pane.remove(self._sourcetree) self._sourcetree = None - + def _has_settings_schema(self): schemas = Gio.Settings.list_schemas() if not SETTINGS_SCHEMA in schemas: return False else: return True - + def _init_settings(self): """ Initialize GSettings if available. """ if self._has_settings_schema(): @@ -386,15 +390,13 @@ def _init_settings(self): self.expand_rows = True self.sort_list = True self.ctags_executable = 'ctags' - + def _load_active_document_symbols(self): """ Load the symbols for the given URI. """ self._sourcetree.clear() self._is_loaded = False # do not load if not the active tab in the panel panel = self.window.get_side_panel() - if not panel.item_is_active(self._sourcetree): - return document = self.window.get_active_document() if document: @@ -405,7 +407,7 @@ def _load_active_document_symbols(self): if uri is not None: if uri[:7] == "file://": # use get_parse_name() to get path in UTF-8 - filename = location.get_parse_name() + filename = location.get_parse_name() self._sourcetree.parse_file(filename, uri) elif self.load_remote_files: basename = location.get_basename() @@ -413,7 +415,7 @@ def _load_active_document_symbols(self): contents = document.get_text(document.get_start_iter(), document.get_end_iter(), True) - os.write(fd, contents) + os.write(fd, bytes(contents, 'UTF-8')) os.close(fd) while Gtk.events_pending(): Gtk.main_iteration() @@ -421,10 +423,10 @@ def _load_active_document_symbols(self): os.unlink(filename) self._loaded_document = document self._is_loaded = True - + def on_active_tab_changed(self, window, tab, data=None): self._load_active_document_symbols() - + def on_setting_changed(self, settings, key, data=None): """ self.load_remote_files = True @@ -442,7 +444,7 @@ def on_setting_changed(self, settings, key, data=None): self.sort_list = self._settings.get_boolean(key) elif key == 'ctags-executable': self.ctags_executable = self._settings.get_string(key) - + if self._sourcetree is not None: self._sourcetree.ctags_executable = self.ctags_executable self._sourcetree.show_line_numbers = self.show_line_numbers @@ -450,19 +452,19 @@ def on_setting_changed(self, settings, key, data=None): self._sourcetree.sort_list = self.sort_list self._sourcetree.expanded_rows = {} self._load_active_document_symbols() - - def on_sourcetree_focus(self, direction, data=None): + + def on_sourcetree_draw(self, sourcetree, data=None): if not self._is_loaded: self._load_active_document_symbols() return False - + def on_tab_state_changed(self, window, data=None): self._load_active_document_symbols() - + def on_tab_removed(self, window, tab, data=None): if not self.window.get_active_document(): self._sourcetree.clear() - + def on_tag_activated(self, sourcetree, location, data=None): """ Go to the line where the double-clicked symbol is defined. """ uri, line = location @@ -472,13 +474,10 @@ def on_tag_activated(self, sourcetree, location, data=None): line = int(line) - 1 # lines start from 0 document.goto_line(line) view.scroll_to_cursor() - + def _version_check(self): """ Make sure the exhuberant ctags is installed. """ - self.ctags_version = ctags.get_ctags_version(self.ctags_executable) + self.ctags_version = ctags.get_ctags_version(self.ctags_executable) if not self.ctags_version: - self._log.warn("Could not find ctags executable: %s" % + self._log.warn("Could not find ctags executable: %s" % (self.ctags_executable)) - - -