[geany/geany-plugins] 6af2bf: Merge pull request #107 from elextr/geanypy

Frank Lanitz git-noreply at xxxxx
Sat Jun 15 11:59:32 UTC 2013


Branch:      refs/heads/master
Author:      Frank Lanitz <frank at frank.uvena.de>
Committer:   Frank Lanitz <frank at frank.uvena.de>
Date:        Sat, 15 Jun 2013 11:59:32 UTC
Commit:      6af2bf6d078a9d828e68dbc5c27eca9009b1e708
             https://github.com/geany/geany-plugins/commit/6af2bf6d078a9d828e68dbc5c27eca9009b1e708

Log Message:
-----------
Merge pull request #107 from elextr/geanypy

Adding Geanypy to geany-plugins


Modified Paths:
--------------
    .gitignore
    MAINTAINERS
    Makefile.am
    build/geanypy.m4
    configure.ac
    geanypy/AUTHORS
    geanypy/COPYING
    geanypy/ChangeLog
    geanypy/Makefile.am
    geanypy/NEWS
    geanypy/README
    geanypy/doc/make.bat
    geanypy/doc/source/api.rst
    geanypy/doc/source/app.rst
    geanypy/doc/source/conf.py
    geanypy/doc/source/dialogs.rst
    geanypy/doc/source/document.rst
    geanypy/doc/source/geany.rst
    geanypy/doc/source/index.rst
    geanypy/doc/source/install.rst
    geanypy/doc/source/intro.rst
    geanypy/doc/source/quickstart.rst
    geanypy/doc/source/starting.rst
    geanypy/geany/Makefile.am
    geanypy/geany/__init__.py
    geanypy/geany/console.py
    geanypy/geany/loader.py
    geanypy/geany/manager.py
    geanypy/geany/plugin.py
    geanypy/geany/signalmanager.py
    geanypy/m4/ax_python_devel.m4
    geanypy/m4/ax_python_library.m4
    geanypy/plugins/Makefile.am
    geanypy/plugins/console.py
    geanypy/plugins/demo.py
    geanypy/plugins/hello.py
    geanypy/src/Makefile.am
    geanypy/src/geanypy-app.c
    geanypy/src/geanypy-dialogs.c
    geanypy/src/geanypy-document.c
    geanypy/src/geanypy-document.h
    geanypy/src/geanypy-editor.c
    geanypy/src/geanypy-editor.h
    geanypy/src/geanypy-encoding.c
    geanypy/src/geanypy-encoding.h
    geanypy/src/geanypy-filetypes.c
    geanypy/src/geanypy-filetypes.h
    geanypy/src/geanypy-highlighting.c
    geanypy/src/geanypy-indentprefs.c
    geanypy/src/geanypy-interfaceprefs.c
    geanypy/src/geanypy-main.c
    geanypy/src/geanypy-mainwidgets.c
    geanypy/src/geanypy-msgwindow.c
    geanypy/src/geanypy-navqueue.c
    geanypy/src/geanypy-plugin.c
    geanypy/src/geanypy-plugin.h
    geanypy/src/geanypy-prefs.c
    geanypy/src/geanypy-project.c
    geanypy/src/geanypy-project.h
    geanypy/src/geanypy-scinotification.c
    geanypy/src/geanypy-scinotifyheader.c
    geanypy/src/geanypy-scintilla.c
    geanypy/src/geanypy-scintilla.h
    geanypy/src/geanypy-search.c
    geanypy/src/geanypy-signalmanager.c
    geanypy/src/geanypy-signalmanager.h
    geanypy/src/geanypy-templates.c
    geanypy/src/geanypy-uiutils.c
    geanypy/src/geanypy-uiutils.h
    geanypy/src/geanypy.h
    geanypy/src/makefile.win32
    po/POTFILES.in

Modified: .gitignore
1 files changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -7,6 +7,7 @@
 *.so
 *.dll
 *.exe
+*.pyc
 deps.mak
 .deps/
 .libs/


Modified: MAINTAINERS
6 files changed, 6 insertions(+), 0 deletions(-)
===================================================================
@@ -114,6 +114,12 @@ M: Yura Siamashka <yurand2 at gmail.com>
 W: http://plugins.geany.org/geanyprj.html
 S: Odd Fixes
 
+geanypy
+P: Lex Trotman <elextr at gmail.com>
+M: Lex Trotman <elextr at gmail.com>
+W: http://plugins.geany.org/geanypy.html
+S: Maintained
+
 geanysendmail
 P: Frank Lanitz <frank at frank.uvena.de>
 M: Frank Lanitz <frank at frank.uvena.de>


Modified: Makefile.am
6 files changed, 5 insertions(+), 1 deletions(-)
===================================================================
@@ -1,4 +1,4 @@
-ACLOCAL_AMFLAGS = -I build/cache -I build -I build/bundled --install
+ACLOCAL_AMFLAGS = -I build/cache -I build -I build/bundled -I geanypy/m4 --install
 
 SUBDIRS = po
 
@@ -70,6 +70,10 @@ if ENABLE_GEANYPRJ
 SUBDIRS += geanyprj
 endif
 
+if ENABLE_GEANYPY
+SUBDIRS += geanypy
+endif
+
 if ENABLE_GEANYSENDMAIL
 SUBDIRS += geanysendmail
 endif


Modified: build/geanypy.m4
20 files changed, 20 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,20 @@
+AC_DEFUN([GP_CHECK_GEANYPY],
+[
+    GP_ARG_DISABLE([Geanypy], [auto])
+    GP_CHECK_PLUGIN_GTK2_ONLY([Geanypy])
+    GP_COMMIT_PLUGIN_STATUS([Geanypy])
+    PKG_CHECK_MODULES([PYGTK], [pygtk-2.0])
+    AX_PYTHON_DEVEL([>= '2.6'])
+    AX_PYTHON_LIBRARY(,[AC_MSG_ERROR([Cannot find Python library])])
+    AC_SUBST([PYTHON])
+	AC_DEFINE_UNQUOTED(
+	    [GEANYPY_PYTHON_LIBRARY],
+	    ["$PYTHON_LIBRARY"],
+	    [Location of Python library to dlopen()])
+    AC_CONFIG_FILES([
+        geanypy/Makefile
+        geanypy/src/Makefile
+        geanypy/geany/Makefile
+        geanypy/plugins/Makefile
+    ])
+])


Modified: configure.ac
1 files changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -41,6 +41,7 @@ GP_CHECK_GEANYMACRO
 GP_CHECK_GEANYMINISCRIPT
 GP_CHECK_GEANYNUMBEREDBOOKMARKS
 GP_CHECK_GEANYPRJ
+GP_CHECK_GEANYPY
 GP_CHECK_GEANYSENDMAIL
 GP_CHECK_GEANYVC
 GP_CHECK_GEANYPG


Modified: geanypy/AUTHORS
2 files changed, 2 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,2 @@
+Matthew Brush <mbrush at codebrainz.ca>
+Lex Trotman <elextr at gmail.com> (Geany-Plugins port)


Modified: geanypy/COPYING
339 files changed, 339 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.


Modified: geanypy/ChangeLog
1 files changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1 @@
+Unused


Modified: geanypy/Makefile.am
4 files changed, 4 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,4 @@
+include $(top_srcdir)/build/vars.auxfiles.mk
+#ACLOCAL_AMFLAGS += -I geanypy/m4
+SUBDIRS = src geany plugins
+plugin = geanypy


Modified: geanypy/NEWS
1 files changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1 @@
+UnNEWSed


Modified: geanypy/README
73 files changed, 73 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,73 @@
+GeanyPy
+=======
+
+Write Geany plugins in Python!
+
+Provides most of the standard Geany C API for Python.
+
+**Please note:** GeanyPy here in geany-plugins is based on the upstream 
+at https://github.com/codebrainz/geanypy which is still under 
+development, however it is useful as is.  Parts of the existing API 
+which mirror the Geany C API will probably not change unless the Geany API 
+changes, however new API may be added.  Also documentation is needed, 
+contributions are welcome.
+
+How it works
+------------
+
+In the ``src/`` directory is a normal Geany plugin (``plugin.c``) which loads the
+Python interpreter.  It then loads some C Python modules (``dialogs.c``,
+``documents.c``, etc.) that interface with Geany's C API.  After that, it loads
+the ``geany`` Python module which is just glue/sugar-coating to make the C
+module more "Pythonic".
+
+To write a plugin, inherit from the ``geany.Plugin`` class and implmenent the
+required members (see ``geany/plugin.py`` documentation comments).  Then put the
+plugin in a searched plugin path.  Currently two locations are search for
+plugins.  The first is ``PREFIX/share/geany/geanypy/plugins`` and the recommended
+location is under your personal Geany directory (usually
+``~/.config/geany/plugins/geanypy/plugins``).  To load or unload plugins, click
+the Python Plugin Manager item under the Tools menu which will appear when you
+activate GeanyPy through Geany's regular plugin manager.
+
+When the GeanyPy plugin is loaded, it has an option to add a new tab to 
+the notebook in the message window area that contains an interactive 
+Python shell with the ``geany`` module pre-imported.  You can tinker 
+around with API with this console, for example::
+
+
+    import geany
+    
+    doc = geany.document.open_file("/some/file/here")
+    
+    print(doc.file_name)
+    print(doc.file_type.display_name)
+    
+    geany.dialogs.show_msgbox("Hello World")
+    
+    geany.main_widgets.window.set_title("Hello Window")
+    
+    def on_document_open(doc):
+        print("Document '%s' was opened" % doc.file_name)
+    
+    geany.signals.connect('document-open', on_document_open)
+    
+
+Dependencies
+------------
+
+To build GeanyPy you need the following dependencies:
+
+* Python 2.6 or 2.7 and development files.
+* Geany 1.24+ and development files
+* PyGTK 2.0 and development files
+
+Running on Windows
+------------------
+
+Not currently supported for the geany-plugins version, see upstream 
+https://github.com/codebrainz/geanypy.
+
+Upstream developed by Matthew Brush.
+
+Geany-plugins version maintained by elextr at lists.geany.org


