[geany/geany-plugins] 1a2f8c: Merge pull request #161 from techee/geanyctags

Frank Lanitz git-noreply at xxxxx
Sun Oct 19 08:10:37 UTC 2014


Branch:      refs/heads/master
Author:      Frank Lanitz <frank at frank.uvena.de>
Committer:   Frank Lanitz <frank at frank.uvena.de>
Date:        Sun, 19 Oct 2014 08:10:37 UTC
Commit:      1a2f8c4d658e2471d02983295861f2824eb4ae1c
             https://github.com/geany/geany-plugins/commit/1a2f8c4d658e2471d02983295861f2824eb4ae1c

Log Message:
-----------
Merge pull request #161 from techee/geanyctags

Add the GeanyCtags plugin


Modified Paths:
--------------
    MAINTAINERS
    Makefile.am
    build/geanyctags.m4
    configure.ac
    geanyctags/AUTHORS
    geanyctags/COPYING
    geanyctags/ChangeLog
    geanyctags/Makefile.am
    geanyctags/NEWS
    geanyctags/README
    geanyctags/src/Makefile.am
    geanyctags/src/geanyctags.c
    geanyctags/src/readtags.c
    geanyctags/src/readtags.h
    geanyctags/wscript_build
    geanyctags/wscript_configure
    po/POTFILES.in

Modified: MAINTAINERS
6 lines changed, 6 insertions(+), 0 deletions(-)
===================================================================
@@ -60,6 +60,12 @@ M: Matthew Brush <matt at geany.org>
 W: http://plugins.geany.org/devhelp.html
 S: Maintained
 
+geanyctags
+P: Jiří Techet <techet at gmail.com>
+M: Jiří Techet <techet at gmail.com>
+W: http://plugins.geany.org/geanyctags.html
+S: Maintained
+
 geanydoc
 P: Yura Siamashka <yurand2 at gmail.com>
 M: Yura Siamashka <yurand2 at gmail.com>


Modified: Makefile.am
4 lines changed, 4 insertions(+), 0 deletions(-)
===================================================================
@@ -35,6 +35,10 @@ if ENABLE_DEVHELP
 SUBDIRS += devhelp
 endif
 
+if ENABLE_GEANYCTAGS
+SUBDIRS += geanyctags
+endif
+
 if ENABLE_GEANYDOC
 SUBDIRS += geanydoc
 endif


Modified: build/geanyctags.m4
9 lines changed, 9 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,9 @@
+AC_DEFUN([GP_CHECK_GEANYCTAGS],
+[
+    GP_ARG_DISABLE([GeanyCtags], [auto])
+    GP_COMMIT_PLUGIN_STATUS([GeanyCtags])
+    AC_CONFIG_FILES([
+        geanyctags/Makefile
+        geanyctags/src/Makefile
+    ])
+])


Modified: configure.ac
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -34,6 +34,7 @@ GP_CHECK_COMMANDER
 GP_CHECK_DEBUGGER
 GP_CHECK_DEFINEFORMAT
 GP_CHECK_DEVHELP
+GP_CHECK_GEANYCTAGS
 GP_CHECK_GEANYDOC
 GP_CHECK_GEANYEXTRASEL
 GP_CHECK_GEANYGENDOC


Modified: geanyctags/AUTHORS
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1 @@
+Jiří Techet <techet at gmail.com>


Modified: geanyctags/COPYING
340 lines changed, 340 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+	51 Franklin St, 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 Library 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 St, 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 Library General
+Public License instead of this License.


Modified: geanyctags/ChangeLog
0 lines changed, 0 insertions(+), 0 deletions(-)
===================================================================
No diff available, check online


Modified: geanyctags/Makefile.am
4 lines changed, 4 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,4 @@
+include $(top_srcdir)/build/vars.auxfiles.mk
+
+SUBDIRS = src
+plugin = geanyctags


Modified: geanyctags/NEWS
0 lines changed, 0 insertions(+), 0 deletions(-)
===================================================================
No diff available, check online


