pax_global_header00006660000000000000000000000064116323725310014515gustar00rootroot0000000000000052 comment=b0095449d38dfafd1251702a5fa2e1e27c30a2d4 Quixotix-gedit-source-code-browser-1bea6e1/000077500000000000000000000000001163237253100210165ustar00rootroot00000000000000Quixotix-gedit-source-code-browser-1bea6e1/.gitignore000066400000000000000000000000061163237253100230020ustar00rootroot00000000000000*.pyc Quixotix-gedit-source-code-browser-1bea6e1/README.markdown000066400000000000000000000102451163237253100235210ustar00rootroot00000000000000Gedit Source Code Browser ========================= A source code class and function browser plugin for Gedit 3. * Author: Micah Carrick This plugin will add a new tab to the side pane in the Gedit text editor which shows symbols (functions, classes, variables, etc.) for the active document. Clicking a symbol in the list wil jump to the line on which that symbol is defined. See the [ctags supported languages](http://ctags.sourceforge.net/languages.html) for a list of the 41 programming languages supported by this plugin. Requirements ------------ This plugins is for Gedit 3 and is **not compatible with Gedit 2.x**. The Gedit Source Code Browser plugin uses [Exuberant Ctags](http://ctags.sourceforge.net/) to parse symbols out of source code. Exuberant Ctags is avaialable in the software repository for many distributions. To make sure you have ctags correctly installed, issue the following command: ctags --version Make sure that you see *Exuberant* Ctags in the version output. Installation ------------ 1. Download this repository by clicking the Downloads button at the top of the github page or issue the following command in a terminal: git clone git://github.com/Quixotix/gedit-source-code-browser.git 2. Copy the file `sourcecodebrowser.plugin` and the folder `sourcecodebrowser` to `~/.local/share/gedit/plugins/`. 3. Restart Gedit. 4. Activate the plugin in Gedit by choosing 'Edit > Preferences', the selecting the 'Plugins' tab, and checking the box next to 'Soucre Code Browser'. 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/ cp org.gnome.gedit.plugins.sourcecodebrowser.gschema.xml /usr/share/glib-2.0/schemas/ glib-compile-schemas /usr/share/glib-2.0/schemas/ Screenshots ----------- ![Python code in Source Code Browser](http://www.micahcarrick.com/images/gedit-source-code-browser/python.png) Known Issues ------------ * CSS is not supported. This issue is about ctags and not this plugin. You can [extend ctags](http://ctags.sourceforge.net/EXTENDING.html) to add support for any language you like. Many people have provided their fixes to on internet such as this [patch for CSS support](http://scie.nti.st/2006/12/22/how-to-add-css-support-to-ctags). * PHP is supported, however, PHP5 classes are not well supported. This is again an issue with ctags. There are numerous fixes to be found onn the internet such as these [patches for better PHP5 support](http://www.jejik.com/articles/2008/11/patching_exuberant-ctags_for_better_php5_support_in_vim/). License ------- Copyright (c) 2011, Micah Carrick All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Micah Carrick nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser.plugin000066400000000000000000000004631163237253100260000ustar00rootroot00000000000000[Plugin] Loader=python Module=sourcecodebrowser IAge=3 Name=Source Code Browser Description=A source code class and function browser for Gedit 3. Authors=Micah Carrick Copyright=Copyright © 2011 Micah Carrick Website=https://github.com/Quixotix/gedit-source-code-browser Version=3.0.3 Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/000077500000000000000000000000001163237253100245555ustar00rootroot00000000000000Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/__init__.py000066400000000000000000000000721163237253100266650ustar00rootroot00000000000000import plugin from plugin import SourceCodeBrowserPlugin Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/ctags.py000066400000000000000000000112021163237253100262240ustar00rootroot00000000000000import os import subprocess import shlex def get_ctags_version(executable=None): """ Return the text output from the --version option to ctags or None if ctags executable cannot be found. Use executable for custom ctags builds and/or path. """ args = shlex.split("ctags --version") try: p = subprocess.Popen(args, 0, shell=False, stdout=subprocess.PIPE, executable=executable) version = p.communicate()[0] except: version = None return version class Tag(object): """ Represents a ctags "tag" found in some source code. """ def __init__(self, name): self.name = name self.file = None self.ex_command = None self.kind = None self.fields = {} class Kind(object): """ Represents a ctags "kind" found in some source code such as "member" or "class". """ def __init__(self, name): self.name = name self.language = None def group_name(self): """ Return the kind name as a group name. For example, 'variable' would be 'Variables'. Pluralization is complex but this method is not. It works more often than not. """ group = self.name if self.name[-1] == 's': group += 'es' elif self.name[-1] == 'y': group = self.name[0:-1] + 'ies' else: group += 's' return group.capitalize() def icon_name(self): """ Return the icon name in the form of 'source-'. """ return 'source-' + self.name class Parser(object): """ Ctags Parser Parses the output of a ctags command into a list of tags and a dictionary of kinds. """ def has_kind(self, kind_name): """ Return true if kind_name is found in the list of kinds. """ if kind_name in self.kinds: return True else: return False def __init__(self): self.tags = [] self.kinds = {} self.tree = {} def parse(self, command, executable=None): """ Parse ctags tags from the output of a ctags command. For example: ctags -n --fields=fiKmnsSzt -f - some_file.php """ #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]) def _parse_text(self, text): """ Parses ctags text which may have come from a TAG file or from raw output from a ctags command. """ for line in text.splitlines(): name = None file = None ex_command = None kind = None for i, field in enumerate(line.split("\t")): if i == 0: tag = Tag(field) elif i == 1: tag.file = field elif i == 2: tag.ex_command = field elif i > 2: if ":" in field: key, value = field.split(":")[0:2] tag.fields[key] = value if key == 'kind': kind = Kind(value) if not kind in self.kinds: self.kinds[value] = kind if kind is not None: if 'language' in tag.fields: kind.language = tag.fields['language'] tag.kind = kind self.tags.append(tag) """ def get_tree(self): tree = {} for tag in self.tags: if 'class' in tag.fields: parent = tag.fields['class'] if "." in parent: parents = parent.split(".") node = tree for p in parents: if not p in node: node[p] = {'tag':None, 'children':{}} node = node[p] print node node['tag'] = tag else: if not parent in self.tree: tree[parent] = {'tag':None, 'children':{}} tree[parent]['children'][tag.name] = {'tag':tag, 'children':{}} else: if tag.name in self.tree: tree[tag.name]['tag'] = tag else: tree[tag.name] = {'tag': tag, 'children':{}} return tree """ Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/000077500000000000000000000000001163237253100254665ustar00rootroot00000000000000Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/configure_dialog.ui000066400000000000000000000207711163237253100313340ustar00rootroot00000000000000 False 5 Source Code Browser dialog True True False vertical 2 False end gtk-close True True True False True False True 0 False True end 0 True False vertical Show _line numbers in tree True True False False True 0 True False True 6 0 Load symbols from _remote files True True False False True 0 True False True 6 1 Start with rows _expanded True True False False True 0 True False True 6 2 _Sort list alphabetically True True False False True 0 True False True 6 3 True False True False 0 ctags executable False True 0 True True ctags True False True 6 1 False True 6 4 False True 6 1 button1 Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/000077500000000000000000000000001163237253100266015ustar00rootroot00000000000000Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16/000077500000000000000000000000001163237253100273665ustar00rootroot00000000000000Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16/missing-image.png000066400000000000000000000011001163237253100326150ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8jAwf@҄J>!OPyA}B"nD,B_CHP(ED E^"KHb{0Ǧ |Thfd$K8[RJ(>~: "+-=|LT%e d2a8nlrÉv F%hTFT_B8 ƫF²Ic(c96*br*r6/v>1,K+ )xAYt:!DݥZ.zYޔ6_| :" `&rhH c՛bFd% ,.y>z}KMMMc쐔jz+f-\KGQ{qBH]֚-^63kd*+KYnIinr=J&lȫles6G,.[ֹp Oa h!DљV]e:.G}dL[@"b >beL5ŖX3orB[IENDB`source-code-browser.png000066400000000000000000000014761163237253100337160ustar00rootroot00000000000000Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8[HQ96wQ,z(RiќYei"QRLQ(Q!(E"̒ -H9/G6;=(a*#̉}gYV04HFt  W# h":^0)Pyht@E% c?Ό"VItxbc,<@k(PqEj&s~Nr]iaG5`g ֺ)sk\\s:1ZN)Eo{{kSG)깃!zN)eT o+DjjA=sFG̃I(=l~k09ZO/RÝ0Z~m2w^A~c#_/9ƘL ,x%ziN?mTz'H?;>Yޔ6_| :" `&rhH c՛bFd% ,.y>z}KMMMc쐔jz+f-\KGQ{qBH]֚-^63kd*+KYnIinr=J&lȫles6G,.[ֹp Oa h!DљV]e:.G}dL[@"b >beL5ŖX3orB[IENDB`Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16/source-define.png000066400000000000000000000006501163237253100326250ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<%IDAT81jA@݄h{!6:MH#b>Mj xA ٝ]) ~nUDh H%71ol6DUL&&2<5M/U=TUV!ovq=WPU#z}t:LSe`0*(|,(bqn3/~P1rc fsnZ ÿhDezMj xA ٝ]) ~nUDh H%71ol6DUL&&2<5M/U=TUV!ovq=WPU#z}t:LSe`0*(|,(bqn3/~P1rc fsnZ ÿhDezMj xA ٝ]) ~nUDh H%71ol6DUL&&2<5M/U=TUV!ovq=WPU#z}t:LSe`0*(|,(bqn3/~P1rc fsnZ ÿhDezј&<SK'7j[V:" [.A+_5 c,9dȰ~#=95{2:{FimjU$yvvay~=14>ێ,_}2^f*0 lo7[fMD^W0@ XD$'Ё($8U_oorIENDB`Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16/source-macro.png000066400000000000000000000006501163237253100324740ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<%IDAT81jA@݄h{!6:MH#b>Mj xA ٝ]) ~nUDh H%71ol6DUL&&2<5M/U=TUV!ovq=WPU#z}t:LSe`0*(|,(bqn3/~P1rc fsnZ ÿhDezј&<SK'7j[V:" [.A+_5 c,9dȰ~#=95{2:{FimjU$yvvay~=14>ێ,_}2^f*0 lo7[fMD^W0@ XD$'Ё($8U_oorIENDB`Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16/source-method.png000066400000000000000000000011341163237253100326510ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8MkQN^F[RJh!* -F-X4+K4J(,0kA\C,]ԍ `QWڢ$1i&ؙd9nD$g{y6=p jy# <[8dŢ9t[.";Zll^$ W_&&&L/&]粿޶B|cSXjj,w~ª񩆹v64_"3…<8v)lu/cBO_mmYO !ƒ>ј&<SK'7j[V:" [.A+_5 c,9dȰ~#=95{2:{FimjU$yvvay~=14>ێ,_}2^f*0 lo7[fMD^W0@ XD$'Ё($8U_oorIENDB`Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16/source-namespace.png000066400000000000000000000012471163237253100333320ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<$IDAT8KQH1`Z } AڇDAZHm"4?@Aݸf!N,[D Qfts[|9?s9q7~?~r#$8IU5s&u,Pyvr׺G5JDE64?kAWLxiӗһjs:[ Er "EfqW]_YYFZ A:9q/~$ֲP-5% \8wh=]1H8ӓn()*߻:П:S)WI̶% f;婁Tқ;}! mcxkJf6r̘z'z,V%_S͢5ZO粙\6q9cms,,Smsu__;$ H@BT~} oh?É>8:zJ(҂~ `-VK0 "+F08(btL$gvJay+CCcJNiӞ_TTIENDB`Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16/source-property.png000066400000000000000000000007301163237253100332560ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<UIDAT8+QuG^%jJdLYI B,VֲGYNSXXMz3w2cc1_^iYsovntCihbÒzPABId#Xf[ FmXTDPJ8椢9׃KW8O_·J)e[Y$ʈ ʈZxuYN/Th+BtAjf"m:iduhf\+#*jGEX/b'1^ZS;\&u|bR [+#RE[(\=W;e•Ȅ1<Sx>3c  +<)gԌ[q//2 f[UFS3§g.֛P SWi39zD&+$I}q?A@$ltG#f6p_F1dyr⽆\˺JvQc# u7 U^TUn^/diuk*d04 6`²7TMFpCT<.ӦemxvJNƸu] )fKIG Sp6 Do,b2ţ7c)zaV)k!gKUX!*NSWf gcG]ˆL5B:6d ^OIɾ VY& Dox|IENDB`Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16/source-table.png000066400000000000000000000012651163237253100324650ustar00rootroot00000000000000PNG  IHDRabKGDlllH> pHYs  tIME ,9n(>5tEXtComment(c) 2004 Jakub Steiner Created with The GIMPًoIDAT8͒KOSa@}- J%HҊi Xc/Vn P6EˤK }$}|.xҺ4q639s-ɲ|,IRG,ǚ-sUU4?y2"!Xir@wp88T*,ƶ,lr!3lIiB{ FuEV^nf9zpjlۆ+JzX,Ju,\V3@Q#fgg󬮮l6, Ϗab"d2_d Ø{(@!uq4M\ץP(0( U8;;U8u~iwT* 0==;.`'D"MSsa3>>N:=@Qrɗd2,= lEEQlmVJ'[V¶03P}EMU߯g>^ #iIENDB`Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/data/icons/16x16/source-typedef.png000066400000000000000000000006501163237253100330330ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<%IDAT81jA@݄h{!6:MH#b>Mj xA ٝ]) ~nUDh H%71ol6DUL&&2<5M/U=TUV!ovq=WPU#z}t:LSe`0*(|,(bqn3/~P1rc fsnZ ÿhDez false Show Line Numbers Show the line number of each item in the source tree. true Load Remote Files Download remote files into a temporary file for parsing the symbols. true Expand Rows Expand the entire source tree when initially loaded. true Sort List Sorts the list of symbols alphabetically rather than by where they occur in the source file. 'ctags' Ctags Executable Specifies the executable path for Exuberant Ctags. Quixotix-gedit-source-code-browser-1bea6e1/sourcecodebrowser/plugin.py000066400000000000000000000505271163237253100264360ustar00rootroot00000000000000import os import sys import logging import tempfile import ctags from gi.repository import GObject, GdkPixbuf, Gedit, Gtk, PeasGtk, Gio logging.basicConfig() LOG_LEVEL = logging.WARN 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): """ 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) 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' self.expand_rows = True 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. """ 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, 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: 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 self._treeview = Gtk.TreeView.new_with_model(self._store) self._treeview.set_headers_visible(False) column = Gtk.TreeViewColumn("Symbol") cell = Gtk.CellRendererPixbuf() column.pack_start(cell, False) column.add_attribute(cell, 'pixbuf', 0) cell = Gtk.CellRendererText() 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 be found. """ tag_iter = self._store.iter_children(parent_iter) while tag_iter: 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 is not found under the specirfied parent_iter. """ kind_iter = self._store.iter_children(parent_iter) while kind_iter: 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, markup)) return kind_iter def load(self, kinds, tags, uri): """ 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: parent_iter = None pixbuf = self.get_pixbuf(tag.kind.icon_name()) if 'line' in tag.fields and self.show_line_numbers: markup = "%s [%s]" % (tag.name, tag.fields['line']) 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'], markup)) # 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()) if 'line' in tag.fields and self.show_line_numbers: markup = "%s [%s]" % (tag.name, tag.fields['line']) else: markup = tag.name for parent_tag in tags: if parent_tag.name == tag.fields['class']: break 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'], markup)) # 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: self._store.set_sort_column_id(1, Gtk.SortType.ASCENDING) # expand if uri in self.expanded_rows: for strpath in self.expanded_rows[uri]: path = Gtk.TreePath.new_from_string(strpath) if path: self._treeview.expand_row(path, False) elif uri not in self.expanded_rows and self.expand_rows: self._treeview.expand_all() """ curiter = self._store.get_iter_first() while curiter: path = self._store.get_path(curiter) self._treeview.expand_row(path, False) curiter = self._store.iter_next(iter) """ def on_row_activated(self, treeview, path, column, data=None): """ If the row has uri and line number information, emits the tag-activated signal so that the editor can jump to the tag's location. """ model = treeview.get_model() iter = model.get_iter(path) uri = model.get_value(iter, 3) line = model.get_value(iter, 4) if uri and line: self.emit("tag-activated", (uri, line)) def parse_file(self, path, uri): """ Parse symbols out of a file using exhuberant ctags. The path is the local filename to pass to ctags, and the uri is the actual URI as known by Gedit. They would be different for remote files. """ command = "ctags -nu --fields=fiKlmnsSzt -f - '%s'" % path #self._log.debug(command) try: parser = ctags.Parser() parser.parse(command, self.ctags_executable) except Exception as e: self._log.warn("Could not execute ctags: %s (executable=%s)", 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._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() try: count = builder.add_objects_from_file(filename, ["configure_widget"]) assert(count > 0) except Exception as e: self._log.error("Failed to load %s: %s." % (filename, str(e))) return None widget = builder.get_object("configure_widget") widget.set_border_width(12) if not has_schema: widget.set_sensitive(False) else: self._settings = Gio.Settings.new(SETTINGS_SCHEMA) builder.get_object("show_line_numbers").set_active( self._settings.get_boolean('show-line-numbers') ) builder.get_object("expand_rows").set_active( self._settings.get_boolean('expand-rows') ) builder.get_object("load_remote_files").set_active( self._settings.get_boolean('load-remote-files') ) builder.get_object("sort_list").set_active( self._settings.get_boolean('sort-list') ) builder.get_object("ctags_executable").set_text( self._settings.get_string('ctags-executable') ) 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" window = GObject.property(type=Gedit.Window) def __init__(self): GObject.Object.__init__(self) self._log = logging.getLogger(self.__class__.__name__) self._log.setLevel(LOG_LEVEL) self._is_loaded = False self._ctags_version = None 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") self._init_settings() self._version_check() self._sourcetree = SourceTree() self._sourcetree.ctags_executable = self.ctags_executable self._sourcetree.show_line_numbers = self.show_line_numbers 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) self._handlers = [] hid = self._sourcetree.connect("focus", self.on_sourcetree_focus) self._handlers.append((self._sourcetree, hid)) if self.ctags_version is not None: hid = self._sourcetree.connect('tag-activated', self.on_tag_activated) self._handlers.append((self._sourcetree, hid)) hid = self.window.connect("active-tab-state-changed", self.on_tab_state_changed) self._handlers.append((self.window, hid)) hid = self.window.connect("active-tab-changed", self.on_active_tab_changed) self._handlers.append((self.window, hid)) hid = self.window.connect("tab-removed", self.on_tab_removed) self._handlers.append((self.window, hid)) else: self._sourcetree.set_sensitive(False) def do_deactivate(self): """ Deactivate the plugin """ self._log.debug("Deactivating plugin") for obj, hid in self._handlers: obj.disconnect(hid) self._handlers = None pane = self.window.get_side_panel() pane.remove_item(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(): settings = Gio.Settings.new(SETTINGS_SCHEMA) self.load_remote_files = settings.get_boolean("load-remote-files") self.show_line_numbers = settings.get_boolean("show-line-numbers") self.expand_rows = settings.get_boolean("expand-rows") self.sort_list = settings.get_boolean("sort-list") self.ctags_executable = settings.get_string("ctags-executable") settings.connect("changed::load-remote-files", self.on_setting_changed) settings.connect("changed::show-line-numbers", self.on_setting_changed) settings.connect("changed::expand-rows", self.on_setting_changed) settings.connect("changed::sort-list", self.on_setting_changed) settings.connect("changed::ctags-executable", self.on_setting_changed) self._settings = settings else: self._log.warn("Settings schema not installed. Plugin will not be configurable.") self._settings = None self.load_remote_files = True self.show_line_numbers = False 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: location = document.get_location() if location: uri = location.get_uri() self._log.debug("Loading %s...", uri) if uri is not None: if uri[:7] == "file://": # use get_parse_name() to get path in UTF-8 filename = location.get_parse_name() self._sourcetree.parse_file(filename, uri) elif self.load_remote_files: basename = location.get_basename() fd, filename = tempfile.mkstemp('.'+basename) contents = document.get_text(document.get_start_iter(), document.get_end_iter(), True) os.write(fd, contents) os.close(fd) while Gtk.events_pending(): Gtk.main_iteration() self._sourcetree.parse_file(filename, uri) 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 self.show_line_numbers = False self.expand_rows = True self.ctags_executable = 'ctags' """ if key == 'load-remote-files': self.load_remote_files = self._settings.get_boolean(key) elif key == 'show-line-numbers': self.show_line_numbers = self._settings.get_boolean(key) elif key == 'expand-rows': self.expand_rows = self._settings.get_boolean(key) elif key == 'sort-list': 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 self._sourcetree.expand_rows = self.expand_rows self._sourcetree.sort_list = self.sort_list self._sourcetree.expanded_rows = {} self._load_active_document_symbols() def on_sourcetree_focus(self, direction, 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 self._log.debug("%s, line %s." % (uri, line)) document = self.window.get_active_document() view = self.window.get_active_view() 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) if not self.ctags_version: self._log.warn("Could not find ctags executable: %s" % (self.ctags_executable))