Modified: geanypy/doc/make.bat
170 files changed, 170 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,170 @@
+ at ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+	:help
+	echo.Please use `make ^<target^>` where ^<target^> is one of
+	echo.  html       to make standalone HTML files
+	echo.  dirhtml    to make HTML files named index.html in directories
+	echo.  singlehtml to make a single large HTML file
+	echo.  pickle     to make pickle files
+	echo.  json       to make JSON files
+	echo.  htmlhelp   to make HTML files and a HTML help project
+	echo.  qthelp     to make HTML files and a qthelp project
+	echo.  devhelp    to make HTML files and a Devhelp project
+	echo.  epub       to make an epub
+	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+	echo.  text       to make text files
+	echo.  man        to make manual pages
+	echo.  changes    to make an overview over all changed/added/deprecated items
+	echo.  linkcheck  to check all external links for integrity
+	echo.  doctest    to run all doctests embedded in the documentation if enabled
+	goto end
+)
+
+if "%1" == "clean" (
+	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+	del /q /s %BUILDDIR%\*
+	goto end
+)
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "singlehtml" (
+	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "htmlhelp" (
+	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+	goto end
+)
+
+if "%1" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\GeanyPy.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\GeanyPy.ghc
+	goto end
+)
+
+if "%1" == "devhelp" (
+	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished.
+	goto end
+)
+
+if "%1" == "epub" (
+	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The epub file is in %BUILDDIR%/epub.
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "text" (
+	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The text files are in %BUILDDIR%/text.
+	goto end
+)
+
+if "%1" == "man" (
+	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The manual pages are in %BUILDDIR%/man.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "linkcheck" (
+	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+:end


Modified: geanypy/doc/source/api.rst
67 files changed, 67 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,67 @@
+API Documentation
+*****************
+
+GeanyPy's API mimics quite closely Geany's C plugin API.  The following
+documentation is broken down by file/module:
+
+The :mod:`geany` modules:
+
+.. toctree::
+    :maxdepth: 2
+
+    app.rst
+    dialogs.rst
+    document.rst
+
+The :mod:`geany` package and module
+===================================
+
+.. module:: geany
+
+All of GeanyPy's bindings are inside the :mod:`geany` package which also
+contains some stuff in it's :mod:`__init__` file, acting like a module itself.
+
+
+.. data:: app
+
+    An instance of :class:`app.App` to store application information.
+
+.. data:: main_widgets
+
+    An instance of :class:`mainwidgets.MainWidgets` to provide access to
+    Geany's important GTK+ widgets.
+
+.. data:: signals
+
+    An instance of :class:`signalmanager.SignalManager` which manages the
+    connection, disconnection, and emission of Geany's signals.  You can
+    use this as follows::
+
+        geany.signals.connect('document-open', some_callback_function)
+
+.. function:: is_realized()
+
+    This function, which is actually in the :mod:`geany.main` module will tell
+    you if Geany's main window is realized (shown).
+
+.. function:: locale_init()
+
+    Again, from the :mod:`geany.main` module, this will initialize the `gettext`
+    translation system.
+
+.. function:: reload_configuration()
+
+    Also from the :mod:`geany.main` module, this function will cause Geany to
+    reload most if it's configuration files without restarting.
+
+    Currently the following files are reloaded:
+
+        * all template files
+        * new file templates
+        * the New (with template) menus will be updated
+        * Snippets (snippets.conf)
+        * filetype extensions (filetype_extensions.conf)
+        * `settings` and `build_settings` sections of the filetype definition files.
+
+    Plugins may call this function if they changed any of these files (e.g. a
+    configuration file editor plugin).


Modified: geanypy/doc/source/app.rst
40 files changed, 40 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,40 @@
+The :mod:`app` module
+*********************
+
+.. module:: app
+    :synopsis: Application settings
+
+This modules contains a class to access application settings.
+
+:class:`App` Objects
+====================
+
+.. class:: App
+
+This class is initialized automatically and by the :mod:`geany` module and
+shouldn't be initalized by users.  An instance of it is available through
+the :data:`geany.app` attribute of the :mod:`geany` module.
+
+All members of the :class:`App` are read-only properties.
+
+    .. attribute:: App.configdir
+
+        User configuration directory, usually ~/.config/geany.  To store configuration
+        files for your plugin, it's a good idea to use something like this::
+
+            conf_path = os.path.join(geany.app.configdir, "plugins", "yourplugin",
+                            "yourconfigfile.conf")
+
+    .. attribute:: App.debug_mode
+
+        If True, debug messages should be printed.  For example, if you want to make
+        a :py:func:`print` function that only prints when :attr:`App.debug_mode`
+        is active, you could do something like this::
+
+            def debug_print(message):
+                if geany.app.debug_mode:
+                    print(message)
+
+    .. attribute:: App.project
+
+        If not :py:obj:`None`, the a :class:`project.Project` for currently active project.


Modified: geanypy/doc/source/conf.py
216 files changed, 216 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+#
+# GeanyPy documentation build configuration file, created by
+# sphinx-quickstart on Sun Aug  7 12:42:52 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'GeanyPy'
+copyright = u'2011, Matthew Brush (mbrush [at] codebrainz [dot] ca)'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'GeanyPydoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'GeanyPy.tex', u'GeanyPy Documentation',
+   u'Matthew Brush \\textless{}mbrush at codebrainz.ca\\textgreater{}', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'geanypy', u'GeanyPy Documentation',
+     [u'Matthew Brush <mbrush at codebrainz.ca>'], 1)
+]


Modified: geanypy/doc/source/dialogs.rst
55 files changed, 55 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,55 @@
+The :mod:`dialogs` module
+*************************
+
+.. module:: dialogs
+    :synopsis: Showing dialogs to the user
+
+This module contains some help functions to show file-related dialogs,
+miscellaneous dialogs, etc.  You can of course just use the :mod:`gtk` module
+to create your own dialogs as well.
+
+.. function:: show_input([title=None[, parent=None[, label_text=None[, default_text=None]]]])
+
+    Shows a :class:`gtk.Dialog` to ask the user for text input.
+
+    :param title: The window title for the dialog.
+    :param parent: The parent :class:`gtk.Window` for the dialog, for example :data:`geany.main_widgets.window`.
+    :param label_text: Text to put in the label just about the input entry box.
+    :param default_text: Default text to put in the input entry box.
+
+    :return: A string containing the text the user entered.
+
+.. function:: show_input_numeric([title=None[, label_text=None[, value=0.0[, minimum=0.0[, maximum=100.0[, step=1.0]]]]]])
+
+    Shows a :class:`gtk.Dialog` to ask the user to enter a numeric value
+    using a :class:`gtk.SpinButton`.
+
+    :param title: The window title for the dialog.
+    :param label_text: Text to put in the label just about the numeric entry box.
+    :param value: The initial value in the numeric entry box.
+    :param minimum: The minimum allowed value that can be entered.
+    :param maximum: The maximum allowed value that can be entered.
+    :param step: Amount to increment the numeric entry when it's value is moved up or down (ex, using arrows).
+
+    :return: The value entered if the dialog was closed with ok, or :data:`None` if it was cancelled.
+
+.. function:: show_msgbox(text[, msgtype=gtk.MESSAGE_INFO])
+
+    Shows a :class:`gtk.Dialog` to show the user a message.
+
+    :param text: The text to show in the message box.
+    :param msgtype: The message type which is one of the `Gtk Message Type Constants <http://www.pygtk.org/docs/pygtk/gtk-constants.html#gtk-message-type-constants>`_.
+
+.. function:: show_question(text)
+
+    Shows a :class:`gtk.Dialog` to ask the user a Yes/No question.
+
+    :param text: The text to show in the question dialog.
+
+    :return: :data:`True` if the Yes button was pressed, :data:`False` if the No button was pressed.
+
+.. function:: show_save_as()
+
+    Shows Geany's `Save As` dialog.
+
+    :return: :data:`True` if the file was saved, :data:`False` otherwise.


Modified: geanypy/doc/source/document.rst
199 files changed, 199 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,199 @@
+The :mod:`document` module
+**************************
+
+.. module:: document
+    :synopsis: Document-related functions and classes
+
+This module provides functions for working with documents.  Most of the module-level
+functions are used for creating instances of the :class:`Document` object.
+
+
+.. function:: find_by_filename(filename)
+
+    Finds a document with the given filename from the open documents.
+
+    :param filename: Filename of :class:`Document` to find.
+
+    :return: A :class:`Document` instance for the found document or :data:`None`.
+
+.. function:: get_current()
+
+    Gets the currently active document.
+
+    :return: A :class:`Document` instance for the currently active document or :data:`None` if no documents are open.
+
+.. function:: get_from_page(page_num)
+
+    Gets a document based on it's :class:`gtk.Notebook` page number.
+
+    :param page_num: The tab number of the document in the documents notebook.
+
+    :return: A :class:`Document` instance for the corresponding document or :data:`None` if no document matched.
+
+.. function:: get_from_index(index)
+
+    Gets a document based on its index in Geany's documents array.
+
+    :param index: The index of the document in Geany's documents array.
+
+    :return: A :class:`Document` instance for the corresponding document or :data:`None` if not document matched, or the document that matched isn't valid.
+
+.. function:: new([filename=None[, filetype=None[, text=None]]])
+
+    Creates a document file.
+
+    :param filename: The documents filename, or :data:`None` for `untitled`.
+    :param filetype: The documents filetype or :data:`None` to auto-detect it from `filename` (if it's not :data:`None`)
+    :param text: Initial text to put in the new document or :data:`None` to leave it blank
+
+    :return: A :class:`Document` instance for the new document.
+
+.. function:: open(filename[, read_only=False[, filetype=None[, forced_enc=None]]])
+
+    Open an existing document file.
+
+    :param filename: Filename of the document to open.
+    :param read_only: Whether to open the document in read-only mode.
+    :param filetype: Filetype to open the document as or :data:`None` to detect it automatically.
+    :param forced_enc: The file encoding to use or :data:`None` to auto-detect it.
+
+    :return: A :class:`Document` instance for the opened document or :data:`None` if it couldn't be opened.
+
+.. function:: open_files(filenames, read_only=False, filetype="", forced_enc="")
+
+    Open multiple files.  This actually calls :func:`open` once for each filename in `filenames`.
+
+    :param filenames: List of filenames to open.
+    :param read_only: Whether to open the document in read-only mode.
+    :param filetype: Filetype to open the document as or :data:`None` to detect it automatically.
+    :param forced_enc: The file encoding to use or :data:`None` to auto-detect it.
+
+.. function:: remove_page(page_num)
+
+    Remove a document from the documents array based on it's page number in the documents notebook.
+
+    :param page_num: The tab number of the document in the documents notebook.
+
+    :return: :data:`True` if the document was actually removed or :data:`False` otherwise.
+
+.. function:: get_documents_list()
+
+    Get a list of open documents.
+
+    :return: A list of :class:`Document` instances, one for each open document.
+
+
+:class:`Document` Objects
+=========================
+
+.. class:: Document
+
+    The main class holding information about a specific document.  Unless
+    otherwise noted, the attributes are read-only properties.
+
+    .. attribute:: Document.basename_for_display
+
+        The last part of the filename for this document, possibly truncated to a maximum length in case the filename is very long.
+
+    .. attribute:: Document.notebook_page
+
+        The page number in the :class:`gtk.Notebook` containing documents.
+
+    .. attribute:: Document.status_color
+
+        Gets the status color of the document, or :data:`None` if the default widget coloring should be used.  The color is red if the document has changes, green if it's read-only or :data:`None` if the document is unmodified but writable.  The value is a tuple of the RGB values for red, green, and blue respectively.
+
+    .. attribute:: Document.encoding
+
+        The encoding of this document.  Must be a valid string representation of an encoding.  This property is read-write.
+
+    .. attribute:: Document.file_type
+
+        The file type of this document as a :class:`Filetype` instance.  This property is read-write.
+
+    .. attribute:: Document.text_changed
+
+        Whether this document's text has been changed since it was last saved.
+
+    .. attribute:: Document.file_name
+
+        The file name of this document.
+
+    .. attribute:: Document.has_bom
+
+        Indicates whether the document's file has a byte-order-mark.
+
+    .. attribute:: Document.has_tags
+
+        Indicates whether this document supports source code symbols (tags) to show in the sidebar.
+
+    .. attribute:: Document.index
+
+        Index of the document in Geany's documents array.
+
+    .. attribute:: Document.is_valid
+
+        Indicates whether this document is active and all properties are set correctly.
+
+    .. attribute:: Document.read_only
+
+        Whether the document is in read-only mode.
+
+    .. attribute:: Document.real_path
+
+        The link-dereferenced, locale-encoded file name for this document.
+
+    .. attribute:: Document.editor
+
+        The :class:`Editor` instance associated with this document.
+
+    .. method:: Document.close()
+
+        Close this document.
+
+        :return: :data:`True` if the document was closed, :data:`False` otherwise.
+
+    .. method:: Document.reload([forced_enc=None])
+
+        Reloads this document.
+
+        :param forced_enc: The encoding to use when reloading this document or :data:`None` to auto-detect it.
+
+        :return: :data:`True` if the document was actually reloaded or :data:`False` otherwise.
+
+    .. method:: Document.rename(new_filename)
+
+        Rename this document to a new file name.  Only the file on disk is actually
+        renamed, you still have to call :meth:`save_as` to change the document object.
+        It also stops monitoring for file changes to prevent receiving too many file
+        change events while renaming.  File monitoring is setup again in :meth:`save_as`.
+
+        :param new_filename: The new filename to rename to.
+
+    .. method:: Document.save([force=False])
+
+        Saves this documents file on disk.
+
+        Saving may include replacing tabs by spaces, stripping trailing spaces and adding
+        a final new line at the end of the file, depending on user preferences.  Then,
+        the `document-before-save` signal is emitted, allowing plugins to modify the
+        document before it's saved, and the data is actually written to disk.  The
+        file type is set again or auto-detected if it wasn't set yet.  Afterwards,
+        the `document-save` signal is emitted for plugins.  If the file is not modified,
+        this method does nothing unless `force` is set to :data:`True`.
+
+        **Note:** You should ensure that :attr:`file_name` is not :data:`None` before
+        calling this; otherwise call :func:`dialogs.show_save_as`.
+
+        :param force: Whether to save the document even if it's not modified.
+
+        :return: :data:`True` if the file was saved or :data:`False` if the file could not or should not be saved.
+
+    .. method:: Document.save_as(new_filename)
+
+        Saves the document with a new filename, detecting the filetype.
+
+        :param new_filename: The new filename.
+
+        :return: :data:`True` if the file was saved or :data:`False` if it could not be saved.
+


Modified: geanypy/doc/source/geany.rst
0 files changed, 0 insertions(+), 0 deletions(-)
===================================================================
No diff available, check online


Modified: geanypy/doc/source/index.rst
26 files changed, 26 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,26 @@
+.. GeanyPy documentation master file, created by
+   sphinx-quickstart on Sun Aug  7 12:42:52 2011.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to GeanyPy's documentation!
+===================================
+
+Contents:
+
+.. toctree::
+    :maxdepth: 2
+
+    intro.rst
+    install.rst
+    starting.rst
+    quickstart.rst
+    api.rst
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+


Modified: geanypy/doc/source/install.rst
146 files changed, 146 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,146 @@
+Installation
+************
+
+Currently there are no binary packages available for installing GeanyPy so it
+must be installed from source.  The following instructions will describe how
+to do this.
+
+Getting the Source
+==================
+
+The best way currently to get GeanyPy is to check it out from `it's repository
+on GitHub.com <https://github.com/codebrainz/geanypy>`_.  You can clone
+GeanyPy's `master` branch by issuing the following command in a directory
+where you want to store its source code::
+
+    $ git clone git://github.com/codebrainz/geanypy.git
+    $ cd geanypy
+
+Alternatively, you can download the `master` branch
+`compressed into a tarball
+<https://github.com/codebrainz/geanypy/tarball/master>`_
+or `zip file <https://github.com/codebrainz/geanypy/zipball/master>`_.  Then
+extract it where you want to store GeanyPy's source, for example::
+
+    $ cd ~/src
+    $ wget -O geanypy.tar.gz https://github.com/codebrainz/geanypy/tarball/master
+    $ tar xf geanypy.tar.gz
+    $ cd codebrainz-geanypy-*
+
+The first method using `Git <http://git-scm.com/>`_ is the best, since it
+allows you to update your copy of GeanyPy quite easily and also makes it
+easier to contribute back to the GeanyPy project if you want to.
+
+Dependencies and where to get them
+==================================
+
+Of course depending on what operating system and distribution you're using,
+getting setup for this process may vary wildly.  At present, the following
+dependencies are required to compile GeanyPy:
+
+GCC, Autotools, and all the usual build tools
+---------------------------------------------
+
+For example on Debian (Ubuntu, Mint, etc.) do::
+
+    $ apt-get install build-essential
+
+Or on Fedora, something like this should do::
+
+    $ yum groupinstall "Development Tools" "Legacy Software Development"
+
+The latest development version of Geany (0.21+)
+-----------------------------------------------
+
+Since GeanyPy is wrapping the current development version of Geany, to use it
+you are required to use that version of Geany.  Until the next Geany release,
+you must either checkout the source code from Geany's
+`Subversion repository <http://www.geany.org/Download/SVN>`_ or
+`Git mirror <http://git.geany.org>`_ or you can get one of the
+`Nightly builds <http://nightly.geany.org>`_ if you'd rather not compile
+it yourself.
+
+For more information on installing Geany, please refer to
+`Geany's excellent manual
+<http://www.geany.org/manual/current/index.html#installation>`_
+
+Grabbing the dependencies for Geany on a Debian-based disto could be similar to
+this::
+
+    $ apt-get install libgtk2.0-0 libgtk2.0-0-dev
+
+Or you might even have better luck with::
+
+    $ apt-get build-dep geany
+
+A quick session for installing Geany on a Debian-based distro might look
+something like this::
+
+    $ cd ~/src
+    $ git clone http://git.geany.org/git/geany
+    $ cd geany
+    $ ./autogen.sh
+    $ ./configure
+    $ make
+    $ make install # possibly as root
+
+By default, Geany will install into `/usr/local` so if you want to install it
+somewhere else, for example `/opt/geany`, then you would run the `configure`
+command above with the `prefix` argument, like::
+
+    $ ./configure --prefix=/opt/geany
+
+It's important when installing GeanyPy later that you configure it with the
+same prefix where Geany is installed, otherwise Geany won't find the GeanyPy
+plugin.
+
+Python 2.X and development files
+--------------------------------
+
+As GeanPy makes use of Python's C API to gain access to Geany's C plugin API,
+both Python and the development files are required to compile GeanyPy.  In
+theory, any Python version >= 2.6 and < 3.0 should be compatible with GeanyPy.
+You can download Python `from its website <http://www.python.org/download>`_ or
+you can install the required packages using your distro's package manager, for
+example with Debian-based distros, run this::
+
+    $ apt-get install python python-dev
+
+**Note:** Python 3.0+ is not supported yet, although at some point in the
+future, there are plans support it.
+
+PyGTK and development files
+---------------------------
+
+Since Geany uses GTK+ as it's UI toolkit, GeanyPy uses PyGTK to interact with
+Geany's UI.  You can either `download PyGTK from it's website
+<http://www.pygtk.org/downloads.html>`_ or you can install it with your
+system's pacakge manager, for example in Debian distros::
+
+    $ apt-get install python-gtk2 python-gtk2-dev
+
+**Note:** Although PyGTK is all but deprecated (or is completely deprecated?)
+in favour of the newer and shinier PyGobject/GObject-introspection, it is
+still used in new code in GeanyPy due to lack of documentation and pacakge
+support for the newer stuff.
+
+One fell swoop
+--------------
+
+If you're running a Debian-based distro, you should be able to install all
+the required dependencies, not including Geany itself, with the following
+command (as root)::
+
+    $ apt-get install build-essential libgtk2.0-0 libgtk2.0-dev \
+        python python-dev python-gtk2 python-gtk2-dev
+
+And finally ... installing GeanyPy
+==================================
+
+Once all the dependencies are satisfied, installing GeanyPy should be pretty
+straight-forward, continuing on from `Getting the Source`_ above::
+
+    $ ./autogen.sh
+    $ ./configure --prefix=/the/same/prefix/used/for/geany
+    $ make
+    $ make install # possibly as root


Modified: geanypy/doc/source/intro.rst
8 files changed, 8 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,8 @@
+Introduction
+************
+
+GeanyPy allows people to write their Geany plugins in Python making
+authoring a plugin much more accessible to non C programmers.  What follows
+is a description of installing and using the GeanyPy plugin, paving the way
+for the rest of the documentation to covert the details of programming with
+the GeanyPy bindings of the Geany  API.


Modified: geanypy/doc/source/quickstart.rst
121 files changed, 121 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,121 @@
+Writing a Plugin - Quick Start Guide
+************************************
+
+This is just a quick tutorial to describe writing a GeanyPy compatible plugin
+in Python.  Writing a plugin should be pretty straightforward for any Python
+programmers, especially those familiar with writing `regular C plugins for
+Geany <http://www.geany.org/manual/reference/howto.html>`_.
+
+To illustrate the similarities to the C API, the example at the end of this
+section will create the same plugin as in Geany's Plugin Howto, except
+obviously written in Python.
+
+The Plugin Interface
+====================
+
+The first thing any plugin will want to do is to import the `geany` module::
+
+    import geany
+
+**Note:** Due to the way the Geany plugin framework works, importing the
+`geany` module will certain fail if you just try running it standalone, outside
+of Geany/GeanyPy.
+
+After that, you create a regular Python class which inherits from the
+`geany.Plugin` class::
+
+    import geany
+
+    class HelloWorld(geany.Plugin):
+        pass
+
+This will allow GeanyPy's Python Plugin Manager to locate the plugin as a
+GeanyPy plugin.  If it doesn't inherit from `geany.Plugin` it will not be
+detected.
+
+There are a few more parts of the interface that must be implemented in order
+for the plugin to be detected by GeanyPy::
+
+    import geany
+
+    class HelloWorld(geany.Plugin):
+
+        __plugin_name__ = "HelloWorld" # required
+        __plugin_version__ = "version of your plugin"
+        __plugin_description__ = "description of your plugin"
+        __plugin_author__ = "Your Name <your email address>"
+
+These allow the Python Plugin Manager to glean information about your plugin
+which will be shown in the managers plugin list.  All but the `__plugin_name__`
+attributes are optional, though recommended.
+
+The next thing that's needed is an entry-point to the plugin.  Since Python
+classes have a initialization method already, this seems like a logical
+entry-point::
+
+    import geany
+
+    class HelloWorld(geany.Plugin):
+
+        __plugin_name__ = "HelloWorld" # required
+        __plugin_version__ = "version of your plugin"
+        __plugin_description__ = "description of your plugin"
+        __plugin_author__ = "Your Name <your email address>"
+
+        def __init__(self):
+            do_stuff_when_loaded()
+
+If you have some de-initialization code that needs to be run, you can add
+a `cleanup` method to the class that is guaranteed to be called when your
+plugin is unloaded, however it's optional::
+
+    import geany
+
+    class HelloWorld(geany.Plugin):
+
+        __plugin_name__ = "HelloWorld" # required
+        __plugin_version__ = "version of your plugin"
+        __plugin_description__ = "description of your plugin"
+        __plugin_author__ = "Your Name <your email address>"
+
+        def __init__(self):
+            do_stuff_when_loaded()
+
+        def cleanup(self):
+            do_stuff_when_unloaded()
+
+And there you have it!  That's the minimum requirements for writing a plugin
+that will be detected by GeanyPy.  Ok, it doesn't do anything yet, but it
+will be shown in the Python Plugin Manager and can be loaded and unloaded.
+
+Real-world Example
+==================
+
+To put it into context, here's a plugin that mimics the plugin in
+`Geany's Plugin Howto <http://www.geany.org/manual/reference/howto.html>`_::
+
+    import gtk
+    import geany
+
+    class HelloWorld(geany.Plugin):
+
+        __plugin_name__ = "HelloWorld"
+        __plugin_version__ = "1.0"
+        __plugin_description__ = "Just another tool to say hello world"
+        __plugin_author__ = "John Doe <john.doe at example.org>"
+
+        def __init__(self):
+            self.menu_item = gtk.MenuItem("Hello World")
+            self.menu_item.show()
+            geany.main_widgets.tools_menu.append(self.menu_item)
+            self.menu_item.connect("activate", self.on_hello_item_clicked)
+
+        def cleanup(self):
+            self.menu_item.destroy()
+
+        def on_hello_item_clicked(widget, data):
+            geany.dialogs.show_msgbox("Hello World")
+
+Hopefully this makes it clear how to write a Python plugin using GeanyPy.  This
+sample plugin is provided with GeanyPy if you want to try it out.
+


Modified: geanypy/doc/source/starting.rst
64 files changed, 64 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,64 @@
+Getting Started
+***************
+
+Before diving into the details and API docs for programming plugins with
+GeanyPy, it's important to note how it works and some features it provides.
+
+What the heck is GeanyPy, really?
+=================================
+
+GeanyPy is "just another Geany plugin", really.  Geany sees GeanyPy as any
+other `plugin <http://www.geany.org/manual/current/index.html#plugins>`_, so
+to activate GeanyPy, use Geany's
+`Plugin Manager <http://www.geany.org/manual/current/index.html#plugin-manager>`_
+under the Tools menu as you would for any other plugin.
+
+Once the GeanyPy plugin has been activated, a few elements are added to Geany's
+user interface as described below.
+
+Python Plugin Manager
+=====================
+
+Under the Tools menu, you will find the Python Plugin Manager, which is meant
+to be similar to Geany's own Plugin Manager.  This is where you will activate
+any plugins written in Python.
+
+The Python Plugin Manager looks in exactly two places for plugins:
+
+1. For system-wide plugins, it will search in PREFIX/share/geany/geanypy/plugins.
+2. In Geany's config directory under your home directory, typically ~/.config/geany/plugins/geanypy/plugins.
+
+Where `PREFIX` is the prefix used at configure time with Geany/GeanyPy (see
+the previous section, Installation).  Both of these paths may vary depending on
+your platform, but for most \*nix systems, the above paths should hold true.
+
+Any plugins which follow the proper interface found in either of those two
+directories will be listed in the Python Plugin Manager and you will be able
+to activate and deactivate them there.
+
+Python Console
+==============
+
+Another pretty cool feature of GeanyPy is the Python Console, which similar
+to the regular Python interactive interpreter console, but it's found in the
+Message Window area (bottom) in Geany.  The `geany` Python module used to
+interact with Geany will be pre-imported for you, so you can mess around with
+Geany using the console, without ever having to even write a plugin.
+
+**Credits:** The Python Console was taken, almost in its entirety, from the
+`medit text editor <http://mooedit.sourceforge.net>`_.  Props to the
+author(s) for such a nice `piece of source code
+<https://bitbucket.org/medit/medit/src/83c24f751493/moo/moopython/plugins/lib/pyconsole.py>`_
+
+Future Plans
+============
+
+Some time in the near future, there should be support for sending text from
+the active document into the Python Console.  It will also be possible to
+have the Python Console either in a separate window, in the sidebar notebook
+or in the message window notebook.
+
+Also, either integration with Geany's keybindings UI under the preferences
+dialog or a separate but similar UI just for Python plugins will be added.
+Currently using keybindings requires a certain amount of hackery, due to
+Geany expecting all plugins to be shared libraries written in C.


Modified: geanypy/geany/Makefile.am
18 files changed, 18 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,18 @@
+geanypy_sources				=	__init__.py \
+								console.py \
+								manager.py \
+								loader.py \
+								plugin.py \
+								signalmanager.py
+geanypy_objects				=	$(geanypy_sources:.py=.pyc)
+geanypydir					=	$(libdir)/geany/geanypy/geany
+geanypy_DATA				=	$(geanypy_sources)  $(geanypy_objects)
+
+EXTRA_DIST					=	$(geanypy_sources)
+CLEANFILES					=	$(geanypy_objects)
+
+.SUFFIXES = .pyc .py
+
+.py.pyc:
+	$(AM_V_GEN) \
+	$(PYTHON) -c "from py_compile import compile; compile('$<', '$@')"


Modified: geanypy/geany/__init__.py
69 files changed, 69 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,69 @@
+"""
+Package file that exposes some of Geany's guts as its own attibutes.  Any
+objects where it only makes sense to have one instance of are initialed here
+and set as attributes.
+
+You can sort of think of this file as the GeanyData struct from the C API,
+though no real attempt is made to mimic that structure here.
+"""
+
+import app
+import console
+import dialogs
+import document
+import editor
+import encoding
+import filetypes
+import highlighting
+import loader
+import main
+import manager
+import msgwindow
+import navqueue
+import prefs
+import project
+import scintilla
+import search
+import templates
+import ui_utils
+
+from app import App
+from prefs import Prefs, ToolPrefs
+from main import is_realized, locale_init, reload_configuration
+from signalmanager import SignalManager
+from ui_utils import MainWidgets, InterfacePrefs
+from search import SearchPrefs
+from templates import TemplatePrefs
+
+
+__all__ = [ "Plugin",
+            "is_realized",
+            "locale_init",
+            "reload_configuration",
+            "main_widgets",
+            "interface_prefs",
+            "app",
+            "general_prefs",
+            "search_prefs",
+            "template_prefs",
+            "tool_prefs",
+            "signals" ]
+
+# Geany's application data fields
+app = App()
+
+# Import GTK+ widgets that are part of Geany's UI
+main_widgets = MainWidgets()
+
+# Preferences
+general_prefs = Prefs() # GeanyData->prefs but name clashes with module
+interface_prefs = InterfacePrefs()
+search_prefs = SearchPrefs()
+template_prefs = TemplatePrefs()
+tool_prefs = ToolPrefs()
+
+# GObject to connect signal handlers on and which emits signals.
+signals = SignalManager()
+
+import plugin
+from plugin import Plugin


Modified: geanypy/geany/console.py
665 files changed, 665 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,665 @@
+#!/usr/bin/env python
+#
+#  pyconsole.py
+#
+#  Copyright (C) 2004-2010 by Yevgen Muntyan <emuntyan at users.sourceforge.net>
+#  Thanks to Geoffrey French for ideas.
+#
+#  This file is part of medit.  medit is free software; you can
+#  redistribute it and/or modify it under the terms of the
+#  GNU Lesser General Public License as published by the
+#  Free Software Foundation; either version 2.1 of the License,
+#  or (at your option) any later version.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with medit.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# This module 'runs' python interpreter in a TextView widget.
+# The main class is Console, usage is:
+# Console(locals=None, banner=None, completer=None, use_rlcompleter=True, start_script='') -
+# it creates the widget and 'starts' interactive session; see the end of
+# this file. If start_script is not empty, it pastes it as it was entered from keyboard.
+#
+# Console has "command" signal which is emitted when code is about to
+# be executed. You may connect to it using console.connect or console.connect_after
+# to get your callback ran before or after the code is executed.
+#
+# To modify output appearance, set attributes of console.stdout_tag and
+# console.stderr_tag.
+#
+# Console may subclass a type other than gtk.TextView, to allow syntax highlighting and stuff,
+# e.g.:
+#   console_type = pyconsole.ConsoleType(moo.TextView)
+#   console = console_type(use_rlcompleter=False, start_script="import moo\nimport gtk\n")
+#
+# This widget is not a replacement for real terminal with python running
+# inside: GtkTextView is not a terminal.
+# The use case is: you have a python program, you create this widget,
+# and inspect your program interiors.
+
+import gtk
+import gtk.gdk as gdk
+import gobject
+import pango
+import gtk.keysyms as _keys
+import code
+import sys
+import keyword
+import re
+
+# commonprefix() from posixpath
+def _commonprefix(m):
+    "Given a list of pathnames, returns the longest common leading component"
+    if not m: return ''
+    prefix = m[0]
+    for item in m:
+        for i in range(len(prefix)):
+            if prefix[:i+1] != item[:i+1]:
+                prefix = prefix[:i]
+                if i == 0:
+                    return ''
+                break
+    return prefix
+
+class _ReadLine(object):
+
+    class Output(object):
+        def __init__(self, console, tag_name):
+            object.__init__(self)
+            self.buffer = console.get_buffer()
+            self.tag_name = tag_name
+        def write(self, text):
+            pos = self.buffer.get_iter_at_mark(self.buffer.get_insert())
+            self.buffer.insert_with_tags_by_name(pos, text, self.tag_name)
+
+    class History(object):
+        def __init__(self):
+            object.__init__(self)
+            self.items = ['']
+            self.ptr = 0
+            self.edited = {}
+
+        def commit(self, text):
+            if text and self.items[-1] != text:
+                self.items.append(text)
+            self.ptr = 0
+            self.edited = {}
+
+        def get(self, dir, text):
+            if len(self.items) == 1:
+                return None
+
+            if text != self.items[self.ptr]:
+                self.edited[self.ptr] = text
+            elif self.edited.has_key(self.ptr):
+                del self.edited[self.ptr]
+
+            self.ptr = self.ptr + dir
+            if self.ptr >= len(self.items):
+                self.ptr = 0
+            elif self.ptr < 0:
+                self.ptr = len(self.items) - 1
+
+            try:
+                return self.edited[self.ptr]
+            except KeyError:
+                return self.items[self.ptr]
+
+    def __init__(self):
+        object.__init__(self)
+
+        self.set_wrap_mode(gtk.WRAP_CHAR)
+        self.modify_font(pango.FontDescription("Monospace"))
+
+        self.buffer = self.get_buffer()
+        self.buffer.connect("insert-text", self.on_buf_insert)
+        self.buffer.connect("delete-range", self.on_buf_delete)
+        self.buffer.connect("mark-set", self.on_buf_mark_set)
+        self.do_insert = False
+        self.do_delete = False
+
+        self.stdout_tag = self.buffer.create_tag("stdout", foreground="#006000")
+        self.stderr_tag = self.buffer.create_tag("stderr", foreground="#B00000")
+        self._stdout = _ReadLine.Output(self, "stdout")
+        self._stderr = _ReadLine.Output(self, "stderr")
+
+        self.cursor = self.buffer.create_mark("cursor",
+                                              self.buffer.get_start_iter(),
+                                              False)
+        insert = self.buffer.get_insert()
+        self.cursor.set_visible(True)
+        insert.set_visible(False)
+
+        self.ps = ''
+        self.in_raw_input = False
+        self.run_on_raw_input = None
+        self.tab_pressed = 0
+        self.history = _ReadLine.History()
+        self.nonword_re = re.compile("[^\w\._]")
+
+    def freeze_undo(self):
+        try: self.begin_not_undoable_action()
+        except: pass
+
+    def thaw_undo(self):
+        try: self.end_not_undoable_action()
+        except: pass
+
+    def raw_input(self, ps=None):
+        if ps:
+            self.ps = ps
+        else:
+            self.ps = ''
+
+        iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
+
+        if ps:
+            self.freeze_undo()
+            self.buffer.insert(iter, self.ps)
+            self.thaw_undo()
+
+        self.__move_cursor_to(iter)
+        self.scroll_to_mark(self.cursor, 0.2)
+
+        self.in_raw_input = True
+
+        if self.run_on_raw_input:
+            run_now = self.run_on_raw_input
+            self.run_on_raw_input = None
+            self.buffer.insert_at_cursor(run_now + '\n')
+
+    def on_buf_mark_set(self, buffer, iter, mark):
+        if mark is not buffer.get_insert():
+            return
+        start = self.__get_start()
+        end = self.__get_end()
+        if iter.compare(self.__get_start()) >= 0 and \
+           iter.compare(self.__get_end()) <= 0:
+                buffer.move_mark_by_name("cursor", iter)
+                self.scroll_to_mark(self.cursor, 0.2)
+
+    def __insert(self, iter, text):
+        self.do_insert = True
+        self.buffer.insert(iter, text)
+        self.do_insert = False
+
+    def on_buf_insert(self, buf, iter, text, len):
+        if not self.in_raw_input or self.do_insert or not len:
+            return
+        buf.stop_emission("insert-text")
+        lines = text.splitlines()
+        need_eol = False
+        for l in lines:
+            if need_eol:
+                self._commit()
+                iter = self.__get_cursor()
+            else:
+                cursor = self.__get_cursor()
+                if iter.compare(self.__get_start()) < 0:
+                    iter = cursor
+                elif iter.compare(self.__get_end()) > 0:
+                    iter = cursor
+                else:
+                    self.__move_cursor_to(iter)
+            need_eol = True
+            self.__insert(iter, l)
+        self.__move_cursor(0)
+
+    def __delete(self, start, end):
+        self.do_delete = True
+        self.buffer.delete(start, end)
+        self.do_delete = False
+
+    def on_buf_delete(self, buf, start, end):
+        if not self.in_raw_input or self.do_delete:
+            return
+
+        buf.stop_emission("delete-range")
+
+        start.order(end)
+        line_start = self.__get_start()
+        line_end = self.__get_end()
+
+        if start.compare(line_end) > 0:
+            return
+        if end.compare(line_start) < 0:
+            return
+
+        self.__move_cursor(0)
+
+        if start.compare(line_start) < 0:
+            start = line_start
+        if end.compare(line_end) > 0:
+            end = line_end
+        self.__delete(start, end)
+
+    def do_key_press_event(self, event, parent_type):
+        if not self.in_raw_input:
+            return parent_type.do_key_press_event(self, event)
+
+        tab_pressed = self.tab_pressed
+        self.tab_pressed = 0
+        handled = True
+
+        state = event.state & (gdk.SHIFT_MASK |
+                                gdk.CONTROL_MASK |
+                                gdk.MOD1_MASK)
+        keyval = event.keyval
+
+        if not state:
+            if keyval == _keys.Return:
+                self._commit()
+            elif keyval == _keys.Up:
+                self.__history(-1)
+            elif keyval == _keys.Down:
+                self.__history(1)
+            elif keyval == _keys.Left:
+                self.__move_cursor(-1)
+            elif keyval == _keys.Right:
+                self.__move_cursor(1)
+            elif keyval == _keys.Home:
+                self.__move_cursor(-10000)
+            elif keyval == _keys.End:
+                self.__move_cursor(10000)
+            elif keyval == _keys.Tab:
+                cursor = self.__get_cursor()
+                if cursor.starts_line():
+                    handled = False
+                else:
+                    cursor.backward_char()
+                    if cursor.get_char().isspace():
+                        handled = False
+                    else:
+                        self.tab_pressed = tab_pressed + 1
+                        self.__complete()
+            else:
+                handled = False
+        elif state == gdk.CONTROL_MASK:
+            if keyval == _keys.u:
+                start = self.__get_start()
+                end = self.__get_cursor()
+                self.__delete(start, end)
+            else:
+                handled = False
+        else:
+            handled = False
+
+        if not handled:
+            return parent_type.do_key_press_event(self, event)
+        else:
+            return True
+
+    def __history(self, dir):
+        text = self._get_line()
+        new_text = self.history.get(dir, text)
+        if not new_text is None:
+            self.__replace_line(new_text)
+        self.__move_cursor(0)
+        self.scroll_to_mark(self.cursor, 0.2)
+
+    def __get_cursor(self):
+        return self.buffer.get_iter_at_mark(self.cursor)
+    def __get_start(self):
+        iter = self.__get_cursor()
+        iter.set_line_offset(len(self.ps))
+        return iter
+    def __get_end(self):
+        iter = self.__get_cursor()
+        if not iter.ends_line():
+            iter.forward_to_line_end()
+        return iter
+
+    def __get_text(self, start, end):
+        return self.buffer.get_text(start, end, False)
+
+    def __move_cursor_to(self, iter):
+        self.buffer.place_cursor(iter)
+        self.buffer.move_mark_by_name("cursor", iter)
+
+    def __move_cursor(self, howmany):
+        iter = self.__get_cursor()
+        end = self.__get_cursor()
+        if not end.ends_line():
+            end.forward_to_line_end()
+        line_len = end.get_line_offset()
+        move_to = iter.get_line_offset() + howmany
+        move_to = min(max(move_to, len(self.ps)), line_len)
+        iter.set_line_offset(move_to)
+        self.__move_cursor_to(iter)
+
+    def __delete_at_cursor(self, howmany):
+        iter = self.__get_cursor()
+        end = self.__get_cursor()
+        if not end.ends_line():
+            end.forward_to_line_end()
+        line_len = end.get_line_offset()
+        erase_to = iter.get_line_offset() + howmany
+        if erase_to > line_len:
+            erase_to = line_len
+        elif erase_to < len(self.ps):
+            erase_to = len(self.ps)
+        end.set_line_offset(erase_to)
+        self.__delete(iter, end)
+
+    def __get_width(self):
+        if not (self.flags() & gtk.REALIZED):
+            return 80
+        layout = pango.Layout(self.get_pango_context())
+        letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+        layout.set_text(letters)
+        pix_width = layout.get_pixel_size()[0]
+        return self.allocation.width * len(letters) / pix_width
+
+    def __print_completions(self, completions):
+        line_start = self.__get_text(self.__get_start(), self.__get_cursor())
+        line_end = self.__get_text(self.__get_cursor(), self.__get_end())
+        iter = self.buffer.get_end_iter()
+        self.__move_cursor_to(iter)
+        self.__insert(iter, "\n")
+
+        width = max(self.__get_width(), 4)
+        max_width = max([len(s) for s in completions])
+        n_columns = max(int(width / (max_width + 1)), 1)
+        col_width = int(width / n_columns)
+        total = len(completions)
+        col_length = total / n_columns
+        if total % n_columns:
+            col_length = col_length + 1
+        col_length = max(col_length, 1)
+
+        if col_length == 1:
+            n_columns = total
+            col_width = width / total
+
+        for i in range(col_length):
+            for j in range(n_columns):
+                ind = i + j*col_length
+                if ind < total:
+                    if j == n_columns - 1:
+                        n_spaces = 0
+                    else:
+                        n_spaces = col_width - len(completions[ind])
+                    self.__insert(iter, completions[ind] + " " * n_spaces)
+            self.__insert(iter, "\n")
+
+        self.__insert(iter, "%s%s%s" % (self.ps, line_start, line_end))
+        iter.set_line_offset(len(self.ps) + len(line_start))
+        self.__move_cursor_to(iter)
+        self.scroll_to_mark(self.cursor, 0.2)
+
+    def __complete(self):
+        text = self.__get_text(self.__get_start(), self.__get_cursor())
+        start = ''
+        word = text
+        nonwords = self.nonword_re.findall(text)
+        if nonwords:
+            last = text.rfind(nonwords[-1]) + len(nonwords[-1])
+            start = text[:last]
+            word = text[last:]
+
+        completions = self.complete(word)
+
+        if completions:
+            prefix = _commonprefix(completions)
+            if prefix != word:
+                start_iter = self.__get_start()
+                start_iter.forward_chars(len(start))
+                end_iter = start_iter.copy()
+                end_iter.forward_chars(len(word))
+                self.__delete(start_iter, end_iter)
+                self.__insert(end_iter, prefix)
+            elif self.tab_pressed > 1:
+                self.freeze_undo()
+                self.__print_completions(completions)
+                self.thaw_undo()
+                self.tab_pressed = 0
+
+    def complete(self, text):
+        return None
+
+    def _get_line(self):
+        start = self.__get_start()
+        end = self.__get_end()
+        return self.buffer.get_text(start, end, False)
+
+    def __replace_line(self, new_text):
+        start = self.__get_start()
+        end = self.__get_end()
+        self.__delete(start, end)
+        self.__insert(end, new_text)
+
+    def _commit(self):
+        end = self.__get_cursor()
+        if not end.ends_line():
+            end.forward_to_line_end()
+        text = self._get_line()
+        self.__move_cursor_to(end)
+        self.freeze_undo()
+        self.__insert(end, "\n")
+        self.in_raw_input = False
+        self.history.commit(text)
+        self.do_raw_input(text)
+        self.thaw_undo()
+
+    def do_raw_input(self, text):
+        pass
+
+
+class _Console(_ReadLine, code.InteractiveInterpreter):
+    def __init__(self, locals=None, banner=None,
+                 completer=None, use_rlcompleter=True,
+                 start_script=None):
+        _ReadLine.__init__(self)
+
+
+        code.InteractiveInterpreter.__init__(self, locals)
+        self.locals["__console__"] = self
+
+        self.start_script = start_script
+        self.completer = completer
+        self.banner = banner
+
+        if not self.completer and use_rlcompleter:
+            try:
+                import rlcompleter
+                self.completer = rlcompleter.Completer()
+            except ImportError:
+                pass
+
+        self.ps1 = ">>> "
+        self.ps2 = "... "
+        self.__start()
+        self.run_on_raw_input = start_script
+        self.raw_input(self.ps1)
+
+    def __start(self):
+        self.cmd_buffer = ""
+
+        self.freeze_undo()
+        self.thaw_undo()
+        self.buffer.set_text("")
+
+        if self.banner:
+            iter = self.buffer.get_start_iter()
+            self.buffer.insert_with_tags_by_name(iter, self.banner, "stdout")
+            if not iter.starts_line():
+                self.buffer.insert(iter, "\n")
+
+    def clear(self, start_script=None):
+        if start_script is None:
+            start_script = self.start_script
+        else:
+            self.start_script = start_script
+
+        self.__start()
+        self.run_on_raw_input = start_script
+
+    def do_raw_input(self, text):
+        if self.cmd_buffer:
+            cmd = self.cmd_buffer + "\n" + text
+        else:
+            cmd = text
+
+        saved_stdout, saved_stderr = sys.stdout, sys.stderr
+        sys.stdout, sys.stderr = self._stdout, self._stderr
+
+        if self.runsource(cmd):
+            self.cmd_buffer = cmd
+            ps = self.ps2
+        else:
+            self.cmd_buffer = ''
+            ps = self.ps1
+
+        sys.stdout, sys.stderr = saved_stdout, saved_stderr
+        self.raw_input(ps)
+
+    def do_command(self, code):
+        try:
+            eval(code, self.locals)
+        except SystemExit:
+            raise
+        except:
+            self.showtraceback()
+
+    def runcode(self, code):
+        if gtk.pygtk_version[1] < 8:
+            self.do_command(code)
+        else:
+            self.emit("command", code)
+
+    def exec_command(self, command):
+        if self._get_line():
+            self._commit()
+        self.buffer.insert_at_cursor(command)
+        self._commit()
+
+    def complete_attr(self, start, end):
+        try:
+            obj = eval(start, self.locals)
+            strings = dir(obj)
+
+            if end:
+                completions = {}
+                for s in strings:
+                    if s.startswith(end):
+                        completions[s] = None
+                completions = completions.keys()
+            else:
+                completions = strings
+
+            completions.sort()
+            return [start + "." + s for s in completions]
+        except:
+            return None
+
+    def complete(self, text):
+        if self.completer:
+            completions = []
+            i = 0
+            try:
+                while 1:
+                    s = self.completer.complete(text, i)
+                    if s:
+                        completions.append(s)
+                        i = i + 1
+                    else:
+                        completions.sort()
+                        return completions
+            except NameError:
+                return None
+
+        dot = text.rfind(".")
+        if dot >= 0:
+            return self.complete_attr(text[:dot], text[dot+1:])
+
+        completions = {}
+        strings = keyword.kwlist
+
+        if self.locals:
+            strings.extend(self.locals.keys())
+
+        try: strings.extend(eval("globals()", self.locals).keys())
+        except: pass
+
+        try:
+            exec "import __builtin__" in self.locals
+            strings.extend(eval("dir(__builtin__)", self.locals))
+        except:
+            pass
+
+        for s in strings:
+            if s.startswith(text):
+                completions[s] = None
+        completions = completions.keys()
+        completions.sort()
+        return completions
+
+
+def ReadLineType(t=gtk.TextView):
+    class readline(t, _ReadLine):
+        def __init__(self, *args, **kwargs):
+            t.__init__(self)
+            _ReadLine.__init__(self, *args, **kwargs)
+        def do_key_press_event(self, event):
+            return _ReadLine.do_key_press_event(self, event, t)
+    gobject.type_register(readline)
+    return readline
+
+def ConsoleType(t=gtk.TextView):
+    class console_type(t, _Console):
+        __gsignals__ = {
+            'command' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)),
+            'key-press-event' : 'override'
+          }
+
+        def __init__(self, *args, **kwargs):
+            if gtk.pygtk_version[1] < 8:
+                gobject.GObject.__init__(self)
+            else:
+                t.__init__(self)
+            _Console.__init__(self, *args, **kwargs)
+
+        def do_command(self, code):
+            return _Console.do_command(self, code)
+
+        def do_key_press_event(self, event):
+            return _Console.do_key_press_event(self, event, t)
+
+    if gtk.pygtk_version[1] < 8:
+        gobject.type_register(console_type)
+
+    return console_type
+
+ReadLine = ReadLineType()
+Console = ConsoleType()
+
+def _create_widget(start_script):
+
+    console = Console(banner="Geany Python Console",
+                          use_rlcompleter=False,
+                          start_script=start_script)
+    return console
+
+def _make_window(start_script="import geany\n"):
+    window = gtk.Window()
+    window.set_title("Python Console")
+    swin = gtk.ScrolledWindow()
+    swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
+    window.add(swin)
+    console = _create_widget(start_script)
+    swin.add(console)
+    window.set_default_size(500, 400)
+    window.show_all()
+
+    if not gtk.main_level():
+        window.connect("destroy", gtk.main_quit)
+        gtk.main()
+
+    return console
+
+if __name__ == '__main__':
+    import sys
+    import os
+    sys.path.insert(0, os.getcwd())
+    _make_window(sys.argv[1:] and '\n'.join(sys.argv[1:]) + '\n' or None)


