Branch: refs/heads/master Author: Jiří Techet techet@gmail.com Committer: Jiří Techet techet@gmail.com Date: Tue, 14 Oct 2014 17:44:41 UTC Commit: eb0a9f945e4914fda3a4d5ed7802c7ef9a3c86e2 https://github.com/geany/geany-plugins/commit/eb0a9f945e4914fda3a4d5ed7802c7...
Log Message: ----------- Add the GeanyCtags plugin
Modified Paths: -------------- 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
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@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 647 lines changed, 647 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,647 @@ +/* + * Copyright 2010-2014 Jiri Techet techet@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@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 +}; + + +PLUGIN_KEY_GROUP(geanyctags, 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 *out, *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, &out, &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(out); + 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. */ + gchar **argv = g_new0(gchar *, 2); + argv[0] = g_strconcat("del ", tag_filename, NULL); + argv[1] = NULL; + utils_spawn_sync(prj->base_path, argv, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL, NULL, NULL)); + g_strfreev(argv); + + 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)) + { + gint len = sci_get_selected_text_length(editor->sci); + + ret = g_malloc(len + 1); + sci_get_selected_text(editor->sci, ret); + } + 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 void kb_callback(guint key_id) +{ + switch (key_id) + { + case KB_FIND_TAG: + on_find_tag(NULL, NULL); + break; + case KB_GENERATE_TAGS: + on_generate_tags(NULL, NULL); + break; + } +} + +void plugin_init(G_GNUC_UNUSED GeanyData * data) +{ + 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(plugin_key_group, KB_GENERATE_TAGS, kb_callback, + 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(plugin_key_group, KB_FIND_TAG, kb_callback, + 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_gt_item); + gtk_widget_destroy(s_sep_item); +}
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
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
plugins-commits@lists.geany.org