From ceb9c66cf3c92798f17c0d14f8d4149dcfeb7a26 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:33:22 -0400 Subject: [PATCH 01/30] Auto-run Templating new plugin for geany/geany-plugins#1577 --- MAINTAINERS | 7 + Makefile.am | 4 + autorun/AUTHORS | 1 + autorun/COPYING | 340 ++++++++++++++++++++++++++++++++++++++ autorun/ChangeLog | 0 autorun/Makefile.am | 4 + autorun/NEWS | 2 + autorun/README | 130 +++++++++++++++ autorun/THANKS | 12 ++ autorun/src/Makefile.am | 16 ++ autorun/src/plugin_main.c | 91 ++++++++++ build/autorun.m4 | 9 + configure.ac | 1 + po/POTFILES.in | 3 + 14 files changed, 620 insertions(+) create mode 100644 autorun/AUTHORS create mode 100644 autorun/COPYING create mode 100644 autorun/ChangeLog create mode 100644 autorun/Makefile.am create mode 100644 autorun/NEWS create mode 100644 autorun/README create mode 100644 autorun/THANKS create mode 100644 autorun/src/Makefile.am create mode 100644 autorun/src/plugin_main.c create mode 100644 build/autorun.m4 diff --git a/MAINTAINERS b/MAINTAINERS index 8a7a8efdea..039847a251 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -33,6 +33,13 @@ M: Pavel Roschin W: S: Maintained +autorun +P: +g: @WrapEarnPass +M: +W: +S: Maintained + codenav P: Federico Reghenzani g: diff --git a/Makefile.am b/Makefile.am index a3f1c10f80..3b5b3da1f0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,6 +23,10 @@ if ENABLE_AUTOMARK SUBDIRS += automark endif +if ENABLE_AUTORUN +SUBDIRS += autorun +endif + if ENABLE_CODENAV SUBDIRS += codenav endif diff --git a/autorun/AUTHORS b/autorun/AUTHORS new file mode 100644 index 0000000000..e71ce84901 --- /dev/null +++ b/autorun/AUTHORS @@ -0,0 +1 @@ +WrapEarnPass diff --git a/autorun/COPYING b/autorun/COPYING new file mode 100644 index 0000000000..8c4c849e23 --- /dev/null +++ b/autorun/COPYING @@ -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. + + + Copyright (C) + + 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. + + , 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. diff --git a/autorun/ChangeLog b/autorun/ChangeLog new file mode 100644 index 0000000000..e69de29bb2 diff --git a/autorun/Makefile.am b/autorun/Makefile.am new file mode 100644 index 0000000000..971a7ec717 --- /dev/null +++ b/autorun/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/build/vars.auxfiles.mk + +SUBDIRS = src +plugin = autorun diff --git a/autorun/NEWS b/autorun/NEWS new file mode 100644 index 0000000000..19c2978907 --- /dev/null +++ b/autorun/NEWS @@ -0,0 +1,2 @@ +Jun 2026 +* Created plugin, initial commit (work in progress) diff --git a/autorun/README b/autorun/README new file mode 100644 index 0000000000..566c466664 --- /dev/null +++ b/autorun/README @@ -0,0 +1,130 @@ +========= +Auto-run +========= + +.. contents:: + +About +===== + +The Auto-run plugin is an extension that makes it possible to add action handlers to +geany. The initial release targets document-save and document-before-save + +Usage +===== + +Enabling the plugin +------------------- +The plugin can be enabled in the plugin manager. +There will also be a new entry in the "Tools" menu. + +The Auto-run menu +------------------ +The Auto-run menu allows you to manage the Project level Auto-run configs. + +Manually setting Auto-run configs +--------------------------------- +You can manually set Project level Auto-run configs under the keyfile heading [autorun], +using https://wiki.geany.org/howtos/configurebuildmenu#build-menu style entries. + +FILETYPEIA_NN_TA + +FILETYPE is a Geany filetype label, e.g. (C|C++|C#|JSON) from geany/filetype_extensions.conf + +IA(Intercepted Action) = (BS|OS), +* BS is Geany document-before-save +* OS is Geany document-save (on save) + +NN = (00|01|...) +NN is a two digit number allowing for multiple tasks for a given entry. + +TA(Target Action) = (CM|WD) +* CM is the command to run +* WD is the working directory to run from. + +Auto-run supports the following replacements in CM and WD +%f - the basename of the file being saved. +%e - the basename but without the last extension. +%d - the absolute path of the directory of the file being saved. +%p - the absolute path of the base directory of the currently open project. + +CBS_00_CM = formatter %f +CBS_00_WD = %d +COS_00_CM = linter %f +COS_00_WD = %p +#Run formatter on %f from %d before file write, and then linter on the newly written %f from %p +# when attempting to save a C file type. + +C++OS_00_CM = cppcheck %f --output-file=%p/cppcheck.log +C++OS_00_WD = %d +C++OS_01_CM = clang-tidy -p %p --format-style=file %f +C++OS_01_WD = %p +#Run cppcheck on %f from %d, and clang-tidy on %f from %p after file write when saving a C++ file type. + +Set Auto-run actions across Projects +------------------------------------ +To set Autorun across Projects without having to edit each .geany file, you can use filedefs +https://www.geany.org/manual/#filetype-definition-files under the keyfile heading [autorun] +with a syntax similar to the Project configs. + +AC_NN_FF +where +AC = (BS|OS), NN = (00|01|...), FF=(LB|CM|WD) + +BS_00_LB = format +BS_00_CM = formatter %f +BS_00_WD = %d + +When Auto-run loads, it will scan all User config dir filedefs/filetypes.* +for any [autorun] sections and set actions for all matching filetypes. +User config dir filedefs settings will be overridden by any Project settings. +filedefs/filetypes.c +BS_00_CM = formatter %f +project.geany +CBS_00_CM = fancyformatter %f + +Autorun will run fancyformatter before save for C files as long as project.geany is open. + +All Auto-run actions will target the Geany Terminal pane. + +Known issues +============ + +Auto-run does not support forking, any commands will be run one after another. + +License +======= + +The Auto-run plugin 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 +========= + +The Auto-run plugin 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 +========================================= + +Please post any ideas, feature requests, questions and bugs in the +github issue tracker. + +History +======= + +This is a short release history showing the major changes: + +**0.1:** + Created plugin diff --git a/autorun/THANKS b/autorun/THANKS new file mode 100644 index 0000000000..056002abd6 --- /dev/null +++ b/autorun/THANKS @@ -0,0 +1,12 @@ +What's this file about? +----------------------- +This file lists all external people that have contributed to this project. + +Workbench Plugin: +------------------------- +This plugin is heavily based on working with the Workbench plugin. +Thanks to LarsGit223. + +Geany and Geany plugins: +------------------------ +Thanks to all Geany developers for creating the best IDE a dev could ask for. diff --git a/autorun/src/Makefile.am b/autorun/src/Makefile.am new file mode 100644 index 0000000000..4c7f0a1e75 --- /dev/null +++ b/autorun/src/Makefile.am @@ -0,0 +1,16 @@ +include $(top_srcdir)/build/vars.build.mk +plugin = autorun + +geanyplugins_LTLIBRARIES = autorun.la + +autorun_la_SOURCES = \ + plugin_main.c + +autorun_la_CPPFLAGS = $(AM_CPPFLAGS) \ + -DG_LOG_DOMAIN=\"Autorun\" +autorun_la_CFLAGS = $(AM_CFLAGS) $(WORKBENCH_CFLAGS) \ + -I$(top_srcdir)/utils/src +autorun_la_LIBADD = $(COMMONLIBS) \ + $(AUTORUN_LIBS) \ + $(top_builddir)/utils/src/libgeanypluginutils.la +include $(top_srcdir)/build/cppcheck.mk diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c new file mode 100644 index 0000000000..853c97263c --- /dev/null +++ b/autorun/src/plugin_main.c @@ -0,0 +1,91 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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 + +/* Handler to read any Project declared Auto-run configs */ +static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ + //if the keyfile has an [autorun] section + //load any handlers + //over any existing [autorun] handlers +} + +/* Handler to clear any old and read any new Project declared Auto-run configs */ +static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data) { + //if the keyfile has an [autorun] section + //load any handlers + //over any existing [autorun] handlers +} + +/* Handler to clear any old Project declared Auto-run configs */ +static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer user_data) { + //if we stashed a filetypes.FILE from plugin-init + //unload handlers and fallback to any filetypes.FILE +} + +/* Handler to run any applicable Auto-run configs after a write*/ +void on_doc_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ + //if there are any [autorun] OS handlers that exist for this FT + //iterate through them and run in Terminal +} + +/* Handler to run any applicable Auto-run configs before a write*/ +void on_doc_before_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ + //if there are any [autorun] BS handlers that exist for this FT + //iterate through them and run them in Terminal +} + +PluginCallback plugin_callbacks[] = { + { "project-open", (GCallback)&on_project_open, TRUE, NULL }, + { "project-close", (GCallback)&on_project_close, TRUE, NULL }, + { "project-save", (GCallback)&on_project_save, TRUE, NULL }, + { "document-save", (GCallback)&on_doc_save, TRUE, NULL}, + { "document-before-save", (GCallback)&on_doc_before_save, TRUE, NULL}, + { NULL, NULL, FALSE, NULL } +}; + + +/* Bring up plugin and load filetypes.FILE that exist for Auto-run */ +static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { + // if there are any filetypes.FILE autorun sections + // stash filetypes.FILE to fallback on-project-close + return TRUE; +} + +/* ensure destruction of any Auto-run objects */ +static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { +} + +G_MODULE_EXPORT +void geany_load_module(GeanyPlugin* plugin) { + // who am we? + plugin->info->name = "Auto-run"; + plugin->info->description = _("Geany action interceptor plugin"); + plugin->info->version = "0.1"; + plugin->info->author = "WrapEarnPass"; + + // english do you speak? + main_locale_init(LOCALEDIR, GETTEXT_PACKAGE); + + // what am we do? + plugin->funcs->init = autorun_init; + plugin->funcs->cleanup = autorun_cleanup; + plugin->funcs->callbacks = plugin_callbacks; + + // go forth and come fifth. + GEANY_PLUGIN_REGISTER(plugin, 225); +} diff --git a/build/autorun.m4 b/build/autorun.m4 new file mode 100644 index 0000000000..448856ce29 --- /dev/null +++ b/build/autorun.m4 @@ -0,0 +1,9 @@ + AC_DEFUN([GP_CHECK_AUTORUN], + [ + GP_ARG_DISABLE([autorun], [auto]) + GP_COMMIT_PLUGIN_STATUS([Autorun]) + AC_CONFIG_FILES([ + autorun/Makefile + autorun/src/Makefile + ]) + ]) diff --git a/configure.ac b/configure.ac index 1e8148a965..5bd32fd293 100644 --- a/configure.ac +++ b/configure.ac @@ -36,6 +36,7 @@ dnl plugin checks GP_CHECK_ADDONS GP_CHECK_AUTOCLOSE GP_CHECK_AUTOMARK +GP_CHECK_AUTORUN GP_CHECK_CODENAV GP_CHECK_COMMANDER GP_CHECK_DEBUGGER diff --git a/po/POTFILES.in b/po/POTFILES.in index 983ad9e626..d9eea99ce7 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -19,6 +19,9 @@ autoclose/src/autoclose.c # Automark automark/src/automark.c +# Autorun +autorun/src/plugin_main.c + # codenav codenav/src/codenavigation.c codenav/src/goto_file.c From 2436cb6e0fccc103ac1ed69da2b970a694f23dbb Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Fri, 19 Jun 2026 18:19:57 -0400 Subject: [PATCH 02/30] add filedef iterator impl add globals def --- autorun/src/Makefile.am | 3 +- autorun/src/plugin_main.c | 95 ++++++++++++++++++++++++++++++++++----- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/autorun/src/Makefile.am b/autorun/src/Makefile.am index 4c7f0a1e75..ea36c83ef6 100644 --- a/autorun/src/Makefile.am +++ b/autorun/src/Makefile.am @@ -4,7 +4,8 @@ plugin = autorun geanyplugins_LTLIBRARIES = autorun.la autorun_la_SOURCES = \ - plugin_main.c + plugin_main.c \ + autorun.c autorun_la_CPPFLAGS = $(AM_CPPFLAGS) \ -DG_LOG_DOMAIN=\"Autorun\" diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index 853c97263c..e09af65eae 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -16,19 +16,25 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include +#include -/* Handler to read any Project declared Auto-run configs */ -static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ - //if the keyfile has an [autorun] section - //load any handlers - //over any existing [autorun] handlers -} +#include "autorun.h" -/* Handler to clear any old and read any new Project declared Auto-run configs */ -static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data) { +/* Handler to read any Project declared Auto-run configs + * For Auto-run, this the same as a project-save event */ +static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ + gsize key_len; + GError * gerr=NULL; + gchar** handlers = g_key_file_get_keys(config,"autorun",&key_len, &gerr); //if the keyfile has an [autorun] section + if( gerr==NULL ){ //load any handlers //over any existing [autorun] handlers + }else{ + g_free(gerr); + } + //The caller of g_key_file_get_keys takes ownership of the returned data, and is responsible for freeing it. + g_free(handlers); } /* Handler to clear any old Project declared Auto-run configs */ @@ -41,18 +47,26 @@ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer void on_doc_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ //if there are any [autorun] OS handlers that exist for this FT //iterate through them and run in Terminal + if(!doc->changed) + { + return; + } } /* Handler to run any applicable Auto-run configs before a write*/ void on_doc_before_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ //if there are any [autorun] BS handlers that exist for this FT //iterate through them and run them in Terminal + if(!doc->changed) + { + return; + } } PluginCallback plugin_callbacks[] = { { "project-open", (GCallback)&on_project_open, TRUE, NULL }, + { "project-save", (GCallback)&on_project_open, TRUE, NULL }, { "project-close", (GCallback)&on_project_close, TRUE, NULL }, - { "project-save", (GCallback)&on_project_save, TRUE, NULL }, { "document-save", (GCallback)&on_doc_save, TRUE, NULL}, { "document-before-save", (GCallback)&on_doc_before_save, TRUE, NULL}, { NULL, NULL, FALSE, NULL } @@ -61,13 +75,72 @@ PluginCallback plugin_callbacks[] = { /* Bring up plugin and load filetypes.FILE that exist for Auto-run */ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { - // if there are any filetypes.FILE autorun sections - // stash filetypes.FILE to fallback on-project-close + if (!autorun_globals || autorun_globals == NULL) { + autorun_globals_init(); + autorun_globals->geany_plugin = plugin; + GeanyData * geany_data = plugin->geany_data; + // if there are any filetypes.FILE autorun sections + gchar * filedef_path = g_build_path(G_DIR_SEPARATOR_S, geany->app->configdir, "filedefs", NULL); + guint filedef_len; + GSList * file_list = utils_get_file_list(filedef_path, &filedef_len, NULL); + if( filedef_len > 0){ + // stash filetypes.FILE to fallback on-project-close + GSList* node; + foreach_slist(node,file_list){ + + if( g_str_has_prefix(node->data, "filetypes.") && g_strcmp0(node->data, "filetypes.README")!=0 ) + { + //we have files to process + g_message("found filedef at %s",(gchar*)node->data); + gsize key_len; + gchar * filedef_file = g_build_filename(G_DIR_SEPARATOR_S, filedef_path, (gchar*)node->data, NULL); + GKeyFile* config=g_key_file_new (); + g_key_file_load_from_file(config,filedef_file ,G_KEY_FILE_NONE, NULL); + GError * gerr=NULL; + gchar** handlers = g_key_file_get_keys(config, "autorun", &key_len, &gerr); + //if the keyfile has an [autorun] section + if( gerr == NULL ){ + g_message("found autorun filedef at %s of %li",(gchar*)node->data,key_len); + + + }else + { + g_free(gerr); + g_message("no autorun filedef at %s",(gchar*)node->data); + } + g_free(filedef_file); + g_free(handlers); + } + } + + } + //cleanup + g_slist_foreach(file_list, (GFunc) g_free, NULL); + g_slist_free(file_list); + g_free(filedef_path); + + + if(geany_data && geany_data->app && geany_data->app->project) { + //if initialized while a project is already open, manually ingest the keyfile + // force a GKeyFile + GKeyFile* config=g_key_file_new (); + gchar* configfile; + configfile = g_build_filename(geany_data->app->project->base_path, geany_data->app->project->file_name, NULL); + msgwin_status_add("%s", configfile); + g_key_file_load_from_file(config, configfile, G_KEY_FILE_NONE, NULL); + on_project_open(NULL, config, NULL); + g_free(configfile); + g_free(config); + } + + + } return TRUE; } /* ensure destruction of any Auto-run objects */ static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { + autorun_globals_cleanup(); } G_MODULE_EXPORT From 3ebe78c488bd37a2e8b38e6ea1af5cc60fc67003 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Sat, 20 Jun 2026 08:31:56 -0400 Subject: [PATCH 03/30] Seperate out the keyfile parsers to cleanup plugin_main Update README to account for non-stdin tools --- autorun/README | 48 ++++++++++++------ autorun/src/autorun.c | 102 ++++++++++++++++++++++++++++++++++++++ autorun/src/autorun.h | 36 ++++++++++++++ autorun/src/plugin_main.c | 83 +++++-------------------------- 4 files changed, 182 insertions(+), 87 deletions(-) create mode 100644 autorun/src/autorun.c create mode 100644 autorun/src/autorun.h diff --git a/autorun/README b/autorun/README index 566c466664..a243b9425d 100644 --- a/autorun/README +++ b/autorun/README @@ -22,6 +22,7 @@ The Auto-run menu ------------------ The Auto-run menu allows you to manage the Project level Auto-run configs. + Manually setting Auto-run configs --------------------------------- You can manually set Project level Auto-run configs under the keyfile heading [autorun], @@ -42,18 +43,12 @@ TA(Target Action) = (CM|WD) * CM is the command to run * WD is the working directory to run from. -Auto-run supports the following replacements in CM and WD -%f - the basename of the file being saved. -%e - the basename but without the last extension. -%d - the absolute path of the directory of the file being saved. -%p - the absolute path of the base directory of the currently open project. - -CBS_00_CM = formatter %f +CBS_00_CM = formatter CBS_00_WD = %d COS_00_CM = linter %f COS_00_WD = %p -#Run formatter on %f from %d before file write, and then linter on the newly written %f from %p -# when attempting to save a C file type. +#Run formatter before file write, and then linter on the newly written %f from %p +#when attempting to save a C file type. C++OS_00_CM = cppcheck %f --output-file=%p/cppcheck.log C++OS_00_WD = %d @@ -69,28 +64,49 @@ with a syntax similar to the Project configs. AC_NN_FF where -AC = (BS|OS), NN = (00|01|...), FF=(LB|CM|WD) +AC = (BS|OS), NN = (00|01|...), FF=(CM|WD) + +BS_00_CM = formatter +OS_00_CM = linter %f -BS_00_LB = format -BS_00_CM = formatter %f -BS_00_WD = %d When Auto-run loads, it will scan all User config dir filedefs/filetypes.* for any [autorun] sections and set actions for all matching filetypes. User config dir filedefs settings will be overridden by any Project settings. -filedefs/filetypes.c + +Example: +(userconfigdir)/filedefs/filetypes.c BS_00_CM = formatter %f -project.geany +(userprojectdir)/project.geany CBS_00_CM = fancyformatter %f Autorun will run fancyformatter before save for C files as long as project.geany is open. All Auto-run actions will target the Geany Terminal pane. +Auto-run replacements +--------------------- + +Auto-run supports the following replacements in CM and WD +%f - the basename of the file being saved. +%e - the basename but without the last extension. +%d - the absolute path of the directory of the file being saved. +%p - the absolute path of the base directory of the currently open project. + +The BS Intercepted Action by default does not need the %f argument as it +operates on the contents of the Scintilla editor. + +Why would BS targets support %f then? +Place the %f replacement in the BS target if you know the CM does not honor STDIN. +In that case Auto-run will dump the contents of Scintilla to a temp file. + +Known examples: cmake-format + + Known issues ============ -Auto-run does not support forking, any commands will be run one after another. +Auto-run does not support forking, any commands will synchronously (spawn_sync). License ======= diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c new file mode 100644 index 0000000000..a6b904ec2a --- /dev/null +++ b/autorun/src/autorun.c @@ -0,0 +1,102 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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 +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "autorun.h" + +AUTORUN_GLOBALS* autorun_globals; + +void autorun_globals_init(GeanyPlugin* plugin) +{ + autorun_globals = g_new(AUTORUN_GLOBALS,1); + autorun_globals->plugin=plugin; + autorun_globals->data=plugin->geany_data; +} +void autorun_globals_cleanup(void) +{ + autorun_globals->plugin=NULL; + autorun_globals->data=NULL; + g_free(autorun_globals); + autorun_globals=NULL; +} +void load_filedefs(void) +{ + // if there are any filetypes.FILE autorun sections + gchar * filedef_path = g_build_path(G_DIR_SEPARATOR_S, autorun_globals->data->app->configdir, "filedefs", NULL); + guint filedef_len; + GSList * file_list = utils_get_file_list(filedef_path, &filedef_len, NULL); + if( filedef_len > 0){ + // stash filetypes.FILE to fallback on-project-close + GSList* node; + foreach_slist(node,file_list){ + if( g_str_has_prefix(node->data, "filetypes.") && g_strcmp0(node->data, "filetypes.README")!=0 ) + { + //we have files to process + gsize key_len; + gchar * filedef_file = g_build_filename(G_DIR_SEPARATOR_S, filedef_path, (gchar*)node->data, NULL); + GKeyFile* config=g_key_file_new (); + g_key_file_load_from_file(config,filedef_file ,G_KEY_FILE_NONE, NULL); + GError * gerr=NULL; + gchar** handlers = g_key_file_get_keys(config, "autorun", &key_len, &gerr); + //if the keyfile has an [autorun] section + if( gerr == NULL ){ + g_message("found %s autorun",(gchar*)node->data); + gchar** handler_key; + foreach_strv( handler_key, handlers){ + g_message(" %s", *handler_key); + } + + }else + { + g_free(gerr); + } + g_free(filedef_file); + g_free(handlers); + } + } + + } + //cleanup + g_slist_foreach(file_list, (GFunc) g_free, NULL); + g_slist_free(file_list); + g_free(filedef_path); +} + +void load_projectdefs(GKeyFile* config){ + gsize key_len; + GError * gerr=NULL; + gchar** handlers = g_key_file_get_keys(config, "autorun", &key_len, &gerr); + //if the keyfile has an [autorun] section + if( gerr==NULL ){ + g_message("found project autorun"); + gchar** handler_key; + //load any handlers + foreach_strv( handler_key, handlers){ + //over any existing [autorun] handlers + g_message(" %s", *handler_key); + } + }else{ + g_free(gerr); + } + //The caller of g_key_file_get_keys takes ownership of the returned data, and is responsible for freeing it. + g_free(handlers); +} diff --git a/autorun/src/autorun.h b/autorun/src/autorun.h new file mode 100644 index 0000000000..ab1bd04493 --- /dev/null +++ b/autorun/src/autorun.h @@ -0,0 +1,36 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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. + */ + +#ifndef __AUTORUN_H__ +#define __AUTORUN_H__ +#include + +typedef struct +{ + GeanyPlugin *plugin; + GeanyData *data; +}AUTORUN_GLOBALS; + +extern AUTORUN_GLOBALS* autorun_globals; + +void autorun_globals_init(GeanyPlugin *plugin); +void autorun_globals_cleanup(void); +void load_filedefs(void); +void load_projectdefs(GKeyFile* config); + +#endif diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index e09af65eae..9e19c3173d 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -23,44 +23,32 @@ /* Handler to read any Project declared Auto-run configs * For Auto-run, this the same as a project-save event */ static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ - gsize key_len; - GError * gerr=NULL; - gchar** handlers = g_key_file_get_keys(config,"autorun",&key_len, &gerr); - //if the keyfile has an [autorun] section - if( gerr==NULL ){ - //load any handlers - //over any existing [autorun] handlers - }else{ - g_free(gerr); - } - //The caller of g_key_file_get_keys takes ownership of the returned data, and is responsible for freeing it. - g_free(handlers); + load_projectdefs(config); } /* Handler to clear any old Project declared Auto-run configs */ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer user_data) { - //if we stashed a filetypes.FILE from plugin-init - //unload handlers and fallback to any filetypes.FILE + //unload project handlers } /* Handler to run any applicable Auto-run configs after a write*/ void on_doc_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ - //if there are any [autorun] OS handlers that exist for this FT - //iterate through them and run in Terminal if(!doc->changed) { return; } + //if there are any [autorun] OS handlers that exist for this FT + //iterate through them and run in Terminal } /* Handler to run any applicable Auto-run configs before a write*/ void on_doc_before_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ - //if there are any [autorun] BS handlers that exist for this FT - //iterate through them and run them in Terminal if(!doc->changed) { return; } + //if there are any [autorun] BS handlers that exist for this FT + //iterate through them and run them in Terminal } PluginCallback plugin_callbacks[] = { @@ -76,64 +64,17 @@ PluginCallback plugin_callbacks[] = { /* Bring up plugin and load filetypes.FILE that exist for Auto-run */ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { if (!autorun_globals || autorun_globals == NULL) { - autorun_globals_init(); - autorun_globals->geany_plugin = plugin; - GeanyData * geany_data = plugin->geany_data; - // if there are any filetypes.FILE autorun sections - gchar * filedef_path = g_build_path(G_DIR_SEPARATOR_S, geany->app->configdir, "filedefs", NULL); - guint filedef_len; - GSList * file_list = utils_get_file_list(filedef_path, &filedef_len, NULL); - if( filedef_len > 0){ - // stash filetypes.FILE to fallback on-project-close - GSList* node; - foreach_slist(node,file_list){ - - if( g_str_has_prefix(node->data, "filetypes.") && g_strcmp0(node->data, "filetypes.README")!=0 ) - { - //we have files to process - g_message("found filedef at %s",(gchar*)node->data); - gsize key_len; - gchar * filedef_file = g_build_filename(G_DIR_SEPARATOR_S, filedef_path, (gchar*)node->data, NULL); - GKeyFile* config=g_key_file_new (); - g_key_file_load_from_file(config,filedef_file ,G_KEY_FILE_NONE, NULL); - GError * gerr=NULL; - gchar** handlers = g_key_file_get_keys(config, "autorun", &key_len, &gerr); - //if the keyfile has an [autorun] section - if( gerr == NULL ){ - g_message("found autorun filedef at %s of %li",(gchar*)node->data,key_len); - - - }else - { - g_free(gerr); - g_message("no autorun filedef at %s",(gchar*)node->data); - } - g_free(filedef_file); - g_free(handlers); - } - } - - } - //cleanup - g_slist_foreach(file_list, (GFunc) g_free, NULL); - g_slist_free(file_list); - g_free(filedef_path); + autorun_globals_init(plugin); + load_filedefs(); - - if(geany_data && geany_data->app && geany_data->app->project) { - //if initialized while a project is already open, manually ingest the keyfile + //if initialized while a project is already open, manually ingest the project + if(autorun_globals->data->app && autorun_globals->data->app->project) { // force a GKeyFile GKeyFile* config=g_key_file_new (); - gchar* configfile; - configfile = g_build_filename(geany_data->app->project->base_path, geany_data->app->project->file_name, NULL); - msgwin_status_add("%s", configfile); - g_key_file_load_from_file(config, configfile, G_KEY_FILE_NONE, NULL); - on_project_open(NULL, config, NULL); - g_free(configfile); + g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + load_projectdefs(config); g_free(config); } - - } return TRUE; } From b75829d5c5f4b018db36ecb86c72fec91ba81a51 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Sat, 20 Jun 2026 20:49:38 -0400 Subject: [PATCH 04/30] update README with known bug and keyfile change --- autorun/README | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/autorun/README b/autorun/README index a243b9425d..87696e727b 100644 --- a/autorun/README +++ b/autorun/README @@ -28,7 +28,7 @@ Manually setting Auto-run configs You can manually set Project level Auto-run configs under the keyfile heading [autorun], using https://wiki.geany.org/howtos/configurebuildmenu#build-menu style entries. -FILETYPEIA_NN_TA +FILETYPE_IA_NN_TA FILETYPE is a Geany filetype label, e.g. (C|C++|C#|JSON) from geany/filetype_extensions.conf @@ -43,17 +43,17 @@ TA(Target Action) = (CM|WD) * CM is the command to run * WD is the working directory to run from. -CBS_00_CM = formatter -CBS_00_WD = %d -COS_00_CM = linter %f -COS_00_WD = %p +C_BS_00_CM = formatter +C_BS_00_WD = %d +C_OS_00_CM = linter %f +C_OS_00_WD = %p #Run formatter before file write, and then linter on the newly written %f from %p #when attempting to save a C file type. -C++OS_00_CM = cppcheck %f --output-file=%p/cppcheck.log -C++OS_00_WD = %d -C++OS_01_CM = clang-tidy -p %p --format-style=file %f -C++OS_01_WD = %p +C++_OS_00_CM = cppcheck %f --output-file=%p/cppcheck.log +C++_OS_00_WD = %d +C++_OS_01_CM = clang-tidy -p %p --format-style=file %f +C++_OS_01_WD = %p #Run cppcheck on %f from %d, and clang-tidy on %f from %p after file write when saving a C++ file type. Set Auto-run actions across Projects @@ -62,9 +62,9 @@ To set Autorun across Projects without having to edit each .geany file, you can https://www.geany.org/manual/#filetype-definition-files under the keyfile heading [autorun] with a syntax similar to the Project configs. -AC_NN_FF +IA_NN_TA where -AC = (BS|OS), NN = (00|01|...), FF=(CM|WD) +IA = (BS|OS), NN = (00|01|...), TA=(CM|WD) BS_00_CM = formatter OS_00_CM = linter %f @@ -107,6 +107,8 @@ Known issues ============ Auto-run does not support forking, any commands will synchronously (spawn_sync). +geany/geany#4603 prevents reloading the Project configs in realtime. + unload and reload the Plugin from Plugin manager as a workaround. License ======= From 1ea3bdf7c506cad6328c9ef3104bf3512d803129 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Sat, 20 Jun 2026 20:51:20 -0400 Subject: [PATCH 05/30] add keyfile parser --- autorun/src/Makefile.am | 3 +- autorun/src/utils.c | 117 ++++++++++++++++++++++++++++++++++++++++ autorun/src/utils.h | 26 +++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 autorun/src/utils.c create mode 100644 autorun/src/utils.h diff --git a/autorun/src/Makefile.am b/autorun/src/Makefile.am index ea36c83ef6..7814f4b44b 100644 --- a/autorun/src/Makefile.am +++ b/autorun/src/Makefile.am @@ -5,7 +5,8 @@ geanyplugins_LTLIBRARIES = autorun.la autorun_la_SOURCES = \ plugin_main.c \ - autorun.c + autorun.c \ + utils.c autorun_la_CPPFLAGS = $(AM_CPPFLAGS) \ -DG_LOG_DOMAIN=\"Autorun\" diff --git a/autorun/src/utils.c b/autorun/src/utils.c new file mode 100644 index 0000000000..4eb9090e8a --- /dev/null +++ b/autorun/src/utils.c @@ -0,0 +1,117 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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 + +#include "utils.h" +#include "autorun.h" + +/* Read a filedef or keyfile for Intercept Actions + * @param action key (should end with CM) + * @param key_file to get remaining values from + * @return true if parsing worked + * false is parsing failed + * */ +gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd){ + //key should be a (filetype_)action_number_flag string + if( key==NULL || cmd ==NULL){ + return FALSE; + } + //is this a key? + if(g_strstr_len(key,-1,"_")==NULL){ + //no + cmd->invalid= TRUE; + return FALSE; + } + //what is this key? + gchar ** tokens; + tokens = g_strsplit(key, "_", 4); + gboolean invalid = FALSE; + + switch (g_strv_length(tokens)){ + //strv of 3 is filedef, 4 is project + case 3:{ + //the caller has to set filetype + if(g_strcmp0(tokens[0],"BS")==0){ + cmd->interceptor= g_strdup(tokens[0]); + }else + if(g_strcmp0(tokens[0],"OS")==0){ + cmd->interceptor= g_strdup(tokens[0]); + }else{ + invalid = TRUE; + } + + gint offset=atoi(tokens[1]); + if(offset<0 ||offset>99 ){ + invalid = TRUE; + }else{ + cmd->order=offset; + } + if(g_strcmp0(tokens[2],"CM")==0){ + cmd->command = utils_get_setting_string(key_file,"autorun",key,""); + gchar* next_key; + next_key=g_strconcat(tokens[0],"_",tokens[1],"_","WD",NULL); + cmd->working_dir = utils_get_setting_string(key_file,"autorun",next_key,""); + g_free(next_key); + }else{ + invalid = TRUE; + } + cmd->invalid = invalid; + break; + } + case 4:{ + cmd->filetype= filetypes_lookup_by_name(tokens[0]); + + if(g_strcmp0(tokens[1],"BS")==0){ + cmd->interceptor= g_strdup(tokens[1]); + }else + if(g_strcmp0(tokens[1],"OS")==0){ + cmd->interceptor= g_strdup(tokens[1]); + }else{ + invalid = TRUE; + } + + gint offset =atoi(tokens[2]); + if(offset<0 ||offset>99 ){ + invalid = TRUE; + }else{ + cmd->order=offset; + } + + if(g_strcmp0(tokens[3],"CM")==0){ + cmd->command = utils_get_setting_string(key_file,"autorun",key,""); + gchar* next_key; + next_key=g_strconcat(tokens[0],"_",tokens[1],"_",tokens[2],"_", "WD",NULL); + cmd->working_dir = utils_get_setting_string(key_file,"autorun",next_key,""); + g_free(next_key); + } + else{ + invalid = TRUE; + } + cmd->invalid = invalid; + break; + } + default:{ + invalid = TRUE; + } + } + + g_strfreev(tokens); + + return cmd->invalid; +} diff --git a/autorun/src/utils.h b/autorun/src/utils.h new file mode 100644 index 0000000000..8af4cb3e38 --- /dev/null +++ b/autorun/src/utils.h @@ -0,0 +1,26 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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. + */ +#ifndef __AUTORUN_UTILS_H__ +#define __AUTORUN_UTILS_H__ + +#include + +#include "autorun.h" +gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd); + +#endif From d8677aa43394cfe1e76615aa3b132e4bd4810a6e Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Sat, 20 Jun 2026 20:53:05 -0400 Subject: [PATCH 06/30] add parser container add _free for autorun defined types --- autorun/src/autorun.c | 127 ++++++++++++++++++++++++++++++++++++------ autorun/src/autorun.h | 18 +++++- 2 files changed, 126 insertions(+), 19 deletions(-) diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c index a6b904ec2a..fcc2765efb 100644 --- a/autorun/src/autorun.c +++ b/autorun/src/autorun.c @@ -16,32 +16,96 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - +#include #include "autorun.h" +#include "utils.h" AUTORUN_GLOBALS* autorun_globals; + +void autorun_cmd_new(AUTORUN_CMD* cmd){ + if(cmd!=NULL) + { + //we're all full here. + return; + } + cmd= g_new0(AUTORUN_CMD,1); + cmd->filetype=NULL; + cmd->interceptor=NULL; + cmd->command=NULL; + cmd->working_dir=NULL; + cmd->invalid=TRUE; + cmd->order=0; +} + +void autorun_cmd_free(AUTORUN_CMD* cmd){ + if(cmd==NULL||cmd->invalid){ + //who are you and why are you in my attic? + return; + } + //free the command + if(cmd->filetype !=NULL){ + cmd->filetype=NULL; + } + if(cmd->interceptor !=NULL){ + g_free(cmd->interceptor); + cmd->interceptor=NULL; + } + if(cmd->command !=NULL){ + g_free(cmd->command); + cmd->command=NULL; + } + if(cmd->working_dir !=NULL) + { + g_free(cmd->working_dir); + cmd->working_dir=NULL; + } +} + +void autorun_cmd_list_free(GSList* command_list){ + if(command_list==NULL){ + //why are you naked? + return; + } + g_message ("command list had %i" ,g_slist_length(command_list)); + g_slist_free_full(command_list,(GDestroyNotify)autorun_cmd_free); +} + void autorun_globals_init(GeanyPlugin* plugin) { - autorun_globals = g_new(AUTORUN_GLOBALS,1); - autorun_globals->plugin=plugin; - autorun_globals->data=plugin->geany_data; + if(!autorun_globals || autorun_globals==NULL){ + autorun_globals = g_new0(AUTORUN_GLOBALS,1); + autorun_globals->plugin=plugin; + autorun_globals->data=plugin->geany_data; + autorun_globals->filedef_commands=NULL; + autorun_globals->project_commands=NULL; + } } -void autorun_globals_cleanup(void) + +void autorun_globals_free(void) { - autorun_globals->plugin=NULL; - autorun_globals->data=NULL; - g_free(autorun_globals); - autorun_globals=NULL; + if(autorun_globals!=NULL){ + if(autorun_globals->filedef_commands!=NULL) + { + autorun_cmd_list_free(autorun_globals->filedef_commands); + autorun_globals->filedef_commands=NULL; + } + if(autorun_globals->project_commands!=NULL) + { + autorun_cmd_list_free(autorun_globals->project_commands); + autorun_globals->project_commands=NULL; + } + autorun_globals->data=NULL; + autorun_globals->plugin=NULL; + g_free(autorun_globals); + autorun_globals=NULL; + } } + void load_filedefs(void) { // if there are any filetypes.FILE autorun sections - gchar * filedef_path = g_build_path(G_DIR_SEPARATOR_S, autorun_globals->data->app->configdir, "filedefs", NULL); + gchar * filedef_path = g_build_path(G_DIR_SEPARATOR_S, autorun_globals->data->app->configdir, GEANY_FILEDEFS_SUBDIR, NULL); guint filedef_len; GSList * file_list = utils_get_file_list(filedef_path, &filedef_len, NULL); if( filedef_len > 0){ @@ -63,8 +127,23 @@ void load_filedefs(void) gchar** handler_key; foreach_strv( handler_key, handlers){ g_message(" %s", *handler_key); + if(g_str_has_suffix(*handler_key,"CM")){ + AUTORUN_CMD cmd; + autorun_cmd_new(&cmd); + parse_intercept_actions(*handler_key, config, &cmd ); + if(!cmd.invalid){ + cmd.filetype=filetypes_detect_from_file(node->data); + g_message ("filetype was %s", filetypes_get_display_name(cmd.filetype)); + //add the command + autorun_globals->filedef_commands = g_slist_prepend (autorun_globals->filedef_commands, &cmd); + g_message ("filedef list is %i" ,g_slist_length(autorun_globals->filedef_commands)); + } + else{ + //free the command + autorun_cmd_free(&cmd); + } + } } - }else { g_free(gerr); @@ -75,9 +154,9 @@ void load_filedefs(void) } } - //cleanup + //cleanup g_slist_foreach(file_list, (GFunc) g_free, NULL); - g_slist_free(file_list); + g_slist_free(file_list); g_free(filedef_path); } @@ -93,6 +172,20 @@ void load_projectdefs(GKeyFile* config){ foreach_strv( handler_key, handlers){ //over any existing [autorun] handlers g_message(" %s", *handler_key); + if(g_str_has_suffix(*handler_key,"CM")){ + AUTORUN_CMD cmd; + autorun_cmd_new(&cmd); + parse_intercept_actions(*handler_key, config, &cmd ); + if(!cmd.invalid){ + //add the command + autorun_globals->project_commands = g_slist_prepend (autorun_globals->project_commands, &cmd); + g_message ("proj command list is %i" ,g_slist_length(autorun_globals->filedef_commands)); + } + else{ + //free the command + autorun_cmd_free(&cmd); + } + } } }else{ g_free(gerr); diff --git a/autorun/src/autorun.h b/autorun/src/autorun.h index ab1bd04493..c6b681c11f 100644 --- a/autorun/src/autorun.h +++ b/autorun/src/autorun.h @@ -24,12 +24,26 @@ typedef struct { GeanyPlugin *plugin; GeanyData *data; + GSList * filedef_commands; + GSList * project_commands; }AUTORUN_GLOBALS; -extern AUTORUN_GLOBALS* autorun_globals; +typedef struct +{ + GeanyFiletype * filetype; + gchar* interceptor; + gushort order; + gchar* command; + gchar* working_dir; + gboolean invalid; +}AUTORUN_CMD; +extern AUTORUN_GLOBALS* autorun_globals; void autorun_globals_init(GeanyPlugin *plugin); -void autorun_globals_cleanup(void); +void autorun_globals_free(void); +void autorun_cmd_list_free(GSList* command_list); +void autorun_cmd_free(AUTORUN_CMD* cmd); +void autorun_cmd_new(AUTORUN_CMD* cmd); void load_filedefs(void); void load_projectdefs(GKeyFile* config); From dee19753306b16436ddba0f7dcf406a1ea0690d7 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Sat, 20 Jun 2026 20:54:49 -0400 Subject: [PATCH 07/30] disable project-save handler due to geany/geany#4603 wire up _free for autorun defined types --- autorun/src/plugin_main.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index 9e19c3173d..cf97409ca8 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -15,20 +15,31 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + #include #include #include "autorun.h" -/* Handler to read any Project declared Auto-run configs - * For Auto-run, this the same as a project-save event */ +/* Handler to read any Project declared Auto-run configs */ static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ + g_message("on_project_open"); load_projectdefs(config); } +// this handler is currently disconnected due to geany/geany#4603 +/* Handler to read any Project declared Auto-run configs */ +//static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ +// g_message("on_project_save"); +// load_projectdefs(config); +//} + + /* Handler to clear any old Project declared Auto-run configs */ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer user_data) { //unload project handlers + autorun_cmd_list_free(autorun_globals->project_commands); + autorun_globals->project_commands=NULL; } /* Handler to run any applicable Auto-run configs after a write*/ @@ -53,7 +64,7 @@ void on_doc_before_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_U PluginCallback plugin_callbacks[] = { { "project-open", (GCallback)&on_project_open, TRUE, NULL }, - { "project-save", (GCallback)&on_project_open, TRUE, NULL }, + //{ "project-save", (GCallback)&on_project_save, TRUE, NULL },// geany/geany#4603 { "project-close", (GCallback)&on_project_close, TRUE, NULL }, { "document-save", (GCallback)&on_doc_save, TRUE, NULL}, { "document-before-save", (GCallback)&on_doc_before_save, TRUE, NULL}, @@ -64,24 +75,28 @@ PluginCallback plugin_callbacks[] = { /* Bring up plugin and load filetypes.FILE that exist for Auto-run */ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { if (!autorun_globals || autorun_globals == NULL) { + g_message("autorun_init start"); autorun_globals_init(plugin); load_filedefs(); - - //if initialized while a project is already open, manually ingest the project + + //if initialized while a project is already open, manually ingest the project if(autorun_globals->data->app && autorun_globals->data->app->project) { // force a GKeyFile GKeyFile* config=g_key_file_new (); g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); load_projectdefs(config); - g_free(config); + g_free(config); } } + g_message("autorun_init end"); return TRUE; } /* ensure destruction of any Auto-run objects */ static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { - autorun_globals_cleanup(); + g_message("autorun_cleanup start"); + autorun_globals_free(); + g_message("autorun_cleanup end"); } G_MODULE_EXPORT From deba0e00469c5dc991df070b94bb7256c0437068 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:11:09 -0400 Subject: [PATCH 08/30] FIX cmd allocator add spawn controller impl --- autorun/src/Makefile.am | 3 +- autorun/src/autorun.c | 82 ++++++++++++++++++++------------------- autorun/src/autorun.h | 6 +-- autorun/src/plugin_main.c | 42 +++++++++++++++----- autorun/src/utils.c | 32 +++++++++++++-- autorun/src/utils.h | 2 + 6 files changed, 110 insertions(+), 57 deletions(-) diff --git a/autorun/src/Makefile.am b/autorun/src/Makefile.am index 7814f4b44b..b4c7c5ad6e 100644 --- a/autorun/src/Makefile.am +++ b/autorun/src/Makefile.am @@ -6,7 +6,8 @@ geanyplugins_LTLIBRARIES = autorun.la autorun_la_SOURCES = \ plugin_main.c \ autorun.c \ - utils.c + utils.c \ + spawn.c autorun_la_CPPFLAGS = $(AM_CPPFLAGS) \ -DG_LOG_DOMAIN=\"Autorun\" diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c index fcc2765efb..6b82ffee3a 100644 --- a/autorun/src/autorun.c +++ b/autorun/src/autorun.c @@ -23,29 +23,28 @@ AUTORUN_GLOBALS* autorun_globals; -void autorun_cmd_new(AUTORUN_CMD* cmd){ - if(cmd!=NULL) - { - //we're all full here. - return; - } - cmd= g_new0(AUTORUN_CMD,1); - cmd->filetype=NULL; +AUTORUN_CMD* autorun_cmd_new(){ + AUTORUN_CMD* cmd=NULL; + cmd=g_new0(AUTORUN_CMD,1); + cmd->file_type=NULL; cmd->interceptor=NULL; cmd->command=NULL; cmd->working_dir=NULL; cmd->invalid=TRUE; cmd->order=0; + return cmd; } void autorun_cmd_free(AUTORUN_CMD* cmd){ - if(cmd==NULL||cmd->invalid){ + /* do not check for cmd->invalid because it could be a partial command + * or invalidated after assembly. */ + if(cmd==NULL){ //who are you and why are you in my attic? return; } //free the command - if(cmd->filetype !=NULL){ - cmd->filetype=NULL; + if(cmd->file_type !=NULL){ + cmd->file_type=NULL; } if(cmd->interceptor !=NULL){ g_free(cmd->interceptor); @@ -110,7 +109,7 @@ void load_filedefs(void) GSList * file_list = utils_get_file_list(filedef_path, &filedef_len, NULL); if( filedef_len > 0){ // stash filetypes.FILE to fallback on-project-close - GSList* node; + GSList* node=NULL; foreach_slist(node,file_list){ if( g_str_has_prefix(node->data, "filetypes.") && g_strcmp0(node->data, "filetypes.README")!=0 ) { @@ -128,22 +127,24 @@ void load_filedefs(void) foreach_strv( handler_key, handlers){ g_message(" %s", *handler_key); if(g_str_has_suffix(*handler_key,"CM")){ - AUTORUN_CMD cmd; - autorun_cmd_new(&cmd); - parse_intercept_actions(*handler_key, config, &cmd ); - if(!cmd.invalid){ - cmd.filetype=filetypes_detect_from_file(node->data); - g_message ("filetype was %s", filetypes_get_display_name(cmd.filetype)); + AUTORUN_CMD* cmd = autorun_cmd_new(); + cmd->file_type=filetypes_detect_from_file(node->data); + g_message ("filetype was %s", filetypes_get_display_name(cmd->file_type)); + parse_intercept_actions(*handler_key, config, cmd ); + if(!cmd->invalid){ //add the command - autorun_globals->filedef_commands = g_slist_prepend (autorun_globals->filedef_commands, &cmd); + autorun_globals->filedef_commands = g_slist_prepend(autorun_globals->filedef_commands, cmd); g_message ("filedef list is %i" ,g_slist_length(autorun_globals->filedef_commands)); + } else{ //free the command - autorun_cmd_free(&cmd); + autorun_cmd_free(cmd); } } } + //flip it around + autorun_globals->filedef_commands = g_slist_reverse(autorun_globals->filedef_commands); }else { g_free(gerr); @@ -152,8 +153,8 @@ void load_filedefs(void) g_free(handlers); } } - } + //cleanup g_slist_foreach(file_list, (GFunc) g_free, NULL); g_slist_free(file_list); @@ -166,27 +167,28 @@ void load_projectdefs(GKeyFile* config){ gchar** handlers = g_key_file_get_keys(config, "autorun", &key_len, &gerr); //if the keyfile has an [autorun] section if( gerr==NULL ){ - g_message("found project autorun"); - gchar** handler_key; - //load any handlers - foreach_strv( handler_key, handlers){ - //over any existing [autorun] handlers - g_message(" %s", *handler_key); - if(g_str_has_suffix(*handler_key,"CM")){ - AUTORUN_CMD cmd; - autorun_cmd_new(&cmd); - parse_intercept_actions(*handler_key, config, &cmd ); - if(!cmd.invalid){ - //add the command - autorun_globals->project_commands = g_slist_prepend (autorun_globals->project_commands, &cmd); - g_message ("proj command list is %i" ,g_slist_length(autorun_globals->filedef_commands)); - } - else{ - //free the command - autorun_cmd_free(&cmd); + g_message("found project autorun"); + gchar** handler_key; + //load any handlers + foreach_strv( handler_key, handlers){ + //over any existing [autorun] handlers + g_message(" %s", *handler_key); + if(g_str_has_suffix(*handler_key,"CM")){ + AUTORUN_CMD* cmd = autorun_cmd_new(); + parse_intercept_actions(*handler_key, config, cmd ); + if(!cmd->invalid){ + //add the command + autorun_globals->project_commands = g_slist_prepend (autorun_globals->project_commands, cmd); + } + else{ + //free the command + autorun_cmd_free(cmd); + } } } - } + //flip it around + autorun_globals->project_commands = g_slist_reverse(autorun_globals->project_commands); + g_message ("proj command list is %i" ,g_slist_length(autorun_globals->project_commands)); }else{ g_free(gerr); } diff --git a/autorun/src/autorun.h b/autorun/src/autorun.h index c6b681c11f..d578bf167d 100644 --- a/autorun/src/autorun.h +++ b/autorun/src/autorun.h @@ -24,13 +24,13 @@ typedef struct { GeanyPlugin *plugin; GeanyData *data; - GSList * filedef_commands; + GSList * filedef_commands; GSList * project_commands; }AUTORUN_GLOBALS; typedef struct { - GeanyFiletype * filetype; + GeanyFiletype * file_type; gchar* interceptor; gushort order; gchar* command; @@ -43,7 +43,7 @@ void autorun_globals_init(GeanyPlugin *plugin); void autorun_globals_free(void); void autorun_cmd_list_free(GSList* command_list); void autorun_cmd_free(AUTORUN_CMD* cmd); -void autorun_cmd_new(AUTORUN_CMD* cmd); +AUTORUN_CMD* autorun_cmd_new(void); void load_filedefs(void); void load_projectdefs(GKeyFile* config); diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index cf97409ca8..f8bd61c898 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -20,13 +20,21 @@ #include #include "autorun.h" +#include "spawn.h" /* Handler to read any Project declared Auto-run configs */ static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ - g_message("on_project_open"); - load_projectdefs(config); + g_message("on_project_open start"); + //TODO make this more smarter + if(autorun_globals->project_commands==NULL) + { + load_projectdefs(config); + } + g_message("on_project_open end"); + } + // this handler is currently disconnected due to geany/geany#4603 /* Handler to read any Project declared Auto-run configs */ //static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ @@ -37,32 +45,41 @@ static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC /* Handler to clear any old Project declared Auto-run configs */ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer user_data) { +g_message("project close start"); //unload project handlers autorun_cmd_list_free(autorun_globals->project_commands); autorun_globals->project_commands=NULL; +g_message("project close end"); } /* Handler to run any applicable Auto-run configs after a write*/ void on_doc_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ +g_message("on save start"); + /* disabled because geany/geany#4604 never sets doc->changed*/ if(!doc->changed) { + g_message("doc not changed?"); return; } - //if there are any [autorun] OS handlers that exist for this FT - //iterate through them and run in Terminal + /**/ + dispatch_run("OS", doc); +g_message("on save end"); } + /* Handler to run any applicable Auto-run configs before a write*/ void on_doc_before_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ +g_message("before save start"); if(!doc->changed) { + g_message("doc not changed?"); return; } - //if there are any [autorun] BS handlers that exist for this FT - //iterate through them and run them in Terminal + dispatch_run("BS", doc); +g_message("before save end"); } -PluginCallback plugin_callbacks[] = { +PluginCallback plugin_callbacks[] = { { "project-open", (GCallback)&on_project_open, TRUE, NULL }, //{ "project-save", (GCallback)&on_project_save, TRUE, NULL },// geany/geany#4603 { "project-close", (GCallback)&on_project_close, TRUE, NULL }, @@ -74,10 +91,11 @@ PluginCallback plugin_callbacks[] = { /* Bring up plugin and load filetypes.FILE that exist for Auto-run */ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { + g_message("autorun_init start"); if (!autorun_globals || autorun_globals == NULL) { - g_message("autorun_init start"); autorun_globals_init(plugin); load_filedefs(); + g_message ("filedef command list is %i" ,g_slist_length(autorun_globals->filedef_commands)); //if initialized while a project is already open, manually ingest the project if(autorun_globals->data->app && autorun_globals->data->app->project) { @@ -85,6 +103,7 @@ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { GKeyFile* config=g_key_file_new (); g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); load_projectdefs(config); + g_message ("proj command list is %i" ,g_slist_length(autorun_globals->project_commands)); g_free(config); } } @@ -95,12 +114,15 @@ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { /* ensure destruction of any Auto-run objects */ static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { g_message("autorun_cleanup start"); - autorun_globals_free(); + if ( autorun_globals != NULL) { + autorun_globals_free(); + } g_message("autorun_cleanup end"); } G_MODULE_EXPORT void geany_load_module(GeanyPlugin* plugin) { +g_message("load_module start"); // who am we? plugin->info->name = "Auto-run"; plugin->info->description = _("Geany action interceptor plugin"); @@ -117,4 +139,6 @@ void geany_load_module(GeanyPlugin* plugin) { // go forth and come fifth. GEANY_PLUGIN_REGISTER(plugin, 225); +g_message("load_module end"); + } diff --git a/autorun/src/utils.c b/autorun/src/utils.c index 4eb9090e8a..f5d937cedb 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -16,7 +16,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include +#include +#include +#include #include "utils.h" #include "autorun.h" @@ -42,10 +44,16 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm gchar ** tokens; tokens = g_strsplit(key, "_", 4); gboolean invalid = FALSE; - + switch (g_strv_length(tokens)){ //strv of 3 is filedef, 4 is project case 3:{ + g_message("processing filedef!"); + g_message("type is null? %s", (cmd->file_type ==NULL)?"true":"false"); + g_message("filetype already %s", filetypes_get_display_name(cmd->file_type)); + g_message("interceptor %s",tokens[0]); + g_message("order %s",tokens[1]); + g_message("flag %s",tokens[2]); //the caller has to set filetype if(g_strcmp0(tokens[0],"BS")==0){ cmd->interceptor= g_strdup(tokens[0]); @@ -55,7 +63,7 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm }else{ invalid = TRUE; } - + gint offset=atoi(tokens[1]); if(offset<0 ||offset>99 ){ invalid = TRUE; @@ -75,7 +83,13 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm break; } case 4:{ - cmd->filetype= filetypes_lookup_by_name(tokens[0]); + g_message("processing project!"); + g_message("filetype %s",tokens[0] ); + g_message("interceptor %s",tokens[1]); + g_message("order %s",tokens[2]); + g_message("flag %s",tokens[3]); + + cmd->file_type= filetypes_lookup_by_name(tokens[0]); if(g_strcmp0(tokens[1],"BS")==0){ cmd->interceptor= g_strdup(tokens[1]); @@ -115,3 +129,13 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm return cmd->invalid; } + +/* invoke mktemp() in a crossplatformy way */ +void autorun_make_temp(gchar*) +{ + //g_get_tmp_dir() + //g_chdir() + //sg_mkstemp() +} + + diff --git a/autorun/src/utils.h b/autorun/src/utils.h index 8af4cb3e38..1866442e79 100644 --- a/autorun/src/utils.h +++ b/autorun/src/utils.h @@ -22,5 +22,7 @@ #include "autorun.h" gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd); +void g_make_temp(gchar*); + #endif From e6198f4208dc3cf5ea9889a7d1d11f60a5293e1a Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:12:09 -0400 Subject: [PATCH 09/30] spawn controller should honor order --- autorun/src/spawn.c | 78 +++++++++++++++++++++++++++++++++++++++++++++ autorun/src/spawn.h | 26 +++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 autorun/src/spawn.c create mode 100644 autorun/src/spawn.h diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c new file mode 100644 index 0000000000..a475ee34fd --- /dev/null +++ b/autorun/src/spawn.c @@ -0,0 +1,78 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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 +#include "spawn.h" +#include "autorun.h" + +void dispatch_run(const gchar * interceptor, GeanyDocument *doc) +{ + GSList* command_list=NULL; + g_message ("target interceptor %s" ,interceptor); + + //build the commands backwads + GSList* elem=NULL; + foreach_slist(elem,autorun_globals->filedef_commands ) + { + AUTORUN_CMD*cmd =((AUTORUN_CMD*)elem->data); + if(!cmd->invalid && g_strcmp0(cmd->interceptor,interceptor)==0 && cmd->file_type==doc->file_type ) + { + command_list =g_slist_prepend(command_list, cmd); + } + } + + foreach_slist(elem,autorun_globals->project_commands ) + { + AUTORUN_CMD*cmd =((AUTORUN_CMD*)elem->data); + if(!cmd->invalid && g_strcmp0(cmd->interceptor,interceptor)==0 && cmd->file_type==doc->file_type ) + { + command_list =g_slist_prepend(command_list, cmd); + } + } + //now flip it on them. + command_list = g_slist_reverse(command_list); + g_message ("command list in %s %i" ,interceptor,g_slist_length(command_list)); + if(g_slist_length(command_list)>0){ + //now if there are any cmds in scope, honor order + + GSList* meh[g_slist_length(command_list)]= {}; + foreach_slist(elem, command_list){ + AUTORUN_CMD*cmd =((AUTORUN_CMD*)elem->data); + g_message("Staged %s in %s # %i", cmd->command, cmd->working_dir, cmd->order ); + meh[cmd->order] =elem; + } + + for(uint i=0; idata; + g_message("Run %s in %s # %i", cmd->command, cmd->working_dir, cmd->order ); + } + } + + g_slist_free(command_list); +} + +gboolean run_command(AUTORUN_CMD* cmd) +{ + if(cmd->invalid) + { + return FALSE; + } + return TRUE; +} diff --git a/autorun/src/spawn.h b/autorun/src/spawn.h new file mode 100644 index 0000000000..42ce4d2c6a --- /dev/null +++ b/autorun/src/spawn.h @@ -0,0 +1,26 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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. + */ + +#ifndef __AUTORUN_SPAWN_H__ +#define __AUTORUN_SPAWN_H__ +#include +#include "autorun.h" +gboolean run_command(AUTORUN_CMD* cmd); +void dispatch_run(const gchar * interceptor, GeanyDocument *doc); + +#endif From d1e97089551932666ba7c06eda3b1a6a462a5848 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:34:10 -0400 Subject: [PATCH 10/30] Update README to remove %e Remove my mktemp impl as gtk has one Flesh out the spawn.c handler Ensure %f %d and %p are honored in spawn Prevent spawn from actually editing before-save until testing completed document-save is handled entirely externally and is live ***THIS WILL ALTER YOUR FILESYSTEM IF YOU ADD A HANDLER*** Updates geany/geany-plugins#1577 --- autorun/README | 1 - autorun/src/plugin_main.c | 9 ++- autorun/src/spawn.c | 148 +++++++++++++++++++++++++++++++++++++- autorun/src/utils.c | 13 ---- 4 files changed, 152 insertions(+), 19 deletions(-) diff --git a/autorun/README b/autorun/README index 87696e727b..00a017ce8e 100644 --- a/autorun/README +++ b/autorun/README @@ -89,7 +89,6 @@ Auto-run replacements Auto-run supports the following replacements in CM and WD %f - the basename of the file being saved. -%e - the basename but without the last extension. %d - the absolute path of the directory of the file being saved. %p - the absolute path of the base directory of the currently open project. diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index f8bd61c898..edb9ca45df 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -55,14 +55,16 @@ g_message("project close end"); /* Handler to run any applicable Auto-run configs after a write*/ void on_doc_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ g_message("on save start"); - /* disabled because geany/geany#4604 never sets doc->changed*/ + /* disabled because geany/geany#4604 never sets doc->changed if(!doc->changed) { g_message("doc not changed?"); return; } - /**/ + */ + ui_progress_bar_start(NULL); dispatch_run("OS", doc); + ui_progress_bar_stop(); g_message("on save end"); } @@ -75,7 +77,9 @@ g_message("before save start"); g_message("doc not changed?"); return; } + ui_progress_bar_start(NULL); dispatch_run("BS", doc); + ui_progress_bar_stop(); g_message("before save end"); } @@ -111,6 +115,7 @@ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { return TRUE; } + /* ensure destruction of any Auto-run objects */ static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { g_message("autorun_cleanup start"); diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index a475ee34fd..e63fc91af8 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -16,8 +16,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include +#include #include "spawn.h" #include "autorun.h" +#include "utils.h" + void dispatch_run(const gchar * interceptor, GeanyDocument *doc) { @@ -48,7 +51,6 @@ void dispatch_run(const gchar * interceptor, GeanyDocument *doc) g_message ("command list in %s %i" ,interceptor,g_slist_length(command_list)); if(g_slist_length(command_list)>0){ //now if there are any cmds in scope, honor order - GSList* meh[g_slist_length(command_list)]= {}; foreach_slist(elem, command_list){ AUTORUN_CMD*cmd =((AUTORUN_CMD*)elem->data); @@ -61,10 +63,150 @@ void dispatch_run(const gchar * interceptor, GeanyDocument *doc) break;//stop on the first null; } AUTORUN_CMD* cmd=(AUTORUN_CMD*)meh[i]->data; - g_message("Run %s in %s # %i", cmd->command, cmd->working_dir, cmd->order ); + + //prep replacements. + gchar* target_dir; + if(doc->real_path!=NULL){ + target_dir = g_path_get_dirname(doc->real_path); + }else{ + //where else but here? + target_dir = g_strdup("."); + } + gchar* target_projdir; + if(autorun_globals->data->app->project!=NULL && autorun_globals->data->app->project->base_path!=NULL){ + target_projdir=utils_get_locale_from_utf8(autorun_globals->data->app->project->base_path); + } + else{ + //fold %p to %d if no project. + target_projdir = g_strdup(target_dir); + } + gchar* target_file=NULL; + GFile* tmpfile= NULL; + GFileIOStream* iostream=NULL; + gboolean success= FALSE; + gchar* before_contents; + //we need scintilla in a buffer regardless of %f + gint con_len = sci_get_length(doc->editor->sci); + g_message("SCI is %i long", con_len); + before_contents = sci_get_contents(doc->editor->sci, con_len); + + if(g_strcmp0(interceptor,"BS")==0 && (g_strrstr(cmd->command,"%f") || g_strrstr(cmd->working_dir,"%f") )){ + //%f means something special + tmpfile= g_file_new_tmp("ar.aXXXXXX", &iostream, NULL); + if(tmpfile!=NULL){ + target_file = g_file_get_path(tmpfile); + g_message("dumpinging temp to %s", target_file); + success = g_file_replace_contents( tmpfile, before_contents ,con_len ,NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,NULL,NULL,NULL ); + }else{ + success=FALSE; + } + }else{ + //%f just means file. + success=TRUE; + target_file = utils_get_locale_from_utf8(doc->file_name); + } + + //do the replacement bits + //replace %f + GString* command = g_string_new(cmd->command); + g_string_replace(command, "%f",target_file,0); + GString* working_dir = g_string_new(cmd->working_dir); + g_string_replace(working_dir, "%f",target_file,0); + + //replace %d + g_string_replace(command, "%d",target_dir,0); + g_string_replace(working_dir, "%d",target_dir,0); + + //replace %p + g_string_replace(command, "%p",target_projdir,0); + g_string_replace(working_dir, "%p",target_projdir,0); + + //it looks like we're running; + SpawnWriteData* stdin_data; + GString * stdout_data=g_string_new(NULL); + GString * stderr_data=g_string_new(NULL); + if(success){ + g_message("Run %s",command->str); + g_message(" in %s # %i", working_dir->str, cmd->order ); + + if(g_strcmp0(interceptor,"BS")==0 && success && tmpfile==NULL ){ + //need to send stdin. + stdin_data=g_new0(SpawnWriteData,1); + stdin_data->ptr = before_contents; + } + else{ + //no stdin + stdin_data=NULL; + } + gchar **env; + env = utils_copy_environment(NULL, + "GEANY_FUNCNAME", __func__, + NULL); + GError* error=NULL; + success = spawn_sync(working_dir->str,command->str,NULL, env, stdin_data, stdout_data, stderr_data, NULL, &error); + g_strfreev(env); + if(!success){ + //somewhat bad had occur + msgwin_status_add("Command failed with %s", error->message); + ui_set_statusbar(FALSE,"Auto-run %s interceptor failed.",interceptor); + g_error_free(error); + } + }else{ + g_message("Failed somewhere before me"); + } + + //if we made a tmpfile, grab the results and del the file. + if(success && tmpfile!=NULL){ + gchar* read=NULL; + g_file_get_contents(target_file,&read,NULL,NULL); + if (read!=NULL){ + g_message("read was goodish"); + //TODO when this is working remove this conditional. + if(g_strcmp0(read,before_contents)==0){ + g_message("Its fine"); + }else{ + g_message("EVERYTHING ON FIRE"); + } + //sci_set_text(doc->editor->sci,contents); + g_free(read); + }else{ + g_message("read was nullish"); + } + } + else if(success && g_strcmp0(interceptor,"BS")==0) + { + //update scintilla from the spawn. + //sci_set_text(doc->editor->sci,spawn_results); + } + + + + if(tmpfile!=NULL){ + g_unlink(target_file); + } + + g_string_free(stdout_data,TRUE); + g_string_free(stderr_data,TRUE); + + + if(stdin_data!=NULL){ + g_free(stdin_data); + } + + if(before_contents!=NULL){ + g_free(before_contents); + } + + g_string_free(working_dir,TRUE); + g_string_free(command,TRUE); + g_free(target_dir); + g_free(target_projdir); + if(target_file!=NULL){ + g_free(target_file); + } } } - + g_slist_free(command_list); } diff --git a/autorun/src/utils.c b/autorun/src/utils.c index f5d937cedb..bc61faab76 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -17,9 +17,7 @@ */ #include -#include #include - #include "utils.h" #include "autorun.h" @@ -49,7 +47,6 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm //strv of 3 is filedef, 4 is project case 3:{ g_message("processing filedef!"); - g_message("type is null? %s", (cmd->file_type ==NULL)?"true":"false"); g_message("filetype already %s", filetypes_get_display_name(cmd->file_type)); g_message("interceptor %s",tokens[0]); g_message("order %s",tokens[1]); @@ -129,13 +126,3 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm return cmd->invalid; } - -/* invoke mktemp() in a crossplatformy way */ -void autorun_make_temp(gchar*) -{ - //g_get_tmp_dir() - //g_chdir() - //sg_mkstemp() -} - - From 2890710261b198f5b0f89db96a09f3d91437ee43 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Mon, 22 Jun 2026 18:53:17 -0400 Subject: [PATCH 11/30] Enable before-save interceptor Update all documents with .clang-format backed formatting changes --- autorun/README | 44 +++--- autorun/src/.clang-format | 214 +++++++++++++++++++++++++++ autorun/src/autorun.c | 219 +++++++++++++--------------- autorun/src/autorun.h | 1 + autorun/src/plugin_main.c | 65 ++++----- autorun/src/spawn.c | 298 +++++++++++++++++++------------------- autorun/src/utils.c | 171 +++++++++++----------- autorun/src/utils.h | 2 - 8 files changed, 603 insertions(+), 411 deletions(-) create mode 100644 autorun/src/.clang-format diff --git a/autorun/README b/autorun/README index 00a017ce8e..0346461de3 100644 --- a/autorun/README +++ b/autorun/README @@ -22,6 +22,28 @@ The Auto-run menu ------------------ The Auto-run menu allows you to manage the Project level Auto-run configs. +Auto-run replacements +--------------------- + +Auto-run supports the following replacements in CM and WD +%f - the basename of the file being saved. +%a - the absolute path of the file being saved. +%d - the absolute path of the directory of the file being saved. +%p - the absolute path of the base directory of the currently open project. + +The BS Intercepted Action by default does not need the %a argument as it +operates on the contents of the Scintilla editor. + +Why would BS targets support %a then? +Place the %a replacement in the BS target if you know the CM does not honor STDIN. +In that case Auto-run will dump the contents of Scintilla to a temp file. + +clang-format, which reads stdin, would like to the filename for formatting rules. +e.g. clang-format --assume-filename=%f + +cmake-format, which does not read stdin +e.g. cmake-format %a + Manually setting Auto-run configs --------------------------------- @@ -45,9 +67,9 @@ TA(Target Action) = (CM|WD) C_BS_00_CM = formatter C_BS_00_WD = %d -C_OS_00_CM = linter %f +C_OS_00_CM = linter %a C_OS_00_WD = %p -#Run formatter before file write, and then linter on the newly written %f from %p +#Run formatter before file write, and then linter on the newly written %d/%f from %p #when attempting to save a C file type. C++_OS_00_CM = cppcheck %f --output-file=%p/cppcheck.log @@ -84,24 +106,6 @@ Autorun will run fancyformatter before save for C files as long as project.geany All Auto-run actions will target the Geany Terminal pane. -Auto-run replacements ---------------------- - -Auto-run supports the following replacements in CM and WD -%f - the basename of the file being saved. -%d - the absolute path of the directory of the file being saved. -%p - the absolute path of the base directory of the currently open project. - -The BS Intercepted Action by default does not need the %f argument as it -operates on the contents of the Scintilla editor. - -Why would BS targets support %f then? -Place the %f replacement in the BS target if you know the CM does not honor STDIN. -In that case Auto-run will dump the contents of Scintilla to a temp file. - -Known examples: cmake-format - - Known issues ============ diff --git a/autorun/src/.clang-format b/autorun/src/.clang-format new file mode 100644 index 0000000000..4608ee667c --- /dev/null +++ b/autorun/src/.clang-format @@ -0,0 +1,214 @@ +Language: Cpp +BasedOnStyle: 'LLVM' +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 170 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 0 +ContinuationIndentWidth: 0 +Cpp11BracedListStyle: false +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: true +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 0 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RequiresClausePosition: OwnLine +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: After +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 2 +UseCRLF: false +UseTab: Always +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME + + diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c index 6b82ffee3a..22f06fb404 100644 --- a/autorun/src/autorun.c +++ b/autorun/src/autorun.c @@ -16,137 +16,127 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include #include "autorun.h" #include "utils.h" +#include AUTORUN_GLOBALS* autorun_globals; - -AUTORUN_CMD* autorun_cmd_new(){ - AUTORUN_CMD* cmd=NULL; - cmd=g_new0(AUTORUN_CMD,1); - cmd->file_type=NULL; - cmd->interceptor=NULL; - cmd->command=NULL; - cmd->working_dir=NULL; - cmd->invalid=TRUE; - cmd->order=0; +AUTORUN_CMD* autorun_cmd_new() { + AUTORUN_CMD* cmd = NULL; + cmd = g_new0(AUTORUN_CMD, 1); + cmd->file_type = NULL; + cmd->interceptor = NULL; + cmd->command = NULL; + cmd->working_dir = NULL; + cmd->invalid = TRUE; + cmd->order = 0; return cmd; } -void autorun_cmd_free(AUTORUN_CMD* cmd){ - /* do not check for cmd->invalid because it could be a partial command - * or invalidated after assembly. */ - if(cmd==NULL){ - //who are you and why are you in my attic? - return; - } - //free the command - if(cmd->file_type !=NULL){ - cmd->file_type=NULL; - } - if(cmd->interceptor !=NULL){ - g_free(cmd->interceptor); - cmd->interceptor=NULL; - } - if(cmd->command !=NULL){ - g_free(cmd->command); - cmd->command=NULL; - } - if(cmd->working_dir !=NULL) - { - g_free(cmd->working_dir); - cmd->working_dir=NULL; - } +void autorun_cmd_free(AUTORUN_CMD* cmd) { + /* do not check for cmd->invalid because it could be a partial command + * or invalidated after assembly. */ + if (cmd == NULL) { + // who are you and why are you in my attic? + return; + } + // free the command + if (cmd->file_type != NULL) { + cmd->file_type = NULL; + } + if (cmd->interceptor != NULL) { + g_free(cmd->interceptor); + cmd->interceptor = NULL; + } + if (cmd->command != NULL) { + g_free(cmd->command); + cmd->command = NULL; + } + if (cmd->working_dir != NULL) { + g_free(cmd->working_dir); + cmd->working_dir = NULL; + } } -void autorun_cmd_list_free(GSList* command_list){ - if(command_list==NULL){ - //why are you naked? - return; - } - g_message ("command list had %i" ,g_slist_length(command_list)); - g_slist_free_full(command_list,(GDestroyNotify)autorun_cmd_free); +void autorun_cmd_list_free(GSList* command_list) { + if (command_list == NULL) { + // why are you naked? + return; + } + g_message("command list had %i", g_slist_length(command_list)); + g_slist_free_full(command_list, (GDestroyNotify)autorun_cmd_free); } -void autorun_globals_init(GeanyPlugin* plugin) -{ - if(!autorun_globals || autorun_globals==NULL){ - autorun_globals = g_new0(AUTORUN_GLOBALS,1); - autorun_globals->plugin=plugin; - autorun_globals->data=plugin->geany_data; - autorun_globals->filedef_commands=NULL; - autorun_globals->project_commands=NULL; +void autorun_globals_init(GeanyPlugin* plugin) { + if (!autorun_globals || autorun_globals == NULL) { + autorun_globals = g_new0(AUTORUN_GLOBALS, 1); + autorun_globals->plugin = plugin; + autorun_globals->data = plugin->geany_data; + autorun_globals->filedef_commands = NULL; + autorun_globals->project_commands = NULL; } } -void autorun_globals_free(void) -{ - if(autorun_globals!=NULL){ - if(autorun_globals->filedef_commands!=NULL) - { +void autorun_globals_free(void) { + if (autorun_globals != NULL) { + if (autorun_globals->filedef_commands != NULL) { autorun_cmd_list_free(autorun_globals->filedef_commands); - autorun_globals->filedef_commands=NULL; + autorun_globals->filedef_commands = NULL; } - if(autorun_globals->project_commands!=NULL) - { + if (autorun_globals->project_commands != NULL) { autorun_cmd_list_free(autorun_globals->project_commands); - autorun_globals->project_commands=NULL; + autorun_globals->project_commands = NULL; } - autorun_globals->data=NULL; - autorun_globals->plugin=NULL; + autorun_globals->data = NULL; + autorun_globals->plugin = NULL; g_free(autorun_globals); - autorun_globals=NULL; + autorun_globals = NULL; } } -void load_filedefs(void) -{ +void load_filedefs(void) { // if there are any filetypes.FILE autorun sections - gchar * filedef_path = g_build_path(G_DIR_SEPARATOR_S, autorun_globals->data->app->configdir, GEANY_FILEDEFS_SUBDIR, NULL); + gchar* filedef_path = g_build_path(G_DIR_SEPARATOR_S, autorun_globals->data->app->configdir, GEANY_FILEDEFS_SUBDIR, NULL); guint filedef_len; - GSList * file_list = utils_get_file_list(filedef_path, &filedef_len, NULL); - if( filedef_len > 0){ + GSList* file_list = utils_get_file_list(filedef_path, &filedef_len, NULL); + if (filedef_len > 0) { // stash filetypes.FILE to fallback on-project-close - GSList* node=NULL; - foreach_slist(node,file_list){ - if( g_str_has_prefix(node->data, "filetypes.") && g_strcmp0(node->data, "filetypes.README")!=0 ) - { - //we have files to process + GSList* node = NULL; + foreach_slist(node, file_list) { + if (g_str_has_prefix(node->data, "filetypes.") && g_strcmp0(node->data, "filetypes.README") != 0) { + // we have files to process gsize key_len; - gchar * filedef_file = g_build_filename(G_DIR_SEPARATOR_S, filedef_path, (gchar*)node->data, NULL); - GKeyFile* config=g_key_file_new (); - g_key_file_load_from_file(config,filedef_file ,G_KEY_FILE_NONE, NULL); - GError * gerr=NULL; + gchar* filedef_file = g_build_filename(G_DIR_SEPARATOR_S, filedef_path, (gchar*)node->data, NULL); + GKeyFile* config = g_key_file_new(); + g_key_file_load_from_file(config, filedef_file, G_KEY_FILE_NONE, NULL); + GError* gerr = NULL; gchar** handlers = g_key_file_get_keys(config, "autorun", &key_len, &gerr); - //if the keyfile has an [autorun] section - if( gerr == NULL ){ - g_message("found %s autorun",(gchar*)node->data); + // if the keyfile has an [autorun] section + if (gerr == NULL) { + g_message("found %s autorun", (gchar*)node->data); gchar** handler_key; - foreach_strv( handler_key, handlers){ + foreach_strv(handler_key, handlers) { g_message(" %s", *handler_key); - if(g_str_has_suffix(*handler_key,"CM")){ + if (g_str_has_suffix(*handler_key, "CM")) { AUTORUN_CMD* cmd = autorun_cmd_new(); - cmd->file_type=filetypes_detect_from_file(node->data); - g_message ("filetype was %s", filetypes_get_display_name(cmd->file_type)); - parse_intercept_actions(*handler_key, config, cmd ); - if(!cmd->invalid){ - //add the command + cmd->file_type = filetypes_detect_from_file(node->data); + g_message("filetype was %s", filetypes_get_display_name(cmd->file_type)); + parse_intercept_actions(*handler_key, config, cmd); + if (!cmd->invalid) { + // add the command autorun_globals->filedef_commands = g_slist_prepend(autorun_globals->filedef_commands, cmd); - g_message ("filedef list is %i" ,g_slist_length(autorun_globals->filedef_commands)); + g_message("filedef list is %i", g_slist_length(autorun_globals->filedef_commands)); - } - else{ - //free the command + } else { + // free the command autorun_cmd_free(cmd); } } } - //flip it around + // flip it around autorun_globals->filedef_commands = g_slist_reverse(autorun_globals->filedef_commands); - }else - { + } else { g_free(gerr); } g_free(filedef_file); @@ -154,44 +144,43 @@ void load_filedefs(void) } } } - - //cleanup - g_slist_foreach(file_list, (GFunc) g_free, NULL); + + // cleanup + g_slist_foreach(file_list, (GFunc)g_free, NULL); g_slist_free(file_list); g_free(filedef_path); } -void load_projectdefs(GKeyFile* config){ +void load_projectdefs(GKeyFile* config) { gsize key_len; - GError * gerr=NULL; + GError* gerr = NULL; gchar** handlers = g_key_file_get_keys(config, "autorun", &key_len, &gerr); - //if the keyfile has an [autorun] section - if( gerr==NULL ){ + // if the keyfile has an [autorun] section + if (gerr == NULL) { g_message("found project autorun"); gchar** handler_key; - //load any handlers - foreach_strv( handler_key, handlers){ - //over any existing [autorun] handlers + // load any handlers + foreach_strv(handler_key, handlers) { + // over any existing [autorun] handlers g_message(" %s", *handler_key); - if(g_str_has_suffix(*handler_key,"CM")){ + if (g_str_has_suffix(*handler_key, "CM")) { AUTORUN_CMD* cmd = autorun_cmd_new(); - parse_intercept_actions(*handler_key, config, cmd ); - if(!cmd->invalid){ - //add the command - autorun_globals->project_commands = g_slist_prepend (autorun_globals->project_commands, cmd); - } - else{ - //free the command + parse_intercept_actions(*handler_key, config, cmd); + if (!cmd->invalid) { + // add the command + autorun_globals->project_commands = g_slist_prepend(autorun_globals->project_commands, cmd); + } else { + // free the command autorun_cmd_free(cmd); } } } - //flip it around + // flip it around autorun_globals->project_commands = g_slist_reverse(autorun_globals->project_commands); - g_message ("proj command list is %i" ,g_slist_length(autorun_globals->project_commands)); - }else{ + g_message("proj command list is %i", g_slist_length(autorun_globals->project_commands)); + } else { g_free(gerr); } - //The caller of g_key_file_get_keys takes ownership of the returned data, and is responsible for freeing it. + // The caller of g_key_file_get_keys takes ownership of the returned data, and is responsible for freeing it. g_free(handlers); } diff --git a/autorun/src/autorun.h b/autorun/src/autorun.h index d578bf167d..9bb157dd22 100644 --- a/autorun/src/autorun.h +++ b/autorun/src/autorun.h @@ -39,6 +39,7 @@ typedef struct }AUTORUN_CMD; extern AUTORUN_GLOBALS* autorun_globals; + void autorun_globals_init(GeanyPlugin *plugin); void autorun_globals_free(void); void autorun_cmd_list_free(GSList* command_list); diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index edb9ca45df..43df8e8d3f 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -23,38 +23,34 @@ #include "spawn.h" /* Handler to read any Project declared Auto-run configs */ -static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ +static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data) { g_message("on_project_open start"); - //TODO make this more smarter - if(autorun_globals->project_commands==NULL) - { + // TODO make this more smarter + if (autorun_globals->project_commands == NULL) { load_projectdefs(config); } g_message("on_project_open end"); - } - // this handler is currently disconnected due to geany/geany#4603 /* Handler to read any Project declared Auto-run configs */ -//static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ +// static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ // g_message("on_project_save"); // load_projectdefs(config); -//} - +// } /* Handler to clear any old Project declared Auto-run configs */ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer user_data) { -g_message("project close start"); - //unload project handlers + g_message("project close start"); + // unload project handlers autorun_cmd_list_free(autorun_globals->project_commands); - autorun_globals->project_commands=NULL; -g_message("project close end"); + autorun_globals->project_commands = NULL; + g_message("project close end"); } /* Handler to run any applicable Auto-run configs after a write*/ -void on_doc_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ -g_message("on save start"); +void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { + g_message("on save start"); /* disabled because geany/geany#4604 never sets doc->changed if(!doc->changed) { @@ -65,33 +61,32 @@ g_message("on save start"); ui_progress_bar_start(NULL); dispatch_run("OS", doc); ui_progress_bar_stop(); -g_message("on save end"); + g_message("on save end"); } - /* Handler to run any applicable Auto-run configs before a write*/ -void on_doc_before_save(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, G_GNUC_UNUSED gpointer user_data){ -g_message("before save start"); - if(!doc->changed) - { +void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { + g_message("before save start"); + if (!doc->changed) { g_message("doc not changed?"); return; } ui_progress_bar_start(NULL); dispatch_run("BS", doc); ui_progress_bar_stop(); -g_message("before save end"); + g_message("before save end"); } +// clang-format off PluginCallback plugin_callbacks[] = { { "project-open", (GCallback)&on_project_open, TRUE, NULL }, //{ "project-save", (GCallback)&on_project_save, TRUE, NULL },// geany/geany#4603 { "project-close", (GCallback)&on_project_close, TRUE, NULL }, - { "document-save", (GCallback)&on_doc_save, TRUE, NULL}, - { "document-before-save", (GCallback)&on_doc_before_save, TRUE, NULL}, + { "document-save", (GCallback)&on_doc_save, TRUE, NULL }, + { "document-before-save", (GCallback)&on_doc_before_save, TRUE, NULL }, { NULL, NULL, FALSE, NULL } }; - +// clang-format on /* Bring up plugin and load filetypes.FILE that exist for Auto-run */ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { @@ -99,15 +94,15 @@ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { if (!autorun_globals || autorun_globals == NULL) { autorun_globals_init(plugin); load_filedefs(); - g_message ("filedef command list is %i" ,g_slist_length(autorun_globals->filedef_commands)); + g_message("filedef command list is %i", g_slist_length(autorun_globals->filedef_commands)); - //if initialized while a project is already open, manually ingest the project - if(autorun_globals->data->app && autorun_globals->data->app->project) { + // if initialized while a project is already open, manually ingest the project + if (autorun_globals->data->app && autorun_globals->data->app->project) { // force a GKeyFile - GKeyFile* config=g_key_file_new (); - g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + GKeyFile* config = g_key_file_new(); + g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); load_projectdefs(config); - g_message ("proj command list is %i" ,g_slist_length(autorun_globals->project_commands)); + g_message("proj command list is %i", g_slist_length(autorun_globals->project_commands)); g_free(config); } } @@ -115,11 +110,10 @@ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { return TRUE; } - /* ensure destruction of any Auto-run objects */ static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { g_message("autorun_cleanup start"); - if ( autorun_globals != NULL) { + if (autorun_globals != NULL) { autorun_globals_free(); } g_message("autorun_cleanup end"); @@ -127,7 +121,7 @@ static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { G_MODULE_EXPORT void geany_load_module(GeanyPlugin* plugin) { -g_message("load_module start"); + g_message("load_module start"); // who am we? plugin->info->name = "Auto-run"; plugin->info->description = _("Geany action interceptor plugin"); @@ -144,6 +138,5 @@ g_message("load_module start"); // go forth and come fifth. GEANY_PLUGIN_REGISTER(plugin, 225); -g_message("load_module end"); - + g_message("load_module end"); } diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index e63fc91af8..feff8af5c6 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -15,205 +15,201 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include #include "spawn.h" -#include "autorun.h" +#include "autorun.h" #include "utils.h" +#include +#include - -void dispatch_run(const gchar * interceptor, GeanyDocument *doc) -{ - GSList* command_list=NULL; - g_message ("target interceptor %s" ,interceptor); - - //build the commands backwads - GSList* elem=NULL; - foreach_slist(elem,autorun_globals->filedef_commands ) - { - AUTORUN_CMD*cmd =((AUTORUN_CMD*)elem->data); - if(!cmd->invalid && g_strcmp0(cmd->interceptor,interceptor)==0 && cmd->file_type==doc->file_type ) - { - command_list =g_slist_prepend(command_list, cmd); +void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { + g_message("dispatch_run %%f %s", doc->file_name); + g_message("dispatch_run %%d %s", doc->real_path); + + GSList* command_list = NULL; + g_message("target interceptor %s", interceptor); + + // build the commands backwads + GSList* elem = NULL; + foreach_slist(elem, autorun_globals->filedef_commands) { + AUTORUN_CMD* cmd = ((AUTORUN_CMD*)elem->data); + if (!cmd->invalid && g_strcmp0(cmd->interceptor, interceptor) == 0 && cmd->file_type == doc->file_type) { + command_list = g_slist_prepend(command_list, cmd); } } - - foreach_slist(elem,autorun_globals->project_commands ) - { - AUTORUN_CMD*cmd =((AUTORUN_CMD*)elem->data); - if(!cmd->invalid && g_strcmp0(cmd->interceptor,interceptor)==0 && cmd->file_type==doc->file_type ) - { - command_list =g_slist_prepend(command_list, cmd); + + foreach_slist(elem, autorun_globals->project_commands) { + AUTORUN_CMD* cmd = ((AUTORUN_CMD*)elem->data); + if (!cmd->invalid && g_strcmp0(cmd->interceptor, interceptor) == 0 && cmd->file_type == doc->file_type) { + command_list = g_slist_prepend(command_list, cmd); } } - //now flip it on them. + // now flip it on them. command_list = g_slist_reverse(command_list); - g_message ("command list in %s %i" ,interceptor,g_slist_length(command_list)); - if(g_slist_length(command_list)>0){ - //now if there are any cmds in scope, honor order - GSList* meh[g_slist_length(command_list)]= {}; - foreach_slist(elem, command_list){ - AUTORUN_CMD*cmd =((AUTORUN_CMD*)elem->data); - g_message("Staged %s in %s # %i", cmd->command, cmd->working_dir, cmd->order ); - meh[cmd->order] =elem; + g_message("command list in %s %i", interceptor, g_slist_length(command_list)); + if (g_slist_length(command_list) > 0) { + // now if there are any cmds in scope, honor order + GSList* meh[g_slist_length(command_list)] = {}; + foreach_slist(elem, command_list) { + AUTORUN_CMD* cmd = ((AUTORUN_CMD*)elem->data); + g_message("Staged %s in %s # %i", cmd->command, cmd->working_dir, cmd->order); + meh[cmd->order] = elem; } - for(uint i=0; idata; + AUTORUN_CMD* cmd = (AUTORUN_CMD*)meh[i]->data; - //prep replacements. + // prep replacements. gchar* target_dir; - if(doc->real_path!=NULL){ + if (doc->real_path != NULL) { target_dir = g_path_get_dirname(doc->real_path); - }else{ - //where else but here? + } else { + // where else but here? target_dir = g_strdup("."); } gchar* target_projdir; - if(autorun_globals->data->app->project!=NULL && autorun_globals->data->app->project->base_path!=NULL){ - target_projdir=utils_get_locale_from_utf8(autorun_globals->data->app->project->base_path); - } - else{ - //fold %p to %d if no project. + if (autorun_globals->data->app->project != NULL && autorun_globals->data->app->project->base_path != NULL) { + target_projdir = utils_get_locale_from_utf8(autorun_globals->data->app->project->base_path); + } else { + // fold %p to %d if no project. target_projdir = g_strdup(target_dir); } - gchar* target_file=NULL; - GFile* tmpfile= NULL; - GFileIOStream* iostream=NULL; - gboolean success= FALSE; + gchar* target_file = NULL; + GFile* tmpfile = NULL; + GFileIOStream* iostream = NULL; + gboolean success = FALSE; gchar* before_contents; - //we need scintilla in a buffer regardless of %f + // we need scintilla in a buffer regardless of %a gint con_len = sci_get_length(doc->editor->sci); g_message("SCI is %i long", con_len); before_contents = sci_get_contents(doc->editor->sci, con_len); - if(g_strcmp0(interceptor,"BS")==0 && (g_strrstr(cmd->command,"%f") || g_strrstr(cmd->working_dir,"%f") )){ - //%f means something special - tmpfile= g_file_new_tmp("ar.aXXXXXX", &iostream, NULL); - if(tmpfile!=NULL){ + if (g_strcmp0(interceptor, "BS") == 0 && (g_strrstr(cmd->command, "%a") || g_strrstr(cmd->working_dir, "%a"))) { + //%a means something special + tmpfile = g_file_new_tmp("ar.aXXXXXX", &iostream, NULL); + if (tmpfile != NULL) { target_file = g_file_get_path(tmpfile); - g_message("dumpinging temp to %s", target_file); - success = g_file_replace_contents( tmpfile, before_contents ,con_len ,NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,NULL,NULL,NULL ); - }else{ - success=FALSE; + g_message("dumping SCI to %s", target_file); + success = g_file_replace_contents(tmpfile, before_contents, con_len, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, NULL); + } else { + success = FALSE; } - }else{ - //%f just means file. - success=TRUE; + } else { + //%a just means file. + success = TRUE; target_file = utils_get_locale_from_utf8(doc->file_name); } - //do the replacement bits - //replace %f - GString* command = g_string_new(cmd->command); - g_string_replace(command, "%f",target_file,0); - GString* working_dir = g_string_new(cmd->working_dir); - g_string_replace(working_dir, "%f",target_file,0); - - //replace %d - g_string_replace(command, "%d",target_dir,0); - g_string_replace(working_dir, "%d",target_dir,0); - - //replace %p - g_string_replace(command, "%p",target_projdir,0); - g_string_replace(working_dir, "%p",target_projdir,0); - - //it looks like we're running; + // do the replacement bits + gchar* target_name = g_path_get_basename(target_file); + // replace %f + GString* command = g_string_new(cmd->command); + g_string_replace(command, "%f", target_name, 0); + GString* working_dir = g_string_new(cmd->working_dir); + g_string_replace(working_dir, "%f", target_name, 0); + + // replace %a + g_string_replace(command, "%a", target_file, 0); + g_string_replace(working_dir, "%a", target_file, 0); + + // replace %d + g_string_replace(command, "%d", target_dir, 0); + g_string_replace(working_dir, "%d", target_dir, 0); + + // replace %p + g_string_replace(command, "%p", target_projdir, 0); + g_string_replace(working_dir, "%p", target_projdir, 0); + + // it looks like we're running; SpawnWriteData* stdin_data; - GString * stdout_data=g_string_new(NULL); - GString * stderr_data=g_string_new(NULL); - if(success){ - g_message("Run %s",command->str); - g_message(" in %s # %i", working_dir->str, cmd->order ); - - if(g_strcmp0(interceptor,"BS")==0 && success && tmpfile==NULL ){ - //need to send stdin. - stdin_data=g_new0(SpawnWriteData,1); + GString* stdout_data = g_string_new(NULL); + GString* stderr_data = g_string_new(NULL); + if (success) { + g_message("Run %s", command->str); + g_message(" in %s # %i", working_dir->str, cmd->order); + + if (g_strcmp0(interceptor, "BS") == 0 && success && tmpfile == NULL) { + // need to send stdin. + g_message("Send to stdin"); + stdin_data = g_new0(SpawnWriteData, 1); stdin_data->ptr = before_contents; + stdin_data->size = con_len; + } else { + g_message("Read from file"); + // no stdin + stdin_data = NULL; } - else{ - //no stdin - stdin_data=NULL; - } - gchar **env; - env = utils_copy_environment(NULL, - "GEANY_FUNCNAME", __func__, - NULL); - GError* error=NULL; - success = spawn_sync(working_dir->str,command->str,NULL, env, stdin_data, stdout_data, stderr_data, NULL, &error); + gchar** env; + env = utils_copy_environment(NULL, "GEANY_FUNCNAME", __func__, NULL); + GError* error = NULL; + success = spawn_sync(working_dir->str, command->str, NULL, env, stdin_data, stdout_data, stderr_data, NULL, &error); g_strfreev(env); - if(!success){ - //somewhat bad had occur + if (!success) { + g_message("!success"); + // somewhat bad had occur msgwin_status_add("Command failed with %s", error->message); - ui_set_statusbar(FALSE,"Auto-run %s interceptor failed.",interceptor); + ui_set_statusbar(FALSE, "Auto-run %s interceptor failed.", interceptor); g_error_free(error); - } - }else{ - g_message("Failed somewhere before me"); - } - - //if we made a tmpfile, grab the results and del the file. - if(success && tmpfile!=NULL){ - gchar* read=NULL; - g_file_get_contents(target_file,&read,NULL,NULL); - if (read!=NULL){ - g_message("read was goodish"); - //TODO when this is working remove this conditional. - if(g_strcmp0(read,before_contents)==0){ - g_message("Its fine"); - }else{ - g_message("EVERYTHING ON FIRE"); + } else { + g_message("success"); + // if we made a tmpfile, grab the results and del the file. + if (tmpfile != NULL) { + gchar* read = NULL; + g_file_get_contents(target_file, &read, NULL, NULL); + if (read != NULL) { + g_message("read was goodish"); + // TODO when this is working remove this conditional. + if (g_strcmp0(read, before_contents) == 0) { + g_message("Temp no change"); + } else { + g_message("Temp changed"); + msgwin_compiler_add_string(COLOR_BLACK, read); + } + // sci_set_text(doc->editor->sci,contents); + g_free(read); + } else { + g_message("read was nullish"); + } + } else if (g_strcmp0(interceptor, "BS") == 0) { + // update scintilla from the spawn. + sci_set_text(doc->editor->sci, stdout_data->str); + msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); } - //sci_set_text(doc->editor->sci,contents); - g_free(read); - }else{ - g_message("read was nullish"); } - } - else if(success && g_strcmp0(interceptor,"BS")==0) - { - //update scintilla from the spawn. - //sci_set_text(doc->editor->sci,spawn_results); - } - - - if(tmpfile!=NULL){ - g_unlink(target_file); - } + if (tmpfile != NULL) { + g_unlink(target_file); + } - g_string_free(stdout_data,TRUE); - g_string_free(stderr_data,TRUE); + g_string_free(stdout_data, TRUE); + g_string_free(stderr_data, TRUE); + if (stdin_data != NULL) { + g_free(stdin_data); + } - if(stdin_data!=NULL){ - g_free(stdin_data); - } + if (before_contents != NULL) { + g_free(before_contents); + } - if(before_contents!=NULL){ - g_free(before_contents); - } - - g_string_free(working_dir,TRUE); - g_string_free(command,TRUE); - g_free(target_dir); - g_free(target_projdir); - if(target_file!=NULL){ - g_free(target_file); + g_string_free(working_dir, TRUE); + g_string_free(command, TRUE); + g_free(target_name); + g_free(target_dir); + g_free(target_projdir); + if (target_file != NULL) { + g_free(target_file); + } } } } - g_slist_free(command_list); } -gboolean run_command(AUTORUN_CMD* cmd) -{ - if(cmd->invalid) - { +gboolean run_command(AUTORUN_CMD* cmd) { + if (cmd->invalid) { return FALSE; } return TRUE; diff --git a/autorun/src/utils.c b/autorun/src/utils.c index bc61faab76..6a2053be64 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -16,10 +16,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include #include "utils.h" #include "autorun.h" +#include +#include /* Read a filedef or keyfile for Intercept Actions * @param action key (should end with CM) @@ -27,102 +27,99 @@ * @return true if parsing worked * false is parsing failed * */ -gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd){ - //key should be a (filetype_)action_number_flag string - if( key==NULL || cmd ==NULL){ +gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd) { + // key should be a (filetype_)action_number_flag string + if (key == NULL || cmd == NULL) { return FALSE; } - //is this a key? - if(g_strstr_len(key,-1,"_")==NULL){ - //no - cmd->invalid= TRUE; + // is this a key? + if (g_strstr_len(key, -1, "_") == NULL) { + // no + cmd->invalid = TRUE; return FALSE; } - //what is this key? - gchar ** tokens; + // what is this key? + gchar** tokens; tokens = g_strsplit(key, "_", 4); gboolean invalid = FALSE; - switch (g_strv_length(tokens)){ - //strv of 3 is filedef, 4 is project - case 3:{ - g_message("processing filedef!"); - g_message("filetype already %s", filetypes_get_display_name(cmd->file_type)); - g_message("interceptor %s",tokens[0]); - g_message("order %s",tokens[1]); - g_message("flag %s",tokens[2]); - //the caller has to set filetype - if(g_strcmp0(tokens[0],"BS")==0){ - cmd->interceptor= g_strdup(tokens[0]); - }else - if(g_strcmp0(tokens[0],"OS")==0){ - cmd->interceptor= g_strdup(tokens[0]); - }else{ - invalid = TRUE; - } + switch (g_strv_length(tokens)) { + // strv of 3 is filedef, 4 is project + case 3: { + g_message("processing filedef!"); + g_message("filetype already %s", filetypes_get_display_name(cmd->file_type)); + g_message("interceptor %s", tokens[0]); + g_message("order %s", tokens[1]); + g_message("flag %s", tokens[2]); + // the caller has to set filetype + if (g_strcmp0(tokens[0], "BS") == 0) { + cmd->interceptor = g_strdup(tokens[0]); + } else if (g_strcmp0(tokens[0], "OS") == 0) { + cmd->interceptor = g_strdup(tokens[0]); + } else { + invalid = TRUE; + } + + gint offset = atoi(tokens[1]); + if (offset < 0 || offset > 99) { + invalid = TRUE; + } else { + cmd->order = offset; + } + if (g_strcmp0(tokens[2], "CM") == 0) { + cmd->command = utils_get_setting_string(key_file, "autorun", key, ""); + gchar* next_key; + next_key = g_strconcat(tokens[0], "_", tokens[1], "_", "WD", NULL); + cmd->working_dir = utils_get_setting_string(key_file, "autorun", next_key, ""); + g_free(next_key); + } else { + invalid = TRUE; + } + cmd->invalid = invalid; + break; + } + case 4: { + g_message("processing project!"); + g_message("filetype %s", tokens[0]); + g_message("interceptor %s", tokens[1]); + g_message("order %s", tokens[2]); + g_message("flag %s", tokens[3]); - gint offset=atoi(tokens[1]); - if(offset<0 ||offset>99 ){ - invalid = TRUE; - }else{ - cmd->order=offset; - } - if(g_strcmp0(tokens[2],"CM")==0){ - cmd->command = utils_get_setting_string(key_file,"autorun",key,""); - gchar* next_key; - next_key=g_strconcat(tokens[0],"_",tokens[1],"_","WD",NULL); - cmd->working_dir = utils_get_setting_string(key_file,"autorun",next_key,""); - g_free(next_key); - }else{ - invalid = TRUE; - } - cmd->invalid = invalid; - break; - } - case 4:{ - g_message("processing project!"); - g_message("filetype %s",tokens[0] ); - g_message("interceptor %s",tokens[1]); - g_message("order %s",tokens[2]); - g_message("flag %s",tokens[3]); - - cmd->file_type= filetypes_lookup_by_name(tokens[0]); + cmd->file_type = filetypes_lookup_by_name(tokens[0]); - if(g_strcmp0(tokens[1],"BS")==0){ - cmd->interceptor= g_strdup(tokens[1]); - }else - if(g_strcmp0(tokens[1],"OS")==0){ - cmd->interceptor= g_strdup(tokens[1]); - }else{ - invalid = TRUE; - } - - gint offset =atoi(tokens[2]); - if(offset<0 ||offset>99 ){ - invalid = TRUE; - }else{ - cmd->order=offset; - } + if (g_strcmp0(tokens[1], "BS") == 0) { + cmd->interceptor = g_strdup(tokens[1]); + } else if (g_strcmp0(tokens[1], "OS") == 0) { + cmd->interceptor = g_strdup(tokens[1]); + } else { + invalid = TRUE; + } - if(g_strcmp0(tokens[3],"CM")==0){ - cmd->command = utils_get_setting_string(key_file,"autorun",key,""); - gchar* next_key; - next_key=g_strconcat(tokens[0],"_",tokens[1],"_",tokens[2],"_", "WD",NULL); - cmd->working_dir = utils_get_setting_string(key_file,"autorun",next_key,""); - g_free(next_key); - } - else{ - invalid = TRUE; - } - cmd->invalid = invalid; - break; - } - default:{ - invalid = TRUE; - } + gint offset = atoi(tokens[2]); + if (offset < 0 || offset > 99) { + invalid = TRUE; + } else { + cmd->order = offset; + } + + if (g_strcmp0(tokens[3], "CM") == 0) { + cmd->command = utils_get_setting_string(key_file, "autorun", key, ""); + gchar* next_key; + next_key = g_strconcat(tokens[0], "_", tokens[1], "_", tokens[2], "_", "WD", NULL); + cmd->working_dir = utils_get_setting_string(key_file, "autorun", next_key, ""); + g_free(next_key); + } else { + invalid = TRUE; + } + cmd->invalid = invalid; + break; + } + default: { + invalid = TRUE; + } } - + g_strfreev(tokens); - + return cmd->invalid; } diff --git a/autorun/src/utils.h b/autorun/src/utils.h index 1866442e79..8af4cb3e38 100644 --- a/autorun/src/utils.h +++ b/autorun/src/utils.h @@ -22,7 +22,5 @@ #include "autorun.h" gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd); -void g_make_temp(gchar*); - #endif From 14a0127a2c23b198066535637e64ec2b093b0cfe Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:18:15 -0400 Subject: [PATCH 12/30] Updated project to use clang for .h as well Clarified README to point to Status and Compiler instead of Terminal reordered all includes to make them uniform --- autorun/README | 3 ++- autorun/src/autorun.c | 3 ++- autorun/src/autorun.h | 35 +++++++++++++++++------------------ autorun/src/spawn.c | 25 ++++++++----------------- autorun/src/spawn.h | 10 ++++++---- autorun/src/utils.c | 5 +++-- autorun/src/utils.h | 5 +++-- 7 files changed, 41 insertions(+), 45 deletions(-) diff --git a/autorun/README b/autorun/README index 0346461de3..0a1775d071 100644 --- a/autorun/README +++ b/autorun/README @@ -104,7 +104,8 @@ CBS_00_CM = fancyformatter %f Autorun will run fancyformatter before save for C files as long as project.geany is open. -All Auto-run actions will target the Geany Terminal pane. +All Auto-run action status changes will be in the Geany Status window. +All Auto-run errors will be in the Geany Compiler status window. Known issues ============ diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c index 22f06fb404..475a018990 100644 --- a/autorun/src/autorun.c +++ b/autorun/src/autorun.c @@ -16,9 +16,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include + #include "autorun.h" #include "utils.h" -#include AUTORUN_GLOBALS* autorun_globals; diff --git a/autorun/src/autorun.h b/autorun/src/autorun.h index 9bb157dd22..a6e971bbf2 100644 --- a/autorun/src/autorun.h +++ b/autorun/src/autorun.h @@ -18,29 +18,28 @@ #ifndef __AUTORUN_H__ #define __AUTORUN_H__ + #include -typedef struct -{ - GeanyPlugin *plugin; - GeanyData *data; - GSList * filedef_commands; - GSList * project_commands; -}AUTORUN_GLOBALS; +typedef struct { + GeanyPlugin* plugin; + GeanyData* data; + GSList* filedef_commands; + GSList* project_commands; +} AUTORUN_GLOBALS; -typedef struct -{ - GeanyFiletype * file_type; - gchar* interceptor; - gushort order; - gchar* command; - gchar* working_dir; - gboolean invalid; -}AUTORUN_CMD; +typedef struct { + GeanyFiletype* file_type; + gchar* interceptor; + gushort order; + gchar* command; + gchar* working_dir; + gboolean invalid; +} AUTORUN_CMD; extern AUTORUN_GLOBALS* autorun_globals; -void autorun_globals_init(GeanyPlugin *plugin); +void autorun_globals_init(GeanyPlugin* plugin); void autorun_globals_free(void); void autorun_cmd_list_free(GSList* command_list); void autorun_cmd_free(AUTORUN_CMD* cmd); @@ -48,4 +47,4 @@ AUTORUN_CMD* autorun_cmd_new(void); void load_filedefs(void); void load_projectdefs(GKeyFile* config); -#endif +#endif \ No newline at end of file diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index feff8af5c6..b5857f83fb 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -15,12 +15,14 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "spawn.h" -#include "autorun.h" -#include "utils.h" + #include #include +#include "autorun.h" +#include "spawn.h" +#include "utils.h" + void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { g_message("dispatch_run %%f %s", doc->file_name); g_message("dispatch_run %%d %s", doc->real_path); @@ -160,14 +162,10 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { g_file_get_contents(target_file, &read, NULL, NULL); if (read != NULL) { g_message("read was goodish"); - // TODO when this is working remove this conditional. - if (g_strcmp0(read, before_contents) == 0) { - g_message("Temp no change"); - } else { - g_message("Temp changed"); - msgwin_compiler_add_string(COLOR_BLACK, read); + if (g_strcmp0(read, before_contents) != 0) { + // no point in stealing the document focus + sci_set_text(doc->editor->sci, read); } - // sci_set_text(doc->editor->sci,contents); g_free(read); } else { g_message("read was nullish"); @@ -207,10 +205,3 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { } g_slist_free(command_list); } - -gboolean run_command(AUTORUN_CMD* cmd) { - if (cmd->invalid) { - return FALSE; - } - return TRUE; -} diff --git a/autorun/src/spawn.h b/autorun/src/spawn.h index 42ce4d2c6a..ed4a3d33fe 100644 --- a/autorun/src/spawn.h +++ b/autorun/src/spawn.h @@ -15,12 +15,14 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - + #ifndef __AUTORUN_SPAWN_H__ #define __AUTORUN_SPAWN_H__ + #include + #include "autorun.h" -gboolean run_command(AUTORUN_CMD* cmd); -void dispatch_run(const gchar * interceptor, GeanyDocument *doc); -#endif +void dispatch_run(const gchar* interceptor, GeanyDocument* doc); + +#endif \ No newline at end of file diff --git a/autorun/src/utils.c b/autorun/src/utils.c index 6a2053be64..0e2f753785 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -16,11 +16,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "utils.h" -#include "autorun.h" #include #include +#include "autorun.h" +#include "utils.h" + /* Read a filedef or keyfile for Intercept Actions * @param action key (should end with CM) * @param key_file to get remaining values from diff --git a/autorun/src/utils.h b/autorun/src/utils.h index 8af4cb3e38..07c19bf028 100644 --- a/autorun/src/utils.h +++ b/autorun/src/utils.h @@ -18,9 +18,10 @@ #ifndef __AUTORUN_UTILS_H__ #define __AUTORUN_UTILS_H__ -#include +#include #include "autorun.h" + gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd); -#endif +#endif \ No newline at end of file From ae7e926e126a46a09eb3824886ce89a882a270ef Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:14:17 -0400 Subject: [PATCH 13/30] cleanup logging ensure translation setup was done fixed tempfile writes for %a tools added CMakeLists.txt as a template for clang-tidy runs --- autorun/CMakeLists.txt | 22 +++++ autorun/src/autorun.c | 33 +++---- autorun/src/plugin_main.c | 49 +++++----- autorun/src/spawn.c | 185 +++++++++++++++++++++++++++----------- autorun/src/utils.c | 33 +++---- 5 files changed, 199 insertions(+), 123 deletions(-) create mode 100644 autorun/CMakeLists.txt diff --git a/autorun/CMakeLists.txt b/autorun/CMakeLists.txt new file mode 100644 index 0000000000..1499d686d1 --- /dev/null +++ b/autorun/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.27) + +list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +project( + autorun + VERSION 0.0.1 + LANGUAGES C + DESCRIPTION "Geany action interceptor plugin") + +# the `pkg_check_modules` function is created with this call +find_package(PkgConfig REQUIRED) +# these calls create special `PkgConfig::` variables +pkg_check_modules(geany REQUIRED IMPORTED_TARGET GLOBAL geany) + +add_library(${PROJECT_NAME} SHARED src/plugin_main.c src/autorun.c src/utils.c + src/spawn.c) +# prevent cmake from prefixing the shared library +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +target_link_libraries(${PROJECT_NAME} PRIVATE PkgConfig::geany) +# install to users bin +install(TARGETS ${PROJECT_NAME} DESTINATION plugins) diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c index 475a018990..a31b5496aa 100644 --- a/autorun/src/autorun.c +++ b/autorun/src/autorun.c @@ -38,39 +38,38 @@ AUTORUN_CMD* autorun_cmd_new() { void autorun_cmd_free(AUTORUN_CMD* cmd) { /* do not check for cmd->invalid because it could be a partial command * or invalidated after assembly. */ - if (cmd == NULL) { + if (!cmd) { // who are you and why are you in my attic? return; } // free the command - if (cmd->file_type != NULL) { + if (cmd->file_type) { cmd->file_type = NULL; } - if (cmd->interceptor != NULL) { + if (cmd->interceptor) { g_free(cmd->interceptor); cmd->interceptor = NULL; } - if (cmd->command != NULL) { + if (cmd->command) { g_free(cmd->command); cmd->command = NULL; } - if (cmd->working_dir != NULL) { + if (cmd->working_dir) { g_free(cmd->working_dir); cmd->working_dir = NULL; } } void autorun_cmd_list_free(GSList* command_list) { - if (command_list == NULL) { + if (!command_list) { // why are you naked? return; } - g_message("command list had %i", g_slist_length(command_list)); g_slist_free_full(command_list, (GDestroyNotify)autorun_cmd_free); } void autorun_globals_init(GeanyPlugin* plugin) { - if (!autorun_globals || autorun_globals == NULL) { + if (!autorun_globals) { autorun_globals = g_new0(AUTORUN_GLOBALS, 1); autorun_globals->plugin = plugin; autorun_globals->data = plugin->geany_data; @@ -80,12 +79,12 @@ void autorun_globals_init(GeanyPlugin* plugin) { } void autorun_globals_free(void) { - if (autorun_globals != NULL) { - if (autorun_globals->filedef_commands != NULL) { + if (autorun_globals) { + if (autorun_globals->filedef_commands) { autorun_cmd_list_free(autorun_globals->filedef_commands); autorun_globals->filedef_commands = NULL; } - if (autorun_globals->project_commands != NULL) { + if (autorun_globals->project_commands) { autorun_cmd_list_free(autorun_globals->project_commands); autorun_globals->project_commands = NULL; } @@ -114,21 +113,16 @@ void load_filedefs(void) { GError* gerr = NULL; gchar** handlers = g_key_file_get_keys(config, "autorun", &key_len, &gerr); // if the keyfile has an [autorun] section - if (gerr == NULL) { - g_message("found %s autorun", (gchar*)node->data); + if (!gerr) { gchar** handler_key; foreach_strv(handler_key, handlers) { - g_message(" %s", *handler_key); if (g_str_has_suffix(*handler_key, "CM")) { AUTORUN_CMD* cmd = autorun_cmd_new(); cmd->file_type = filetypes_detect_from_file(node->data); - g_message("filetype was %s", filetypes_get_display_name(cmd->file_type)); parse_intercept_actions(*handler_key, config, cmd); if (!cmd->invalid) { // add the command autorun_globals->filedef_commands = g_slist_prepend(autorun_globals->filedef_commands, cmd); - g_message("filedef list is %i", g_slist_length(autorun_globals->filedef_commands)); - } else { // free the command autorun_cmd_free(cmd); @@ -157,13 +151,11 @@ void load_projectdefs(GKeyFile* config) { GError* gerr = NULL; gchar** handlers = g_key_file_get_keys(config, "autorun", &key_len, &gerr); // if the keyfile has an [autorun] section - if (gerr == NULL) { - g_message("found project autorun"); + if (!gerr) { gchar** handler_key; // load any handlers foreach_strv(handler_key, handlers) { // over any existing [autorun] handlers - g_message(" %s", *handler_key); if (g_str_has_suffix(*handler_key, "CM")) { AUTORUN_CMD* cmd = autorun_cmd_new(); parse_intercept_actions(*handler_key, config, cmd); @@ -178,7 +170,6 @@ void load_projectdefs(GKeyFile* config) { } // flip it around autorun_globals->project_commands = g_slist_reverse(autorun_globals->project_commands); - g_message("proj command list is %i", g_slist_length(autorun_globals->project_commands)); } else { g_free(gerr); } diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index 43df8e8d3f..7ddcf53df9 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -24,57 +24,64 @@ /* Handler to read any Project declared Auto-run configs */ static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data) { - g_message("on_project_open start"); - // TODO make this more smarter - if (autorun_globals->project_commands == NULL) { + if (!autorun_globals->project_commands) { load_projectdefs(config); } - g_message("on_project_open end"); } // this handler is currently disconnected due to geany/geany#4603 /* Handler to read any Project declared Auto-run configs */ // static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ -// g_message("on_project_save"); // load_projectdefs(config); // } /* Handler to clear any old Project declared Auto-run configs */ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer user_data) { - g_message("project close start"); // unload project handlers autorun_cmd_list_free(autorun_globals->project_commands); autorun_globals->project_commands = NULL; - g_message("project close end"); } /* Handler to run any applicable Auto-run configs after a write*/ -void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { - g_message("on save start"); +static void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { /* disabled because geany/geany#4604 never sets doc->changed if(!doc->changed) { - g_message("doc not changed?"); return; } */ + if (sci_get_length(doc->editor->sci) < 1) { + // no point in processing a file so short + return; + } ui_progress_bar_start(NULL); + /* + * dont reset the msgwin here, as document-save + * is the second stage of document-before-save + * and the messages will all be logically related + * to the user selecting File>Save + */ dispatch_run("OS", doc); ui_progress_bar_stop(); - g_message("on save end"); } /* Handler to run any applicable Auto-run configs before a write*/ -void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { - g_message("before save start"); +static void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { if (!doc->changed) { - g_message("doc not changed?"); + return; + } + if (sci_get_length(doc->editor->sci) < 1) { + // no point in processing a file so short return; } ui_progress_bar_start(NULL); + // status is used for return codes, so dont clear that one + // messages is used for stdout + msgwin_clear_tab(MSG_MESSAGE); + // compiler is used for stderr + msgwin_clear_tab(MSG_COMPILER); dispatch_run("BS", doc); ui_progress_bar_stop(); - g_message("before save end"); } // clang-format off @@ -90,11 +97,9 @@ PluginCallback plugin_callbacks[] = { /* Bring up plugin and load filetypes.FILE that exist for Auto-run */ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { - g_message("autorun_init start"); - if (!autorun_globals || autorun_globals == NULL) { + if (!autorun_globals) { autorun_globals_init(plugin); load_filedefs(); - g_message("filedef command list is %i", g_slist_length(autorun_globals->filedef_commands)); // if initialized while a project is already open, manually ingest the project if (autorun_globals->data->app && autorun_globals->data->app->project) { @@ -102,26 +107,21 @@ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { GKeyFile* config = g_key_file_new(); g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); load_projectdefs(config); - g_message("proj command list is %i", g_slist_length(autorun_globals->project_commands)); g_free(config); } } - g_message("autorun_init end"); return TRUE; } /* ensure destruction of any Auto-run objects */ static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { - g_message("autorun_cleanup start"); - if (autorun_globals != NULL) { + if (autorun_globals) { autorun_globals_free(); } - g_message("autorun_cleanup end"); } G_MODULE_EXPORT void geany_load_module(GeanyPlugin* plugin) { - g_message("load_module start"); // who am we? plugin->info->name = "Auto-run"; plugin->info->description = _("Geany action interceptor plugin"); @@ -138,5 +138,4 @@ void geany_load_module(GeanyPlugin* plugin) { // go forth and come fifth. GEANY_PLUGIN_REGISTER(plugin, 225); - g_message("load_module end"); } diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index b5857f83fb..292f8bba2e 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -24,11 +24,7 @@ #include "utils.h" void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { - g_message("dispatch_run %%f %s", doc->file_name); - g_message("dispatch_run %%d %s", doc->real_path); - GSList* command_list = NULL; - g_message("target interceptor %s", interceptor); // build the commands backwads GSList* elem = NULL; @@ -38,7 +34,6 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { command_list = g_slist_prepend(command_list, cmd); } } - foreach_slist(elem, autorun_globals->project_commands) { AUTORUN_CMD* cmd = ((AUTORUN_CMD*)elem->data); if (!cmd->invalid && g_strcmp0(cmd->interceptor, interceptor) == 0 && cmd->file_type == doc->file_type) { @@ -47,32 +42,30 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { } // now flip it on them. command_list = g_slist_reverse(command_list); - g_message("command list in %s %i", interceptor, g_slist_length(command_list)); if (g_slist_length(command_list) > 0) { // now if there are any cmds in scope, honor order - GSList* meh[g_slist_length(command_list)] = {}; + GSList* command_order[g_slist_length(command_list)] = {}; foreach_slist(elem, command_list) { AUTORUN_CMD* cmd = ((AUTORUN_CMD*)elem->data); - g_message("Staged %s in %s # %i", cmd->command, cmd->working_dir, cmd->order); - meh[cmd->order] = elem; + command_order[cmd->order] = elem; } for (uint i = 0; i < g_slist_length(command_list); i++) { - if (meh[i] == NULL) { + if (!command_order[i]) { break; // stop on the first null; } - AUTORUN_CMD* cmd = (AUTORUN_CMD*)meh[i]->data; + AUTORUN_CMD* cmd = (AUTORUN_CMD*)command_order[i]->data; // prep replacements. gchar* target_dir; - if (doc->real_path != NULL) { + if (doc->real_path) { target_dir = g_path_get_dirname(doc->real_path); } else { // where else but here? target_dir = g_strdup("."); } gchar* target_projdir; - if (autorun_globals->data->app->project != NULL && autorun_globals->data->app->project->base_path != NULL) { + if (autorun_globals->data->app->project && autorun_globals->data->app->project->base_path) { target_projdir = utils_get_locale_from_utf8(autorun_globals->data->app->project->base_path); } else { // fold %p to %d if no project. @@ -83,18 +76,21 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { GFileIOStream* iostream = NULL; gboolean success = FALSE; gchar* before_contents; + // we need scintilla in a buffer regardless of %a gint con_len = sci_get_length(doc->editor->sci); - g_message("SCI is %i long", con_len); + gint cursor_at = sci_get_current_position(doc->editor->sci); before_contents = sci_get_contents(doc->editor->sci, con_len); - if (g_strcmp0(interceptor, "BS") == 0 && (g_strrstr(cmd->command, "%a") || g_strrstr(cmd->working_dir, "%a"))) { //%a means something special tmpfile = g_file_new_tmp("ar.aXXXXXX", &iostream, NULL); - if (tmpfile != NULL) { + if (tmpfile) { + // dont need the iostream, but gtk wont make a temp without it. + g_io_stream_close((GIOStream*)iostream, NULL, NULL); target_file = g_file_get_path(tmpfile); - g_message("dumping SCI to %s", target_file); - success = g_file_replace_contents(tmpfile, before_contents, con_len, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, NULL); + // remove the trailing \0 on write + success = g_file_set_contents_full(target_file, before_contents, con_len - 1, G_FILE_SET_CONTENTS_CONSISTENT, 0660, NULL); + } else { success = FALSE; } @@ -124,71 +120,148 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { g_string_replace(command, "%p", target_projdir, 0); g_string_replace(working_dir, "%p", target_projdir, 0); + if (success) { + success = spawn_check_command(command->str, FALSE, NULL); + } // it looks like we're running; - SpawnWriteData* stdin_data; + GString* stdout_data = g_string_new(NULL); GString* stderr_data = g_string_new(NULL); if (success) { - g_message("Run %s", command->str); - g_message(" in %s # %i", working_dir->str, cmd->order); - - if (g_strcmp0(interceptor, "BS") == 0 && success && tmpfile == NULL) { + gchar** env; + env = utils_copy_environment(NULL, "GEANY_FUNCNAME", __func__, NULL); + gint ret; + GError* error = NULL; + SpawnWriteData* stdin_data; + if (g_strcmp0(interceptor, "BS") == 0 && success && !tmpfile) { // need to send stdin. - g_message("Send to stdin"); stdin_data = g_new0(SpawnWriteData, 1); stdin_data->ptr = before_contents; stdin_data->size = con_len; } else { - g_message("Read from file"); // no stdin stdin_data = NULL; } - gchar** env; - env = utils_copy_environment(NULL, "GEANY_FUNCNAME", __func__, NULL); - GError* error = NULL; - success = spawn_sync(working_dir->str, command->str, NULL, env, stdin_data, stdout_data, stderr_data, NULL, &error); + success = spawn_sync(working_dir->str, command->str, NULL, env, stdin_data, stdout_data, stderr_data, &ret, &error); + if (stdin_data) { + g_free(stdin_data); + } g_strfreev(env); if (!success) { - g_message("!success"); // somewhat bad had occur - msgwin_status_add("Command failed with %s", error->message); - ui_set_statusbar(FALSE, "Auto-run %s interceptor failed.", interceptor); + msgwin_status_add(_("Command failed with %s"), error->message); + ui_set_statusbar(FALSE, _("%s interceptor failed."), interceptor); g_error_free(error); + // invalid command + cmd->invalid = TRUE; } else { - g_message("success"); // if we made a tmpfile, grab the results and del the file. - if (tmpfile != NULL) { - gchar* read = NULL; - g_file_get_contents(target_file, &read, NULL, NULL); - if (read != NULL) { - g_message("read was goodish"); - if (g_strcmp0(read, before_contents) != 0) { - // no point in stealing the document focus - sci_set_text(doc->editor->sci, read); + if (tmpfile) { + if (ret == 0) { + gchar* read = NULL; + g_file_get_contents(target_file, &read, NULL, NULL); + if (read) { + if (g_strcmp0(read, before_contents) != 0) { + // no point in stealing the document focus + sci_set_text(doc->editor->sci, read); + sci_set_current_position(doc->editor->sci, cursor_at, TRUE); + } + // maybe it put the output in stdout. + if (strlen(stdout_data->str) > 0) { + sci_set_text(doc->editor->sci, stdout_data->str); + sci_set_current_position(doc->editor->sci, cursor_at, TRUE); + } + g_free(read); } - g_free(read); } else { - g_message("read was nullish"); + msgwin_status_add(_("%s ran but returned an error."), command->str); + // parse out a linenumber, assuming the stdout data is file:line:col + gchar* lineptr = g_strstr_len(stdout_data->str, 255, ":"); + gint linenum = 1; + if (lineptr) { + lineptr++; // skip the colon + linenum = atoi(lineptr); + if (linenum == 0) { + linenum = 1; + } + } + msgwin_msg_add(COLOR_BLACK, linenum, doc, "%s", stdout_data->str); + if (strlen(stderr_data->str) > 0) { + msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); + } } } else if (g_strcmp0(interceptor, "BS") == 0) { - // update scintilla from the spawn. - sci_set_text(doc->editor->sci, stdout_data->str); - msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); + // we spawned a child, but that child might have successfully failed. + if (ret == 0) { + sci_set_text(doc->editor->sci, stdout_data->str); + sci_set_current_position(doc->editor->sci, cursor_at, TRUE); + if (strlen(stderr_data->str) > 0) { + msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); + } + } else { + msgwin_status_add(_("%s ran but returned an error."), command->str); + gchar* lineptr = g_strstr_len(stdout_data->str, 255, ":"); + gint linenum = 1; + if (lineptr) { + lineptr++; // skip the colon + linenum = atoi(lineptr); + if (linenum == 0) { + linenum = 1; + } + } + msgwin_msg_add(COLOR_BLACK, linenum, doc, "%s", stdout_data->str); + if (strlen(stderr_data->str) > 0) { + msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); + } + } + } else if (g_strcmp0(interceptor, "OS") == 0) { + // we spawned a child, but that child might have successfully failed. + if (ret == 0) { + // parse out a linenumber, assuming the stdout data is file:line:col + gchar* lineptr = g_strstr_len(stdout_data->str, 255, ":"); + gint linenum = 1; + if (lineptr) { + lineptr++; // skip the colon + linenum = atoi(lineptr); + if (linenum == 0) { + linenum = 1; + } + } + + msgwin_msg_add(COLOR_BLACK, linenum, doc, "%s", stdout_data->str); + if (strlen(stderr_data->str) > 0) { + msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); + } + } else { + msgwin_status_add(_("%s ran but returned an error."), command->str); + // parse out a linenumber, assuming the stdout data is file:line:col + gchar* lineptr = g_strstr_len(stdout_data->str, 255, ":"); + gint linenum = 1; + if (lineptr) { + lineptr++; // skip the colon + linenum = atoi(lineptr); + if (linenum == 0) { + linenum = 1; + } + } + msgwin_msg_add(COLOR_BLACK, linenum, doc, "%s", stdout_data->str); + if (strlen(stderr_data->str) > 0) { + msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); + } + } } } - if (tmpfile != NULL) { - g_unlink(target_file); + if (tmpfile) { + // TODO unlink the temp file after testing passes + // g_unlink(target_file); + g_object_unref(tmpfile); } g_string_free(stdout_data, TRUE); g_string_free(stderr_data, TRUE); - if (stdin_data != NULL) { - g_free(stdin_data); - } - - if (before_contents != NULL) { + if (before_contents) { g_free(before_contents); } @@ -197,9 +270,13 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { g_free(target_name); g_free(target_dir); g_free(target_projdir); - if (target_file != NULL) { + if (target_file) { g_free(target_file); } + } else { + // cant assemble this command, don't try again + cmd->invalid = TRUE; + msgwin_status_add(_("Cannot parse command %s"), command->str); } } } diff --git a/autorun/src/utils.c b/autorun/src/utils.c index 0e2f753785..a3fcc6fce4 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -30,40 +30,35 @@ * */ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd) { // key should be a (filetype_)action_number_flag string - if (key == NULL || cmd == NULL) { + if (!key || !cmd) { return FALSE; } // is this a key? - if (g_strstr_len(key, -1, "_") == NULL) { + if (!g_strstr_len(key, -1, "_")) { // no cmd->invalid = TRUE; return FALSE; } + cmd->invalid = FALSE; // what is this key? gchar** tokens; tokens = g_strsplit(key, "_", 4); - gboolean invalid = FALSE; switch (g_strv_length(tokens)) { // strv of 3 is filedef, 4 is project case 3: { - g_message("processing filedef!"); - g_message("filetype already %s", filetypes_get_display_name(cmd->file_type)); - g_message("interceptor %s", tokens[0]); - g_message("order %s", tokens[1]); - g_message("flag %s", tokens[2]); // the caller has to set filetype if (g_strcmp0(tokens[0], "BS") == 0) { cmd->interceptor = g_strdup(tokens[0]); } else if (g_strcmp0(tokens[0], "OS") == 0) { cmd->interceptor = g_strdup(tokens[0]); } else { - invalid = TRUE; + cmd->invalid = TRUE; } gint offset = atoi(tokens[1]); if (offset < 0 || offset > 99) { - invalid = TRUE; + cmd->invalid = TRUE; } else { cmd->order = offset; } @@ -74,18 +69,11 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm cmd->working_dir = utils_get_setting_string(key_file, "autorun", next_key, ""); g_free(next_key); } else { - invalid = TRUE; + cmd->invalid = TRUE; } - cmd->invalid = invalid; break; } case 4: { - g_message("processing project!"); - g_message("filetype %s", tokens[0]); - g_message("interceptor %s", tokens[1]); - g_message("order %s", tokens[2]); - g_message("flag %s", tokens[3]); - cmd->file_type = filetypes_lookup_by_name(tokens[0]); if (g_strcmp0(tokens[1], "BS") == 0) { @@ -93,12 +81,12 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm } else if (g_strcmp0(tokens[1], "OS") == 0) { cmd->interceptor = g_strdup(tokens[1]); } else { - invalid = TRUE; + cmd->invalid = TRUE; } gint offset = atoi(tokens[2]); if (offset < 0 || offset > 99) { - invalid = TRUE; + cmd->invalid = TRUE; } else { cmd->order = offset; } @@ -110,13 +98,12 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm cmd->working_dir = utils_get_setting_string(key_file, "autorun", next_key, ""); g_free(next_key); } else { - invalid = TRUE; + cmd->invalid = TRUE; } - cmd->invalid = invalid; break; } default: { - invalid = TRUE; + cmd->invalid = TRUE; } } From e08436917cacc646a406dfe2a785818ab9234878 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:35:57 -0400 Subject: [PATCH 14/30] Enable tmpfile cleaner now that testing completed --- autorun/src/spawn.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 292f8bba2e..330fbd617e 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -253,8 +253,7 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { } if (tmpfile) { - // TODO unlink the temp file after testing passes - // g_unlink(target_file); + g_unlink(target_file); g_object_unref(tmpfile); } From 1f2d6e578a9a16a92aeb1cc5218f4fb34cfd1bbb Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:39:51 -0400 Subject: [PATCH 15/30] fix eof marker on *.h to make github happy --- autorun/src/autorun.h | 3 ++- autorun/src/spawn.h | 3 ++- autorun/src/utils.h | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/autorun/src/autorun.h b/autorun/src/autorun.h index a6e971bbf2..87c78e5041 100644 --- a/autorun/src/autorun.h +++ b/autorun/src/autorun.h @@ -47,4 +47,5 @@ AUTORUN_CMD* autorun_cmd_new(void); void load_filedefs(void); void load_projectdefs(GKeyFile* config); -#endif \ No newline at end of file +#endif + diff --git a/autorun/src/spawn.h b/autorun/src/spawn.h index ed4a3d33fe..bba30089ee 100644 --- a/autorun/src/spawn.h +++ b/autorun/src/spawn.h @@ -25,4 +25,5 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc); -#endif \ No newline at end of file +#endif + diff --git a/autorun/src/utils.h b/autorun/src/utils.h index 07c19bf028..fafc1d86ff 100644 --- a/autorun/src/utils.h +++ b/autorun/src/utils.h @@ -24,4 +24,5 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd); -#endif \ No newline at end of file +#endif + From eff48f4d9615c639e1cae335fee67dfebe05a4dd Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:41:45 -0400 Subject: [PATCH 16/30] add Tool>Auto-run menu to force config reloads --- autorun/THANKS | 5 +++ autorun/src/Makefile.am | 3 +- autorun/src/menu.c | 84 +++++++++++++++++++++++++++++++++++++++ autorun/src/menu.h | 25 ++++++++++++ autorun/src/plugin_main.c | 3 ++ 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 autorun/src/menu.c create mode 100644 autorun/src/menu.h diff --git a/autorun/THANKS b/autorun/THANKS index 056002abd6..b180f4bf0b 100644 --- a/autorun/THANKS +++ b/autorun/THANKS @@ -7,6 +7,11 @@ Workbench Plugin: This plugin is heavily based on working with the Workbench plugin. Thanks to LarsGit223. +Project Organizer Plugin: +------------------------- +This plugin is heavily based on working with the Project Organizer plugin. +Thanks to Jiri Techet. + Geany and Geany plugins: ------------------------ Thanks to all Geany developers for creating the best IDE a dev could ask for. diff --git a/autorun/src/Makefile.am b/autorun/src/Makefile.am index b4c7c5ad6e..4d6006a595 100644 --- a/autorun/src/Makefile.am +++ b/autorun/src/Makefile.am @@ -7,7 +7,8 @@ autorun_la_SOURCES = \ plugin_main.c \ autorun.c \ utils.c \ - spawn.c + spawn.c \ + menu.c autorun_la_CPPFLAGS = $(AM_CPPFLAGS) \ -DG_LOG_DOMAIN=\"Autorun\" diff --git a/autorun/src/menu.c b/autorun/src/menu.c new file mode 100644 index 0000000000..3a9b32514b --- /dev/null +++ b/autorun/src/menu.c @@ -0,0 +1,84 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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 + +#include "autorun.h" +#include "menu.h" + +typedef struct { + GtkWidget* menu; + GtkWidget* root_item; + GtkWidget* item_reload; +} AR_MENU_DATA; +static AR_MENU_DATA menu_data; + +/* The function handles the menu item "Reload" */ +static void autorun_menu_reload_cb(G_GNUC_UNUSED GtkMenuItem* menuitem, G_GNUC_UNUSED gpointer user_data) { + // force unloading of any loaded project configs + autorun_cmd_list_free(autorun_globals->project_commands); + autorun_globals->project_commands = NULL; + // force unloading of any filedef configs. + autorun_cmd_list_free(autorun_globals->filedef_commands); + autorun_globals->filedef_commands = NULL; + msgwin_status_add(_("Reloading Auto-run filetypes.* configs")); + // load filedefs + load_filedefs(); + // load project + if (autorun_globals->data->app && autorun_globals->data->app->project) { + msgwin_status_add(_("Reloading Auto-run project configs")); + // force a GKeyFile + GKeyFile* config = g_key_file_new(); + g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + load_projectdefs(config); + g_free(config); + } +} + +/** Setup the workbench menu. + * + **/ +gboolean menu_init(void) { + /* Create menu and root item/label */ + menu_data.menu = gtk_menu_new(); + menu_data.root_item = gtk_menu_item_new_with_label("Auto-run"); + gtk_widget_show(menu_data.root_item); + + /* Create new menu item "Reload" */ + GtkWidget* icon = gtk_image_new_from_icon_name("view-refresh", GTK_ICON_SIZE_MENU); + /* + * deprecated since gtk3.10, but it looks like crap using + * the "functionally equivalent" menu( box (img,label) ) + */ + menu_data.item_reload = gtk_image_menu_item_new_with_label(_("Reload")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_data.item_reload), icon); + gtk_widget_show_all(menu_data.item_reload); + gtk_menu_shell_append(GTK_MENU_SHELL(menu_data.menu), menu_data.item_reload); + g_signal_connect(menu_data.item_reload, "activate", G_CALLBACK(autorun_menu_reload_cb), NULL); + + /* Add our menu to the main window (left of the help menu) */ + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_data.root_item), menu_data.menu); + gtk_container_add(GTK_CONTAINER(autorun_globals->data->main_widgets->tools_menu), menu_data.root_item); + + return TRUE; +} + +/** Cleanup menu data/mem. + * + **/ +void menu_cleanup(void) { gtk_widget_destroy(menu_data.root_item); } diff --git a/autorun/src/menu.h b/autorun/src/menu.h new file mode 100644 index 0000000000..4259f837b5 --- /dev/null +++ b/autorun/src/menu.h @@ -0,0 +1,25 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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. + */ + +#ifndef __AUTORUN_MENU_H__ +#define __AUTORUN_MENU_H__ + +gboolean menu_init(void); +void menu_cleanup(void); + +#endif \ No newline at end of file diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index 7ddcf53df9..d0cf4c6c7d 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -20,6 +20,7 @@ #include #include "autorun.h" +#include "menu.h" #include "spawn.h" /* Handler to read any Project declared Auto-run configs */ @@ -110,6 +111,7 @@ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { g_free(config); } } + menu_init(); return TRUE; } @@ -118,6 +120,7 @@ static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { if (autorun_globals) { autorun_globals_free(); } + menu_cleanup(); } G_MODULE_EXPORT From 5be2652b83df86229ed91353e26013f6b8a8f770 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:45:23 -0400 Subject: [PATCH 17/30] Add Tool>Reload and Project>Config menus Fixed a parsing issue with empty string commands Had to turn project-save back on in order to store Project gkeyfile changes --- autorun/CMakeLists.txt | 3 +- autorun/src/Makefile.am | 3 +- autorun/src/autorun.c | 3 + autorun/src/autorun.h | 2 +- autorun/src/menu.c | 19 ++- autorun/src/plugin_main.c | 74 ++++++++--- autorun/src/project.c | 258 ++++++++++++++++++++++++++++++++++++++ autorun/src/project.h | 32 +++++ autorun/src/spawn.c | 4 +- autorun/src/utils.c | 6 +- autorun/src/utils.h | 1 - 11 files changed, 377 insertions(+), 28 deletions(-) create mode 100644 autorun/src/project.c create mode 100644 autorun/src/project.h diff --git a/autorun/CMakeLists.txt b/autorun/CMakeLists.txt index 1499d686d1..dd68f943da 100644 --- a/autorun/CMakeLists.txt +++ b/autorun/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.27) +set(CMAKE_EXPORT_COMPILE_COMMANDS on) list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake") project( @@ -14,7 +15,7 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(geany REQUIRED IMPORTED_TARGET GLOBAL geany) add_library(${PROJECT_NAME} SHARED src/plugin_main.c src/autorun.c src/utils.c - src/spawn.c) + src/spawn.c src/menu.c src/project.c) # prevent cmake from prefixing the shared library set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") target_link_libraries(${PROJECT_NAME} PRIVATE PkgConfig::geany) diff --git a/autorun/src/Makefile.am b/autorun/src/Makefile.am index 4d6006a595..39d27419db 100644 --- a/autorun/src/Makefile.am +++ b/autorun/src/Makefile.am @@ -8,7 +8,8 @@ autorun_la_SOURCES = \ autorun.c \ utils.c \ spawn.c \ - menu.c + menu.c \ + project.c autorun_la_CPPFLAGS = $(AM_CPPFLAGS) \ -DG_LOG_DOMAIN=\"Autorun\" diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c index a31b5496aa..7c5d52c6ac 100644 --- a/autorun/src/autorun.c +++ b/autorun/src/autorun.c @@ -75,6 +75,7 @@ void autorun_globals_init(GeanyPlugin* plugin) { autorun_globals->data = plugin->geany_data; autorun_globals->filedef_commands = NULL; autorun_globals->project_commands = NULL; + autorun_globals->dirtybit = FALSE; } } @@ -120,6 +121,7 @@ void load_filedefs(void) { AUTORUN_CMD* cmd = autorun_cmd_new(); cmd->file_type = filetypes_detect_from_file(node->data); parse_intercept_actions(*handler_key, config, cmd); + if (!cmd->invalid) { // add the command autorun_globals->filedef_commands = g_slist_prepend(autorun_globals->filedef_commands, cmd); @@ -134,6 +136,7 @@ void load_filedefs(void) { } else { g_free(gerr); } + g_key_file_free(config); g_free(filedef_file); g_free(handlers); } diff --git a/autorun/src/autorun.h b/autorun/src/autorun.h index 87c78e5041..06c567ee0f 100644 --- a/autorun/src/autorun.h +++ b/autorun/src/autorun.h @@ -26,6 +26,7 @@ typedef struct { GeanyData* data; GSList* filedef_commands; GSList* project_commands; + gboolean dirtybit; } AUTORUN_GLOBALS; typedef struct { @@ -48,4 +49,3 @@ void load_filedefs(void); void load_projectdefs(GKeyFile* config); #endif - diff --git a/autorun/src/menu.c b/autorun/src/menu.c index 3a9b32514b..5b89dc997e 100644 --- a/autorun/src/menu.c +++ b/autorun/src/menu.c @@ -20,6 +20,7 @@ #include "autorun.h" #include "menu.h" +#include "utils.h" typedef struct { GtkWidget* menu; @@ -40,14 +41,13 @@ static void autorun_menu_reload_cb(G_GNUC_UNUSED GtkMenuItem* menuitem, G_GNUC_U // load filedefs load_filedefs(); // load project - if (autorun_globals->data->app && autorun_globals->data->app->project) { + GKeyFile* config = g_key_file_new(); + gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + if (ret) { msgwin_status_add(_("Reloading Auto-run project configs")); - // force a GKeyFile - GKeyFile* config = g_key_file_new(); - g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); load_projectdefs(config); - g_free(config); } + g_key_file_free(config); } /** Setup the workbench menu. @@ -81,4 +81,11 @@ gboolean menu_init(void) { /** Cleanup menu data/mem. * **/ -void menu_cleanup(void) { gtk_widget_destroy(menu_data.root_item); } +void menu_cleanup(void) { + gtk_widget_destroy(GTK_WIDGET(menu_data.item_reload)); + menu_data.item_reload = NULL; + gtk_widget_destroy(GTK_WIDGET(menu_data.root_item)); + menu_data.root_item = NULL; + // since this is attached to main_widgets, attempting to destroy it creates an assertion error + menu_data.menu = NULL; +} diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index d0cf4c6c7d..827e3dbe93 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -21,7 +21,9 @@ #include "autorun.h" #include "menu.h" +#include "project.h" #include "spawn.h" +#include "utils.h" /* Handler to read any Project declared Auto-run configs */ static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data) { @@ -32,9 +34,13 @@ static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC // this handler is currently disconnected due to geany/geany#4603 /* Handler to read any Project declared Auto-run configs */ -// static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data){ -// load_projectdefs(config); -// } +static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data) { + // write notebook to .geany keyfile + // has to be here, and not project-dialog-confirmed because Geany manages the write to the GKeyFile + project_save_properties_tab(config); + // ensure any changes from the project config are picked up before the next execution + autorun_globals->dirtybit = TRUE; +} /* Handler to clear any old Project declared Auto-run configs */ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer user_data) { @@ -45,12 +51,18 @@ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer /* Handler to run any applicable Auto-run configs after a write*/ static void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { - /* disabled because geany/geany#4604 never sets doc->changed - if(!doc->changed) - { - return; + // make sure we update the projectdefs if the config is dirty + if (autorun_globals->dirtybit) { + // reload the projectdefs + GKeyFile* config = g_key_file_new(); + gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + if (ret) { + load_projectdefs(config); + } + g_key_file_free(config); + autorun_globals->dirtybit = FALSE; } - */ + if (sci_get_length(doc->editor->sci) < 1) { // no point in processing a file so short return; @@ -68,6 +80,18 @@ static void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_U /* Handler to run any applicable Auto-run configs before a write*/ static void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { + // make sure we update the projectdefs if the config is dirty + if (autorun_globals->dirtybit) { + // reload the projectdefs + GKeyFile* config = g_key_file_new(); + gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + if (ret) { + load_projectdefs(config); + } + g_key_file_free(config); + autorun_globals->dirtybit = FALSE; + } + if (!doc->changed) { return; } @@ -84,14 +108,31 @@ static void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G dispatch_run("BS", doc); ui_progress_bar_stop(); } +static void on_project_dialog_open(G_GNUC_UNUSED GObject* obj, GtkWidget* notebook, G_GNUC_UNUSED gpointer user_data) { + // populate notebook for current file type. + project_show_properties_tab(notebook); +} +static void on_project_dialog_confirm(G_GNUC_UNUSED GObject* obj, GtkWidget* notebook, G_GNUC_UNUSED gpointer user_data) { + // mark the config for reload + autorun_globals->dirtybit = TRUE; + // hide from the Build menu + project_hide_properties_tab(notebook); +} +static void on_project_dialog_close(G_GNUC_UNUSED GObject* obj, GtkWidget* notebook, G_GNUC_UNUSED gpointer user_data) { + // hide from the Build menu + project_hide_properties_tab(notebook); +} // clang-format off PluginCallback plugin_callbacks[] = { { "project-open", (GCallback)&on_project_open, TRUE, NULL }, - //{ "project-save", (GCallback)&on_project_save, TRUE, NULL },// geany/geany#4603 + { "project-save", (GCallback)&on_project_save, TRUE, NULL }, { "project-close", (GCallback)&on_project_close, TRUE, NULL }, { "document-save", (GCallback)&on_doc_save, TRUE, NULL }, { "document-before-save", (GCallback)&on_doc_before_save, TRUE, NULL }, + { "project-dialog-open", (GCallback) & on_project_dialog_open, FALSE, NULL}, + { "project-dialog-confirmed", (GCallback) & on_project_dialog_confirm, TRUE, NULL}, + { "project-dialog-close", (GCallback) & on_project_dialog_close, TRUE, NULL}, { NULL, NULL, FALSE, NULL } }; // clang-format on @@ -102,16 +143,18 @@ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { autorun_globals_init(plugin); load_filedefs(); - // if initialized while a project is already open, manually ingest the project - if (autorun_globals->data->app && autorun_globals->data->app->project) { - // force a GKeyFile + if (autorun_globals->data->app->project) { + // if initialized while a project is already open, manually ingest the project GKeyFile* config = g_key_file_new(); - g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); - load_projectdefs(config); - g_free(config); + gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + if (ret) { + load_projectdefs(config); + } + g_key_file_free(config); } } menu_init(); + project_properties_tab_init(); return TRUE; } @@ -121,6 +164,7 @@ static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { autorun_globals_free(); } menu_cleanup(); + project_properties_tab_cleanup(); } G_MODULE_EXPORT diff --git a/autorun/src/project.c b/autorun/src/project.c new file mode 100644 index 0000000000..4dcfdc8f26 --- /dev/null +++ b/autorun/src/project.c @@ -0,0 +1,258 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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 + +#include "autorun.h" +#include "project.h" +#include "utils.h" + +typedef struct { + GtkWidget* notebook; + GtkWidget* tab; + GtkWidget* before_save_command; + GtkWidget* before_save_workdir; + GtkWidget* on_save_command; + GtkWidget* on_save_workdir; +} AR_PROJECT_DATA; +static AR_PROJECT_DATA project_data; + +void project_save_properties_tab(GKeyFile* config) { + // grab the current filetype + if (!document_get_current()) { + // Auto-run only supports file specific handlers + return; + } + gchar* filetype_name = document_get_current()->file_type->name; + gchar *before_save_command, *before_save_workdir, *on_save_command, *on_save_workdir; + before_save_command = g_strdup(gtk_entry_get_text(GTK_ENTRY(project_data.before_save_command))); + before_save_workdir = g_strdup(gtk_entry_get_text(GTK_ENTRY(project_data.before_save_workdir))); + on_save_command = g_strdup(gtk_entry_get_text(GTK_ENTRY(project_data.on_save_command))); + on_save_workdir = g_strdup(gtk_entry_get_text(GTK_ENTRY(project_data.on_save_workdir))); + + // write the configs to file. + gchar *bs_cm_key, *bs_wd_key, *os_cm_key, *os_wd_key; + bs_cm_key = g_strconcat(filetype_name, "_", "BS", "_", "00", "_", "CM", NULL); + if (before_save_command && strlen(before_save_command) > 1) { + // set it + g_key_file_set_string(config, "autorun", bs_cm_key, before_save_command); + } else { + // delete it + g_key_file_remove_key(config, "autorun", bs_cm_key, NULL); + } + bs_wd_key = g_strconcat(filetype_name, "_", "BS", "_", "00", "_", "WD", NULL); + if (before_save_workdir && strlen(before_save_command) > 1) { + // set it + g_key_file_set_string(config, "autorun", bs_wd_key, before_save_workdir); + } else { + // delete it + g_key_file_remove_key(config, "autorun", bs_wd_key, NULL); + } + os_cm_key = g_strconcat(filetype_name, "_", "OS", "_", "00", "_", "CM", NULL); + if (on_save_command && strlen(before_save_command) > 1) { + // set it + g_key_file_set_string(config, "autorun", os_cm_key, on_save_command); + } else { + // delete it + g_key_file_remove_key(config, "autorun", os_cm_key, NULL); + } + os_wd_key = g_strconcat(filetype_name, "_", "OS", "_", "00", "_", "WD", NULL); + if (on_save_workdir && strlen(before_save_command) > 1) { + // set it + g_key_file_set_string(config, "autorun", os_wd_key, on_save_workdir); + } else { + // delete it + g_key_file_remove_key(config, "autorun", os_wd_key, NULL); + } + + g_free(before_save_command); + g_free(before_save_workdir); + g_free(on_save_command); + g_free(on_save_workdir); + g_free(bs_cm_key); + g_free(bs_wd_key); + g_free(os_cm_key); + g_free(os_wd_key); +} + +void project_hide_properties_tab(GtkWidget* notebook) { + gtk_notebook_detach_tab(GTK_NOTEBOOK(notebook), project_data.tab); + project_data.notebook = NULL; +} + +void project_show_properties_tab(GtkWidget* notebook) { + // grab the current filetype + if (g_strcmp0("None", document_get_current()->file_type->name) == 0 || !autorun_globals->data->app->project) { + // Auto-run only supports file specific handlers + // hide the tab + return; + } + + // um, are we on this noteboot? + if (!project_data.notebook) { + GtkWidget* label = gtk_label_new("Auto-run"); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), GTK_WIDGET(g_object_ref(project_data.tab)), label); + gtk_widget_show_all(notebook); + project_data.notebook = notebook; + } + + gchar* filetype_name = document_get_current()->file_type->name; + + // attach the configs to the project + GKeyFile* config = g_key_file_new(); + gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + if (ret) { + gchar *before_save_command, *before_save_workdir, *on_save_command, *on_save_workdir; + + // read the current configs + gchar* key_name; + /* this looks wierd, but _ BS OS CM are all fixed strings and prevents + * static allocation of a fixed string only used here. */ + key_name = g_strconcat(filetype_name, "_", "BS", "_", "00", "_", "CM", NULL); + before_save_command = utils_get_setting_string(config, "autorun", key_name, ""); + g_free(key_name); + key_name = g_strconcat(filetype_name, "_", "BS", "_", "00", "_", "WD", NULL); + before_save_workdir = utils_get_setting_string(config, "autorun", key_name, ""); + g_free(key_name); + key_name = g_strconcat(filetype_name, "_", "OS", "_", "00", "_", "CM", NULL); + on_save_command = utils_get_setting_string(config, "autorun", key_name, ""); + g_free(key_name); + key_name = g_strconcat(filetype_name, "_", "OS", "_", "00", "_", "WD", NULL); + on_save_workdir = utils_get_setting_string(config, "autorun", key_name, ""); + g_free(key_name); + + // update the notebook + gtk_entry_set_text(GTK_ENTRY(project_data.before_save_command), before_save_command); + gtk_entry_set_text(GTK_ENTRY(project_data.before_save_workdir), before_save_workdir); + gtk_entry_set_text(GTK_ENTRY(project_data.on_save_command), on_save_command); + gtk_entry_set_text(GTK_ENTRY(project_data.on_save_workdir), on_save_workdir); + + if (before_save_command) { + g_free(before_save_command); + } + if (before_save_workdir) { + g_free(before_save_workdir); + } + if (on_save_command) { + g_free(on_save_command); + } + if (on_save_workdir) { + g_free(on_save_workdir); + } + } + g_key_file_free(config); +} + +void project_properties_tab_cleanup(void) { + gtk_widget_destroy(GTK_WIDGET(project_data.before_save_command)); + project_data.before_save_command = NULL; + gtk_widget_destroy(GTK_WIDGET(project_data.before_save_workdir)); + project_data.before_save_workdir = NULL; + gtk_widget_destroy(GTK_WIDGET(project_data.on_save_command)); + project_data.on_save_command = NULL; + gtk_widget_destroy(GTK_WIDGET(project_data.on_save_workdir)); + project_data.on_save_workdir = NULL; + g_object_ref_sink(project_data.tab); + gtk_widget_destroy(GTK_WIDGET(project_data.tab)); + project_data.tab = NULL; + // noteboot isn't ours. + project_data.notebook = NULL; +} + +void project_properties_tab_init(void) { + if (project_data.before_save_command) { + // this smells familiar, lets not. + return; + } + GtkWidget *vbox, *hbox, *ebox, *table_box; + GtkWidget *label, *seperator; + GtkSizeGroup* size_group; + + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + + table_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_box_set_spacing(GTK_BOX(table_box), 6); + + size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + seperator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_box_pack_start(GTK_BOX(table_box), seperator, TRUE, FALSE, 0); + + label = gtk_label_new(_("Before Save Command")); + gtk_label_set_xalign(GTK_LABEL(label), 0.0); + gtk_size_group_add_widget(size_group, label); + project_data.before_save_command = gtk_entry_new(); + ui_entry_add_clear_icon(GTK_ENTRY(project_data.before_save_command)); + gtk_widget_set_tooltip_text(project_data.before_save_command, + _("supports replacements: ") _("%f = filename, ") _("%a = filename with path, ") _("%d = path, ") _("%p = project directory")); + + // gtk_entry_set_text(GTK_ENTRY(e->before_save_command), str); + + ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ebox), project_data.before_save_command, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(table_box), ebox, TRUE, FALSE, 0); + + label = gtk_label_new(_("Working Directory")); + gtk_label_set_xalign(GTK_LABEL(label), 0.0); + gtk_size_group_add_widget(size_group, label); + project_data.before_save_workdir = gtk_entry_new(); + ui_entry_add_clear_icon(GTK_ENTRY(project_data.before_save_workdir)); + gtk_widget_set_tooltip_text(project_data.before_save_workdir, + _("supports replacements: ") _("%f = filename, ") _("%a = filename with path, ") _("%d = path, ") _("%p = project directory")); + + ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ebox), project_data.before_save_workdir, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(table_box), ebox, TRUE, FALSE, 0); + + seperator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_box_pack_start(GTK_BOX(table_box), seperator, TRUE, FALSE, 0); + + label = gtk_label_new(_("On Save Command")); + gtk_label_set_xalign(GTK_LABEL(label), 0.0); + gtk_size_group_add_widget(size_group, label); + project_data.on_save_command = gtk_entry_new(); + ui_entry_add_clear_icon(GTK_ENTRY(project_data.on_save_command)); + gtk_widget_set_tooltip_text(project_data.on_save_command, + _("supports replacements: ") _("%f = filename, ") _("%a = filename with path, ") _("%d = path, ") _("%p = project directory")); + ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ebox), project_data.on_save_command, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(table_box), ebox, TRUE, FALSE, 0); + label = gtk_label_new(_("Working Directory")); + gtk_label_set_xalign(GTK_LABEL(label), 0.0); + gtk_size_group_add_widget(size_group, label); + project_data.on_save_workdir = gtk_entry_new(); + ui_entry_add_clear_icon(GTK_ENTRY(project_data.on_save_workdir)); + gtk_widget_set_tooltip_text(project_data.on_save_workdir, + _("supports replacements: ") _("%f = filename, ") _("%a = filename with path, ") _("%d = path, ") _("%p = project directory")); + ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ebox), project_data.on_save_workdir, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(table_box), ebox, TRUE, FALSE, 0); + + seperator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_box_pack_start(GTK_BOX(table_box), seperator, TRUE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(vbox), table_box, FALSE, FALSE, 6); + + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 6); + + project_data.tab = GTK_WIDGET(g_object_ref(hbox)); +} diff --git a/autorun/src/project.h b/autorun/src/project.h new file mode 100644 index 0000000000..04e58124f0 --- /dev/null +++ b/autorun/src/project.h @@ -0,0 +1,32 @@ +/* + * Copyright 2026 WrapEarnPass + * + * 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. + */ + +#ifndef __AUTORUN_PROJECT_H__ +#define __AUTORUN_PROJECT_H__ + +#include + +#include "autorun.h" + +void project_save_properties_tab(GKeyFile* config); +void project_show_properties_tab(GtkWidget* notebook); +void project_hide_properties_tab(GtkWidget* notebook); +void project_properties_tab_cleanup(void); +void project_properties_tab_init(void); + +#endif diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 330fbd617e..3e3c984c1c 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -133,7 +133,7 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { gint ret; GError* error = NULL; SpawnWriteData* stdin_data; - if (g_strcmp0(interceptor, "BS") == 0 && success && !tmpfile) { + if (g_strcmp0(interceptor, "BS") == 0 && !tmpfile) { // need to send stdin. stdin_data = g_new0(SpawnWriteData, 1); stdin_data->ptr = before_contents; @@ -275,7 +275,7 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { } else { // cant assemble this command, don't try again cmd->invalid = TRUE; - msgwin_status_add(_("Cannot parse command %s"), command->str); + msgwin_status_add(_("Cannot parse command '%s'"), command->str); } } } diff --git a/autorun/src/utils.c b/autorun/src/utils.c index a3fcc6fce4..b96a340523 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -17,7 +17,6 @@ */ #include -#include #include "autorun.h" #include "utils.h" @@ -64,6 +63,11 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm } if (g_strcmp0(tokens[2], "CM") == 0) { cmd->command = utils_get_setting_string(key_file, "autorun", key, ""); + if (strlen(cmd->command) < 2) { + // GKeyFile doesnt actually delete keys, but sets them to "" + cmd->invalid = TRUE; + break; + } gchar* next_key; next_key = g_strconcat(tokens[0], "_", tokens[1], "_", "WD", NULL); cmd->working_dir = utils_get_setting_string(key_file, "autorun", next_key, ""); diff --git a/autorun/src/utils.h b/autorun/src/utils.h index fafc1d86ff..dca504ff87 100644 --- a/autorun/src/utils.h +++ b/autorun/src/utils.h @@ -25,4 +25,3 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd); #endif - From c86f2fce68db6bbca9a054648385253e4e868c59 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Thu, 25 Jun 2026 22:15:12 -0400 Subject: [PATCH 18/30] move all output to Compiler try to use Compilers built in clicky-jump instead of creating a new parser Update README to note the change in the output location --- autorun/README | 8 +- autorun/src/plugin_main.c | 4 +- autorun/src/spawn.c | 176 ++++++++++++++------------------------ autorun/src/spawn.h | 1 - autorun/src/utils.c | 57 ++++++++++-- autorun/src/utils.h | 3 +- 6 files changed, 122 insertions(+), 127 deletions(-) diff --git a/autorun/README b/autorun/README index 0a1775d071..a9425c2deb 100644 --- a/autorun/README +++ b/autorun/README @@ -104,15 +104,15 @@ CBS_00_CM = fancyformatter %f Autorun will run fancyformatter before save for C files as long as project.geany is open. -All Auto-run action status changes will be in the Geany Status window. -All Auto-run errors will be in the Geany Compiler status window. +All Auto-run output will be in the Geany Compiler status window. Known issues ============ Auto-run does not support forking, any commands will synchronously (spawn_sync). -geany/geany#4603 prevents reloading the Project configs in realtime. - unload and reload the Plugin from Plugin manager as a workaround. +geany/geany#4603 impacts reloading the Project configs in realtime. + Tools>Auto-run>Reload will force a reload. +Compiler won't follow links unless at least one Build command has run. License ======= diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index 827e3dbe93..9cd97b20b1 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -101,9 +101,7 @@ static void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G } ui_progress_bar_start(NULL); // status is used for return codes, so dont clear that one - // messages is used for stdout - msgwin_clear_tab(MSG_MESSAGE); - // compiler is used for stderr + // compiler is used for all output msgwin_clear_tab(MSG_COMPILER); dispatch_run("BS", doc); ui_progress_bar_stop(); diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 3e3c984c1c..a66a5af312 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -81,7 +81,8 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { gint con_len = sci_get_length(doc->editor->sci); gint cursor_at = sci_get_current_position(doc->editor->sci); before_contents = sci_get_contents(doc->editor->sci, con_len); - if (g_strcmp0(interceptor, "BS") == 0 && (g_strrstr(cmd->command, "%a") || g_strrstr(cmd->working_dir, "%a"))) { + + if (g_strcmp0(interceptor, "BS") == 0 && g_strrstr(cmd->command, "%a")) { //%a means something special tmpfile = g_file_new_tmp("ar.aXXXXXX", &iostream, NULL); if (tmpfile) { @@ -102,16 +103,14 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { // do the replacement bits gchar* target_name = g_path_get_basename(target_file); - // replace %f + GString* command = g_string_new(cmd->command); + // replace %f g_string_replace(command, "%f", target_name, 0); - GString* working_dir = g_string_new(cmd->working_dir); - g_string_replace(working_dir, "%f", target_name, 0); - // replace %a g_string_replace(command, "%a", target_file, 0); - g_string_replace(working_dir, "%a", target_file, 0); + GString* working_dir = g_string_new(cmd->working_dir); // replace %d g_string_replace(command, "%d", target_dir, 0); g_string_replace(working_dir, "%d", target_dir, 0); @@ -121,17 +120,17 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { g_string_replace(working_dir, "%p", target_projdir, 0); if (success) { - success = spawn_check_command(command->str, FALSE, NULL); + success = spawn_check_command(command->str, TRUE, NULL); } // it looks like we're running; - GString* stdout_data = g_string_new(NULL); - GString* stderr_data = g_string_new(NULL); if (success) { gchar** env; env = utils_copy_environment(NULL, "GEANY_FUNCNAME", __func__, NULL); gint ret; GError* error = NULL; + GString* stdout_data = g_string_new(NULL); + GString* stderr_data = g_string_new(NULL); SpawnWriteData* stdin_data; if (g_strcmp0(interceptor, "BS") == 0 && !tmpfile) { // need to send stdin. @@ -142,120 +141,78 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { // no stdin stdin_data = NULL; } + + gchar* status_msg = g_strdup_printf("Running %s (from %s)", command->str, working_dir->str); + msgwin_compiler_add_string(COLOR_BLUE, status_msg); + g_free(status_msg); + // runnit success = spawn_sync(working_dir->str, command->str, NULL, env, stdin_data, stdout_data, stderr_data, &ret, &error); if (stdin_data) { g_free(stdin_data); } + // cleanup anything we dont need in processing. g_strfreev(env); - if (!success) { - // somewhat bad had occur - msgwin_status_add(_("Command failed with %s"), error->message); - ui_set_statusbar(FALSE, _("%s interceptor failed."), interceptor); - g_error_free(error); - // invalid command - cmd->invalid = TRUE; - } else { - // if we made a tmpfile, grab the results and del the file. - if (tmpfile) { - if (ret == 0) { - gchar* read = NULL; - g_file_get_contents(target_file, &read, NULL, NULL); - if (read) { - if (g_strcmp0(read, before_contents) != 0) { - // no point in stealing the document focus - sci_set_text(doc->editor->sci, read); - sci_set_current_position(doc->editor->sci, cursor_at, TRUE); - } - // maybe it put the output in stdout. - if (strlen(stdout_data->str) > 0) { - sci_set_text(doc->editor->sci, stdout_data->str); - sci_set_current_position(doc->editor->sci, cursor_at, TRUE); - } - g_free(read); - } - } else { - msgwin_status_add(_("%s ran but returned an error."), command->str); - // parse out a linenumber, assuming the stdout data is file:line:col - gchar* lineptr = g_strstr_len(stdout_data->str, 255, ":"); - gint linenum = 1; - if (lineptr) { - lineptr++; // skip the colon - linenum = atoi(lineptr); - if (linenum == 0) { - linenum = 1; - } - } - msgwin_msg_add(COLOR_BLACK, linenum, doc, "%s", stdout_data->str); - if (strlen(stderr_data->str) > 0) { - msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); - } + g_free(target_name); + g_free(target_dir); + g_free(target_projdir); + + if (success && ret == 0) { + // ran and didn't throw an error + // do we need to update the editor? + if (g_strcmp0(interceptor, "BS") == 0) { + // maybe. + // could be in tmpfile, stdout + gchar* read_tmp = NULL; + if (tmpfile) { + g_file_get_contents(target_file, &read_tmp, NULL, NULL); } - } else if (g_strcmp0(interceptor, "BS") == 0) { - // we spawned a child, but that child might have successfully failed. - if (ret == 0) { + // check for the stupid case + if (strlen(stdout_data->str) > 0 && tmpfile && g_strcmp0(read_tmp, before_contents) != 0 && g_strcmp0(stdout_data->str, read_tmp) != 0) { + // if there are changes to the tempfile AND stdout + // this command is stupid. there is no way to know + // which output is the output to output. + msgwin_status_add(_("Command failed with inconsistant output.")); + } else if (strlen(stdout_data->str) > 0) { sci_set_text(doc->editor->sci, stdout_data->str); sci_set_current_position(doc->editor->sci, cursor_at, TRUE); - if (strlen(stderr_data->str) > 0) { - msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); - } - } else { - msgwin_status_add(_("%s ran but returned an error."), command->str); - gchar* lineptr = g_strstr_len(stdout_data->str, 255, ":"); - gint linenum = 1; - if (lineptr) { - lineptr++; // skip the colon - linenum = atoi(lineptr); - if (linenum == 0) { - linenum = 1; - } - } - msgwin_msg_add(COLOR_BLACK, linenum, doc, "%s", stdout_data->str); - if (strlen(stderr_data->str) > 0) { - msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); - } + } else if (tmpfile && g_strcmp0(read_tmp, before_contents) != 0) { + sci_set_text(doc->editor->sci, read_tmp); + // no point in stealing the document focus + sci_set_current_position(doc->editor->sci, cursor_at, TRUE); } - } else if (g_strcmp0(interceptor, "OS") == 0) { - // we spawned a child, but that child might have successfully failed. - if (ret == 0) { - // parse out a linenumber, assuming the stdout data is file:line:col - gchar* lineptr = g_strstr_len(stdout_data->str, 255, ":"); - gint linenum = 1; - if (lineptr) { - lineptr++; // skip the colon - linenum = atoi(lineptr); - if (linenum == 0) { - linenum = 1; - } - } - - msgwin_msg_add(COLOR_BLACK, linenum, doc, "%s", stdout_data->str); - if (strlen(stderr_data->str) > 0) { - msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); - } - } else { - msgwin_status_add(_("%s ran but returned an error."), command->str); - // parse out a linenumber, assuming the stdout data is file:line:col - gchar* lineptr = g_strstr_len(stdout_data->str, 255, ":"); - gint linenum = 1; - if (lineptr) { - lineptr++; // skip the colon - linenum = atoi(lineptr); - if (linenum == 0) { - linenum = 1; - } - } - msgwin_msg_add(COLOR_BLACK, linenum, doc, "%s", stdout_data->str); - if (strlen(stderr_data->str) > 0) { - msgwin_compiler_add_string(COLOR_BLACK, stderr_data->str); - } + if (read_tmp) { + g_free(read_tmp); } } + // dont update the editor if it ran with an error. + } else if (!success) { + // refused to run + msgwin_status_add(_("Command failed with %s"), error->message); + ui_set_statusbar(FALSE, _("%s interceptor failed."), interceptor); + g_error_free(error); + // invalid command, don't process it again. + cmd->invalid = TRUE; + } + + // GUI output formatting? + // stdout + // BS stdout goes to the scintilla editor + if (g_strcmp0(interceptor, "BS") != 0 && strlen(stdout_data->str) > 0) { + parse_output(stdout_data->str); + } + + // stderr + if (strlen(stderr_data->str) > 0) { + parse_output(stderr_data->str); } if (tmpfile) { g_unlink(target_file); g_object_unref(tmpfile); } + if (target_file) { + g_free(target_file); + } g_string_free(stdout_data, TRUE); g_string_free(stderr_data, TRUE); @@ -266,12 +223,6 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { g_string_free(working_dir, TRUE); g_string_free(command, TRUE); - g_free(target_name); - g_free(target_dir); - g_free(target_projdir); - if (target_file) { - g_free(target_file); - } } else { // cant assemble this command, don't try again cmd->invalid = TRUE; @@ -279,5 +230,6 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { } } } + g_slist_free(command_list); } diff --git a/autorun/src/spawn.h b/autorun/src/spawn.h index bba30089ee..d36ca0e96e 100644 --- a/autorun/src/spawn.h +++ b/autorun/src/spawn.h @@ -26,4 +26,3 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc); #endif - diff --git a/autorun/src/utils.c b/autorun/src/utils.c index b96a340523..f371aaa9ea 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -24,19 +24,17 @@ /* Read a filedef or keyfile for Intercept Actions * @param action key (should end with CM) * @param key_file to get remaining values from - * @return true if parsing worked - * false is parsing failed * */ -gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd) { +void parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd) { // key should be a (filetype_)action_number_flag string if (!key || !cmd) { - return FALSE; + return; } // is this a key? if (!g_strstr_len(key, -1, "_")) { // no cmd->invalid = TRUE; - return FALSE; + return; } cmd->invalid = FALSE; // what is this key? @@ -112,6 +110,53 @@ gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cm } g_strfreev(tokens); +} + +// split stdout and stderr into +// blocks and shove them into the correct tabs +void parse_output(gchar* input) { + // make sure we're using utf8 + gchar* output = encodings_convert_to_utf8(input, -1, NULL); + // split the output on \n + gchar** lines = g_regex_split_simple("\\n", output, 0, G_REGEX_MATCH_NEWLINE_ANY); + gchar** line = NULL; + + // foreach line, if it contains a filename, it is red. + foreach_strv(line, lines) { + guint i; + gboolean found = FALSE; + GeanyData* geany_data = autorun_globals->data; + foreach_document(i) { + GeanyDocument* doc_test = documents[i]; + gchar* locale_name = utils_get_locale_from_utf8(doc_test->file_name); + gchar* base_test = g_path_get_basename(locale_name); + if (g_strrstr(*line, base_test)) { + // contains a known filename + found = TRUE; + break; + } + if (locale_name) { + g_free(locale_name); + } + if (base_test) { + g_free(base_test); + } + } + // make known filenames clicky (perhaps) + // the compiler requires each newline to be its own message + // or the compiler clicky breaks. + // the compiler requires each clicky to be RED + // or the compiler clicky breaks. + if (found) { + msgwin_compiler_add_string(COLOR_RED, *line); + } else { + msgwin_compiler_add_string(COLOR_BLACK, *line); + } + } - return cmd->invalid; + line = NULL; + g_strfreev(lines); + if (output) { + g_free(output); + } } diff --git a/autorun/src/utils.h b/autorun/src/utils.h index dca504ff87..11d4faf468 100644 --- a/autorun/src/utils.h +++ b/autorun/src/utils.h @@ -22,6 +22,7 @@ #include "autorun.h" -gboolean parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd); +void parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd); +void parse_output(gchar* output); #endif From 007595dcc237b691d89ac43a47655c1e601c9b0d Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Fri, 26 Jun 2026 13:10:36 -0400 Subject: [PATCH 19/30] Add async handler Fix uniform AUTORUN_ namespaces Updated README to reflect async changes Fix progressbar handler actually works now --- autorun/README | 3 +- autorun/src/autorun.c | 1 + autorun/src/autorun.h | 1 + autorun/src/menu.c | 4 +- autorun/src/menu.h | 2 +- autorun/src/plugin_main.c | 10 +- autorun/src/project.c | 4 +- autorun/src/project.h | 2 +- autorun/src/spawn.c | 320 +++++++++++++++++++++++++------------- autorun/src/spawn.h | 3 +- 10 files changed, 227 insertions(+), 123 deletions(-) diff --git a/autorun/README b/autorun/README index a9425c2deb..a4d03b04bc 100644 --- a/autorun/README +++ b/autorun/README @@ -109,7 +109,8 @@ All Auto-run output will be in the Geany Compiler status window. Known issues ============ -Auto-run does not support forking, any commands will synchronously (spawn_sync). +Auto-run runs before-save targets synchronously. This may cause Geany to freeze. + Move slow commands to on-save to run asynchronously. geany/geany#4603 impacts reloading the Project configs in realtime. Tools>Auto-run>Reload will force a reload. Compiler won't follow links unless at least one Build command has run. diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c index 7c5d52c6ac..54db1a0c93 100644 --- a/autorun/src/autorun.c +++ b/autorun/src/autorun.c @@ -76,6 +76,7 @@ void autorun_globals_init(GeanyPlugin* plugin) { autorun_globals->filedef_commands = NULL; autorun_globals->project_commands = NULL; autorun_globals->dirtybit = FALSE; + autorun_globals->children = 0; } } diff --git a/autorun/src/autorun.h b/autorun/src/autorun.h index 06c567ee0f..d0ebd43b72 100644 --- a/autorun/src/autorun.h +++ b/autorun/src/autorun.h @@ -27,6 +27,7 @@ typedef struct { GSList* filedef_commands; GSList* project_commands; gboolean dirtybit; + gint children; } AUTORUN_GLOBALS; typedef struct { diff --git a/autorun/src/menu.c b/autorun/src/menu.c index 5b89dc997e..6191a43b49 100644 --- a/autorun/src/menu.c +++ b/autorun/src/menu.c @@ -26,8 +26,8 @@ typedef struct { GtkWidget* menu; GtkWidget* root_item; GtkWidget* item_reload; -} AR_MENU_DATA; -static AR_MENU_DATA menu_data; +} AUTORUN_MENU_DATA; +static AUTORUN_MENU_DATA menu_data; /* The function handles the menu item "Reload" */ static void autorun_menu_reload_cb(G_GNUC_UNUSED GtkMenuItem* menuitem, G_GNUC_UNUSED gpointer user_data) { diff --git a/autorun/src/menu.h b/autorun/src/menu.h index 4259f837b5..4d1d0f45b7 100644 --- a/autorun/src/menu.h +++ b/autorun/src/menu.h @@ -22,4 +22,4 @@ gboolean menu_init(void); void menu_cleanup(void); -#endif \ No newline at end of file +#endif diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index 9cd97b20b1..6a3e55ad66 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -67,15 +67,13 @@ static void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_U // no point in processing a file so short return; } - ui_progress_bar_start(NULL); /* * dont reset the msgwin here, as document-save * is the second stage of document-before-save * and the messages will all be logically related * to the user selecting File>Save */ - dispatch_run("OS", doc); - ui_progress_bar_stop(); + dispatch_run_async(doc); } /* Handler to run any applicable Auto-run configs before a write*/ @@ -99,12 +97,12 @@ static void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G // no point in processing a file so short return; } - ui_progress_bar_start(NULL); // status is used for return codes, so dont clear that one // compiler is used for all output msgwin_clear_tab(MSG_COMPILER); - dispatch_run("BS", doc); - ui_progress_bar_stop(); + dispatch_run_sync(doc); + // no point in ui_progress_bar here, as + // sync processes lock Geany up until they finish } static void on_project_dialog_open(G_GNUC_UNUSED GObject* obj, GtkWidget* notebook, G_GNUC_UNUSED gpointer user_data) { // populate notebook for current file type. diff --git a/autorun/src/project.c b/autorun/src/project.c index 4dcfdc8f26..abdfa1ab31 100644 --- a/autorun/src/project.c +++ b/autorun/src/project.c @@ -28,8 +28,8 @@ typedef struct { GtkWidget* before_save_workdir; GtkWidget* on_save_command; GtkWidget* on_save_workdir; -} AR_PROJECT_DATA; -static AR_PROJECT_DATA project_data; +} AUTORUN_PROJECT_DATA; +static AUTORUN_PROJECT_DATA project_data; void project_save_properties_tab(GKeyFile* config) { // grab the current filetype diff --git a/autorun/src/project.h b/autorun/src/project.h index 04e58124f0..b54602fd35 100644 --- a/autorun/src/project.h +++ b/autorun/src/project.h @@ -29,4 +29,4 @@ void project_hide_properties_tab(GtkWidget* notebook); void project_properties_tab_cleanup(void); void project_properties_tab_init(void); -#endif +#endif diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index a66a5af312..79402fb8a9 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -22,8 +22,24 @@ #include "autorun.h" #include "spawn.h" #include "utils.h" +// +// if (spawn_async(working_dir, locale_term_cmd, NULL, NULL, &(run_info[cmdindex].pid), &error)) { +// g_child_watch_add(run_info[cmdindex].pid, (GChildWatchFunc)run_exit_cb, (gpointer) & (run_info[cmdindex])); +// build_menu_update(doc); +//} -void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { +/* + * Build the @interceptor from @autorun_globals, and return the parsed command to @command + * If the parsed command creates a tempfile, return it in cmd->interceptor + * */ + +typedef struct { + GString* stdout; + GString* stderr; + GeanyDocument* doc; + +} AUTORUN_ASYNC_DATA; +static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** command) { GSList* command_list = NULL; // build the commands backwads @@ -78,9 +94,6 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { gchar* before_contents; // we need scintilla in a buffer regardless of %a - gint con_len = sci_get_length(doc->editor->sci); - gint cursor_at = sci_get_current_position(doc->editor->sci); - before_contents = sci_get_contents(doc->editor->sci, con_len); if (g_strcmp0(interceptor, "BS") == 0 && g_strrstr(cmd->command, "%a")) { //%a means something special @@ -90,7 +103,10 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { g_io_stream_close((GIOStream*)iostream, NULL, NULL); target_file = g_file_get_path(tmpfile); // remove the trailing \0 on write + gint con_len = sci_get_length(doc->editor->sci); + before_contents = sci_get_contents(doc->editor->sci, con_len); success = g_file_set_contents_full(target_file, before_contents, con_len - 1, G_FILE_SET_CONTENTS_CONSISTENT, 0660, NULL); + g_free(before_contents); } else { success = FALSE; @@ -104,132 +120,218 @@ void dispatch_run(const gchar* interceptor, GeanyDocument* doc) { // do the replacement bits gchar* target_name = g_path_get_basename(target_file); - GString* command = g_string_new(cmd->command); + GString* command_str = g_string_new(cmd->command); // replace %f - g_string_replace(command, "%f", target_name, 0); + g_string_replace(command_str, "%f", target_name, 0); // replace %a - g_string_replace(command, "%a", target_file, 0); + g_string_replace(command_str, "%a", target_file, 0); - GString* working_dir = g_string_new(cmd->working_dir); + GString* working_dir_str = g_string_new(cmd->working_dir); // replace %d - g_string_replace(command, "%d", target_dir, 0); - g_string_replace(working_dir, "%d", target_dir, 0); + g_string_replace(command_str, "%d", target_dir, 0); + g_string_replace(working_dir_str, "%d", target_dir, 0); // replace %p - g_string_replace(command, "%p", target_projdir, 0); - g_string_replace(working_dir, "%p", target_projdir, 0); + g_string_replace(command_str, "%p", target_projdir, 0); + g_string_replace(working_dir_str, "%p", target_projdir, 0); if (success) { - success = spawn_check_command(command->str, TRUE, NULL); + success = spawn_check_command(command_str->str, TRUE, NULL); } - // it looks like we're running; - if (success) { - gchar** env; - env = utils_copy_environment(NULL, "GEANY_FUNCNAME", __func__, NULL); - gint ret; - GError* error = NULL; - GString* stdout_data = g_string_new(NULL); - GString* stderr_data = g_string_new(NULL); - SpawnWriteData* stdin_data; - if (g_strcmp0(interceptor, "BS") == 0 && !tmpfile) { - // need to send stdin. - stdin_data = g_new0(SpawnWriteData, 1); - stdin_data->ptr = before_contents; - stdin_data->size = con_len; + // attach this command to the outgoing GSList + AUTORUN_CMD* cmdout = autorun_cmd_new(); + cmdout->command = g_string_free_and_steal(command_str); + cmdout->working_dir = g_string_free_and_steal(working_dir_str); + if (tmpfile) { + cmdout->interceptor = g_strdup(target_file); } else { - // no stdin - stdin_data = NULL; + cmdout->interceptor = NULL; } + cmdout->invalid = FALSE; + *command = g_slist_append(*command, cmdout); + } else { + // cant assemble this command, don't try again + cmd->invalid = TRUE; + msgwin_status_add(_("Cannot parse command '%s'"), command_str->str); + } + // cleanup + g_free(target_dir); + g_free(target_projdir); + if (tmpfile) { + g_object_unref(tmpfile); + } + g_free(target_file); + } + // cleanup + // autorun_cmd_list_free(command_list); + // we dont own the contents of command_list. + g_slist_free(command_list); + } +} - gchar* status_msg = g_strdup_printf("Running %s (from %s)", command->str, working_dir->str); - msgwin_compiler_add_string(COLOR_BLUE, status_msg); - g_free(status_msg); - // runnit - success = spawn_sync(working_dir->str, command->str, NULL, env, stdin_data, stdout_data, stderr_data, &ret, &error); - if (stdin_data) { - g_free(stdin_data); - } - // cleanup anything we dont need in processing. - g_strfreev(env); - g_free(target_name); - g_free(target_dir); - g_free(target_projdir); - - if (success && ret == 0) { - // ran and didn't throw an error - // do we need to update the editor? - if (g_strcmp0(interceptor, "BS") == 0) { - // maybe. - // could be in tmpfile, stdout - gchar* read_tmp = NULL; - if (tmpfile) { - g_file_get_contents(target_file, &read_tmp, NULL, NULL); - } - // check for the stupid case - if (strlen(stdout_data->str) > 0 && tmpfile && g_strcmp0(read_tmp, before_contents) != 0 && g_strcmp0(stdout_data->str, read_tmp) != 0) { - // if there are changes to the tempfile AND stdout - // this command is stupid. there is no way to know - // which output is the output to output. - msgwin_status_add(_("Command failed with inconsistant output.")); - } else if (strlen(stdout_data->str) > 0) { - sci_set_text(doc->editor->sci, stdout_data->str); - sci_set_current_position(doc->editor->sci, cursor_at, TRUE); - } else if (tmpfile && g_strcmp0(read_tmp, before_contents) != 0) { - sci_set_text(doc->editor->sci, read_tmp); - // no point in stealing the document focus - sci_set_current_position(doc->editor->sci, cursor_at, TRUE); - } - if (read_tmp) { - g_free(read_tmp); - } - } - // dont update the editor if it ran with an error. - } else if (!success) { - // refused to run - msgwin_status_add(_("Command failed with %s"), error->message); - ui_set_statusbar(FALSE, _("%s interceptor failed."), interceptor); - g_error_free(error); - // invalid command, don't process it again. - cmd->invalid = TRUE; - } +static void stdio_cb(GString* string, GIOCondition condition, gpointer data) { + // dup the string to data. + if (condition == G_IO_IN || condition == G_IO_PRI) { + g_string_append((GString*)data, string->str); + } +} - // GUI output formatting? - // stdout - // BS stdout goes to the scintilla editor - if (g_strcmp0(interceptor, "BS") != 0 && strlen(stdout_data->str) > 0) { - parse_output(stdout_data->str); - } +static void stdioend_cb(GPid pid, gint wait_status, gpointer user_data) { + AUTORUN_ASYNC_DATA* exit_data = (AUTORUN_ASYNC_DATA*)user_data; + parse_output(exit_data->stdout->str); + g_string_free(exit_data->stdout, TRUE); + parse_output(exit_data->stderr->str); + g_string_free(exit_data->stderr, TRUE); + g_free(exit_data); + if (--autorun_globals->children < 1) { + ui_progress_bar_stop(); + ui_set_statusbar(FALSE, _("Auto-run finished %s"), exit_data->doc->file_name); + autorun_globals->children = 0; + } +} - // stderr - if (strlen(stderr_data->str) > 0) { - parse_output(stderr_data->str); - } +// prefer running commands async, so the editor plays nice +void dispatch_run_async(GeanyDocument* doc) { + GSList* runnables = NULL; + parse_command("OS", doc, &runnables); + GSList* runnable; + foreach_slist(runnable, runnables) { + // convenience + AUTORUN_CMD* current_cmd = (AUTORUN_CMD*)runnable->data; - if (tmpfile) { - g_unlink(target_file); - g_object_unref(tmpfile); - } - if (target_file) { - g_free(target_file); - } + gchar** env; + env = utils_copy_environment(NULL, "GEANY_FUNCNAME", __func__, NULL); + GError* error = NULL; - g_string_free(stdout_data, TRUE); - g_string_free(stderr_data, TRUE); + gchar* status_msg = g_strdup_printf(_("Running %s (from %s)"), current_cmd->command, current_cmd->working_dir); + msgwin_compiler_add_string(COLOR_BLUE, status_msg); + g_free(status_msg); + GString* stdout_data = g_string_new(NULL); + GString* stderr_data = g_string_new(NULL); + AUTORUN_ASYNC_DATA* end_data = g_new0(AUTORUN_ASYNC_DATA, 1); + end_data->stdout = stdout_data; + end_data->stderr = stderr_data; + end_data->doc = doc; + GPid* child_pid = NULL; + gboolean success = FALSE; + ui_set_statusbar(FALSE, _("Auto-run running %s"), current_cmd->command); + ui_progress_bar_start("Auto-run"); + ++autorun_globals->children; + success = spawn_with_callbacks(current_cmd->working_dir, current_cmd->command, NULL, env, SPAWN_ASYNC | SPAWN_LINE_BUFFERED, NULL, NULL, stdio_cb, stdout_data, 0, + stdio_cb, stderr_data, 0, stdioend_cb, end_data, child_pid, &error); - if (before_contents) { - g_free(before_contents); - } + // cleanup anything we dont need in processing. + g_strfreev(env); - g_string_free(working_dir, TRUE); - g_string_free(command, TRUE); - } else { - // cant assemble this command, don't try again - cmd->invalid = TRUE; - msgwin_status_add(_("Cannot parse command '%s'"), command->str); + if (!success) { + if (--autorun_globals->children < 1) { + ui_progress_bar_stop(); + autorun_globals->children = 0; } + // refused to run + msgwin_status_add(_("Command failed with %s"), error->message); + g_error_free(error); } } + autorun_cmd_list_free(runnables); +} - g_slist_free(command_list); +// this should only ever be for interceptor=BS, as it locks up the editor +void dispatch_run_sync(GeanyDocument* doc) { + GSList* runnables = NULL; + parse_command("BS", doc, &runnables); + GSList* runnable; + foreach_slist(runnable, runnables) { + // convienience + AUTORUN_CMD* current_cmd = (AUTORUN_CMD*)runnable->data; + // pull the tempfile from the overloaded cmd->interceptor + gboolean has_tmpfile = (current_cmd->interceptor) ? TRUE : FALSE; + gchar* tmpfile_path = current_cmd->interceptor; + + gchar** env; + env = utils_copy_environment(NULL, "GEANY_FUNCNAME", __func__, NULL); + gint ret; + GError* error = NULL; + GString* stdout_data = g_string_new(NULL); + GString* stderr_data = g_string_new(NULL); + SpawnWriteData* stdin_data; + + gint con_len = sci_get_length(doc->editor->sci); + gchar* before_contents = sci_get_contents(doc->editor->sci, con_len); + if (!has_tmpfile) { + // need to send stdin. + stdin_data = g_new0(SpawnWriteData, 1); + stdin_data->ptr = before_contents; + stdin_data->size = con_len; + } else { + // no stdin + stdin_data = NULL; + } + gchar* status_msg = g_strdup_printf(_("Running %s (from %s)"), current_cmd->command, current_cmd->working_dir); + msgwin_compiler_add_string(COLOR_BLUE, status_msg); + g_free(status_msg); + // runnit + gboolean success = spawn_sync(current_cmd->working_dir, current_cmd->command, NULL, env, stdin_data, stdout_data, stderr_data, &ret, &error); + if (stdin_data) { + g_free(stdin_data); + } + // cleanup anything we dont need in processing. + g_strfreev(env); + + if (success && ret == 0) { + // ran and didn't throw an error + // do we need to update the editor? + // maybe. + // could be in tmpfile, stdout + gchar* read_tmp = NULL; + if (has_tmpfile) { + g_file_get_contents(tmpfile_path, &read_tmp, NULL, NULL); + } + gint cursor_at = sci_get_current_position(doc->editor->sci); + // check for the stupid case + if (strlen(stdout_data->str) > 0 && has_tmpfile && g_strcmp0(read_tmp, before_contents) != 0 && g_strcmp0(stdout_data->str, read_tmp) != 0) { + // if there are changes to the tempfile AND stdout + // this command is stupid. there is no way to know + // which output is the output to output. + msgwin_status_add(_("Command failed with inconsistent output.")); + } else if (strlen(stdout_data->str) > 0) { + sci_set_text(doc->editor->sci, stdout_data->str); + // put ze cursor bek + sci_set_current_position(doc->editor->sci, cursor_at, TRUE); + } else if (has_tmpfile && g_strcmp0(read_tmp, before_contents) != 0) { + sci_set_text(doc->editor->sci, read_tmp); + // put ze cursor bek + sci_set_current_position(doc->editor->sci, cursor_at, TRUE); + } + if (read_tmp) { + g_free(read_tmp); + } + // dont update the editor if it ran with an error. + } else if (!success) { + // refused to run + msgwin_status_add(_("Command failed with %s"), error->message); + ui_set_statusbar(FALSE, _("Auto-run before-save interceptor failed.")); + g_error_free(error); + } + + // GUI output formatting? + // BS stdout goes to the scintilla editor + // stderr + if (strlen(stderr_data->str) > 0) { + parse_output(stderr_data->str); + } + + if (has_tmpfile) { + g_unlink(tmpfile_path); + } + + g_string_free(stdout_data, TRUE); + g_string_free(stderr_data, TRUE); + + if (before_contents) { + g_free(before_contents); + } + } + autorun_cmd_list_free(runnables); } diff --git a/autorun/src/spawn.h b/autorun/src/spawn.h index d36ca0e96e..22b5edbe72 100644 --- a/autorun/src/spawn.h +++ b/autorun/src/spawn.h @@ -23,6 +23,7 @@ #include "autorun.h" -void dispatch_run(const gchar* interceptor, GeanyDocument* doc); +void dispatch_run_async(GeanyDocument* doc); +void dispatch_run_sync(GeanyDocument* doc); #endif From 2d0836c0387514e06806f001dd72e9a8da5134bf Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Fri, 26 Jun 2026 13:23:16 -0400 Subject: [PATCH 20/30] Fix timer assertion by only starting the progress once. --- autorun/src/spawn.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 79402fb8a9..416951d081 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -196,6 +196,7 @@ void dispatch_run_async(GeanyDocument* doc) { GSList* runnables = NULL; parse_command("OS", doc, &runnables); GSList* runnable; + ui_progress_bar_start("Auto-run"); foreach_slist(runnable, runnables) { // convenience AUTORUN_CMD* current_cmd = (AUTORUN_CMD*)runnable->data; @@ -216,7 +217,7 @@ void dispatch_run_async(GeanyDocument* doc) { GPid* child_pid = NULL; gboolean success = FALSE; ui_set_statusbar(FALSE, _("Auto-run running %s"), current_cmd->command); - ui_progress_bar_start("Auto-run"); + ++autorun_globals->children; success = spawn_with_callbacks(current_cmd->working_dir, current_cmd->command, NULL, env, SPAWN_ASYNC | SPAWN_LINE_BUFFERED, NULL, NULL, stdio_cb, stdout_data, 0, stdio_cb, stderr_data, 0, stdioend_cb, end_data, child_pid, &error); From 1c32a206495a26b9f7b8f8dc3224e8d20abacaf8 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Mon, 29 Jun 2026 08:31:57 -0400 Subject: [PATCH 21/30] fix use after free --- autorun/src/spawn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 416951d081..403e18c6ba 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -183,12 +183,12 @@ static void stdioend_cb(GPid pid, gint wait_status, gpointer user_data) { g_string_free(exit_data->stdout, TRUE); parse_output(exit_data->stderr->str); g_string_free(exit_data->stderr, TRUE); - g_free(exit_data); if (--autorun_globals->children < 1) { ui_progress_bar_stop(); ui_set_statusbar(FALSE, _("Auto-run finished %s"), exit_data->doc->file_name); autorun_globals->children = 0; } + g_free(exit_data); } // prefer running commands async, so the editor plays nice From 1c74207c883c9ec3aef8788c1db704b3c4c8b315 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Mon, 29 Jun 2026 12:23:44 -0400 Subject: [PATCH 22/30] fix actually free cmd housekeeping comments to ensure they are uniformly formatted change macro gluing tooltips to strings to fix code formatting --- autorun/CMakeLists.txt | 12 ++++++- autorun/src/autorun.c | 26 ++++++++++---- autorun/src/menu.c | 13 +++---- autorun/src/menu.h | 2 ++ autorun/src/plugin_main.c | 20 ++++++----- autorun/src/project.c | 37 ++++++++++++++------ autorun/src/project.h | 2 -- autorun/src/spawn.c | 72 +++++++++++++++++++++++---------------- autorun/src/spawn.h | 2 -- autorun/src/utils.c | 20 +++++------ 10 files changed, 127 insertions(+), 79 deletions(-) diff --git a/autorun/CMakeLists.txt b/autorun/CMakeLists.txt index dd68f943da..adfd5cba05 100644 --- a/autorun/CMakeLists.txt +++ b/autorun/CMakeLists.txt @@ -1,7 +1,8 @@ cmake_minimum_required(VERSION 3.27) set(CMAKE_EXPORT_COMPILE_COMMANDS on) -list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +set(CMAKE_INSTALL_PREFIX "$ENV{HOME}/.local/lib/geany") project( autorun @@ -9,6 +10,15 @@ project( LANGUAGES C DESCRIPTION "Geany action interceptor plugin") +set(CMAKE_C_FLAGS "-Wall") +set(CMAKE_C_FLAGS_DEBUG "-g") +set(CMAKE_C_FLAGS_RELEASE "-O3") + +set(CMAKE_CXX_FLAGS "-Wall") +set(CMAKE_CXX_FLAGS_DEBUG "-g" ) +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + +add_compile_definitions(LOCALEDIR="/usr/share/locale") # the `pkg_check_modules` function is created with this call find_package(PkgConfig REQUIRED) # these calls create special `PkgConfig::` variables diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c index 54db1a0c93..f5f343e076 100644 --- a/autorun/src/autorun.c +++ b/autorun/src/autorun.c @@ -23,6 +23,8 @@ AUTORUN_GLOBALS* autorun_globals; +/* allocate an AUTORUN_CMD struct + * should be cleaned up with a matching call to autorun_cmd_free */ AUTORUN_CMD* autorun_cmd_new() { AUTORUN_CMD* cmd = NULL; cmd = g_new0(AUTORUN_CMD, 1); @@ -35,6 +37,7 @@ AUTORUN_CMD* autorun_cmd_new() { return cmd; } +/* free an AUTORUN_CMD in a safe manner */ void autorun_cmd_free(AUTORUN_CMD* cmd) { /* do not check for cmd->invalid because it could be a partial command * or invalidated after assembly. */ @@ -42,7 +45,7 @@ void autorun_cmd_free(AUTORUN_CMD* cmd) { // who are you and why are you in my attic? return; } - // free the command + // free the command properties if (cmd->file_type) { cmd->file_type = NULL; } @@ -58,8 +61,11 @@ void autorun_cmd_free(AUTORUN_CMD* cmd) { g_free(cmd->working_dir); cmd->working_dir = NULL; } + // and the struct itself + g_free(cmd); } +/* helper to free a GSList of AUTORUN_CMD */ void autorun_cmd_list_free(GSList* command_list) { if (!command_list) { // why are you naked? @@ -68,6 +74,7 @@ void autorun_cmd_list_free(GSList* command_list) { g_slist_free_full(command_list, (GDestroyNotify)autorun_cmd_free); } +/* establish the autorun_globals */ void autorun_globals_init(GeanyPlugin* plugin) { if (!autorun_globals) { autorun_globals = g_new0(AUTORUN_GLOBALS, 1); @@ -80,6 +87,7 @@ void autorun_globals_init(GeanyPlugin* plugin) { } } +/* teardown any linked properties from autorun_globals */ void autorun_globals_free(void) { if (autorun_globals) { if (autorun_globals->filedef_commands) { @@ -97,13 +105,15 @@ void autorun_globals_free(void) { } } +/* helper function to process .config/geany/filedefs/ + * to load any [autorun] handlers */ void load_filedefs(void) { - // if there are any filetypes.FILE autorun sections + // load the config dir gchar* filedef_path = g_build_path(G_DIR_SEPARATOR_S, autorun_globals->data->app->configdir, GEANY_FILEDEFS_SUBDIR, NULL); guint filedef_len; GSList* file_list = utils_get_file_list(filedef_path, &filedef_len, NULL); + // find any filetypes.FILE if (filedef_len > 0) { - // stash filetypes.FILE to fallback on-project-close GSList* node = NULL; foreach_slist(node, file_list) { if (g_str_has_prefix(node->data, "filetypes.") && g_strcmp0(node->data, "filetypes.README") != 0) { @@ -124,7 +134,7 @@ void load_filedefs(void) { parse_intercept_actions(*handler_key, config, cmd); if (!cmd->invalid) { - // add the command + // add the command backwards autorun_globals->filedef_commands = g_slist_prepend(autorun_globals->filedef_commands, cmd); } else { // free the command @@ -132,7 +142,7 @@ void load_filedefs(void) { } } } - // flip it around + // flip the GSList around autorun_globals->filedef_commands = g_slist_reverse(autorun_globals->filedef_commands); } else { g_free(gerr); @@ -150,6 +160,8 @@ void load_filedefs(void) { g_free(filedef_path); } +/* helper function to process Project .geany files + * to load any [autorun] handlers */ void load_projectdefs(GKeyFile* config) { gsize key_len; GError* gerr = NULL; @@ -164,7 +176,7 @@ void load_projectdefs(GKeyFile* config) { AUTORUN_CMD* cmd = autorun_cmd_new(); parse_intercept_actions(*handler_key, config, cmd); if (!cmd->invalid) { - // add the command + // add the command backwards autorun_globals->project_commands = g_slist_prepend(autorun_globals->project_commands, cmd); } else { // free the command @@ -172,7 +184,7 @@ void load_projectdefs(GKeyFile* config) { } } } - // flip it around + // flip the GSList around autorun_globals->project_commands = g_slist_reverse(autorun_globals->project_commands); } else { g_free(gerr); diff --git a/autorun/src/menu.c b/autorun/src/menu.c index 6191a43b49..28695b8b86 100644 --- a/autorun/src/menu.c +++ b/autorun/src/menu.c @@ -20,7 +20,6 @@ #include "autorun.h" #include "menu.h" -#include "utils.h" typedef struct { GtkWidget* menu; @@ -50,16 +49,14 @@ static void autorun_menu_reload_cb(G_GNUC_UNUSED GtkMenuItem* menuitem, G_GNUC_U g_key_file_free(config); } -/** Setup the workbench menu. - * - **/ +/* initialize the Auto-run Tool menu */ gboolean menu_init(void) { - /* Create menu and root item/label */ + // Create menu and root item/label menu_data.menu = gtk_menu_new(); menu_data.root_item = gtk_menu_item_new_with_label("Auto-run"); gtk_widget_show(menu_data.root_item); - /* Create new menu item "Reload" */ + // Create new menu item "Reload" GtkWidget* icon = gtk_image_new_from_icon_name("view-refresh", GTK_ICON_SIZE_MENU); /* * deprecated since gtk3.10, but it looks like crap using @@ -78,9 +75,7 @@ gboolean menu_init(void) { return TRUE; } -/** Cleanup menu data/mem. - * - **/ +/* Gtk widget cleanup for Auto-run Tool menu */ void menu_cleanup(void) { gtk_widget_destroy(GTK_WIDGET(menu_data.item_reload)); menu_data.item_reload = NULL; diff --git a/autorun/src/menu.h b/autorun/src/menu.h index 4d1d0f45b7..a7650df3c5 100644 --- a/autorun/src/menu.h +++ b/autorun/src/menu.h @@ -19,6 +19,8 @@ #ifndef __AUTORUN_MENU_H__ #define __AUTORUN_MENU_H__ +#include + gboolean menu_init(void); void menu_cleanup(void); diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index 6a3e55ad66..506f4e39dc 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -23,7 +23,6 @@ #include "menu.h" #include "project.h" #include "spawn.h" -#include "utils.h" /* Handler to read any Project declared Auto-run configs */ static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data) { @@ -32,7 +31,6 @@ static void on_project_open(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC } } -// this handler is currently disconnected due to geany/geany#4603 /* Handler to read any Project declared Auto-run configs */ static void on_project_save(G_GNUC_UNUSED GObject* obj, GKeyFile* config, G_GNUC_UNUSED gpointer user_data) { // write notebook to .geany keyfile @@ -49,7 +47,7 @@ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer autorun_globals->project_commands = NULL; } -/* Handler to run any applicable Auto-run configs after a write*/ +/* Handler to run any applicable Auto-run configs after a write */ static void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { // make sure we update the projectdefs if the config is dirty if (autorun_globals->dirtybit) { @@ -76,7 +74,7 @@ static void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_U dispatch_run_async(doc); } -/* Handler to run any applicable Auto-run configs before a write*/ +/* Handler to run any applicable Auto-run configs before a write */ static void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { // make sure we update the projectdefs if the config is dirty if (autorun_globals->dirtybit) { @@ -104,18 +102,24 @@ static void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G // no point in ui_progress_bar here, as // sync processes lock Geany up until they finish } + +/* callback to populate Project Auto-run tab for current file type. */ static void on_project_dialog_open(G_GNUC_UNUSED GObject* obj, GtkWidget* notebook, G_GNUC_UNUSED gpointer user_data) { - // populate notebook for current file type. + // attach to the Project Properties if needed project_show_properties_tab(notebook); } + +/* callback to refresh Auto-run configs after Project save */ static void on_project_dialog_confirm(G_GNUC_UNUSED GObject* obj, GtkWidget* notebook, G_GNUC_UNUSED gpointer user_data) { // mark the config for reload autorun_globals->dirtybit = TRUE; // hide from the Build menu project_hide_properties_tab(notebook); } +/* callback to detach Auto-run tab from Project + * workaround for geany/geany#4606 */ static void on_project_dialog_close(G_GNUC_UNUSED GObject* obj, GtkWidget* notebook, G_GNUC_UNUSED gpointer user_data) { - // hide from the Build menu + // detach from the Project Properties project_hide_properties_tab(notebook); } @@ -134,7 +138,7 @@ PluginCallback plugin_callbacks[] = { // clang-format on /* Bring up plugin and load filetypes.FILE that exist for Auto-run */ -static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { +static gboolean autorun_init(GeanyPlugin* plugin, G_GNUC_UNUSED gpointer pdata) { if (!autorun_globals) { autorun_globals_init(plugin); load_filedefs(); @@ -155,7 +159,7 @@ static gboolean autorun_init(GeanyPlugin* plugin, gpointer pdata) { } /* ensure destruction of any Auto-run objects */ -static void autorun_cleanup(GeanyPlugin* plugin, gpointer pdata) { +static void autorun_cleanup(G_GNUC_UNUSED GeanyPlugin* plugin, G_GNUC_UNUSED gpointer pdata) { if (autorun_globals) { autorun_globals_free(); } diff --git a/autorun/src/project.c b/autorun/src/project.c index abdfa1ab31..a63568b95b 100644 --- a/autorun/src/project.c +++ b/autorun/src/project.c @@ -21,6 +21,7 @@ #include "project.h" #include "utils.h" +// container for GtkWidget references typedef struct { GtkWidget* notebook; GtkWidget* tab; @@ -31,6 +32,8 @@ typedef struct { } AUTORUN_PROJECT_DATA; static AUTORUN_PROJECT_DATA project_data; +/* callback to save changes to the Project to disk + * config keyfile provided by Geany */ void project_save_properties_tab(GKeyFile* config) { // grab the current filetype if (!document_get_current()) { @@ -89,11 +92,15 @@ void project_save_properties_tab(GKeyFile* config) { g_free(os_wd_key); } +/* ensure that the Project>Auto-run is hidden when not in use + * notebook a reference to Project Properties from Geany */ void project_hide_properties_tab(GtkWidget* notebook) { gtk_notebook_detach_tab(GTK_NOTEBOOK(notebook), project_data.tab); project_data.notebook = NULL; } +/* ensure that the Project>Auto-run is shown when needed + * notebook a reference to Project Properties from Geany */ void project_show_properties_tab(GtkWidget* notebook) { // grab the current filetype if (g_strcmp0("None", document_get_current()->file_type->name) == 0 || !autorun_globals->data->app->project) { @@ -157,6 +164,7 @@ void project_show_properties_tab(GtkWidget* notebook) { g_key_file_free(config); } +/* Gtk teardown for Auto-run tab */ void project_properties_tab_cleanup(void) { gtk_widget_destroy(GTK_WIDGET(project_data.before_save_command)); project_data.before_save_command = NULL; @@ -173,7 +181,9 @@ void project_properties_tab_cleanup(void) { project_data.notebook = NULL; } +/* Gtk setup for Auto-run tab */ void project_properties_tab_init(void) { + // do we need to build a tab? if (project_data.before_save_command) { // this smells familiar, lets not. return; @@ -197,10 +207,10 @@ void project_properties_tab_init(void) { gtk_size_group_add_widget(size_group, label); project_data.before_save_command = gtk_entry_new(); ui_entry_add_clear_icon(GTK_ENTRY(project_data.before_save_command)); - gtk_widget_set_tooltip_text(project_data.before_save_command, - _("supports replacements: ") _("%f = filename, ") _("%a = filename with path, ") _("%d = path, ") _("%p = project directory")); - - // gtk_entry_set_text(GTK_ENTRY(e->before_save_command), str); + gtk_widget_set_tooltip_text( + project_data.before_save_command, + _("supports replacements: %f = filename, %a = filename with path, %d = path, %p = project directory") + ); ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0); @@ -212,8 +222,10 @@ void project_properties_tab_init(void) { gtk_size_group_add_widget(size_group, label); project_data.before_save_workdir = gtk_entry_new(); ui_entry_add_clear_icon(GTK_ENTRY(project_data.before_save_workdir)); - gtk_widget_set_tooltip_text(project_data.before_save_workdir, - _("supports replacements: ") _("%f = filename, ") _("%a = filename with path, ") _("%d = path, ") _("%p = project directory")); + gtk_widget_set_tooltip_text( + project_data.before_save_workdir, + _("supports replacements: %f = filename, %a = filename with path, %d = path, %p = project directory") + ); ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0); @@ -228,8 +240,10 @@ void project_properties_tab_init(void) { gtk_size_group_add_widget(size_group, label); project_data.on_save_command = gtk_entry_new(); ui_entry_add_clear_icon(GTK_ENTRY(project_data.on_save_command)); - gtk_widget_set_tooltip_text(project_data.on_save_command, - _("supports replacements: ") _("%f = filename, ") _("%a = filename with path, ") _("%d = path, ") _("%p = project directory")); + gtk_widget_set_tooltip_text( + project_data.on_save_command, + _("supports replacements: %f = filename, %a = filename with path, %d = path, %p = project directory") + ); ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(ebox), project_data.on_save_command, TRUE, TRUE, 0); @@ -239,8 +253,10 @@ void project_properties_tab_init(void) { gtk_size_group_add_widget(size_group, label); project_data.on_save_workdir = gtk_entry_new(); ui_entry_add_clear_icon(GTK_ENTRY(project_data.on_save_workdir)); - gtk_widget_set_tooltip_text(project_data.on_save_workdir, - _("supports replacements: ") _("%f = filename, ") _("%a = filename with path, ") _("%d = path, ") _("%p = project directory")); + gtk_widget_set_tooltip_text( + project_data.on_save_workdir, + _("supports replacements: %f = filename, %a = filename with path, %d = path, %p = project directory") + ); ebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(ebox), project_data.on_save_workdir, TRUE, TRUE, 0); @@ -254,5 +270,6 @@ void project_properties_tab_init(void) { hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 6); + // grab a reference to the new tab, so we can detach/retach later. project_data.tab = GTK_WIDGET(g_object_ref(hbox)); } diff --git a/autorun/src/project.h b/autorun/src/project.h index b54602fd35..0d0fcaba7c 100644 --- a/autorun/src/project.h +++ b/autorun/src/project.h @@ -21,8 +21,6 @@ #include -#include "autorun.h" - void project_save_properties_tab(GKeyFile* config); void project_show_properties_tab(GtkWidget* notebook); void project_hide_properties_tab(GtkWidget* notebook); diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 403e18c6ba..1862f56973 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -22,23 +22,19 @@ #include "autorun.h" #include "spawn.h" #include "utils.h" -// -// if (spawn_async(working_dir, locale_term_cmd, NULL, NULL, &(run_info[cmdindex].pid), &error)) { -// g_child_watch_add(run_info[cmdindex].pid, (GChildWatchFunc)run_exit_cb, (gpointer) & (run_info[cmdindex])); -// build_menu_update(doc); -//} - -/* - * Build the @interceptor from @autorun_globals, and return the parsed command to @command - * If the parsed command creates a tempfile, return it in cmd->interceptor - * */ +// container for spawn_with_callbacks IO typedef struct { GString* stdout; GString* stderr; GeanyDocument* doc; } AUTORUN_ASYNC_DATA; + +/* prep a command for running + * interceptor which command type to run + * doc which doc to run against + * command the returned prepped commands, owned by the caller */ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** command) { GSList* command_list = NULL; @@ -91,14 +87,12 @@ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** GFile* tmpfile = NULL; GFileIOStream* iostream = NULL; gboolean success = FALSE; - gchar* before_contents; - - // we need scintilla in a buffer regardless of %a if (g_strcmp0(interceptor, "BS") == 0 && g_strrstr(cmd->command, "%a")) { //%a means something special tmpfile = g_file_new_tmp("ar.aXXXXXX", &iostream, NULL); if (tmpfile) { + gchar* before_contents; // dont need the iostream, but gtk wont make a temp without it. g_io_stream_close((GIOStream*)iostream, NULL, NULL); target_file = g_file_get_path(tmpfile); @@ -170,20 +164,31 @@ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** } } +/* spawn_with_callbacks IO callback + * string the value provided by the IO + * condition what type of IO this was + * data the associated data for this callback */ static void stdio_cb(GString* string, GIOCondition condition, gpointer data) { // dup the string to data. if (condition == G_IO_IN || condition == G_IO_PRI) { g_string_append((GString*)data, string->str); } } - -static void stdioend_cb(GPid pid, gint wait_status, gpointer user_data) { +/* spawn_with_callbacks child exit IO callback + * pid child pid + * wait_status exit type + * user_data the associated data for this child */ +static void stdioend_cb(G_GNUC_UNUSED GPid pid, G_GNUC_UNUSED gint wait_status, gpointer user_data) { + // spawn_with_callbacks runs this last and closes the child AUTORUN_ASYNC_DATA* exit_data = (AUTORUN_ASYNC_DATA*)user_data; + // our stdout/stderr is complete. Display it. parse_output(exit_data->stdout->str); g_string_free(exit_data->stdout, TRUE); parse_output(exit_data->stderr->str); g_string_free(exit_data->stderr, TRUE); + // if all the children have stopped if (--autorun_globals->children < 1) { + // flip the ui_progress_bar back ui_progress_bar_stop(); ui_set_statusbar(FALSE, _("Auto-run finished %s"), exit_data->doc->file_name); autorun_globals->children = 0; @@ -191,14 +196,15 @@ static void stdioend_cb(GPid pid, gint wait_status, gpointer user_data) { g_free(exit_data); } -// prefer running commands async, so the editor plays nice +/* function to dispatch a command asynchronously + * doc the GeanyDocument to run against */ void dispatch_run_async(GeanyDocument* doc) { GSList* runnables = NULL; + // get commands in scope parse_command("OS", doc, &runnables); GSList* runnable; ui_progress_bar_start("Auto-run"); foreach_slist(runnable, runnables) { - // convenience AUTORUN_CMD* current_cmd = (AUTORUN_CMD*)runnable->data; gchar** env; @@ -216,9 +222,10 @@ void dispatch_run_async(GeanyDocument* doc) { end_data->doc = doc; GPid* child_pid = NULL; gboolean success = FALSE; + // set the the ui_statusbar and record a child ui_set_statusbar(FALSE, _("Auto-run running %s"), current_cmd->command); - ++autorun_globals->children; + success = spawn_with_callbacks(current_cmd->working_dir, current_cmd->command, NULL, env, SPAWN_ASYNC | SPAWN_LINE_BUFFERED, NULL, NULL, stdio_cb, stdout_data, 0, stdio_cb, stderr_data, 0, stdioend_cb, end_data, child_pid, &error); @@ -226,11 +233,13 @@ void dispatch_run_async(GeanyDocument* doc) { g_strfreev(env); if (!success) { + // if that was the only child and it failed if (--autorun_globals->children < 1) { + // flip the ui_progress_bar back ui_progress_bar_stop(); autorun_globals->children = 0; } - // refused to run + // update status with refused to run msgwin_status_add(_("Command failed with %s"), error->message); g_error_free(error); } @@ -238,13 +247,15 @@ void dispatch_run_async(GeanyDocument* doc) { autorun_cmd_list_free(runnables); } -// this should only ever be for interceptor=BS, as it locks up the editor +/* function to dispatch a command synchronously + * doc the GeanyDocument to run against + * + * only intended for intercept type BS, as it locks up the editor */ void dispatch_run_sync(GeanyDocument* doc) { GSList* runnables = NULL; parse_command("BS", doc, &runnables); GSList* runnable; foreach_slist(runnable, runnables) { - // convienience AUTORUN_CMD* current_cmd = (AUTORUN_CMD*)runnable->data; // pull the tempfile from the overloaded cmd->interceptor gboolean has_tmpfile = (current_cmd->interceptor) ? TRUE : FALSE; @@ -274,17 +285,17 @@ void dispatch_run_sync(GeanyDocument* doc) { g_free(status_msg); // runnit gboolean success = spawn_sync(current_cmd->working_dir, current_cmd->command, NULL, env, stdin_data, stdout_data, stderr_data, &ret, &error); + + // cleanup anything we dont need in processing. if (stdin_data) { g_free(stdin_data); } - // cleanup anything we dont need in processing. g_strfreev(env); if (success && ret == 0) { // ran and didn't throw an error // do we need to update the editor? - // maybe. - // could be in tmpfile, stdout + // could be in tmpfile, or stdout gchar* read_tmp = NULL; if (has_tmpfile) { g_file_get_contents(tmpfile_path, &read_tmp, NULL, NULL); @@ -292,9 +303,9 @@ void dispatch_run_sync(GeanyDocument* doc) { gint cursor_at = sci_get_current_position(doc->editor->sci); // check for the stupid case if (strlen(stdout_data->str) > 0 && has_tmpfile && g_strcmp0(read_tmp, before_contents) != 0 && g_strcmp0(stdout_data->str, read_tmp) != 0) { - // if there are changes to the tempfile AND stdout - // this command is stupid. there is no way to know - // which output is the output to output. + /* if there are changes to the tempfile AND stdout + * this command is stupid. there is no way to know + * which output is the output to output. */ msgwin_status_add(_("Command failed with inconsistent output.")); } else if (strlen(stdout_data->str) > 0) { sci_set_text(doc->editor->sci, stdout_data->str); @@ -316,13 +327,14 @@ void dispatch_run_sync(GeanyDocument* doc) { g_error_free(error); } - // GUI output formatting? - // BS stdout goes to the scintilla editor - // stderr + /* GUI output formatting + * stdout goes to the scintilla editor + * only stderr needs processing */ if (strlen(stderr_data->str) > 0) { parse_output(stderr_data->str); } + // cleanup if (has_tmpfile) { g_unlink(tmpfile_path); } diff --git a/autorun/src/spawn.h b/autorun/src/spawn.h index 22b5edbe72..a5316ec52a 100644 --- a/autorun/src/spawn.h +++ b/autorun/src/spawn.h @@ -21,8 +21,6 @@ #include -#include "autorun.h" - void dispatch_run_async(GeanyDocument* doc); void dispatch_run_sync(GeanyDocument* doc); diff --git a/autorun/src/utils.c b/autorun/src/utils.c index f371aaa9ea..eb6f189ab5 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -22,9 +22,9 @@ #include "utils.h" /* Read a filedef or keyfile for Intercept Actions - * @param action key (should end with CM) - * @param key_file to get remaining values from - * */ + * key which interceptor to read (should end with CM) + * key_file to get remaining values from + * cmd AUTORUN_CMD struct to store data to */ void parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd) { // key should be a (filetype_)action_number_flag string if (!key || !cmd) { @@ -112,8 +112,8 @@ void parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd) { g_strfreev(tokens); } -// split stdout and stderr into -// blocks and shove them into the correct tabs +/* split stdout and stderr into blocks and format them for Compiler + * input the current data to parse */ void parse_output(gchar* input) { // make sure we're using utf8 gchar* output = encodings_convert_to_utf8(input, -1, NULL); @@ -142,11 +142,11 @@ void parse_output(gchar* input) { g_free(base_test); } } - // make known filenames clicky (perhaps) - // the compiler requires each newline to be its own message - // or the compiler clicky breaks. - // the compiler requires each clicky to be RED - // or the compiler clicky breaks. + /* make known filenames clicky (perhaps) + * the compiler requires each newline to be its own message + * or the compiler clicky breaks. + * the compiler requires each clicky to be RED + * or the compiler clicky breaks. */ if (found) { msgwin_compiler_add_string(COLOR_RED, *line); } else { From ecb8948276418a7eef897b5adb516181a5c72706 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Mon, 29 Jun 2026 12:34:21 -0400 Subject: [PATCH 23/30] Auto-run update POTFILES with new dependents --- po/POTFILES.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/po/POTFILES.in b/po/POTFILES.in index d9eea99ce7..071e830915 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -21,6 +21,9 @@ automark/src/automark.c # Autorun autorun/src/plugin_main.c +autorun/src/menu.c +autorun/src/project.c +autorun/src/spawn.c # codenav codenav/src/codenavigation.c From 06103a42fd116176a0a295f3d5a279f23751507b Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:21:09 -0400 Subject: [PATCH 24/30] geany/geany-plugins#1584 --- autorun/src/spawn.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 1862f56973..1207f7925c 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -115,11 +115,15 @@ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** gchar* target_name = g_path_get_basename(target_file); GString* command_str = g_string_new(cmd->command); + // escape to defend against shell interpolation, if possible + gchar* target_name_quote =g_shell_quote(target_name); // replace %f - g_string_replace(command_str, "%f", target_name, 0); - // replace %a - g_string_replace(command_str, "%a", target_file, 0); + g_string_replace(command_str, "%f", target_name_quote, 0); + g_free(target_name_quote); + // replace %a + g_string_replace(command_str, "%a", target_file, 0);\ + GString* working_dir_str = g_string_new(cmd->working_dir); // replace %d g_string_replace(command_str, "%d", target_dir, 0); @@ -128,7 +132,7 @@ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** // replace %p g_string_replace(command_str, "%p", target_projdir, 0); g_string_replace(working_dir_str, "%p", target_projdir, 0); - + if (success) { success = spawn_check_command(command_str->str, TRUE, NULL); } From e214162aa83bcd9fcd26ac867a555052caa196cf Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Fri, 3 Jul 2026 07:45:01 -0400 Subject: [PATCH 25/30] Revert "geany/geany-plugins#1584" g_shell_quote is not portable This reverts commit 06103a42fd116176a0a295f3d5a279f23751507b. --- autorun/src/spawn.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 1207f7925c..1862f56973 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -115,15 +115,11 @@ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** gchar* target_name = g_path_get_basename(target_file); GString* command_str = g_string_new(cmd->command); - // escape to defend against shell interpolation, if possible - gchar* target_name_quote =g_shell_quote(target_name); // replace %f - g_string_replace(command_str, "%f", target_name_quote, 0); - g_free(target_name_quote); - + g_string_replace(command_str, "%f", target_name, 0); // replace %a - g_string_replace(command_str, "%a", target_file, 0);\ - + g_string_replace(command_str, "%a", target_file, 0); + GString* working_dir_str = g_string_new(cmd->working_dir); // replace %d g_string_replace(command_str, "%d", target_dir, 0); @@ -132,7 +128,7 @@ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** // replace %p g_string_replace(command_str, "%p", target_projdir, 0); g_string_replace(working_dir_str, "%p", target_projdir, 0); - + if (success) { success = spawn_check_command(command_str->str, TRUE, NULL); } From 0c328909b8d751fc915b625b23c08a636b773fc5 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:52:05 -0400 Subject: [PATCH 26/30] improve filename escaping update README with notes about filenames this commit is expected long term approach for geany/geany-plugins#1584 for Auto-run --- autorun/README | 32 +++++++++++++++++++++++--------- autorun/src/spawn.c | 21 ++++++++++++++++----- autorun/src/utils.c | 33 +++++++++++++++++++++++++++++++++ autorun/src/utils.h | 1 + 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/autorun/README b/autorun/README index a4d03b04bc..383f1dcdff 100644 --- a/autorun/README +++ b/autorun/README @@ -39,16 +39,19 @@ Place the %a replacement in the BS target if you know the CM does not honor STDI In that case Auto-run will dump the contents of Scintilla to a temp file. clang-format, which reads stdin, would like to the filename for formatting rules. -e.g. clang-format --assume-filename=%f +e.g. clang-format --assume-filename='%f' cmake-format, which does not read stdin -e.g. cmake-format %a +e.g. cmake-format '%a' +Ensure you're setting the correct quoting for arguments. Windows expects " quoted filenames +Linux expects ' quoted filenames Manually setting Auto-run configs --------------------------------- You can manually set Project level Auto-run configs under the keyfile heading [autorun], using https://wiki.geany.org/howtos/configurebuildmenu#build-menu style entries. +The following is RFC2119 compliant. FILETYPE_IA_NN_TA @@ -64,17 +67,19 @@ NN is a two digit number allowing for multiple tasks for a given entry. TA(Target Action) = (CM|WD) * CM is the command to run * WD is the working directory to run from. +CM SHOULD be quoted appropriate to the OS' expectations. +WD MUST NOT be quoted on Linux. C_BS_00_CM = formatter C_BS_00_WD = %d -C_OS_00_CM = linter %a +C_OS_00_CM = linter '%a' C_OS_00_WD = %p #Run formatter before file write, and then linter on the newly written %d/%f from %p #when attempting to save a C file type. -C++_OS_00_CM = cppcheck %f --output-file=%p/cppcheck.log +C++_OS_00_CM = cppcheck '%f' --output-file='%p/cppcheck.log' C++_OS_00_WD = %d -C++_OS_01_CM = clang-tidy -p %p --format-style=file %f +C++_OS_01_CM = clang-tidy -p '%p' --format-style=file '%f' C++_OS_01_WD = %p #Run cppcheck on %f from %d, and clang-tidy on %f from %p after file write when saving a C++ file type. @@ -89,7 +94,7 @@ where IA = (BS|OS), NN = (00|01|...), TA=(CM|WD) BS_00_CM = formatter -OS_00_CM = linter %f +OS_00_CM = linter '%f' When Auto-run loads, it will scan all User config dir filedefs/filetypes.* @@ -98,9 +103,9 @@ User config dir filedefs settings will be overridden by any Project settings. Example: (userconfigdir)/filedefs/filetypes.c -BS_00_CM = formatter %f +BS_00_CM = formatter '%f' (userprojectdir)/project.geany -CBS_00_CM = fancyformatter %f +CBS_00_CM = fancyformatter '%f' Autorun will run fancyformatter before save for C files as long as project.geany is open. @@ -111,9 +116,14 @@ Known issues Auto-run runs before-save targets synchronously. This may cause Geany to freeze. Move slow commands to on-save to run asynchronously. -geany/geany#4603 impacts reloading the Project configs in realtime. +geany/geany#4603 may impact reloading the Project configs in realtime. Tools>Auto-run>Reload will force a reload. Compiler won't follow links unless at least one Build command has run. + https://github.com/geany/geany/issues/4609 +Auto-run attempts a best effort to make filenames safe for the OS, but +some external tools do not correctly handle file and directory names on Linux + https://gitlab.kitware.com/cmake/cmake/-/work_items/27918 + https://github.com/llvm/llvm-project/issues/207396 License ======= @@ -151,3 +161,7 @@ This is a short release history showing the major changes: **0.1:** Created plugin + Implemented filetypes and project config readers + Implemented sync and async launch for handlers + Implemented Project menu, and Tool menu + Tested against Geany 2.1 and 2.2 diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 1862f56973..56eb456269 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -111,22 +111,30 @@ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** target_file = utils_get_locale_from_utf8(doc->file_name); } - // do the replacement bits + // prep for escape and replace gchar* target_name = g_path_get_basename(target_file); + // OS specific escape handling. + gchar* escaped_name = escape_filename(target_name); + g_free(target_name); + gchar* escaped_file = escape_filename(target_file); + gchar* escaped_dir = escape_filename(target_dir); + gchar* escaped_projdir = escape_filename(target_projdir); + + // do the replacement bits GString* command_str = g_string_new(cmd->command); // replace %f - g_string_replace(command_str, "%f", target_name, 0); + g_string_replace(command_str, "%f", escaped_name, 0); // replace %a - g_string_replace(command_str, "%a", target_file, 0); + g_string_replace(command_str, "%a", escaped_file, 0); GString* working_dir_str = g_string_new(cmd->working_dir); // replace %d - g_string_replace(command_str, "%d", target_dir, 0); + g_string_replace(command_str, "%d", escaped_dir, 0); g_string_replace(working_dir_str, "%d", target_dir, 0); // replace %p - g_string_replace(command_str, "%p", target_projdir, 0); + g_string_replace(command_str, "%p", escaped_projdir, 0); g_string_replace(working_dir_str, "%p", target_projdir, 0); if (success) { @@ -151,11 +159,14 @@ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** } // cleanup g_free(target_dir); + g_free(escaped_dir); g_free(target_projdir); + g_free(escaped_projdir); if (tmpfile) { g_object_unref(tmpfile); } g_free(target_file); + g_free(escaped_file); } // cleanup // autorun_cmd_list_free(command_list); diff --git a/autorun/src/utils.c b/autorun/src/utils.c index eb6f189ab5..58a4ad3f4a 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -160,3 +160,36 @@ void parse_output(gchar* input) { g_free(output); } } +/* attempt to escape a filename or directory so it is safe inside of + * a platform specific quote + * the caller owns the returned string */ +gchar* escape_filename(gchar* filename) { + GString* filename_str = g_string_new(filename); +#ifdef G_OS_WIN32 + /*the goal is that the output from this function should work inside of a + double-quoted execve argument. + * From https://www.robvanderwoude.com/escapechars.php*/ + // % %% + g_string_replace(filename_str, "%", "%%", 0); + // ^ ^^ May not always be required in doublequoted strings, but it won't hurt + g_string_replace(filename_str, "^", "^^", 0); + // & ^& + g_string_replace(filename_str, "&", "^&", 0); + // < ^< + g_string_replace(filename_str, "<", "^<", 0); + // > ^> + g_string_replace(filename_str, ">", "^>", 0); + // | ^| + g_string_replace(filename_str, "|", "^|", 0); +#else + /*the goal is that the output from this function should work inside of a + single quoted execve argument. + File: `'~!@#$%^&*()_-+={}[] \ |:;"<>?,.c (created through GUI) + ls suggested + '`'\''~!@#$%^&*()_-+={}[] \ |:;"<>?,.c' + */ + // ' '\'' + g_string_replace(filename_str, "'", "'\\''", 0); +#endif + return g_string_free_and_steal(filename_str); +} diff --git a/autorun/src/utils.h b/autorun/src/utils.h index 11d4faf468..f9991f63a0 100644 --- a/autorun/src/utils.h +++ b/autorun/src/utils.h @@ -24,5 +24,6 @@ void parse_intercept_actions(gchar* key, GKeyFile* key_file, AUTORUN_CMD* cmd); void parse_output(gchar* output); +gchar* escape_filename(gchar* filename); #endif From 66525f3c1732d288733e2740a8a1df4de0a44b5d Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:17:25 -0400 Subject: [PATCH 27/30] fix xcompiler reserved stderr and stdout fix used a uint instead of a guint --- autorun/src/spawn.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/autorun/src/spawn.c b/autorun/src/spawn.c index 56eb456269..f9fa0780d7 100644 --- a/autorun/src/spawn.c +++ b/autorun/src/spawn.c @@ -25,8 +25,8 @@ // container for spawn_with_callbacks IO typedef struct { - GString* stdout; - GString* stderr; + GString* stdout_str; + GString* stderr_str; GeanyDocument* doc; } AUTORUN_ASYNC_DATA; @@ -62,7 +62,7 @@ static void parse_command(const gchar* interceptor, GeanyDocument* doc, GSList** command_order[cmd->order] = elem; } - for (uint i = 0; i < g_slist_length(command_list); i++) { + for (guint i = 0; i < g_slist_length(command_list); i++) { if (!command_order[i]) { break; // stop on the first null; } @@ -193,10 +193,10 @@ static void stdioend_cb(G_GNUC_UNUSED GPid pid, G_GNUC_UNUSED gint wait_status, // spawn_with_callbacks runs this last and closes the child AUTORUN_ASYNC_DATA* exit_data = (AUTORUN_ASYNC_DATA*)user_data; // our stdout/stderr is complete. Display it. - parse_output(exit_data->stdout->str); - g_string_free(exit_data->stdout, TRUE); - parse_output(exit_data->stderr->str); - g_string_free(exit_data->stderr, TRUE); + parse_output(exit_data->stdout_str->str); + g_string_free(exit_data->stdout_str, TRUE); + parse_output(exit_data->stderr_str->str); + g_string_free(exit_data->stderr_str, TRUE); // if all the children have stopped if (--autorun_globals->children < 1) { // flip the ui_progress_bar back @@ -228,8 +228,8 @@ void dispatch_run_async(GeanyDocument* doc) { GString* stdout_data = g_string_new(NULL); GString* stderr_data = g_string_new(NULL); AUTORUN_ASYNC_DATA* end_data = g_new0(AUTORUN_ASYNC_DATA, 1); - end_data->stdout = stdout_data; - end_data->stderr = stderr_data; + end_data->stdout_str = stdout_data; + end_data->stderr_str = stderr_data; end_data->doc = doc; GPid* child_pid = NULL; gboolean success = FALSE; From c9e74f52bfb7bbcdf5310c31a3c9a525c2da66ab Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Wed, 8 Jul 2026 09:27:27 -0400 Subject: [PATCH 28/30] fix geany-plugins#1588 --- autorun/src/autorun.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/autorun/src/autorun.c b/autorun/src/autorun.c index f5f343e076..35c4244816 100644 --- a/autorun/src/autorun.c +++ b/autorun/src/autorun.c @@ -155,8 +155,7 @@ void load_filedefs(void) { } // cleanup - g_slist_foreach(file_list, (GFunc)g_free, NULL); - g_slist_free(file_list); + g_slist_free_full(file_list, (GDestroyNotify)g_free); g_free(filedef_path); } From 8cac2962e60f65dad89615e48e3068b0d31c5c34 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Sun, 26 Jul 2026 09:47:40 -0400 Subject: [PATCH 29/30] FIX crash if no project is open --- autorun/src/menu.c | 14 ++++++++------ autorun/src/plugin_main.c | 30 +++++++++++++++++------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/autorun/src/menu.c b/autorun/src/menu.c index 28695b8b86..e421c0825f 100644 --- a/autorun/src/menu.c +++ b/autorun/src/menu.c @@ -40,13 +40,15 @@ static void autorun_menu_reload_cb(G_GNUC_UNUSED GtkMenuItem* menuitem, G_GNUC_U // load filedefs load_filedefs(); // load project - GKeyFile* config = g_key_file_new(); - gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); - if (ret) { - msgwin_status_add(_("Reloading Auto-run project configs")); - load_projectdefs(config); + if(autorun_globals->data->app->project && autorun_globals->data->app->project->file_name){ + GKeyFile* config = g_key_file_new(); + gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + if (ret) { + msgwin_status_add(_("Reloading Auto-run project configs")); + load_projectdefs(config); + } + g_key_file_free(config); } - g_key_file_free(config); } /* initialize the Auto-run Tool menu */ diff --git a/autorun/src/plugin_main.c b/autorun/src/plugin_main.c index 506f4e39dc..3bcda55a08 100644 --- a/autorun/src/plugin_main.c +++ b/autorun/src/plugin_main.c @@ -51,13 +51,15 @@ static void on_project_close(G_GNUC_UNUSED GObject* obj, G_GNUC_UNUSED gpointer static void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { // make sure we update the projectdefs if the config is dirty if (autorun_globals->dirtybit) { - // reload the projectdefs - GKeyFile* config = g_key_file_new(); - gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); - if (ret) { - load_projectdefs(config); + if(autorun_globals->data->app->project && autorun_globals->data->app->project->file_name ){ + // reload the projectdefs + GKeyFile* config = g_key_file_new(); + gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + if (ret) { + load_projectdefs(config); + } + g_key_file_free(config); } - g_key_file_free(config); autorun_globals->dirtybit = FALSE; } @@ -78,13 +80,15 @@ static void on_doc_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_U static void on_doc_before_save(G_GNUC_UNUSED GObject* obj, GeanyDocument* doc, G_GNUC_UNUSED gpointer user_data) { // make sure we update the projectdefs if the config is dirty if (autorun_globals->dirtybit) { - // reload the projectdefs - GKeyFile* config = g_key_file_new(); - gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); - if (ret) { - load_projectdefs(config); + if(autorun_globals->data->app->project && autorun_globals->data->app->project->file_name ){ + // reload the projectdefs + GKeyFile* config = g_key_file_new(); + gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); + if (ret) { + load_projectdefs(config); + } + g_key_file_free(config); } - g_key_file_free(config); autorun_globals->dirtybit = FALSE; } @@ -143,7 +147,7 @@ static gboolean autorun_init(GeanyPlugin* plugin, G_GNUC_UNUSED gpointer pdata) autorun_globals_init(plugin); load_filedefs(); - if (autorun_globals->data->app->project) { + if (autorun_globals->data->app->project && autorun_globals->data->app->project->file_name) { // if initialized while a project is already open, manually ingest the project GKeyFile* config = g_key_file_new(); gboolean ret = g_key_file_load_from_file(config, autorun_globals->data->app->project->file_name, G_KEY_FILE_NONE, NULL); From ef6dc10ffdc33679528b6e056e43dc3d205e6007 Mon Sep 17 00:00:00 2001 From: WrapEarnPass <293701378+WrapEarnPass@users.noreply.github.com> Date: Sat, 1 Aug 2026 11:25:37 -0400 Subject: [PATCH 30/30] Workaround/Fix for geany#4635 --- autorun/src/utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autorun/src/utils.c b/autorun/src/utils.c index 58a4ad3f4a..9e2daf4c1b 100644 --- a/autorun/src/utils.c +++ b/autorun/src/utils.c @@ -118,7 +118,7 @@ void parse_output(gchar* input) { // make sure we're using utf8 gchar* output = encodings_convert_to_utf8(input, -1, NULL); // split the output on \n - gchar** lines = g_regex_split_simple("\\n", output, 0, G_REGEX_MATCH_NEWLINE_ANY); + gchar** lines = g_regex_split_simple("\R", output, 0, G_REGEX_MATCH_NEWLINE_ANYCRLF); gchar** line = NULL; // foreach line, if it contains a filename, it is red.