Modified: geanypy/geany/loader.py
172 files changed, 172 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,172 @@
+import os
+import imp
+from collections import namedtuple
+import geany
+
+PluginInfo = namedtuple('PluginInfo', 'filename, name, version, description, author, cls')
+
+
+class PluginLoader(object):
+
+	plugins = {}
+
+	def __init__(self, plugin_dirs):
+
+		self.plugin_dirs = plugin_dirs
+
+		self.available_plugins = []
+		for plugin in self.iter_plugin_info():
+			self.available_plugins.append(plugin)
+
+		self.restore_loaded_plugins()
+
+
+	def update_loaded_plugins_file(self):
+		for path in self.plugin_dirs:
+			if os.path.isdir(path):
+				try:
+					state_file = os.path.join(path, '.loaded_plugins')
+					with open(state_file, 'w') as f:
+						for plugfn in self.plugins:
+							f.write("%s\n" % plugfn)
+				except IOError as err:
+					if err.errno == 13: #perms
+						pass
+					else:
+						raise
+
+
+	def restore_loaded_plugins(self):
+		loaded_plugins = []
+		for path in reversed(self.plugin_dirs):
+			state_file = os.path.join(path, ".loaded_plugins")
+			if os.path.exists(state_file):
+				for line in open(state_file):
+					line = line.strip()
+					if line not in loaded_plugins:
+						loaded_plugins.append(line)
+		for filename in loaded_plugins:
+			self.load_plugin(filename)
+
+
+	def load_all_plugins(self):
+
+		for plugin_info in self.iter_plugin_info():
+			if plugin_filename.endswith('test.py'): # hack for testing
+				continue
+			plug = self.load_plugin(plugin_info.filename)
+			if plug:
+				print("Loaded plugin: %s" % plugin_info.filename)
+				print("  Name: %s v%s" % (plug.name, plug.version))
+				print("  Desc: %s" % plug.description)
+				print("  Author: %s" % plug.author)
+
+
+	def unload_all_plugins(self):
+
+		for plugin in self.plugins:
+			self.unload_plugin(plugin)
+
+
+	def reload_all_plugins(self):
+
+		self.unload_all_plugins()
+		self.load_all_plugins()
+
+
+	def iter_plugin_info(self):
+
+		for d in self.plugin_dirs:
+			if os.path.isdir(d):
+				for current_file in os.listdir(d):
+					#check inside folders inside the plugins dir so we can load .py files here as plugins
+					current_path=os.path.abspath(os.path.join(d, current_file))
+					if os.path.isdir(current_path):
+						for plugin_folder_file in os.listdir(current_path):
+							if plugin_folder_file.endswith('.py'):
+								#loop around results if its fails to load will never reach yield
+								for p in self.load_plugin_info(current_path,plugin_folder_file):
+									yield p
+									
+					#not a sub directory so if it ends with .py lets just attempt to load it as a plugin
+					if current_file.endswith('.py'):
+						#loop around results if its fails to load will never reach yield
+						for p in self.load_plugin_info(d,current_file):
+							yield p
+								
+	def load_plugin_info(self,d,f):
+		filename = os.path.abspath(os.path.join(d, f))
+		if filename.endswith("test.py"):
+			pass
+		text = open(filename).read()
+		module_name = os.path.basename(filename)[:-3]
+		try:
+			module = imp.load_source(module_name, filename)
+		except ImportError as exc:
+			print "Error: failed to import settings module ({})".format(exc)
+			module=None
+		if module:	
+			for k, v in module.__dict__.iteritems():
+				if k == geany.Plugin.__name__:
+					continue
+				try:
+					if issubclass(v, geany.Plugin):
+						inf = PluginInfo(
+								filename,
+								getattr(v, '__plugin_name__'),
+								getattr(v, '__plugin_version__', ''),
+								getattr(v, '__plugin_description__', ''),
+								getattr(v, '__plugin_author__', ''),
+								v)
+						yield inf
+						
+				except TypeError:
+					continue
+
+
+	def load_plugin(self, filename):
+
+		for avail in self.available_plugins:
+			if avail.filename == filename:
+				inst = avail.cls()
+				self.plugins[filename] = inst
+				self.update_loaded_plugins_file()
+				geany.ui_utils.set_statusbar('GeanyPy: plugin activated: %s' %
+					inst.name, True)
+				return inst
+
+
+	def unload_plugin(self, filename):
+
+		try:
+			plugin = self.plugins[filename]
+			name = plugin.name
+			plugin.cleanup()
+			del self.plugins[filename]
+			self.update_loaded_plugins_file()
+			geany.ui_utils.set_statusbar('GeanyPy: plugin deactivated: %s' %
+				name, True)
+		except KeyError:
+			print("Unable to unload plugin '%s': it's not loaded" % filename)
+
+
+	def reload_plugin(self, filename):
+
+		if filename in self.plugins:
+			self.unload_plugin(filename)
+		self.load_plugin(filename)
+
+
+	def plugin_has_help(self, filename):
+
+		for plugin_info in self.iter_plugin_info():
+			if plugin_info.filename == filename:
+				return hasattr(plugin_info.cls, 'show_help')
+
+
+	def plugin_has_configure(self, filename):
+
+		try:
+			return hasattr(self.plugins[filename], 'show_configure')
+		except KeyError:
+			return None