Modified: geanyctags/README
115 lines changed, 115 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,115 @@
+==========
+GeanyCtags
+==========
+
+.. contents::
+
+About
+=====
+
+GeanyCtags adds a simple support for generating and querying ctags files for a Geany
+project. It requires that the ctags command is installed in a system path. On
+unix systems, distributions usually provide the ctags package; on Windows, the
+ctags binary can be found in the zip Windows distribution from the ctags home
+page (http://ctags.sourceforge.net).
+
+Even though Geany supports symbol definition searching by itself within the open files
+(and with a plugin support within the whole project), tag regeneration can become
+too slow for really big projects. This is why this plugin was created. It makes
+it possible to generate the tag file only once and just query it when searching
+for a particular symbol definition/declaration. This approach is fine for big projects
+where most of the codebase remains unchanged and the tag positions remain more
+or less static.
+
+Usage
+=====
+
+GeanyCtags only works for Geany projects so in order to use it, a Geany project
+has to be open. The "File patterns" entry under Project->Properties determines 
+for which files the tag list will be generated. All files within the project base
+path satisfying the file patterns will be included for the tag generation.
+
+Tag Generation
+--------------
+
+To generate the list of tags, make sure that
+
+* a project is open, and
+* "File patterns" are specified correctly under Project->Properties
+
+Then, select Project->Generate tags. After this, a file whose name corresponds to
+the project name with the suffix ".tags" is created in the base path of the project.
+
+Tag Querying
+------------
+
+There are two ways to find a symbol's definition/declaration:
+
+* using the context menu by placing a cursor at the searched symbol, right-clicking
+  and selecting one of the two top items "Find Tag Definition" or "Find Tag Declaration".
+* using Project->Find tag
+
+If the symbol is found, the editor is opened at the tag line. In addition, all the
+found tags are written into the Messages window. This is useful when there are more
+tags of the same name - by clicking a line in the Messages window it is possible
+to jump to any of the found tags.
+
+When using the Project->Find tag search, it is possible to select from several
+search types:
+
+* prefix (default) - finds all tags with the specified prefix
+* full - finds all tags matching the name exactly
+* pattern - finds all tags matching the provided glob pattern
+
+Note that the pattern option is the slowest of the three possibilities because it
+has to go through the tag list sequentially instead of using binary search used
+by the other two methods. By default, tag definitions are searched; to search tag
+declarations, select the Declaration option.
+
+Known issues
+============
+
+* Right now it's not possible to just right-click the searched symbol in the editor
+  and search for definition/declaration - first, a cursor has to be placed at
+  the symbol. This is because at the moment the necessary functionality  to do this 
+  is private in Geany and not exported to plugins so it cannot be implemented.
+  I will try to get the necessary functions public in Geany to make it possible
+  in the future.
+* Under Windows, GeanyCtags generates tags for all the files within the project base
+  path instead of just the files defined by the patterns. This happens because
+  of the missing (or at least not normally installed) find command used under
+  unix systems to restrict the file list to those matching the patterns. It
+  would be possible to perform the filtering manually but frankly I'm rather lazy
+  to do so (and don't have Windows installed to test). Patches are welcome.
+
+License
+=======
+
+GeanyCtags is distributed 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.  A copy of this license
+can be found in the file COPYING included with the source code of this
+program.
+
+Downloads
+=========
+
+GeanyCtags is part of the combined Geany Plugins release.
+For more information and downloads, please visit
+http://plugins.geany.org/geany-plugins/
+
+Development Code
+================
+
+Get the code from::
+
+    git clone https://github.com/geany/geany-plugins.git
+
+Ideas, questions, patches and bug reports
+=========================================
+
+If you add something, or fix a bug, please send a patch (in 'diff -u'
+format) to the geany mailing list or to one of the authors listed bellow.
+
+2010-2014 by Jiří Techet
+techet(at)gmail(dot)com


Modified: geanyctags/src/Makefile.am
12 lines changed, 12 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,12 @@
+include $(top_srcdir)/build/vars.build.mk
+
+geanyplugins_LTLIBRARIES = geanyctags.la
+
+geanyctags_la_SOURCES = \
+	geanyctags.c \
+	readtags.h \
+	readtags.c
+
+geanyctags_la_LIBADD = $(COMMONLIBS)
+
+include $(top_srcdir)/build/cppcheck.mk


Modified: geanyctags/src/geanyctags.c
642 lines changed, 642 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,642 @@
+/*
+ *	  Copyright 2010-2014 Jiri Techet <techet at gmail.com>
+ *
+ *	  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.
+ */
+
+#include <sys/time.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <geanyplugin.h>
+
+#include "readtags.h"
+
+#include <errno.h>
+#include <glib/gstdio.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+
+PLUGIN_VERSION_CHECK(211);
+PLUGIN_SET_INFO("GeanyCtags",
+	_("Ctags generation and search plugin for geany projects"),
+	VERSION,
+	"Jiri Techet <techet at gmail.com>");
+
+GeanyPlugin *geany_plugin;
+GeanyData *geany_data;
+GeanyFunctions *geany_functions;
+
+
+static GtkWidget *s_context_fdec_item, *s_context_fdef_item, *s_context_sep_item,
+	*s_gt_item, *s_sep_item, *s_ft_item;
+
+static struct
+{
+	GtkWidget *widget;
+
+	GtkWidget *combo;
+	GtkWidget *combo_match;
+	GtkWidget *case_sensitive;
+	GtkWidget *declaration;
+} s_ft_dialog = {NULL, NULL, NULL, NULL};
+
+
+enum
+{
+	KB_FIND_TAG,
+	KB_GENERATE_TAGS,
+	KB_COUNT
+};
+
+
+
+void plugin_init(G_GNUC_UNUSED GeanyData * data);
+void plugin_cleanup(void);
+
+
+static void set_widgets_sensitive(gboolean sensitive)
+{
+	gtk_widget_set_sensitive(GTK_WIDGET(s_gt_item), sensitive);
+	gtk_widget_set_sensitive(GTK_WIDGET(s_context_fdec_item), sensitive);
+	gtk_widget_set_sensitive(GTK_WIDGET(s_context_fdef_item), sensitive);
+}
+
+static void on_project_open(G_GNUC_UNUSED GObject * obj, GKeyFile * config, G_GNUC_UNUSED gpointer user_data)
+{
+	set_widgets_sensitive(TRUE);
+}
+
+static void on_project_close(G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED gpointer user_data)
+{
+	set_widgets_sensitive(FALSE);
+}
+
+PluginCallback plugin_callbacks[] = {
+	{"project-open", (GCallback) & on_project_open, TRUE, NULL},
+	{"project-close", (GCallback) & on_project_close, TRUE, NULL},
+	{NULL, NULL, FALSE, NULL}
+};
+
+static void spawn_cmd(const gchar *cmd, const gchar *dir)
+{
+	GError *error = NULL;
+	gchar **argv;
+	gchar *working_dir;
+	gchar *utf8_working_dir;
+	gchar *utf8_cmd_string;
+	gchar *err;
+	gint exitcode;
+
+#ifndef G_OS_WIN32
+	/* run within shell so we can use pipes */
+	argv = g_new0(gchar *, 4);
+	argv[0] = g_strdup("/bin/sh");
+	argv[1] = g_strdup("-c");
+	argv[2] = g_strdup(cmd);
+	argv[3] = NULL;
+#else
+	/* no shell on windows */
+	argv = g_new0(gchar *, 2);
+	argv[0] = g_strdup(cmd);
+	argv[1] = NULL;
+#endif
+			
+	utf8_cmd_string = utils_get_utf8_from_locale(cmd);
+	utf8_working_dir = g_strdup(dir);
+	working_dir = utils_get_locale_from_utf8(utf8_working_dir);
+
+	msgwin_clear_tab(MSG_MESSAGE);
+	msgwin_switch_tab(MSG_MESSAGE, TRUE);
+	msgwin_msg_add(COLOR_BLUE, -1, NULL, _("%s (in directory: %s)"), utf8_cmd_string, utf8_working_dir);
+	g_free(utf8_working_dir);
+	g_free(utf8_cmd_string);
+
+	if (!utils_spawn_sync(working_dir, argv, NULL, G_SPAWN_SEARCH_PATH,
+			NULL, NULL, NULL, &err, &exitcode, &error) || exitcode != 0)
+	{
+		if (error != NULL)
+		{
+			msgwin_msg_add(COLOR_RED, -1, NULL, _("Process execution failed (%s)"), error->message);
+			g_error_free(error);
+		}
+		msgwin_msg_add(COLOR_RED, -1, NULL, "%s", err);
+	}
+	else
+	{
+		msgwin_msg_add(COLOR_BLACK, -1, NULL, "%s", err);
+	}
+
+	g_strfreev(argv);
+	g_free(working_dir);
+	g_free(err);
+}
+
+static gchar *get_tags_filename()
+{
+	gchar *ret = NULL;
+
+	if (geany_data->app->project)
+	{
+		ret = utils_remove_ext_from_filename(geany_data->app->project->file_name);
+		SETPTR(ret, g_strconcat(ret, ".tags", NULL));
+	}
+	return ret;
+}
+
+static gchar *generate_find_string(GeanyProject *prj)
+{
+	gchar *ret;
+
+	ret = g_strdup("find");
+
+	if (!EMPTY(prj->file_patterns))
+	{
+		guint i;
+
+		SETPTR(ret, g_strconcat(ret, " \\( -name \"", prj->file_patterns[0], "\"", NULL));
+		for (i = 1; prj->file_patterns[i]; i++)
+			SETPTR(ret, g_strconcat(ret, " -o -name \"", prj->file_patterns[i], "\"", NULL));
+		SETPTR(ret, g_strconcat(ret, " \\)", NULL));
+	}
+	return ret;
+}
+
+
+static void
+on_generate_tags(GtkMenuItem *menuitem, gpointer user_data)
+{
+	GeanyProject *prj;
+
+	prj = geany_data->app->project;
+	if (prj)
+	{
+		gchar *cmd;
+		gchar *tag_filename;
+
+		tag_filename = get_tags_filename();
+
+#ifndef G_OS_WIN32
+		gchar *find_string = generate_find_string(prj);
+		cmd = g_strconcat(find_string,
+			" | ctags --totals --fields=fKsSt --extra=-fq --c-kinds=+p --sort=foldcase --excmd=number -L - -f ",
+			tag_filename, NULL);
+#else
+		/* We don't have find and | on windows, generate tags for all files in the project (-R recursively) */
+		
+		/* Unfortunately, there's a bug in ctags - when run with -R, the first line is
+		 * empty, ctags doesn't recognize the tags file as a valid ctags file and
+		 * refuses to overwrite it. Therefore, we need to delete the tags file manually. */
+		g_unlink(tag_filename);
+
+		cmd = g_strconcat("ctags.exe -R --totals --fields=fKsSt --extra=-fq --c-kinds=+p --sort=foldcase --excmd=number -f ",
+			tag_filename, NULL);
+#endif
+
+		spawn_cmd(cmd, prj->base_path);
+
+		g_free(cmd);
+		g_free(find_string);
+		g_free(tag_filename);
+	}
+}
+
+static void show_entry(tagEntry *entry)
+{
+	const gchar *kind;
+	const gchar *signature;
+	const gchar *scope;
+	const gchar *file;
+	const gchar *name;
+	gchar *scope_str;
+	gchar *kind_str;
+
+	file = entry->file;
+	if (!file)
+		file = "";
+
+	name = entry->name;
+	if (!name)
+		name = "";
+
+	signature = tagsField(entry, "signature");
+	if (!signature)
+		signature = "";
+
+	scope = tagsField(entry, "class");
+	if (!scope)
+		scope = tagsField(entry, "struct");
+	if (!scope)
+		scope = tagsField(entry, "union");
+	if (!scope)
+		scope = tagsField(entry, "enum");
+
+	if (scope)
+		scope_str = g_strconcat(scope, "::", NULL);
+	else
+		scope_str = g_strdup("");
+
+	kind = entry->kind;
+	if (kind)
+	{
+		kind_str = g_strconcat(kind, ":  ", NULL);
+		SETPTR(kind_str, g_strdup_printf("%-14s", kind_str));
+	}
+	else
+		kind_str = g_strdup("");
+
+	msgwin_msg_add(COLOR_BLACK, -1, NULL, "%s:%lu:\n    %s%s%s%s", file,
+		entry->address.lineNumber, kind_str, scope_str, name, signature);
+
+	g_free(scope_str);
+	g_free(kind_str);
+}
+
+
+static gchar *get_selection()
+{
+	gchar *ret = NULL;
+	GeanyDocument *doc = document_get_current();
+	GeanyEditor *editor;
+
+	if (!doc)
+		return NULL;
+
+	editor = doc->editor;
+
+	if (sci_has_selection(editor->sci))
+		ret = sci_get_selection_contents(editor->sci);
+	else
+		ret = editor_get_word_at_pos(editor, -1, GEANY_WORDCHARS);
+
+	return ret;
+}
+
+/* TODO: Not possible to do it the way below because some of the API is private 
+ * in Geany. This means the cursor has to be placed at the symbol first and
+ * then right-clicked (right-clicking without having the cursor at the symbol
+ * doesn't work) */
+ 
+/*
+static gchar *get_selection()
+{
+	gchar *ret = NULL;
+	GeanyDocument *doc = document_get_current();
+
+	if (!doc)
+		return NULL;
+
+	if (!sci_has_selection(doc->editor->sci))
+		sci_set_current_position(doc->editor->sci, editor_info.click_pos, FALSE);
+
+	if (sci_has_selection(doc->editor->sci))
+		return sci_get_selection_contents(doc->editor->sci);
+
+	gint len = sci_get_selected_text_length(doc->editor->sci);
+
+	ret = g_malloc(len + 1);
+	sci_get_selected_text(doc->editor->sci, ret);
+
+	editor_find_current_word(doc->editor, -1,
+		editor_info.current_word, GEANY_MAX_WORD_LENGTH, NULL);
+
+	return editor_info.current_word != 0 ? g_strdup(editor_info.current_word) : NULL;
+}
+*/
+
+typedef enum
+{
+	MATCH_FULL,
+	MATCH_PREFIX,
+	MATCH_PATTERN
+} MatchType;
+
+static gboolean find_first(tagFile *tf, tagEntry *entry, const gchar *name, MatchType match_type)
+{
+	gboolean ret;
+
+	if (match_type == MATCH_PATTERN)
+		ret = tagsFirst(tf, entry) == TagSuccess;
+	else
+	{
+		int options = TAG_IGNORECASE;
+
+		options |= match_type == MATCH_PREFIX ? TAG_PARTIALMATCH : TAG_FULLMATCH;
+		ret = tagsFind(tf, entry, name, options) == TagSuccess;
+	}
+	return ret;
+}
+
+static gboolean find_next(tagFile *tf, tagEntry *entry, MatchType match_type)
+{
+	gboolean ret;
+
+	if (match_type == MATCH_PATTERN)
+		ret = tagsNext(tf, entry) == TagSuccess;
+	else
+		ret = tagsFindNext(tf, entry) == TagSuccess;
+	return ret;
+}
+
+static gboolean filter_tag(tagEntry *entry, GPatternSpec *name, gboolean declaration, gboolean case_sensitive)
+{
+	gboolean filter = TRUE;
+	gchar *entry_name;
+
+	if (!EMPTY(entry->kind))
+	{
+		gboolean is_prototype;
+
+		is_prototype = g_strcmp0(entry->kind, "prototype") == 0;
+		filter = (declaration && !is_prototype) || (!declaration && is_prototype);
+		if (filter)
+			return TRUE;
+	}
+
+	if (case_sensitive)
+		entry_name = g_strdup(entry->name);
+	else
+		entry_name = g_utf8_strdown(entry->name, -1);
+
+	filter = !g_pattern_match_string(name, entry_name);
+
+	g_free(entry_name);
+
+	return filter;
+}
+
+static void find_tags(const gchar *name, gboolean declaration, gboolean case_sensitive, MatchType match_type)
+{
+	tagFile *tf;
+	GeanyProject *prj;
+	gchar *tag_filename = NULL;
+	tagEntry entry;
+	tagFileInfo info;
+
+	prj = geany_data->app->project;
+	if (!prj)
+		return;
+
+	msgwin_clear_tab(MSG_MESSAGE);
+	msgwin_set_messages_dir(prj->base_path);
+
+	tag_filename = get_tags_filename();
+	tf = tagsOpen(tag_filename, &info);
+
+	if (tf)
+	{
+		if (find_first(tf, &entry, name, match_type))
+		{
+			GPatternSpec *name_pat;
+			gchar *name_case;
+
+			if (case_sensitive)
+				name_case = g_strdup(name);
+			else
+				name_case = g_utf8_strdown(name, -1);
+
+			SETPTR(name_case, g_strconcat("*", name_case, "*", NULL));
+			name_pat = g_pattern_spec_new(name_case);
+
+			if (!filter_tag(&entry, name_pat, declaration, case_sensitive))
+			{
+				gchar *path = g_build_filename(prj->base_path, entry.file, NULL);
+				GeanyDocument *doc = document_open_file(path, FALSE, NULL, NULL);
+				if (doc != NULL)
+				{
+					navqueue_goto_line(document_get_current(), doc, entry.address.lineNumber);
+					gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
+				}
+				show_entry(&entry);
+				g_free(path);
+			}
+			
+			while (find_next(tf, &entry, match_type))
+			{
+				if (!filter_tag(&entry, name_pat, declaration, case_sensitive))
+					show_entry(&entry);
+			}
+
+			g_pattern_spec_free(name_pat);
+			g_free(name_case);
+		}
+
+		tagsClose(tf);
+	}
+
+	msgwin_switch_tab(MSG_MESSAGE, TRUE);
+
+	g_free(tag_filename);
+}
+
+static void on_find_declaration(GtkMenuItem *menuitem, gpointer user_data)
+{
+	gchar *name;
+
+	name = get_selection();
+	if (name)
+		find_tags(name, TRUE, TRUE, MATCH_FULL);
+	g_free(name);
+}
+
+static void on_find_definition(GtkMenuItem *menuitem, gpointer user_data)
+{
+	gchar *name;
+
+	name = get_selection();
+	if (name)
+		find_tags(name, FALSE, TRUE, MATCH_FULL);
+	g_free(name);
+}
+
+static void create_dialog_find_file()
+{
+	GtkWidget *label, *vbox, *ebox, *entry;
+	GtkSizeGroup *size_group;
+
+	if (s_ft_dialog.widget)
+		return;
+
+	s_ft_dialog.widget = gtk_dialog_new_with_buttons(
+		_("Find Tag"), GTK_WINDOW(geany->main_widgets->window),
+		GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
+	gtk_dialog_add_button(GTK_DIALOG(s_ft_dialog.widget), "gtk-find", GTK_RESPONSE_ACCEPT);
+	gtk_dialog_set_default_response(GTK_DIALOG(s_ft_dialog.widget), GTK_RESPONSE_ACCEPT);
+
+	vbox = ui_dialog_vbox_new(GTK_DIALOG(s_ft_dialog.widget));
+	gtk_box_set_spacing(GTK_BOX(vbox), 9);
+
+	size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+	label = gtk_label_new(_("Search for:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_size_group_add_widget(size_group, label);
+
+	s_ft_dialog.combo = gtk_combo_box_text_new_with_entry();
+	entry = gtk_bin_get_child(GTK_BIN(s_ft_dialog.combo));
+
+	ui_entry_add_clear_icon(GTK_ENTRY(entry));
+	gtk_entry_set_width_chars(GTK_ENTRY(entry), 40);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
+	ui_entry_add_clear_icon(GTK_ENTRY(entry));
+	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+
+	ebox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(ebox), s_ft_dialog.combo, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
+
+	label = gtk_label_new(_("Match type:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_size_group_add_widget(size_group, label);
+
+	s_ft_dialog.combo_match = gtk_combo_box_new_text();
+	gtk_combo_box_append_text(GTK_COMBO_BOX(s_ft_dialog.combo_match), "full");
+	gtk_combo_box_append_text(GTK_COMBO_BOX(s_ft_dialog.combo_match), "prefix");
+	gtk_combo_box_append_text(GTK_COMBO_BOX(s_ft_dialog.combo_match), "pattern");
+	gtk_combo_box_set_active(GTK_COMBO_BOX(s_ft_dialog.combo_match), 1);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(label), s_ft_dialog.combo_match);
+
+	ebox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(ebox), s_ft_dialog.combo_match, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
+
+	s_ft_dialog.case_sensitive = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
+	gtk_button_set_focus_on_click(GTK_BUTTON(s_ft_dialog.case_sensitive), FALSE);
+
+	s_ft_dialog.declaration = gtk_check_button_new_with_mnemonic(_("_Declaration"));
+	gtk_button_set_focus_on_click(GTK_BUTTON(s_ft_dialog.declaration), FALSE);
+
+	g_object_unref(G_OBJECT(size_group));	/* auto destroy the size group */
+
+	gtk_container_add(GTK_CONTAINER(vbox), s_ft_dialog.case_sensitive);
+	gtk_container_add(GTK_CONTAINER(vbox), s_ft_dialog.declaration);
+	gtk_widget_show_all(vbox);
+}
+
+static void on_find_tag(GtkMenuItem *menuitem, gpointer user_data)
+{
+	gchar *selection;
+	GtkWidget *entry;
+
+	create_dialog_find_file();
+
+	entry = gtk_bin_get_child(GTK_BIN(s_ft_dialog.combo));
+
+	selection = get_selection();
+	if (selection)
+		gtk_entry_set_text(GTK_ENTRY(entry), selection);
+	g_free(selection);
+
+	gtk_widget_grab_focus(entry);
+
+	if (gtk_dialog_run(GTK_DIALOG(s_ft_dialog.widget)) == GTK_RESPONSE_ACCEPT)
+	{
+		const gchar *name;
+		gboolean case_sensitive, declaration;
+		MatchType match_type;
+
+		name = gtk_entry_get_text(GTK_ENTRY(entry));
+		case_sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s_ft_dialog.case_sensitive));
+		declaration = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s_ft_dialog.declaration));
+		match_type = gtk_combo_box_get_active(GTK_COMBO_BOX(s_ft_dialog.combo_match));
+
+		ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(s_ft_dialog.combo), name, 0);
+
+		find_tags(name, declaration, case_sensitive, match_type);
+	}
+
+	gtk_widget_hide(s_ft_dialog.widget);
+}
+
+static gboolean kb_callback(guint key_id)
+{
+	switch (key_id)
+	{
+		case KB_FIND_TAG:
+			on_find_tag(NULL, NULL);
+			return TRUE;
+		case KB_GENERATE_TAGS:
+			on_generate_tags(NULL, NULL);
+			return TRUE;
+	}
+	return FALSE;
+}
+
+void plugin_init(G_GNUC_UNUSED GeanyData * data)
+{
+	GeanyKeyGroup *key_group = plugin_set_key_group(geany_plugin, "GeanyCtags", KB_COUNT, kb_callback);
+	
+	s_context_sep_item = gtk_separator_menu_item_new();
+	gtk_widget_show(s_context_sep_item);
+	gtk_menu_shell_prepend(GTK_MENU_SHELL(geany->main_widgets->editor_menu), s_context_sep_item);
+
+	s_context_fdec_item = gtk_menu_item_new_with_mnemonic(_("Find Tag Declaration (geanyctags)"));
+	gtk_widget_show(s_context_fdec_item);
+	gtk_menu_shell_prepend(GTK_MENU_SHELL(geany->main_widgets->editor_menu), s_context_fdec_item);
+	g_signal_connect((gpointer) s_context_fdec_item, "activate", G_CALLBACK(on_find_declaration), NULL);
+
+	s_context_fdef_item = gtk_menu_item_new_with_mnemonic(_("Find Tag Definition (geanyctags)"));
+	gtk_widget_show(s_context_fdef_item);
+	gtk_menu_shell_prepend(GTK_MENU_SHELL(geany->main_widgets->editor_menu), s_context_fdef_item);
+	g_signal_connect((gpointer) s_context_fdef_item, "activate", G_CALLBACK(on_find_definition), NULL);
+
+	s_sep_item = gtk_separator_menu_item_new();
+	gtk_widget_show(s_sep_item);
+	gtk_container_add(GTK_CONTAINER(geany->main_widgets->project_menu), s_sep_item);
+
+	s_gt_item = gtk_menu_item_new_with_mnemonic(_("Generate tags"));
+	gtk_widget_show(s_gt_item);
+	gtk_container_add(GTK_CONTAINER(geany->main_widgets->project_menu), s_gt_item);
+	g_signal_connect((gpointer) s_gt_item, "activate", G_CALLBACK(on_generate_tags), NULL);
+	keybindings_set_item(key_group, KB_GENERATE_TAGS, NULL,
+		0, 0, "generate_tags", _("Generate tags"), s_gt_item);
+
+	s_ft_item = gtk_menu_item_new_with_mnemonic(_("Find tag"));
+	gtk_widget_show(s_ft_item);
+	gtk_container_add(GTK_CONTAINER(geany->main_widgets->project_menu), s_ft_item);
+	g_signal_connect((gpointer) s_ft_item, "activate", G_CALLBACK(on_find_tag), NULL);
+	keybindings_set_item(key_group, KB_FIND_TAG, NULL,
+		0, 0, "find_tag", _("Find tag"), s_ft_item);
+
+	set_widgets_sensitive(FALSE);
+}
+
+void plugin_cleanup(void)
+{
+	gtk_widget_destroy(s_context_fdec_item);
+	gtk_widget_destroy(s_context_fdef_item);
+	gtk_widget_destroy(s_context_sep_item);
+
+	gtk_widget_destroy(s_ft_item);
+	gtk_widget_destroy(s_gt_item);
+	gtk_widget_destroy(s_sep_item);
+
+	if (s_ft_dialog.widget)
+		gtk_widget_destroy(s_ft_dialog.widget);
+	s_ft_dialog.widget = NULL;
+}


Modified: geanyctags/src/readtags.c
959 lines changed, 959 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,959 @@
+/*
+*   $Id: readtags.c 592 2007-07-31 03:30:41Z dhiebert $
+*
+*   Copyright (c) 1996-2003, Darren Hiebert
+*
+*   This source code is released into the public domain.
+*
+*   This module contains functions for reading tag files.
+*/
+
+/*
+*   INCLUDE FILES
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>  /* to declare off_t */
+
+#include "readtags.h"
+
+/*
+*   MACROS
+*/
+#define TAB '\t'
+
+
+/*
+*   DATA DECLARATIONS
+*/
+typedef struct {
+	size_t size;
+	char *buffer;
+} vstring;
+
+/* Information about current tag file */
+struct sTagFile {
+		/* has the file been opened and this structure initialized? */
+	short initialized;
+		/* format of tag file */
+	short format;
+		/* how is the tag file sorted? */
+	sortType sortMethod;
+		/* pointer to file structure */
+	FILE* fp;
+		/* file position of first character of `line' */
+	off_t pos;
+		/* size of tag file in seekable positions */
+	off_t size;
+		/* last line read */
+	vstring line;
+		/* name of tag in last line read */
+	vstring name;
+		/* defines tag search state */
+	struct {
+				/* file position of last match for tag */
+			off_t pos; 
+				/* name of tag last searched for */
+			char *name;
+				/* length of name for partial matches */
+			size_t nameLength;
+				/* peforming partial match */
+			short partial;
+				/* ignoring case */
+			short ignorecase;
+	} search;
+		/* miscellaneous extension fields */
+	struct {
+				/* number of entries in `list' */
+			unsigned short max;
+				/* list of key value pairs */
+			tagExtensionField *list;
+	} fields;
+		/* buffers to be freed at close */
+	struct {
+			/* name of program author */
+		char *author;
+			/* name of program */
+		char *name;
+			/* URL of distribution */
+		char *url;
+			/* program version */
+		char *version;
+	} program;
+};
+
+/*
+*   DATA DEFINITIONS
+*/
+const char *const EmptyString = "";
+const char *const PseudoTagPrefix = "!_";
+
+/*
+*   FUNCTION DEFINITIONS
+*/
+
+/*
+ * Compare two strings, ignoring case.
+ * Return 0 for match, < 0 for smaller, > 0 for bigger
+ * Make sure case is folded to uppercase in comparison (like for 'sort -f')
+ * This makes a difference when one of the chars lies between upper and lower
+ * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
+ */
+static int struppercmp (const char *s1, const char *s2)
+{
+	int result;
+	do
+	{
+		result = toupper ((int) *s1) - toupper ((int) *s2);
+	} while (result == 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
+	return result;
+}
+
+static int strnuppercmp (const char *s1, const char *s2, size_t n)
+{
+	int result;
+	do
+	{
+		result = toupper ((int) *s1) - toupper ((int) *s2);
+	} while (result == 0  &&  --n > 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
+	return result;
+}
+
+static int growString (vstring *s)
+{
+	int result = 0;
+	size_t newLength;
+	char *newLine;
+	if (s->size == 0)
+	{
+		newLength = 128;
+		newLine = (char*) malloc (newLength);
+		*newLine = '\0';
+	}
+	else
+	{
+		newLength = 2 * s->size;
+		newLine = (char*) realloc (s->buffer, newLength);
+	}
+	if (newLine == NULL)
+		perror ("string too large");
+	else
+	{
+		s->buffer = newLine;
+		s->size = newLength;
+		result = 1;
+	}
+	return result;
+}
+
+/* Copy name of tag out of tag line */
+static void copyName (tagFile *const file)
+{
+	size_t length;
+	const char *end = strchr (file->line.buffer, '\t');
+	if (end == NULL)
+	{
+		end = strchr (file->line.buffer, '\n');
+		if (end == NULL)
+			end = strchr (file->line.buffer, '\r');
+	}
+	if (end != NULL)
+		length = end - file->line.buffer;
+	else
+		length = strlen (file->line.buffer);
+	while (length >= file->name.size)
+		growString (&file->name);
+	strncpy (file->name.buffer, file->line.buffer, length);
+	file->name.buffer [length] = '\0';
+}
+
+static int readTagLineRaw (tagFile *const file)
+{
+	int result = 1;
+	int reReadLine;
+
+	/*  If reading the line places any character other than a null or a
+	 *  newline at the last character position in the buffer (one less than
+	 *  the buffer size), then we must resize the buffer and reattempt to read
+	 *  the line.
+	 */
+	do
+	{
+		char *const pLastChar = file->line.buffer + file->line.size - 2;
+		char *line;
+
+		file->pos = ftell (file->fp);
+		reReadLine = 0;
+		*pLastChar = '\0';
+		line = fgets (file->line.buffer, (int) file->line.size, file->fp);
+		if (line == NULL)
+		{
+			/* read error */
+			if (! feof (file->fp))
+				perror ("readTagLine");
+			result = 0;
+		}
+		else if (*pLastChar != '\0'  &&
+					*pLastChar != '\n'  &&  *pLastChar != '\r')
+		{
+			/*  buffer overflow */
+			growString (&file->line);
+			fseek (file->fp, file->pos, SEEK_SET);
+			reReadLine = 1;
+		}
+		else
+		{
+			size_t i = strlen (file->line.buffer);
+			while (i > 0  &&
+				   (file->line.buffer [i - 1] == '\n' || file->line.buffer [i - 1] == '\r'))
+			{
+				file->line.buffer [i - 1] = '\0';
+				--i;
+			}
+		}
+	} while (reReadLine  &&  result);
+	if (result)
+		copyName (file);
+	return result;
+}
+
+static int readTagLine (tagFile *const file)
+{
+	int result;
+	do
+	{
+		result = readTagLineRaw (file);
+	} while (result && *file->name.buffer == '\0');
+	return result;
+}
+
+static tagResult growFields (tagFile *const file)
+{
+	tagResult result = TagFailure;
+	unsigned short newCount = (unsigned short) 2 * file->fields.max;
+	tagExtensionField *newFields = (tagExtensionField*)
+			realloc (file->fields.list, newCount * sizeof (tagExtensionField));
+	if (newFields == NULL)
+		perror ("too many extension fields");
+	else
+	{
+		file->fields.list = newFields;
+		file->fields.max = newCount;
+		result = TagSuccess;
+	}
+	return result;
+}
+
+static void parseExtensionFields (tagFile *const file, tagEntry *const entry,
+								  char *const string)
+{
+	char *p = string;
+	while (p != NULL  &&  *p != '\0')
+	{
+		while (*p == TAB)
+			*p++ = '\0';
+		if (*p != '\0')
+		{
+			char *colon;
+			char *field = p;
+			p = strchr (p, TAB);
+			if (p != NULL)
+				*p++ = '\0';
+			colon = strchr (field, ':');
+			if (colon == NULL)
+				entry->kind = field;
+			else
+			{
+				const char *key = field;
+				const char *value = colon + 1;
+				*colon = '\0';
+				if (strcmp (key, "kind") == 0)
+					entry->kind = value;
+				else if (strcmp (key, "file") == 0)
+					entry->fileScope = 1;
+				else if (strcmp (key, "line") == 0)
+					entry->address.lineNumber = atol (value);
+				else
+				{
+					if (entry->fields.count == file->fields.max)
+						growFields (file);
+					file->fields.list [entry->fields.count].key = key;
+					file->fields.list [entry->fields.count].value = value;
+					++entry->fields.count;
+				}
+			}
+		}
+	}
+}
+
+static void parseTagLine (tagFile *file, tagEntry *const entry)
+{
+	int i;
+	char *p = file->line.buffer;
+	char *tab = strchr (p, TAB);
+
+	entry->fields.list = NULL;
+	entry->fields.count = 0;
+	entry->kind = NULL;
+	entry->fileScope = 0;
+
+	entry->name = p;
+	if (tab != NULL)
+	{
+		*tab = '\0';
+		p = tab + 1;
+		entry->file = p;
+		tab = strchr (p, TAB);
+		if (tab != NULL)
+		{
+			int fieldsPresent;
+			*tab = '\0';
+			p = tab + 1;
+			if (*p == '/'  ||  *p == '?')
+			{
+				/* parse pattern */
+				int delimiter = *(unsigned char*) p;
+				entry->address.lineNumber = 0;
+				entry->address.pattern = p;
+				do
+				{
+					p = strchr (p + 1, delimiter);
+				} while (p != NULL  &&  *(p - 1) == '\\');
+				if (p == NULL)
+				{
+					/* invalid pattern */
+				}
+				else
+					++p;
+			}
+			else if (isdigit ((int) *(unsigned char*) p))
+			{
+				/* parse line number */
+				entry->address.pattern = p;
+				entry->address.lineNumber = atol (p);
+				while (isdigit ((int) *(unsigned char*) p))
+					++p;
+			}
+			else
+			{
+				/* invalid pattern */
+			}
+			fieldsPresent = (strncmp (p, ";\"", 2) == 0);
+			*p = '\0';
+			if (fieldsPresent)
+				parseExtensionFields (file, entry, p + 2);
+		}
+	}
+	if (entry->fields.count > 0)
+		entry->fields.list = file->fields.list;
+	for (i = entry->fields.count  ;  i < file->fields.max  ;  ++i)
+	{
+		file->fields.list [i].key = NULL;
+		file->fields.list [i].value = NULL;
+	}
+}
+
+static char *duplicate (const char *str)
+{
+	char *result = NULL;
+	if (str != NULL)
+	{
+		result = strdup (str);
+		if (result == NULL)
+			perror (NULL);
+	}
+	return result;
+}
+
+static void readPseudoTags (tagFile *const file, tagFileInfo *const info)
+{
+	fpos_t startOfLine;
+	const size_t prefixLength = strlen (PseudoTagPrefix);
+	if (info != NULL)
+	{
+		info->file.format     = 1;
+		info->file.sort       = TAG_UNSORTED;
+		info->program.author  = NULL;
+		info->program.name    = NULL;
+		info->program.url     = NULL;
+		info->program.version = NULL;
+	}
+	while (1)
+	{
+		fgetpos (file->fp, &startOfLine);
+		if (! readTagLine (file))
+			break;
+		if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
+			break;
+		else
+		{
+			tagEntry entry;
+			const char *key, *value;
+			parseTagLine (file, &entry);
+			key = entry.name + prefixLength;
+			value = entry.file;
+			if (strcmp (key, "TAG_FILE_SORTED") == 0)
+				file->sortMethod = (sortType) atoi (value);
+			else if (strcmp (key, "TAG_FILE_FORMAT") == 0)
+				file->format = (short) atoi (value);
+			else if (strcmp (key, "TAG_PROGRAM_AUTHOR") == 0)
+				file->program.author = duplicate (value);
+			else if (strcmp (key, "TAG_PROGRAM_NAME") == 0)
+				file->program.name = duplicate (value);
+			else if (strcmp (key, "TAG_PROGRAM_URL") == 0)
+				file->program.url = duplicate (value);
+			else if (strcmp (key, "TAG_PROGRAM_VERSION") == 0)
+				file->program.version = duplicate (value);
+			if (info != NULL)
+			{
+				info->file.format     = file->format;
+				info->file.sort       = file->sortMethod;
+				info->program.author  = file->program.author;
+				info->program.name    = file->program.name;
+				info->program.url     = file->program.url;
+				info->program.version = file->program.version;
+			}
+		}
+	}
+	fsetpos (file->fp, &startOfLine);
+}
+
+static void gotoFirstLogicalTag (tagFile *const file)
+{
+	fpos_t startOfLine;
+	const size_t prefixLength = strlen (PseudoTagPrefix);
+	rewind (file->fp);
+	while (1)
+	{
+		fgetpos (file->fp, &startOfLine);
+		if (! readTagLine (file))
+			break;
+		if (strncmp (file->line.buffer, PseudoTagPrefix, prefixLength) != 0)
+			break;
+	}
+	fsetpos (file->fp, &startOfLine);
+}
+
+static tagFile *initialize (const char *const filePath, tagFileInfo *const info)
+{
+	tagFile *result = (tagFile*) calloc ((size_t) 1, sizeof (tagFile));
+	if (result != NULL)
+	{
+		growString (&result->line);
+		growString (&result->name);
+		result->fields.max = 20;
+		result->fields.list = (tagExtensionField*) calloc (
+			result->fields.max, sizeof (tagExtensionField));
+		result->fp = fopen (filePath, "r");
+		if (result->fp == NULL)
+		{
+			free (result);
+			result = NULL;
+			info->status.error_number = errno;
+		}
+		else
+		{
+			fseek (result->fp, 0, SEEK_END);
+			result->size = ftell (result->fp);
+			rewind (result->fp);
+			readPseudoTags (result, info);
+			info->status.opened = 1;
+			result->initialized = 1;
+		}
+	}
+	return result;
+}
+
+static void terminate (tagFile *const file)
+{
+	fclose (file->fp);
+
+	free (file->line.buffer);
+	free (file->name.buffer);
+	free (file->fields.list);
+
+	if (file->program.author != NULL)
+		free (file->program.author);
+	if (file->program.name != NULL)
+		free (file->program.name);
+	if (file->program.url != NULL)
+		free (file->program.url);
+	if (file->program.version != NULL)
+		free (file->program.version);
+	if (file->search.name != NULL)
+		free (file->search.name);
+
+	memset (file, 0, sizeof (tagFile));
+
+	free (file);
+}
+
+static tagResult readNext (tagFile *const file, tagEntry *const entry)
+{
+	tagResult result;
+	if (file == NULL  ||  ! file->initialized)
+		result = TagFailure;
+	else if (! readTagLine (file))
+		result = TagFailure;
+	else
+	{
+		if (entry != NULL)
+			parseTagLine (file, entry);
+		result = TagSuccess;
+	}
+	return result;
+}
+
+static const char *readFieldValue (
+	const tagEntry *const entry, const char *const key)
+{
+	const char *result = NULL;
+	int i;
+	if (strcmp (key, "kind") == 0)
+		result = entry->kind;
+	else if (strcmp (key, "file") == 0)
+		result = EmptyString;
+	else for (i = 0  ;  i < entry->fields.count  &&  result == NULL  ;  ++i)
+		if (strcmp (entry->fields.list [i].key, key) == 0)
+			result = entry->fields.list [i].value;
+	return result;
+}
+
+static int readTagLineSeek (tagFile *const file, const off_t pos)
+{
+	int result = 0;
+	if (fseek (file->fp, pos, SEEK_SET) == 0)
+	{
+		result = readTagLine (file);  /* read probable partial line */
+		if (pos > 0  &&  result)
+			result = readTagLine (file);  /* read complete line */
+	}
+	return result;
+}
+
+static int nameComparison (tagFile *const file)
+{
+	int result;
+	if (file->search.ignorecase)
+	{
+		if (file->search.partial)
+			result = strnuppercmp (file->search.name, file->name.buffer,
+					file->search.nameLength);
+		else
+			result = struppercmp (file->search.name, file->name.buffer);
+	}
+	else
+	{
+		if (file->search.partial)
+			result = strncmp (file->search.name, file->name.buffer,
+					file->search.nameLength);
+		else
+			result = strcmp (file->search.name, file->name.buffer);
+	}
+	return result;
+}
+
+static void findFirstNonMatchBefore (tagFile *const file)
+{
+#define JUMP_BACK 512
+	int more_lines;
+	int comp;
+	off_t start = file->pos;
+	off_t pos = start;
+	do
+	{
+		if (pos < (off_t) JUMP_BACK)
+			pos = 0;
+		else
+			pos = pos - JUMP_BACK;
+		more_lines = readTagLineSeek (file, pos);
+		comp = nameComparison (file);
+	} while (more_lines  &&  comp == 0  &&  pos > 0  &&  pos < start);
+}
+
+static tagResult findFirstMatchBefore (tagFile *const file)
+{
+	tagResult result = TagFailure;
+	int more_lines;
+	off_t start = file->pos;
+	findFirstNonMatchBefore (file);
+	do
+	{
+		more_lines = readTagLine (file);
+		if (nameComparison (file) == 0)
+			result = TagSuccess;
+	} while (more_lines  &&  result != TagSuccess  &&  file->pos < start);
+	return result;
+}
+
+static tagResult findBinary (tagFile *const file)
+{
+	tagResult result = TagFailure;
+	off_t lower_limit = 0;
+	off_t upper_limit = file->size;
+	off_t last_pos = 0;
+	off_t pos = upper_limit / 2;
+	while (result != TagSuccess)
+	{
+		if (! readTagLineSeek (file, pos))
+		{
+			/* in case we fell off end of file */
+			result = findFirstMatchBefore (file);
+			break;
+		}
+		else if (pos == last_pos)
+		{
+			/* prevent infinite loop if we backed up to beginning of file */
+			break;
+		}
+		else
+		{
+			const int comp = nameComparison (file);
+			last_pos = pos;
+			if (comp < 0)
+			{
+				upper_limit = pos;
+				pos = lower_limit + ((upper_limit - lower_limit) / 2);
+			}
+			else if (comp > 0)
+			{
+				lower_limit = pos;
+				pos = lower_limit + ((upper_limit - lower_limit) / 2);
+			}
+			else if (pos == 0)
+				result = TagSuccess;
+			else
+				result = findFirstMatchBefore (file);
+		}
+	}
+	return result;
+}
+
+static tagResult findSequential (tagFile *const file)
+{
+	tagResult result = TagFailure;
+	if (file->initialized)
+	{
+		while (result == TagFailure  &&  readTagLine (file))
+		{
+			if (nameComparison (file) == 0)
+				result = TagSuccess;
+		}
+	}
+	return result;
+}
+
+static tagResult find (tagFile *const file, tagEntry *const entry,
+					   const char *const name, const int options)
+{
+	tagResult result;
+	if (file->search.name != NULL)
+		free (file->search.name);
+	file->search.name = duplicate (name);
+	file->search.nameLength = strlen (name);
+	file->search.partial = (options & TAG_PARTIALMATCH) != 0;
+	file->search.ignorecase = (options & TAG_IGNORECASE) != 0;
+	fseek (file->fp, 0, SEEK_END);
+	file->size = ftell (file->fp);
+	rewind (file->fp);
+	if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
+		(file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase))
+	{
+#ifdef DEBUG
+		printf ("<performing binary search>\n");
+#endif
+		result = findBinary (file);
+	}
+	else
+	{
+#ifdef DEBUG
+		printf ("<performing sequential search>\n");
+#endif
+		result = findSequential (file);
+	}
+
+	if (result != TagSuccess)
+		file->search.pos = file->size;
+	else
+	{
+		file->search.pos = file->pos;
+		if (entry != NULL)
+			parseTagLine (file, entry);
+	}
+	return result;
+}
+
+static tagResult findNext (tagFile *const file, tagEntry *const entry)
+{
+	tagResult result;
+	if ((file->sortMethod == TAG_SORTED      && !file->search.ignorecase) ||
+		(file->sortMethod == TAG_FOLDSORTED  &&  file->search.ignorecase))
+	{
+		result = tagsNext (file, entry);
+		if (result == TagSuccess  && nameComparison (file) != 0)
+			result = TagFailure;
+	}
+	else
+	{
+		result = findSequential (file);
+		if (result == TagSuccess  &&  entry != NULL)
+			parseTagLine (file, entry);
+	}
+	return result;
+}
+
+/*
+*  EXTERNAL INTERFACE
+*/
+
+extern tagFile *tagsOpen (const char *const filePath, tagFileInfo *const info)
+{
+	return initialize (filePath, info);
+}
+
+extern tagResult tagsSetSortType (tagFile *const file, const sortType type)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+	{
+		file->sortMethod = type;
+		result = TagSuccess;
+	}
+	return result;
+}
+
+extern tagResult tagsFirst (tagFile *const file, tagEntry *const entry)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+	{
+		gotoFirstLogicalTag (file);
+		result = readNext (file, entry);
+	}
+	return result;
+}
+
+extern tagResult tagsNext (tagFile *const file, tagEntry *const entry)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+		result = readNext (file, entry);
+	return result;
+}
+
+extern const char *tagsField (const tagEntry *const entry, const char *const key)
+{
+	const char *result = NULL;
+	if (entry != NULL)
+		result = readFieldValue (entry, key);
+	return result;
+}
+
+extern tagResult tagsFind (tagFile *const file, tagEntry *const entry,
+						   const char *const name, const int options)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+		result = find (file, entry, name, options);
+	return result;
+}
+
+extern tagResult tagsFindNext (tagFile *const file, tagEntry *const entry)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+		result = findNext (file, entry);
+	return result;
+}
+
+extern tagResult tagsClose (tagFile *const file)
+{
+	tagResult result = TagFailure;
+	if (file != NULL  &&  file->initialized)
+	{
+		terminate (file);
+		result = TagSuccess;
+	}
+	return result;
+}
+
+/*
+*  TEST FRAMEWORK
+*/
+
+#ifdef READTAGS_MAIN
+
+static const char *TagFileName = "tags";
+static const char *ProgramName;
+static int extensionFields;
+static int SortOverride;
+static sortType SortMethod;
+
+static void printTag (const tagEntry *entry)
+{
+	int i;
+	int first = 1;
+	const char* separator = ";\"";
+	const char* const empty = "";
+/* "sep" returns a value only the first time it is evaluated */
+#define sep (first ? (first = 0, separator) : empty)
+	printf ("%s\t%s\t%s",
+		entry->name, entry->file, entry->address.pattern);
+	if (extensionFields)
+	{
+		if (entry->kind != NULL  &&  entry->kind [0] != '\0')
+			printf ("%s\tkind:%s", sep, entry->kind);
+		if (entry->fileScope)
+			printf ("%s\tfile:", sep);
+#if 0
+		if (entry->address.lineNumber > 0)
+			printf ("%s\tline:%lu", sep, entry->address.lineNumber);
+#endif
+		for (i = 0  ;  i < entry->fields.count  ;  ++i)
+			printf ("%s\t%s:%s", sep, entry->fields.list [i].key,
+				entry->fields.list [i].value);
+	}
+	putchar ('\n');
+#undef sep
+}
+
+static void findTag (const char *const name, const int options)
+{
+	tagFileInfo info;
+	tagEntry entry;
+	tagFile *const file = tagsOpen (TagFileName, &info);
+	if (file == NULL)
+	{
+		fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
+				ProgramName, strerror (info.status.error_number), name);
+		exit (1);
+	}
+	else
+	{
+		if (SortOverride)
+			tagsSetSortType (file, SortMethod);
+		if (tagsFind (file, &entry, name, options) == TagSuccess)
+		{
+			do
+			{
+				printTag (&entry);
+			} while (tagsFindNext (file, &entry) == TagSuccess);
+		}
+		tagsClose (file);
+	}
+}
+
+static void listTags (void)
+{
+	tagFileInfo info;
+	tagEntry entry;
+	tagFile *const file = tagsOpen (TagFileName, &info);
+	if (file == NULL)
+	{
+		fprintf (stderr, "%s: cannot open tag file: %s: %s\n",
+				ProgramName, strerror (info.status.error_number), TagFileName);
+		exit (1);
+	}
+	else
+	{
+		while (tagsNext (file, &entry) == TagSuccess)
+			printTag (&entry);
+		tagsClose (file);
+	}
+}
+
+const char *const Usage =
+	"Find tag file entries matching specified names.\n\n"
+	"Usage: %s [-ilp] [-s[0|1]] [-t file] [name(s)]\n\n"
+	"Options:\n"
+	"    -e           Include extension fields in output.\n"
+	"    -i           Perform case-insensitive matching.\n"
+	"    -l           List all tags.\n"
+	"    -p           Perform partial matching.\n"
+	"    -s[0|1|2]    Override sort detection of tag file.\n"
+	"    -t file      Use specified tag file (default: \"tags\").\n"
+	"Note that options are acted upon as encountered, so order is significant.\n";
+
+extern int main (int argc, char **argv)
+{
+	int options = 0;
+	int actionSupplied = 0;
+	int i;
+	ProgramName = argv [0];
+	if (argc == 1)
+	{
+		fprintf (stderr, Usage, ProgramName);
+		exit (1);
+	}
+	for (i = 1  ;  i < argc  ;  ++i)
+	{
+		const char *const arg = argv [i];
+		if (arg [0] != '-')
+		{
+			findTag (arg, options);
+			actionSupplied = 1;
+		}
+		else
+		{
+			size_t j;
+			for (j = 1  ;  arg [j] != '\0'  ;  ++j)
+			{
+				switch (arg [j])
+				{
+					case 'e': extensionFields = 1;         break;
+					case 'i': options |= TAG_IGNORECASE;   break;
+					case 'p': options |= TAG_PARTIALMATCH; break;
+					case 'l': listTags (); actionSupplied = 1; break;
+			
+					case 't':
+						if (arg [j+1] != '\0')
+						{
+							TagFileName = arg + j + 1;
+							j += strlen (TagFileName);
+						}
+						else if (i + 1 < argc)
+							TagFileName = argv [++i];
+						else
+						{
+							fprintf (stderr, Usage, ProgramName);
+							exit (1);
+						}
+						break;
+					case 's':
+						SortOverride = 1;
+						++j;
+						if (arg [j] == '\0')
+							SortMethod = TAG_SORTED;
+						else if (strchr ("012", arg[j]) != NULL)
+							SortMethod = (sortType) (arg[j] - '0');
+						else
+						{
+							fprintf (stderr, Usage, ProgramName);
+							exit (1);
+						}
+						break;
+					default:
+						fprintf (stderr, "%s: unknown option: %c\n",
+									ProgramName, arg[j]);
+						exit (1);
+						break;
+				}
+			}
+		}
+	}
+	if (! actionSupplied)
+	{
+		fprintf (stderr,
+			"%s: no action specified: specify tag name(s) or -l option\n",
+			ProgramName);
+		exit (1);
+	}
+	return 0;
+}
+
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */


Modified: geanyctags/src/readtags.h
252 lines changed, 252 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,252 @@
+/*
+*   $Id: readtags.h 443 2006-05-30 04:37:13Z darren $
+*
+*   Copyright (c) 1996-2003, Darren Hiebert
+*
+*   This source code is released for the public domain.
+*
+*   This file defines the public interface for looking up tag entries in tag
+*   files.
+*
+*   The functions defined in this interface are intended to provide tag file
+*   support to a software tool. The tag lookups provided are sufficiently fast
+*   enough to permit opening a sorted tag file, searching for a matching tag,
+*   then closing the tag file each time a tag is looked up (search times are
+*   on the order of hundreths of a second, even for huge tag files). This is
+*   the recommended use of this library for most tool applications. Adhering
+*   to this approach permits a user to regenerate a tag file at will without
+*   the tool needing to detect and resynchronize with changes to the tag file.
+*   Even for an unsorted 24MB tag file, tag searches take about one second.
+*/
+#ifndef READTAGS_H
+#define READTAGS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+*  MACROS
+*/
+
+/* Options for tagsSetSortType() */
+typedef enum {
+	TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED
+} sortType ;
+
+/* Options for tagsFind() */
+#define TAG_FULLMATCH     0x0
+#define TAG_PARTIALMATCH  0x1
+
+#define TAG_OBSERVECASE   0x0
+#define TAG_IGNORECASE    0x2
+
+/*
+*  DATA DECLARATIONS
+*/
+
+typedef enum { TagFailure = 0, TagSuccess = 1 } tagResult;
+
+struct sTagFile;
+
+typedef struct sTagFile tagFile;
+
+/* This structure contains information about the tag file. */
+typedef struct {
+
+	struct {
+			/* was the tag file successfully opened? */
+		int opened;
+
+			/* errno value when 'opened' is false */
+		int error_number;
+	} status;
+
+		/* information about the structure of the tag file */
+	struct {
+				/* format of tag file (1 = original, 2 = extended) */
+			short format;
+
+				/* how is the tag file sorted? */
+			sortType sort;
+	} file;
+
+
+		/* information about the program which created this tag file */
+	struct {
+			/* name of author of generating program (may be null) */
+		const char *author;
+
+			/* name of program (may be null) */
+		const char *name;
+
+			/* URL of distribution (may be null) */
+		const char *url;
+
+			/* program version (may be null) */
+		const char *version;
+	} program;
+
+} tagFileInfo;
+
+/* This structure contains information about an extension field for a tag.
+ * These exist at the end of the tag in the form "key:value").
+ */
+typedef struct {
+
+		/* the key of the extension field */
+	const char *key;
+
+		/* the value of the extension field (may be an empty string) */
+	const char *value;
+
+} tagExtensionField;
+
+/* This structure contains information about a specific tag. */
+typedef struct {
+
+		/* name of tag */
+	const char *name;
+
+		/* path of source file containing definition of tag */
+	const char *file;
+
+		/* address for locating tag in source file */
+	struct {
+			/* pattern for locating source line
+			 * (may be NULL if not present) */
+		const char *pattern;
+
+			/* line number in source file of tag definition
+			 * (may be zero if not known) */
+		unsigned long lineNumber;
+	} address;
+
+		/* kind of tag (may by name, character, or NULL if not known) */
+	const char *kind;
+
+		/* is tag of file-limited scope? */
+	short fileScope;
+
+		/* miscellaneous extension fields */
+	struct {
+			/* number of entries in `list' */
+		unsigned short count;
+
+			/* list of key value pairs */
+		tagExtensionField *list;
+	} fields;
+
+} tagEntry;
+
+
+/*
+*  FUNCTION PROTOTYPES
+*/
+
+/*
+*  This function must be called before calling other functions in this
+*  library. It is passed the path to the tag file to read and a (possibly
+*  null) pointer to a structure which, if not null, will be populated with
+*  information about the tag file. If successful, the function will return a
+*  handle which must be supplied to other calls to read information from the
+*  tag file, and info.status.opened will be set to true. If unsuccessful,
+*  info.status.opened will be set to false and info.status.error_number will
+*  be set to the errno value representing the system error preventing the tag
+*  file from being successfully opened.
+*/
+extern tagFile *tagsOpen (const char *const filePath, tagFileInfo *const info);
+
+/*
+*  This function allows the client to override the normal automatic detection
+*  of how a tag file is sorted. Permissible values for `type' are
+*  TAG_UNSORTED, TAG_SORTED, TAG_FOLDSORTED. Tag files in the new extended
+*  format contain a key indicating whether or not they are sorted. However,
+*  tag files in the original format do not contain such a key even when
+*  sorted, preventing this library from taking advantage of fast binary
+*  lookups. If the client knows that such an unmarked tag file is indeed
+*  sorted (or not), it can override the automatic detection. Note that
+*  incorrect lookup results will result if a tag file is marked as sorted when
+*  it actually is not. The function will return TagSuccess if called on an
+*  open tag file or TagFailure if not.
+*/
+extern tagResult tagsSetSortType (tagFile *const file, const sortType type);
+
+/*
+*  Reads the first tag in the file, if any. It is passed the handle to an
+*  opened tag file and a (possibly null) pointer to a structure which, if not
+*  null, will be populated with information about the first tag file entry.
+*  The function will return TagSuccess another tag entry is found, or
+*  TagFailure if not (i.e. it reached end of file).
+*/
+extern tagResult tagsFirst (tagFile *const file, tagEntry *const entry);
+
+/*
+*  Step to the next tag in the file, if any. It is passed the handle to an
+*  opened tag file and a (possibly null) pointer to a structure which, if not
+*  null, will be populated with information about the next tag file entry. The
+*  function will return TagSuccess another tag entry is found, or TagFailure
+*  if not (i.e. it reached end of file). It will always read the first tag in
+*  the file immediately after calling tagsOpen().
+*/
+extern tagResult tagsNext (tagFile *const file, tagEntry *const entry);
+
+/*
+*  Retrieve the value associated with the extension field for a specified key.
+*  It is passed a pointer to a structure already populated with values by a
+*  previous call to tagsNext(), tagsFind(), or tagsFindNext(), and a string
+*  containing the key of the desired extension field. If no such field of the
+*  specified key exists, the function will return null.
+*/
+extern const char *tagsField (const tagEntry *const entry, const char *const key);
+
+/*
+*  Find the first tag matching `name'. The structure pointed to by `entry'
+*  will be populated with information about the tag file entry. If a tag file
+*  is sorted using the C locale, a binary search algorithm is used to search
+*  the tag file, resulting in very fast tag lookups, even in huge tag files.
+*  Various options controlling the matches can be combined by bit-wise or-ing
+*  certain values together. The available values are:
+*
+*    TAG_PARTIALMATCH
+*        Tags whose leading characters match `name' will qualify.
+*
+*    TAG_FULLMATCH
+*        Only tags whose full lengths match `name' will qualify.
+*
+*    TAG_IGNORECASE
+*        Matching will be performed in a case-insenstive manner. Note that
+*        this disables binary searches of the tag file.
+*
+*    TAG_OBSERVECASE
+*        Matching will be performed in a case-senstive manner. Note that
+*        this enables binary searches of the tag file.
+*
+*  The function will return TagSuccess if a tag matching the name is found, or
+*  TagFailure if not.
+*/
+extern tagResult tagsFind (tagFile *const file, tagEntry *const entry, const char *const name, const int options);
+
+/*
+*  Find the next tag matching the name and options supplied to the most recent
+*  call to tagsFind() for the same tag file. The structure pointed to by
+*  `entry' will be populated with information about the tag file entry. The
+*  function will return TagSuccess if another tag matching the name is found,
+*  or TagFailure if not.
+*/
+extern tagResult tagsFindNext (tagFile *const file, tagEntry *const entry);
+
+/*
+*  Call tagsTerminate() at completion of reading the tag file, which will
+*  close the file and free any internal memory allocated. The function will
+*  return TagFailure is no file is currently open, TagSuccess otherwise.
+*/
+extern tagResult tagsClose (tagFile *const file);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
+
+/* vi:set tabstop=4 shiftwidth=4: */


Modified: geanyctags/wscript_build
29 lines changed, 29 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+#
+# WAF build script for geany-plugins - GProject
+#
+# Copyright 2010 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
+#
+# 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.
+#
+# $Id$
+
+from build.wafutils import build_plugin
+
+
+name = 'GeanyCtags'
+includes = ['geanyctags/src']
+
+build_plugin(bld, name, includes=includes)


Modified: geanyctags/wscript_configure
0 lines changed, 0 insertions(+), 0 deletions(-)
===================================================================
No diff available, check online


Modified: po/POTFILES.in
3 lines changed, 3 insertions(+), 0 deletions(-)
===================================================================
@@ -104,6 +104,9 @@ devhelp/devhelp/ige-conf-gconf.c
 devhelp/devhelp/ige-conf.h
 devhelp/devhelp/ige-conf-private.h
 
+# geanyctags
+geanyctags/src/geanyctags.c
+
 # geanydoc
 geanydoc/tests/unittests.c
 geanydoc/src/config.c



--------------
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