Modified: geanypy/geany/manager.py
179 files changed, 179 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,179 @@
+import gtk
+import gobject
+import glib
+from htmlentitydefs import name2codepoint
+from loader import PluginLoader
+
+
+class PluginManager(gtk.Dialog):
+
+	def __init__(self, plugin_dirs=[]):
+		gtk.Dialog.__init__(self, title="Plugin Manager")
+		self.loader = PluginLoader(plugin_dirs)
+
+		self.set_default_size(400, 450)
+		self.set_has_separator(True)
+		icon = self.render_icon(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
+		self.set_icon(icon)
+
+		self.connect("response", lambda w,d: self.hide())
+
+		vbox = gtk.VBox(False, 12)
+		vbox.set_border_width(12)
+
+		lbl = gtk.Label("Choose plugins to load or unload:")
+		lbl.set_alignment(0.0, 0.5)
+		vbox.pack_start(lbl, False, False, 0)
+
+		sw = gtk.ScrolledWindow()
+		sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+		sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+		vbox.pack_start(sw, True, True, 0)
+
+		self.treeview = gtk.TreeView()
+		sw.add(self.treeview)
+
+		vbox.show_all()
+
+		self.get_content_area().add(vbox)
+
+		action_area = self.get_action_area()
+		action_area.set_spacing(0)
+		action_area.set_homogeneous(False)
+
+		btn = gtk.Button(stock=gtk.STOCK_CLOSE)
+		btn.set_border_width(6)
+		btn.connect("clicked", lambda x: self.response(gtk.RESPONSE_CLOSE))
+		action_area.pack_start(btn, False, True, 0)
+		btn.show()
+
+		self.btn_help = gtk.Button(stock=gtk.STOCK_HELP)
+		self.btn_help.set_border_width(6)
+		self.btn_help.set_no_show_all(True)
+		action_area.pack_start(self.btn_help, False, True, 0)
+		action_area.set_child_secondary(self.btn_help, True)
+
+		self.btn_prefs = gtk.Button(stock=gtk.STOCK_PREFERENCES)
+		self.btn_prefs.set_border_width(6)
+		self.btn_prefs.set_no_show_all(True)
+		action_area.pack_start(self.btn_prefs, False, True, 0)
+		action_area.set_child_secondary(self.btn_prefs, True)
+
+		action_area.show()
+
+		self.load_plugins_list()
+
+
+	def on_help_button_clicked(self, button, treeview, model):
+		path = treeview.get_cursor()[0]
+		iter = model.get_iter(path)
+		filename = model.get_value(iter, 2)
+		for plugin in self.loader.available_plugins:
+			if plugin.filename == filename:
+				plugin.cls.show_help()
+				break
+		else:
+			print("Plugin does not support help function")
+
+
+	def on_preferences_button_clicked(self, button, treeview, model):
+		path = treeview.get_cursor()[0]
+		iter = model.get_iter(path)
+		filename = model.get_value(iter, 2)
+		try:
+			self.loader.plugins[filename].show_configure()
+		except KeyError:
+			print("Plugin is not loaded, can't run configure function")
+
+
+	def activate_plugin(self, filename):
+		self.loader.load_plugin(filename)
+
+
+	def deactivate_plugin(self, filename):
+		self.loader.unload_plugin(filename)
+
+
+	def load_plugins_list(self):
+		liststore = gtk.ListStore(gobject.TYPE_BOOLEAN, str, str)
+
+		self.btn_help.connect("clicked",
+			self.on_help_button_clicked, self.treeview, liststore)
+
+		self.btn_prefs.connect("clicked",
+			self.on_preferences_button_clicked, self.treeview, liststore)
+
+		self.treeview.set_model(liststore)
+		self.treeview.set_headers_visible(False)
+		self.treeview.set_grid_lines(True)
+
+		check_renderer = gtk.CellRendererToggle()
+		check_renderer.set_radio(False)
+		check_renderer.connect('toggled', self.on_plugin_load_toggled, liststore)
+		text_renderer = gtk.CellRendererText()
+
+		check_column = gtk.TreeViewColumn(None, check_renderer, active=0)
+		text_column = gtk.TreeViewColumn(None, text_renderer, markup=1)
+
+		self.treeview.append_column(check_column)
+		self.treeview.append_column(text_column)
+
+		self.treeview.connect('row-activated',
+			self.on_row_activated, check_renderer, liststore)
+		self.treeview.connect('cursor-changed',
+			self.on_selected_plugin_changed, liststore)
+
+		self.load_sorted_plugins_info(liststore)
+
+
+	def load_sorted_plugins_info(self, list_store):
+
+		plugin_info_list = list(self.loader.iter_plugin_info())
+		#plugin_info_list.sort(key=lambda pi: pi[1])
+
+		for plugin_info in plugin_info_list:
+
+			lbl = str('<big><b>%s</b></big> <small>%s</small>\n%s\n' +
+					'<small><b>Author:</b> %s\n' +
+					'<b>Filename:</b> %s</small>') % (
+					glib.markup_escape_text(plugin_info.name),
+					glib.markup_escape_text(plugin_info.version),
+					glib.markup_escape_text(plugin_info.description),
+					glib.markup_escape_text(plugin_info.author),
+					glib.markup_escape_@@ Diff output truncated at 100000 characters. @@

--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Plugins-Commits mailing list