From git-noreply at xxxxx Sun Oct 4 09:00:29 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 09:00:29 -0000 Subject: [geany/talks] b29746: Adding a stub for English talk 'A Short introduction' Message-ID: <20151004092020.54A1E5C3DAA@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 09:00:29 UTC Commit: b29746ebfbaab7860edac0d095130341cee56b70 https://github.com/geany/talks/commit/b29746ebfbaab7860edac0d095130341cee56b70 Log Message: ----------- Adding a stub for English talk 'A Short introduction' Modified Paths: -------------- .gitmodules en/A_short_introduction/reveal.js Modified: .gitmodules 3 lines changed, 3 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,3 @@ +[submodule "en/A_short_introduction/reveal.js"] + path = en/A_short_introduction/reveal.js + url = https://github.com/hakimel/reveal.js.git Modified: en/A_short_introduction/reveal.js 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1 @@ +Subproject commit 01c55d4cf6ff60772be8a86320c4a5bcd7719a05 -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 09:09:42 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 09:09:42 -0000 Subject: [geany/talks] 91505d: EN: Starting new slides for an short introduction of Geany Message-ID: <20151004092020.CB63A5C3DB9@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 09:09:42 UTC Commit: 91505dd9ea1fc1418c007f97eb0c750756a51256 https://github.com/geany/talks/commit/91505dd9ea1fc1418c007f97eb0c750756a51256 Log Message: ----------- EN: Starting new slides for an short introduction of Geany Modified Paths: -------------- en/A_short_introduction/index.html Modified: en/A_short_introduction/index.html 97 lines changed, 97 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,97 @@ + + + + + + + Geany, die leichtgewichtige Entwicklungsumgebung + + + + + + + + + + + + + + + + + + + + + + + +
Logo von Geany
+ +
+
+
+

Geany

+

a lightwight IDE

+ Geany-Logo +

+ by Frank Lanitz +

+

LinuxDays 2015
Prague

+
+
+
+

Topics

+
+
+
    +
  1. General overview
  2. +
  3. Some Features
  4. +
  5. Meta
  6. +
+
+
+
+ +
+ + + + + + + + -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 09:10:44 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 09:10:44 -0000 Subject: [geany/talks] 4dda60: Extend gitignore Message-ID: <20151004092021.594E15C3DAA@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 09:10:44 UTC Commit: 4dda608bc0cdaddb0d653c5c00032a3ac74279e8 https://github.com/geany/talks/commit/4dda608bc0cdaddb0d653c5c00032a3ac74279e8 Log Message: ----------- Extend gitignore Modified Paths: -------------- .gitignore Modified: .gitignore 3 lines changed, 3 insertions(+), 0 deletions(-) =================================================================== @@ -1,3 +1,6 @@ *.toc *.log *.aux +*.nav +*.out +*.snm -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 10:24:39 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 10:24:39 -0000 Subject: [geany/geany] 932e98: Update of Greek translation Message-ID: <20151004102513.5F9345C420C@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 10:24:39 UTC Commit: 932e98fa91da4b73956e73576e5c62ed8070a09d https://github.com/geany/geany/commit/932e98fa91da4b73956e73576e5c62ed8070a09d Log Message: ----------- Update of Greek translation Modified Paths: -------------- NEWS po/el.po Modified: NEWS 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -1,7 +1,7 @@ Geany 1.26 (unreleased) Internationalization - * Update translations: sv + * Update translations: sv, el Geany 1.25 (July 12, 2015) Modified: po/el.po 527 lines changed, 269 insertions(+), 258 deletions(-) =================================================================== @@ -1,23 +1,24 @@ # translation of el.po to Greek # Greek translations for geany package. -# Copyright (C) 2008-14 THE geany'S COPYRIGHT HOLDER +# Copyright (C) 2008-2015 THE geany'S COPYRIGHT HOLDER # This file is distributed under the same license as the geany package. # # Stavros Temertzidis , 2008-2014 +# Michael Misirlis , 2015 msgid "" msgstr "" "Project-Id-Version: Geany 1.25\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-06-20 11:22+0200\n" -"PO-Revision-Date: 2014-07-11 17:03+0200\n" -"Last-Translator: Frank Lanitz \n" +"POT-Creation-Date: 2015-09-23 02:36+0000\n" +"PO-Revision-Date: 2015-09-28 16:06+0200\n" +"Last-Translator: Michael Misirlis \n" "Language-Team: Greek\n" "Language: el\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.6.5\n" +"X-Generator: Poedit 1.6.10\n" #: ../geany.desktop.in.h:1 ../data/geany.glade.h:338 msgid "Geany" @@ -103,11 +104,11 @@ msgstr "_?????????" msgid "Open Selected F_ile" msgstr "??????? _??????????? ???????" -#: ../data/geany.glade.h:19 ../src/symbols.c:2367 +#: ../data/geany.glade.h:19 ../src/symbols.c:2368 msgid "Find _Usage" msgstr "?????? _??????" -#: ../data/geany.glade.h:20 ../src/symbols.c:2372 +#: ../data/geany.glade.h:20 ../src/symbols.c:2373 msgid "Find _Document Usage" msgstr "?????? ?????? _????????" @@ -1254,7 +1255,7 @@ msgstr "" "???? ????? ?? ??????? ??????? ??? ??????? ??? ???????? ???? ?????, ?? " "????????????. ?? ????? ????????????? ??? ??????." -#: ../data/geany.glade.h:253 ../src/prefs.c:1607 ../src/symbols.c:627 +#: ../data/geany.glade.h:253 ../src/prefs.c:1607 ../src/symbols.c:628 #: ../plugins/filebrowser.c:1160 msgid "Files" msgstr "??????" @@ -1408,7 +1409,7 @@ msgstr "?_?????" msgid "Keyboard shortcuts" msgstr "???????????? ???????" -#: ../data/geany.glade.h:286 ../src/plugins.c:1316 ../src/plugins.c:1351 +#: ../data/geany.glade.h:286 ../src/plugins.c:1522 ../src/plugins.c:1557 #: ../src/prefs.c:1613 msgid "Keybindings" msgstr "?????????? ???????" @@ -1611,11 +1612,10 @@ msgid "Follow path of the current file" msgstr "?????????? ?? ???????? ??? ????????? ???????" #: ../data/geany.glade.h:328 -msgid "" -"Whether to execute \\\"cd $path\\\" when you switch between opened files" +msgid "Whether to execute \"cd $path\" when you switch between opened files" msgstr "" -"??? ?? ?????????? ?? \\\"cd $path\\\" ???? ?? ??????? ???????? ?????? " -"?????????? ???????" +"??? ?? ?????????? ?? \"cd $path\" ???? ?? ??????? ???????? ?????? ?????????? " +"???????" #: ../data/geany.glade.h:329 msgid "Execute programs in the VTE" @@ -1647,7 +1647,7 @@ msgstr "" msgid "Terminal" msgstr "?????????" -#: ../data/geany.glade.h:334 ../src/prefs.c:1619 ../src/vte.c:308 +#: ../data/geany.glade.h:334 ../src/prefs.c:1619 ../src/vte.c:320 msgid "Terminal" msgstr "?????????" @@ -1814,7 +1814,7 @@ msgstr "????????? _????????" msgid "Find _Previous" msgstr "????????? _????????????" -#: ../data/geany.glade.h:376 ../src/symbols.c:2377 +#: ../data/geany.glade.h:376 ../src/symbols.c:2378 msgid "Find in F_iles..." msgstr "????????? ??? ?_?????..." @@ -1995,18 +1995,16 @@ msgid "Set Line E_ndings" msgstr "?????????? ????_?????? ???????" #: ../data/geany.glade.h:421 -#, fuzzy msgid "Convert and Set to _CR/LF (Windows)" -msgstr "????????? ??? ?????????? ?? _CR/LF (Win)" +msgstr "????????? ??? ?????????? ?? _CR/LF (Windows)" #: ../data/geany.glade.h:422 msgid "Convert and Set to _LF (Unix)" msgstr "????????? ??? ?????????? ?? _LF (Unix)" #: ../data/geany.glade.h:423 -#, fuzzy msgid "Convert and Set to CR (Classic _Mac)" -msgstr "????????? ??? ?????????? ?? CR (_Mac)" +msgstr "????????? ??? ?????????? ?? CR (?????? Mac)" #: ../data/geany.glade.h:424 ../src/keybindings.c:586 msgid "_Clone" @@ -2065,7 +2063,7 @@ msgid "_Apply Default Indentation" msgstr "_???????? ????????? ??????" #. build the code -#: ../data/geany.glade.h:438 ../src/build.c:2371 ../src/build.c:2648 +#: ../data/geany.glade.h:438 ../src/build.c:2389 ../src/build.c:2666 msgid "_Build" msgstr "_????????? (Build)" @@ -2154,7 +2152,7 @@ msgid "Filename:" msgstr "????? ???????:" #: ../data/geany.glade.h:460 ../src/project.c:169 -#: ../plugins/classbuilder.c:468 ../plugins/classbuilder.c:478 +#: ../plugins/classbuilder.c:467 ../plugins/classbuilder.c:477 msgid "Name:" msgstr "?????:" @@ -2266,7 +2264,7 @@ msgstr "?????:" #: ../src/about.c:48 msgid "" -"Copyright (c) 2005-2014\n" +"Copyright (c) 2005-2015\n" "Colomban Wendling\n" "Nick Treleaven\n" "Matthew Brush\n" @@ -2274,7 +2272,7 @@ msgid "" "Frank Lanitz\n" "All rights reserved." msgstr "" -"Copyright (c) 2005-2014\n" +"Copyright (c) 2005-2015\n" "Colomban Wendling\n" "Nick Treleaven\n" "Matthew Brush\n" @@ -2286,49 +2284,49 @@ msgstr "" msgid "About Geany" msgstr "???? Geany" -#: ../src/about.c:213 +#: ../src/about.c:212 msgid "A fast and lightweight IDE" msgstr "??????? ??? ?????? IDE" -#: ../src/about.c:235 +#: ../src/about.c:234 #, c-format msgid "(built on or after %s)" msgstr "(????????????? ??? ? ???? %s)" #. gtk_container_add(GTK_CONTAINER(info_box), cop_label); -#: ../src/about.c:267 +#: ../src/about.c:266 msgid "Info" msgstr "???????????" -#: ../src/about.c:283 +#: ../src/about.c:282 msgid "Developers" msgstr "??????????" -#: ../src/about.c:290 +#: ../src/about.c:289 msgid "maintainer" msgstr "?????????" -#: ../src/about.c:298 ../src/about.c:306 ../src/about.c:314 +#: ../src/about.c:297 ../src/about.c:305 ../src/about.c:313 msgid "developer" msgstr "??????????" -#: ../src/about.c:322 +#: ../src/about.c:321 msgid "translation maintainer" msgstr "????????? ??????????" -#: ../src/about.c:331 +#: ../src/about.c:330 msgid "Translators" msgstr "???????????" -#: ../src/about.c:351 +#: ../src/about.c:350 msgid "Previous Translators" msgstr "???????????? ???????????" -#: ../src/about.c:372 +#: ../src/about.c:371 msgid "Contributors" msgstr "???????????" -#: ../src/about.c:382 +#: ../src/about.c:381 #, c-format msgid "" "Some of the many contributors (for a more detailed list, see the file %s):" @@ -2336,15 +2334,15 @@ msgstr "" "??????? ??? ???? ??????? ?????????????? (??? ??? ??? ????????? ?????, ????? " "?? ?????? %s):" -#: ../src/about.c:408 +#: ../src/about.c:407 msgid "Credits" msgstr "?????" -#: ../src/about.c:425 +#: ../src/about.c:424 msgid "License" msgstr "????? ??????" -#: ../src/about.c:434 +#: ../src/about.c:433 msgid "" "License text could not be found, please visit http://www.gnu.org/licenses/" "gpl-2.0.txt to view it online." @@ -2373,9 +2371,9 @@ msgid "Process failed (%s)" msgstr "? ?????????? ??????? (%s)" #: ../src/build.c:813 -#, fuzzy, c-format +#, c-format msgid "Invalid working directory \"%s\"" -msgstr "???????? ????????? ??? ?????? ???????? \"%s\"" +msgstr "?????? ??????? ???????? \"%s\"" #: ../src/build.c:838 #, c-format @@ -2389,6 +2387,8 @@ msgid "" "File not executed because the terminal may contain some input (press Ctrl+C " "or Enter to clear it)." msgstr "" +"?? ?????? ??? ??????????? ???? ??? ??? ?? ????????? ?????? ?? ???? ?????? " +"?????? (??????? ?? ??????? Ctrl+C ? Enter ??? ?? ?? ??????????)." #: ../src/build.c:1020 msgid "Compilation failed." @@ -2417,7 +2417,7 @@ msgid "_Previous Error" msgstr "_??????????? ??????" #. arguments -#: ../src/build.c:1293 ../src/build.c:2688 +#: ../src/build.c:1293 ../src/build.c:2706 msgid "_Set Build Commands" msgstr "?_?????? ??????? ?????????? (Build)" @@ -2449,95 +2449,94 @@ msgstr "??????? ? ??????? ??? ??????????? (%s)." msgid "No more build errors." msgstr "??? ???? ???? ???? ??? ????????? (build)." -#: ../src/build.c:1738 ../src/build.c:1740 +#: ../src/build.c:1752 ../src/build.c:1754 msgid "Set menu item label" msgstr "?????????? ???????? ??? ???????????? ?????" -#: ../src/build.c:1765 ../src/symbols.c:682 ../src/tools.c:397 +#: ../src/build.c:1779 ../src/symbols.c:683 ../src/tools.c:397 msgid "Label" msgstr "???????" #. command column, holding status and command display -#: ../src/build.c:1766 ../src/symbols.c:677 ../src/tools.c:382 +#: ../src/build.c:1780 ../src/symbols.c:678 ../src/tools.c:382 msgid "Command" msgstr "??????" -#: ../src/build.c:1767 +#: ../src/build.c:1781 msgid "Working directory" msgstr "??????? ????????" -#: ../src/build.c:1768 +#: ../src/build.c:1782 msgid "Reset" msgstr "?????????" -#: ../src/build.c:1815 +#: ../src/build.c:1833 msgid "Click to set menu item label" msgstr "???? ??? ????????? ???????? ??? ???????????? ?????" -#: ../src/build.c:1899 ../src/build.c:1901 +#: ../src/build.c:1917 ../src/build.c:1919 #, c-format msgid "%s commands" msgstr "%s ???????" -#: ../src/build.c:1901 +#: ../src/build.c:1919 msgid "No filetype" msgstr "????? ???? ???????" -#: ../src/build.c:1910 ../src/build.c:1945 +#: ../src/build.c:1928 ../src/build.c:1963 msgid "Error regular expression:" msgstr "?????? regular expression:" -#: ../src/build.c:1938 +#: ../src/build.c:1956 msgid "Independent commands" msgstr "??????????? ???????" -#: ../src/build.c:1970 +#: ../src/build.c:1988 msgid "Note: Item 2 opens a dialog and appends the response to the command." msgstr "" "????????: ?? ??????????? 2 ??????? ??? ??????? ??? ????????? ??? ???????? " "???? ??????." -#: ../src/build.c:1979 +#: ../src/build.c:1997 msgid "Execute commands" msgstr "???????? ???????" -#: ../src/build.c:1991 -#, fuzzy +#: ../src/build.c:2009 msgid "" "%d, %e, %f, %p, %l are substituted in command and directory fields, see " "manual for details." msgstr "" -"%d, %e, %f, %p ???? ?????????????? ???? ?????? ??? ?? ????? ??? ???????, " +"?? %d, %e, %f, %p ????? ?????????????? ???? ?????? ??? ?? ????? ??? ???????, " "????? ?? ?????????? ??? ???????????? ????????????." -#: ../src/build.c:2149 +#: ../src/build.c:2167 msgid "Set Build Commands" msgstr "?????????? ??????? ?????????? (Build)" -#: ../src/build.c:2364 +#: ../src/build.c:2382 msgid "_Compile" msgstr "_???????????" -#: ../src/build.c:2378 ../src/build.c:2408 ../src/build.c:2616 +#: ../src/build.c:2396 ../src/build.c:2426 ../src/build.c:2634 msgid "_Execute" msgstr "_????????" #. build the code with make custom -#: ../src/build.c:2423 ../src/build.c:2614 ../src/build.c:2668 +#: ../src/build.c:2441 ../src/build.c:2632 ../src/build.c:2686 msgid "Make Custom _Target..." msgstr "?????????? _?????????????? ??????????..." #. build the code with make object -#: ../src/build.c:2425 ../src/build.c:2615 ../src/build.c:2676 +#: ../src/build.c:2443 ../src/build.c:2633 ../src/build.c:2694 msgid "Make _Object" msgstr "?????????? _????????????" -#: ../src/build.c:2427 ../src/build.c:2613 +#: ../src/build.c:2445 ../src/build.c:2631 msgid "_Make" msgstr "_?????????? (Make)" #. build the code with make all -#: ../src/build.c:2660 +#: ../src/build.c:2678 msgid "_Make All" msgstr "?????????? (Make) _????" @@ -2676,20 +2675,20 @@ msgstr "?_??????????" msgid "Save the file and rename it" msgstr "?????????? ??? ??????????? ???????" -#: ../src/dialogs.c:696 ../src/win32.c:744 +#: ../src/dialogs.c:696 ../src/win32.c:730 msgid "Error" msgstr "??????" #: ../src/dialogs.c:699 ../src/dialogs.c:778 ../src/dialogs.c:1336 -#: ../src/win32.c:750 +#: ../src/win32.c:736 msgid "Question" msgstr "???????" -#: ../src/dialogs.c:702 ../src/win32.c:756 +#: ../src/dialogs.c:702 ../src/win32.c:742 msgid "Warning" msgstr "?????????????" -#: ../src/dialogs.c:705 ../src/win32.c:762 +#: ../src/dialogs.c:705 ../src/win32.c:748 msgid "Information" msgstr "??????????" @@ -2720,7 +2719,7 @@ msgstr "" #: ../src/dialogs.c:1203 ../src/dialogs.c:1204 ../src/dialogs.c:1205 #: ../src/dialogs.c:1211 ../src/dialogs.c:1212 ../src/dialogs.c:1213 -#: ../src/symbols.c:2181 ../src/symbols.c:2197 ../src/ui_utils.c:289 +#: ../src/symbols.c:2182 ../src/symbols.c:2198 ../src/ui_utils.c:289 msgid "unknown" msgstr "???????" @@ -2826,9 +2825,8 @@ msgid "Any unsaved changes will be lost." msgstr "???? ?? ??????? ??? ??? ????? ??????????? ?? ??????." #: ../src/document.c:1574 -#, fuzzy msgid "Undo history will be lost." -msgstr "???? ?? ??????? ??? ??? ????? ??????????? ?? ??????." +msgstr "?? ???????? ?????????? ?? ?????." #: ../src/document.c:1575 #, c-format @@ -2879,9 +2877,8 @@ msgid "Failed to close file '%s': fclose() failed: %s" msgstr "???????? ????????? ??????? '%s': ?? fclose() ???????: %s" #: ../src/document.c:1981 ../src/document.c:3512 -#, fuzzy msgid "_Overwrite" -msgstr "?????????????;" +msgstr "_?????????????" #: ../src/document.c:1983 ../src/document.c:3515 #, c-format @@ -2901,7 +2898,7 @@ msgstr "?? ?????? \"%s\" ??? ??????? ???? ?????!" #: ../src/document.c:2055 #, c-format msgid "Cannot save read-only document '%s'!" -msgstr "" +msgstr "??? ??????? ?? ??????????? ?? ?????? ???? ??? ???????? '%s'!" #: ../src/document.c:2123 #, c-format @@ -2949,16 +2946,16 @@ msgstr[1] "%s: ????????????? %d ????????????? ??? \ msgid "Do you want to reload it?" msgstr "?????? ?? ?? ??????????????;" -#: ../src/editor.c:4375 +#: ../src/editor.c:4388 msgid "Enter Tab Width" msgstr "???????? ?????? ??? Tab" -#: ../src/editor.c:4376 +#: ../src/editor.c:4389 msgid "Enter the amount of spaces which should be replaced by a tab character." msgstr "" "???????? ??? ??????? ??? ????? ??? ?? ??????????????? ??? ???? ????????? tab." -#: ../src/editor.c:4540 +#: ../src/editor.c:4587 #, c-format msgid "Warning: non-standard hard tab width: %d != 8!" msgstr "?????????????: ?? ???????????? ????? tab: %d != 8!" @@ -3173,13 +3170,13 @@ msgstr "??????? _Markup" msgid "M_iscellaneous" msgstr "_???????" -#: ../src/filetypes.c:1200 ../src/win32.c:153 +#: ../src/filetypes.c:1200 ../src/win32.c:156 msgid "All Source" msgstr "??? ?? ??????" #. create meta file filter "All files" -#: ../src/filetypes.c:1225 ../src/project.c:350 ../src/win32.c:143 -#: ../src/win32.c:188 ../src/win32.c:209 ../src/win32.c:214 +#: ../src/filetypes.c:1225 ../src/project.c:350 ../src/win32.c:146 +#: ../src/win32.c:191 ../src/win32.c:212 ../src/win32.c:217 msgid "All files" msgstr "??? ?? ??????" @@ -3215,7 +3212,7 @@ msgid "Color Schemes" msgstr "????????? ????????" #. visual group order -#: ../src/keybindings.c:247 ../src/symbols.c:654 +#: ../src/keybindings.c:247 ../src/symbols.c:655 msgid "File" msgstr "??????" @@ -3251,7 +3248,7 @@ msgstr "????????" msgid "View" msgstr "???????" -#: ../src/keybindings.c:257 ../src/symbols.c:803 +#: ../src/keybindings.c:257 ../src/symbols.c:804 msgid "Document" msgstr "???????" @@ -3931,13 +3928,17 @@ msgstr "" #: ../src/msgwindow.c:1109 msgid "The document has been closed." -msgstr "" +msgstr "???? ?? ??????? ???? ???????." #: ../src/notebook.c:199 msgid "Switch to Document" msgstr "???????? ??? ???????" -#: ../src/plugins.c:184 +#: ../src/notebook.c:451 +msgid "Open in New _Window" +msgstr "??????? ?? ??? _????????" + +#: ../src/plugins.c:174 #, c-format msgid "" "The plugin \"%s\" is not binary compatible with this release of Geany - " @@ -3946,11 +3947,11 @@ msgstr "" "?? ???????? \"%s\" ??? ????? ??????? ??????? (binary compatible) ?? ???? ??? " "?????? ??? Geany - ??????????? ?? ????? ????????????? (recompile)." -#: ../src/plugins.c:748 +#: ../src/plugins.c:956 msgid "_Plugin Manager" msgstr "???????????? _????????????" -#: ../src/plugins.c:1012 +#: ../src/plugins.c:1218 #, c-format msgid "" "Version:\t%s\n" @@ -3961,27 +3962,27 @@ msgstr "" "??????????(???):\t%s\n" "????? ???????:\t%s" -#: ../src/plugins.c:1037 +#: ../src/plugins.c:1243 msgid "No plugins available." msgstr "??? ???????? ????????? ???????????." -#: ../src/plugins.c:1168 +#: ../src/plugins.c:1374 msgid "Active" msgstr "??????" -#: ../src/plugins.c:1174 +#: ../src/plugins.c:1380 msgid "Plugin" msgstr "???????????" -#: ../src/plugins.c:1301 +#: ../src/plugins.c:1507 msgid "Plugins" msgstr "???????????" -#: ../src/plugins.c:1340 +#: ../src/plugins.c:1546 msgid "Choose which plugins should be loaded at startup:" msgstr "??????? ???? ???????? ?? ??????????? ???? ??? ????????:" -#: ../src/pluginutils.c:365 +#: ../src/pluginutils.c:364 msgid "Configure Plugins" msgstr "??????? ?????????:" @@ -3994,11 +3995,11 @@ msgstr "???? ???????? (Grab Key)" msgid "Press the combination of the keys you want to use for \"%s\"." msgstr "????? ??? ????????? ???????? ??? ?????? ?? ??????????????? ??? \"%s\"." -#: ../src/prefs.c:225 ../src/symbols.c:2335 ../src/sidebar.c:752 +#: ../src/prefs.c:225 ../src/symbols.c:2336 ../src/sidebar.c:752 msgid "_Expand All" msgstr "_?????????? ????" -#: ../src/prefs.c:230 ../src/symbols.c:2340 ../src/sidebar.c:758 +#: ../src/prefs.c:230 ../src/symbols.c:2341 ../src/sidebar.c:758 msgid "_Collapse All" msgstr "_??????? ????" @@ -4136,7 +4137,7 @@ msgstr "????" #: ../src/project.c:135 msgid "Move the current documents into the new project's session?" -msgstr "" +msgstr "?????????? ??? ????????? ???????? ??? ???????? ??? ???? ?????;" #: ../src/project.c:153 msgid "New Project" @@ -4280,8 +4281,8 @@ msgstr "" "???????????? ?????????? ???????" #: ../src/search.c:322 -msgid "Use multi-_line matching" -msgstr "" +msgid "Use multi-line matchin_g" +msgstr "????? ???????????? ?? ?????? _???????" #: ../src/search.c:327 msgid "" @@ -4290,6 +4291,11 @@ msgid "" "newline characters are part of the input and can be captured as normal " "characters by the pattern." msgstr "" +"???????? ???????????? ?? regular expression ?? ??? ??? buffer ???? ??? " +"?????? ???? ??????, ???? ?? ??????? ??????????? ??? ??????????? ?? ?????? " +"???????. ?? ???? ?? ?????? ??????????? ?? ?????????? ???? ??????? (newline) " +"????????? ????? ??? ??????? ??? ??????? ?? ????????????? ?? ????????? " +"?????????? ??? ?? ??????? ??????????." #: ../src/search.c:340 msgid "Search _backwards" @@ -4499,31 +4505,37 @@ msgstr "" "instance ??? ?????????? ??? ???? ??????.\n" "???? ????? ??? ?????? ?????? ??? ?? Geany ?? ??????????." -#: ../src/spawn.c:122 +#. TL note: from glib +#: ../src/spawn.c:109 #, c-format msgid "Text was empty (or contained only whitespace)" -msgstr "" +msgstr "?? ??????? ???? ???? (? ???????? ???? ?????????? ????? ???????????)" -#: ../src/spawn.c:137 ../src/spawn.c:183 +#. TL note: from glib +#: ../src/spawn.c:124 ../src/spawn.c:170 #, c-format msgid "Text ended before matching quote was found for %c. (The text was '%s')" msgstr "" +"?? ??????? ???????? ????? ?? ?????? ????????? ??? ?? ????????? ??? ?? %c. " +"(?? ??????? ???? '%s')" -#: ../src/spawn.c:145 ../src/spawn.c:159 +#: ../src/spawn.c:132 ../src/spawn.c:146 #, c-format msgid "A quoted Windows program name must be entirely inside the quotes" msgstr "" +"??? ??????????? ????????? ??? Windows ?????? ?? ????? ???????? ????? ??? " +"???????????." #. or SPAWN error? -#: ../src/spawn.c:235 -#, fuzzy, c-format +#: ../src/spawn.c:241 +#, c-format msgid "Program '%s' not found" -msgstr "? ?????? ??? ???????" +msgstr "?? ????????? '%s' ??? ???????" -#: ../src/spawn.c:272 -#, fuzzy, c-format +#: ../src/spawn.c:278 +#, c-format msgid "TerminateProcess() failed: %s" -msgstr "? ?????????? ??????? (%s)" +msgstr "?? TerminateProcess() ???????: %s" #: ../src/stash.c:1150 msgid "Name" @@ -4533,328 +4545,328 @@ msgstr "?????" msgid "Value" msgstr "????" -#: ../src/symbols.c:633 ../src/symbols.c:683 ../src/symbols.c:793 +#: ../src/symbols.c:634 ../src/symbols.c:684 ../src/symbols.c:794 msgid "Chapter" msgstr "????????" -#: ../src/symbols.c:634 ../src/symbols.c:679 ../src/symbols.c:794 +#: ../src/symbols.c:635 ../src/symbols.c:680 ../src/symbols.c:795 msgid "Section" msgstr "???????" -#: ../src/symbols.c:635 +#: ../src/symbols.c:636 msgid "Sect1" msgstr "????1" -#: ../src/symbols.c:636 +#: ../src/symbols.c:637 msgid "Sect2" msgstr "????2" -#: ../src/symbols.c:637 +#: ../src/symbols.c:638 msgid "Sect3" msgstr "????3" -#: ../src/symbols.c:638 +#: ../src/symbols.c:639 msgid "Appendix" msgstr "?????????" -#: ../src/symbols.c:639 ../src/symbols.c:684 ../src/symbols.c:709 -#: ../src/symbols.c:725 ../src/symbols.c:740 ../src/symbols.c:751 -#: ../src/symbols.c:852 ../src/symbols.c:863 ../src/symbols.c:876 -#: ../src/symbols.c:890 ../src/symbols.c:902 ../src/symbols.c:914 -#: ../src/symbols.c:931 ../src/symbols.c:960 ../src/symbols.c:992 +#: ../src/symbols.c:640 ../src/symbols.c:685 ../src/symbols.c:710 +#: ../src/symbols.c:726 ../src/symbols.c:741 ../src/symbols.c:752 +#: ../src/symbols.c:853 ../src/symbols.c:864 ../src/symbols.c:877 +#: ../src/symbols.c:891 ../src/symbols.c:903 ../src/symbols.c:915 +#: ../src/symbols.c:932 ../src/symbols.c:961 ../src/symbols.c:993 msgid "Other" msgstr "????" -#: ../src/symbols.c:645 ../src/symbols.c:922 ../src/symbols.c:970 +#: ../src/symbols.c:646 ../src/symbols.c:923 ../src/symbols.c:971 msgid "Module" msgstr "??????" -#: ../src/symbols.c:646 ../src/symbols.c:736 ../src/symbols.c:848 -#: ../src/symbols.c:900 ../src/symbols.c:912 ../src/symbols.c:927 -#: ../src/symbols.c:941 +#: ../src/symbols.c:647 ../src/symbols.c:737 ../src/symbols.c:849 +#: ../src/symbols.c:901 ../src/symbols.c:913 ../src/symbols.c:928 +#: ../src/symbols.c:942 msgid "Types" msgstr "?????" -#: ../src/symbols.c:647 +#: ../src/symbols.c:648 msgid "Type constructors" msgstr "????? constructor" -#: ../src/symbols.c:648 ../src/symbols.c:670 ../src/symbols.c:691 -#: ../src/symbols.c:708 ../src/symbols.c:720 ../src/symbols.c:733 -#: ../src/symbols.c:748 ../src/symbols.c:762 ../src/symbols.c:772 -#: ../src/symbols.c:836 ../src/symbols.c:886 ../src/symbols.c:909 -#: ../src/symbols.c:954 ../src/symbols.c:978 +#: ../src/symbols.c:649 ../src/symbols.c:671 ../src/symbols.c:692 +#: ../src/symbols.c:709 ../src/symbols.c:721 ../src/symbols.c:734 +#: ../src/symbols.c:749 ../src/symbols.c:763 ../src/symbols.c:773 +#: ../src/symbols.c:837 ../src/symbols.c:887 ../src/symbols.c:910 +#: ../src/symbols.c:955 ../src/symbols.c:979 msgid "Functions" msgstr "??????????? (Functions)" -#: ../src/symbols.c:653 +#: ../src/symbols.c:654 msgid "Program" msgstr "?????????" -#: ../src/symbols.c:655 ../src/symbols.c:663 ../src/symbols.c:669 +#: ../src/symbols.c:656 ../src/symbols.c:664 ../src/symbols.c:670 msgid "Sections" msgstr "????????" -#: ../src/symbols.c:656 +#: ../src/symbols.c:657 msgid "Paragraph" msgstr "??????????" -#: ../src/symbols.c:657 +#: ../src/symbols.c:658 msgid "Group" msgstr "?????" -#: ../src/symbols.c:658 +#: ../src/symbols.c:659 msgid "Data" msgstr "????????" -#: ../src/symbols.c:664 +#: ../src/symbols.c:665 msgid "Keys" msgstr "???????" -#: ../src/symbols.c:671 ../src/symbols.c:722 ../src/symbols.c:738 -#: ../src/symbols.c:764 ../src/symbols.c:837 ../src/symbols.c:862 -#: ../src/symbols.c:888 ../src/symbols.c:901 ../src/symbols.c:910 -#: ../src/symbols.c:926 ../src/symbols.c:961 ../src/symbols.c:990 +#: ../src/symbols.c:672 ../src/symbols.c:723 ../src/symbols.c:739 +#: ../src/symbols.c:765 ../src/symbols.c:838 ../src/symbols.c:863 +#: ../src/symbols.c:889 ../src/symbols.c:902 ../src/symbols.c:911 +#: ../src/symbols.c:927 ../src/symbols.c:962 ../src/symbols.c:991 msgid "Variables" msgstr "??????????" -#: ../src/symbols.c:678 +#: ../src/symbols.c:679 msgid "Environment" msgstr "??????????" -#: ../src/symbols.c:680 ../src/symbols.c:795 +#: ../src/symbols.c:681 ../src/symbols.c:796 msgid "Subsection" msgstr "??????????" -#: ../src/symbols.c:681 ../src/symbols.c:796 +#: ../src/symbols.c:682 ../src/symbols.c:797 msgid "Subsubsection" msgstr "???-??????????" -#: ../src/symbols.c:692 ../src/symbols.c:717 +#: ../src/symbols.c:693 ../src/symbols.c:718 msgid "Structures" msgstr "????? (Structures)" -#: ../src/symbols.c:699 +#: ../src/symbols.c:700 msgid "Parts" msgstr "???????" -#: ../src/symbols.c:700 +#: ../src/symbols.c:701 msgid "Assembly" msgstr "Assembly" -#: ../src/symbols.c:701 +#: ../src/symbols.c:702 msgid "Steps" msgstr "??????" -#: ../src/symbols.c:716 ../src/symbols.c:814 ../src/symbols.c:860 +#: ../src/symbols.c:717 ../src/symbols.c:815 ../src/symbols.c:861 msgid "Modules" msgstr "???????" -#: ../src/symbols.c:718 ../src/symbols.c:765 +#: ../src/symbols.c:719 ../src/symbols.c:766 msgid "Traits" msgstr "??????????????" -#: ../src/symbols.c:719 +#: ../src/symbols.c:720 msgid "Implementations" msgstr "???????????? (Implementations)" -#: ../src/symbols.c:721 ../src/symbols.c:981 +#: ../src/symbols.c:722 ../src/symbols.c:982 msgid "Typedefs / Enums" msgstr "Typedefs / Enums" -#: ../src/symbols.c:723 ../src/symbols.c:939 ../src/symbols.c:948 -#: ../src/symbols.c:987 +#: ../src/symbols.c:724 ../src/symbols.c:940 ../src/symbols.c:949 +#: ../src/symbols.c:988 msgid "Macros" msgstr "????????????" -#: ../src/symbols.c:724 ../src/symbols.c:817 ../src/symbols.c:826 -#: ../src/symbols.c:835 ../src/symbols.c:873 ../src/symbols.c:899 +#: ../src/symbols.c:725 ../src/symbols.c:818 ../src/symbols.c:827 +#: ../src/symbols.c:836 ../src/symbols.c:874 ../src/symbols.c:900 msgid "Methods" msgstr "???????" -#: ../src/symbols.c:732 ../src/symbols.c:747 ../src/symbols.c:845 -#: ../src/symbols.c:870 ../src/symbols.c:883 +#: ../src/symbols.c:733 ../src/symbols.c:748 ../src/symbols.c:846 +#: ../src/symbols.c:871 ../src/symbols.c:884 msgid "Package" msgstr "??????" -#: ../src/symbols.c:734 ../src/symbols.c:760 ../src/symbols.c:871 -#: ../src/symbols.c:884 ../src/symbols.c:897 ../src/symbols.c:924 -#: ../src/symbols.c:977 +#: ../src/symbols.c:735 ../src/symbols.c:761 ../src/symbols.c:872 +#: ../src/symbols.c:885 ../src/symbols.c:898 ../src/symbols.c:925 +#: ../src/symbols.c:978 msgid "Interfaces" msgstr "???????????? (Interfaces)" -#: ../src/symbols.c:735 ../src/symbols.c:980 +#: ../src/symbols.c:736 ../src/symbols.c:981 msgid "Structs" msgstr "????? (Structs)" -#: ../src/symbols.c:737 ../src/symbols.c:750 ../src/symbols.c:763 -#: ../src/symbols.c:889 ../src/symbols.c:911 +#: ../src/symbols.c:738 ../src/symbols.c:751 ../src/symbols.c:764 +#: ../src/symbols.c:890 ../src/symbols.c:912 msgid "Constants" msgstr "????????" -#: ../src/symbols.c:739 ../src/symbols.c:874 ../src/symbols.c:979 +#: ../src/symbols.c:740 ../src/symbols.c:875 ../src/symbols.c:980 msgid "Members" msgstr "????" -#: ../src/symbols.c:749 ../src/symbols.c:913 ../src/symbols.c:938 +#: ../src/symbols.c:750 ../src/symbols.c:914 ../src/symbols.c:939 msgid "Labels" msgstr "????????" -#: ../src/symbols.c:759 ../src/symbols.c:824 ../src/symbols.c:973 +#: ../src/symbols.c:760 ../src/symbols.c:825 ../src/symbols.c:974 msgid "Namespaces" msgstr "Namespaces" -#: ../src/symbols.c:761 ../src/symbols.c:783 ../src/symbols.c:815 -#: ../src/symbols.c:825 ../src/symbols.c:834 ../src/symbols.c:872 -#: ../src/symbols.c:885 ../src/symbols.c:898 ../src/symbols.c:976 +#: ../src/symbols.c:762 ../src/symbols.c:784 ../src/symbols.c:816 +#: ../src/symbols.c:826 ../src/symbols.c:835 ../src/symbols.c:873 +#: ../src/symbols.c:886 ../src/symbols.c:899 ../src/symbols.c:977 msgid "Classes" msgstr "???????" -#: ../src/symbols.c:773 +#: ../src/symbols.c:774 msgid "Anchors" msgstr "??????? (Anchors)" -#: ../src/symbols.c:774 +#: ../src/symbols.c:775 msgid "H1 Headings" msgstr "H1 ?????????" -#: ../src/symbols.c:775 +#: ../src/symbols.c:776 msgid "H2 Headings" msgstr "H2 ?????????" -#: ../src/symbols.c:776 +#: ../src/symbols.c:777 msgid "H3 Headings" msgstr "H3 ?????????" -#: ../src/symbols.c:784 +#: ../src/symbols.c:785 msgid "ID Selectors" msgstr "ID Selectors" -#: ../src/symbols.c:785 +#: ../src/symbols.c:786 msgid "Type Selectors" msgstr "????? ?????????" -#: ../src/symbols.c:804 +#: ../src/symbols.c:805 msgid "Section Level 1" msgstr "??????? ???????? 1" -#: ../src/symbols.c:805 +#: ../src/symbols.c:806 msgid "Section Level 2" msgstr "??????? ???????? 2" -#: ../src/symbols.c:806 +#: ../src/symbols.c:807 msgid "Section Level 3" msgstr "??????? ???????? 3" -#: ../src/symbols.c:807 +#: ../src/symbols.c:808 msgid "Section Level 4" msgstr "??????? ???????? 4" -#: ../src/symbols.c:816 +#: ../src/symbols.c:817 msgid "Singletons" msgstr "??????????" -#: ../src/symbols.c:827 ../src/symbols.c:955 +#: ../src/symbols.c:828 ../src/symbols.c:956 msgid "Procedures" msgstr "?????????? (Procedures)" -#: ../src/symbols.c:838 +#: ../src/symbols.c:839 msgid "Imports" msgstr "????????? (Imports)" -#: ../src/symbols.c:846 +#: ../src/symbols.c:847 msgid "Entities" msgstr "?????????" -#: ../src/symbols.c:847 +#: ../src/symbols.c:848 msgid "Architectures" msgstr "??????????????" -#: ../src/symbols.c:849 +#: ../src/symbols.c:850 msgid "Functions / Procedures" msgstr "??????????? / ?????????? (Functions / Procedures)" -#: ../src/symbols.c:850 +#: ../src/symbols.c:851 msgid "Variables / Signals" msgstr "?????????? / ??????? (Variables / Signals)" -#: ../src/symbols.c:851 +#: ../src/symbols.c:852 msgid "Processes / Blocks / Components" msgstr "?????????? / ????? / ????????? (Processes / Blocks / Components)" -#: ../src/symbols.c:859 +#: ../src/symbols.c:860 msgid "Events" msgstr "???????? (Events)" -#: ../src/symbols.c:861 +#: ../src/symbols.c:862 msgid "Functions / Tasks" msgstr "??????????? / ???????? (Functions / Tasks)" -#: ../src/symbols.c:875 ../src/symbols.c:930 +#: ../src/symbols.c:876 ../src/symbols.c:931 msgid "Enums" msgstr "Enums" -#: ../src/symbols.c:887 +#: ../src/symbols.c:888 msgid "Properties" msgstr "?????????" -#: ../src/symbols.c:923 +#: ../src/symbols.c:924 msgid "Programs" msgstr "???????????" -#: ../src/symbols.c:925 +#: ../src/symbols.c:926 msgid "Functions / Subroutines" msgstr "??????????? / ???????????" -#: ../src/symbols.c:928 +#: ../src/symbols.c:929 msgid "Components" msgstr "????????? (Components)" -#: ../src/symbols.c:929 +#: ../src/symbols.c:930 msgid "Blocks" msgstr "????? (Blocks)" -#: ../src/symbols.c:940 +#: ../src/symbols.c:941 msgid "Defines" msgstr "??????????" -#: ../src/symbols.c:947 +#: ../src/symbols.c:948 msgid "Targets" msgstr "?????? (Targets)" -#: ../src/symbols.c:956 +#: ../src/symbols.c:957 msgid "Indexes" msgstr "?????????" -#: ../src/symbols.c:957 +#: ../src/symbols.c:958 msgid "Tables" msgstr "???????" -#: ../src/symbols.c:958 +#: ../src/symbols.c:959 msgid "Triggers" msgstr "????????? (Triggers)" -#: ../src/symbols.c:959 +#: ../src/symbols.c:960 msgid "Views" msgstr "????????" -#: ../src/symbols.c:991 +#: ../src/symbols.c:992 msgid "Extern Variables" msgstr "?????????? ??????????" -#: ../src/symbols.c:1755 +#: ../src/symbols.c:1756 #, c-format msgid "Unknown filetype extension for \"%s\".\n" msgstr "??????? ????????? ????? ??????? ??? \"%s\".\n" -#: ../src/symbols.c:1781 +#: ../src/symbols.c:1782 #, c-format msgid "Failed to create tags file, perhaps because no tags were found.\n" msgstr "" "???????? ??????????? ??????? ????????, ??????? ???? ??? ??? ??? ???????? " "????????.\n" -#: ../src/symbols.c:1788 +#: ../src/symbols.c:1789 #, c-format msgid "" "Usage: %s -g \n" @@ -4863,7 +4875,7 @@ msgstr "" "?????: %s -g \n" "\n" -#: ../src/symbols.c:1789 +#: ../src/symbols.c:1790 #, c-format msgid "" "Example:\n" @@ -4874,40 +4886,40 @@ msgstr "" "CFLAGS=`pkg-config gtk+-2.0 --cflags` %s -g gtk2.c.tags /usr/include/gtk-2.0/" "gtk/gtk.h\n" -#: ../src/symbols.c:1803 +#: ../src/symbols.c:1804 msgid "Load Tags" msgstr "??????? ????????" -#: ../src/symbols.c:1810 +#: ../src/symbols.c:1811 msgid "Geany tag files (*.*.tags)" msgstr "?????? ???????? Geany (*.*.tags)" #. For translators: the first wildcard is the filetype, the second the filename -#: ../src/symbols.c:1830 +#: ../src/symbols.c:1831 #, c-format msgid "Loaded %s tags file '%s'." msgstr "?????????? %s ???????? ??????? '%s'." -#: ../src/symbols.c:1833 +#: ../src/symbols.c:1834 #, c-format msgid "Could not load tags file '%s'." msgstr "??? ???? ??????? ?? ???????? ?? ?????? ???????? '%s'." -#: ../src/symbols.c:1971 +#: ../src/symbols.c:1972 #, c-format msgid "Forward declaration \"%s\" not found." msgstr "??? ??????? ? ?????????? ?????? (declaration) \"%s\"." -#: ../src/symbols.c:1973 +#: ../src/symbols.c:1974 #, c-format msgid "Definition of \"%s\" not found." msgstr "? ?????????? ??? \"%s\" ??? ???????." -#: ../src/symbols.c:2350 +#: ../src/symbols.c:2351 msgid "Sort by _Name" msgstr "?????????? ???? _?????" -#: ../src/symbols.c:2357 +#: ../src/symbols.c:2358 msgid "Sort by _Appearance" msgstr "?????????? ???? _????????" @@ -5304,9 +5316,8 @@ msgid "Select File" msgstr "??????? ???????" #: ../src/ui_utils.c:2152 -#, fuzzy msgid "_Filetype Configuration" -msgstr "_???????????? ?????????" +msgstr "????????? _????? ???????" #: ../src/ui_utils.c:2189 msgid "Save All" @@ -5320,11 +5331,11 @@ msgstr "???????? ????" msgid "Geany cannot start!" msgstr "??? ????? ??????? ??? ????????? ?? Geany!" -#: ../src/utils.c:86 +#: ../src/utils.c:87 msgid "Select Browser" msgstr "??????? ????????????" -#: ../src/utils.c:87 +#: ../src/utils.c:88 msgid "" "Failed to spawn the configured browser command. Please correct it or enter " "another one." @@ -5332,60 +5343,60 @@ msgstr "" "???????? ?????????? ( spawn) ??? ??????????? ??????? ?????????. ??????????? " "????????? ??? ? ???????? ??? ????." -#: ../src/utils.c:374 -#, fuzzy +#: ../src/utils.c:375 msgid "Windows (CRLF)" -msgstr "Win (CRLF)" +msgstr "Windows (CRLF)" -#: ../src/utils.c:375 -#, fuzzy +#: ../src/utils.c:376 msgid "Classic Mac (CR)" -msgstr "Mac (CR)" +msgstr "?????? Mac (CR)" -#: ../src/utils.c:376 +#: ../src/utils.c:377 msgid "Unix (LF)" msgstr "Unix (LF)" -#: ../src/utils.c:385 +#: ../src/utils.c:386 msgid "CRLF" -msgstr "" +msgstr "CRLF" -#: ../src/utils.c:386 +#: ../src/utils.c:387 msgid "CR" -msgstr "" +msgstr "CR" -#: ../src/utils.c:387 +#: ../src/utils.c:388 msgid "LF" -msgstr "" +msgstr "LF" -#: ../src/vte.c:477 +#: ../src/vte.c:489 #, c-format msgid "invalid VTE library \"%s\": missing symbol \"%s\"" msgstr "?????????? ?????????? VTE \"%s\": ?????? ?? ??????? \"%s\"" -#: ../src/vte.c:626 +#: ../src/vte.c:638 msgid "_Set Path From Document" msgstr "_??????? ????????? ??? ?? ???????" -#: ../src/vte.c:631 +#: ../src/vte.c:643 msgid "_Restart Terminal" msgstr "_???????????? ??????????" -#: ../src/vte.c:654 +#: ../src/vte.c:666 msgid "_Input Methods" msgstr "_???????? ???????" -#: ../src/vte.c:747 +#: ../src/vte.c:759 msgid "" "Directory not changed because the terminal may contain some input (press Ctrl" "+C or Enter to clear it)." msgstr "" +"? ??????? ??? ?????? ???? ??? ??? ?? ????????? ?????? ?? ???? ?????? ?????? " +"(??????? ?? ??????? Ctrl+C ? Enter ??? ?? ?? ??????????)." -#: ../src/win32.c:208 +#: ../src/win32.c:211 msgid "Geany project files" msgstr "?????? ????? Geany" -#: ../src/win32.c:213 +#: ../src/win32.c:216 msgid "Executables" msgstr "??????????" @@ -5401,99 +5412,99 @@ msgstr "?????????? ?? ?????? ?????? ??? ?? ??? msgid "Create Class" msgstr "?????????? ??????" -#: ../plugins/classbuilder.c:444 +#: ../plugins/classbuilder.c:443 msgid "Create C++ Class" msgstr "?????????? C++ ??????" -#: ../plugins/classbuilder.c:447 +#: ../plugins/classbuilder.c:446 msgid "Create GTK+ Class" msgstr "?????????? GTK+ ??????" -#: ../plugins/classbuilder.c:450 +#: ../plugins/classbuilder.c:449 msgid "Create PHP Class" msgstr "?????????? PHP ??????" -#: ../plugins/classbuilder.c:467 +#: ../plugins/classbuilder.c:466 msgid "Namespace" msgstr "Namespace" -#: ../plugins/classbuilder.c:474 ../plugins/classbuilder.c:476 +#: ../plugins/classbuilder.c:473 ../plugins/classbuilder.c:475 msgid "Class" msgstr "?????" -#: ../plugins/classbuilder.c:483 +#: ../plugins/classbuilder.c:482 msgid "Header file:" msgstr "?????? ???????:" -#: ../plugins/classbuilder.c:485 +#: ../plugins/classbuilder.c:484 msgid "Source file:" msgstr "?????? ??????:" -#: ../plugins/classbuilder.c:487 +#: ../plugins/classbuilder.c:486 msgid "Inheritance" msgstr "????? (Inheritance)" -#: ../plugins/classbuilder.c:489 +#: ../plugins/classbuilder.c:488 msgid "Base class:" msgstr "?????? ?????:" -#: ../plugins/classbuilder.c:497 +#: ../plugins/classbuilder.c:496 msgid "Base source:" msgstr "?????? ?????? ??????" -#: ../plugins/classbuilder.c:502 +#: ../plugins/classbuilder.c:501 msgid "Base header:" msgstr "?????? ??????:" -#: ../plugins/classbuilder.c:510 +#: ../plugins/classbuilder.c:509 msgid "Global" msgstr "??????" -#: ../plugins/classbuilder.c:529 +#: ../plugins/classbuilder.c:528 msgid "Base GType:" msgstr "?????? GType:" -#: ../plugins/classbuilder.c:534 +#: ../plugins/classbuilder.c:533 msgid "Implements:" msgstr "?????????:" -#: ../plugins/classbuilder.c:536 +#: ../plugins/classbuilder.c:535 msgid "Options" msgstr "????????" -#: ../plugins/classbuilder.c:553 +#: ../plugins/classbuilder.c:552 msgid "Create constructor" msgstr "?????????? ??????????" -#: ../plugins/classbuilder.c:558 +#: ../plugins/classbuilder.c:557 msgid "Create destructor" msgstr "?????????? destructor" -#: ../plugins/classbuilder.c:565 +#: ../plugins/classbuilder.c:564 msgid "Is abstract" msgstr "????? abstract" -#: ../plugins/classbuilder.c:568 +#: ../plugins/classbuilder.c:567 msgid "Is singleton" msgstr "????? ??????????" -#: ../plugins/classbuilder.c:578 +#: ../plugins/classbuilder.c:577 msgid "Constructor type:" msgstr "????? Constructor" -#: ../plugins/classbuilder.c:1090 +#: ../plugins/classbuilder.c:1089 msgid "Create Cla_ss" msgstr "?????????? ???_???" -#: ../plugins/classbuilder.c:1096 +#: ../plugins/classbuilder.c:1095 msgid "_C++ Class..." msgstr "_C++ ?????..." -#: ../plugins/classbuilder.c:1099 +#: ../plugins/classbuilder.c:1098 msgid "_GTK+ Class..." msgstr "_GTK+ ?????..." -#: ../plugins/classbuilder.c:1102 +#: ../plugins/classbuilder.c:1101 msgid "_PHP Class..." msgstr "_PHP ?????..." -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 11:04:41 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 11:04:41 -0000 Subject: [geany/talks] 1aed5b: EN: Adding generel and meta information Message-ID: <20151004110452.92CD55C420C@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 11:04:41 UTC Commit: 1aed5bb821217974449ec9d6f220db862eb0861a https://github.com/geany/talks/commit/1aed5bb821217974449ec9d6f220db862eb0861a Log Message: ----------- EN: Adding generel and meta information Modified Paths: -------------- en/A_short_introduction/index.html Modified: en/A_short_introduction/index.html 34 lines changed, 34 insertions(+), 0 deletions(-) =================================================================== @@ -60,6 +60,40 @@
  • Meta
  • +
    +

    About me

    +
      +
    • Professional nerd with background in economics working for a Operations-as-a-Service-company
    • +
    • 10 years user of Geany
    • +
    • Maintainer of geany-plugins
    • +
    • Coordinator of translation work
    • +
    +
    + +
    +
    +

    General overview

    +
    +
    +

    About Geany

    +
      +
    • Editor with features of an IDE
    • +
    • Under development since 2005
    • +
    • Current version 1.25 released summer 2015
    • +
    • Main goals +
        +
      • Low system requirements (running on Raspberry)
      • +
      • Low further dependencies
      • +
      +
    • +
    • Written in C and C++
    • +
    • Based upon Scintilla and GTK2
    • +
    • Licence: GPLv2+
    • +
    +
    +
    +
    +

    The End

    -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 11:55:42 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 11:55:42 -0000 Subject: [geany/talks] 670cec: EN: Adding some structure Message-ID: <20151004125101.425A35C4215@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 11:55:42 UTC Commit: 670cecbbb568028cc5b6a6325d8ae4cd3fbc9b94 https://github.com/geany/talks/commit/670cecbbb568028cc5b6a6325d8ae4cd3fbc9b94 Log Message: ----------- EN: Adding some structure Modified Paths: -------------- en/A_short_introduction/index.html Modified: en/A_short_introduction/index.html 42 lines changed, 42 insertions(+), 0 deletions(-) =================================================================== @@ -93,6 +93,48 @@
    +
    +

    Features

    +
    +
    +

    Syntaxhighlighting

    +
    +
    +

    Build menu

    +
    +
    +

    Keyboard shortcuts

    +
    +
    +

    Templates

    +
    +
    +

    File-Templates

    +
    +
    +

    Snippets

    +
    +
    +

    Example RegEx

    +
    +domain=[._[:alnum:]-]+
    +host=[._[:alnum:]-]+
    +ip=[0-9A-Fa-f.:]+
    +email=[._+=[:alnum:]-]+@[._[:alnum:]-]+
    +date=\w{3} [ :0-9]{11}
    +                        
    +
    +
    +

    Autocompletion

    +
    +
    +

    Symbol browser & document browser

    +
    +
    +

    Plugin interface

    +
    +
    +

    The End

    -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 12:50:55 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 12:50:55 -0000 Subject: [geany/talks] 6bc60f: EN: Change used theme and put some content into structure Message-ID: <20151004125101.C77C75C4217@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 12:50:55 UTC Commit: 6bc60f2a5226ed215ebdb37ad91a8535309ce3bd https://github.com/geany/talks/commit/6bc60f2a5226ed215ebdb37ad91a8535309ce3bd Log Message: ----------- EN: Change used theme and put some content into structure Modified Paths: -------------- en/A_short_introduction/index.html Modified: en/A_short_introduction/index.html 73 lines changed, 72 insertions(+), 1 deletions(-) =================================================================== @@ -15,7 +15,7 @@ - + @@ -95,24 +95,65 @@

    Features

    +

    This is just a selection

    Syntaxhighlighting

    +
      +
    • More than 60 file types
    • +
    • Based upon Scintilla-Projekt
    • +
    • Done with C++ classes -- so called Lexxer
    • +
    • Automatic detection and manual set-able
    • +
    • Self-defined file types (using shippid Lexxer)
    • +

    Build menu

    +
      +
    • Consists of:
        +
      • File type depending entries
      • +
      • Generic entries
      • +
      • Project based commands
      • +
    • +
    • Can be adjusted for personal needs
    • +
    +

    LaTeX-Build menu of Genay

    Keyboard shortcuts

    +
      +
    • Many functions can be controlled via keybaord short cur
    • +
    • Can be reconfigured either via config file or via dialog
    • +
    • Pluings can register own keyboard shortcuts
    • +

    Templates

    +
      +
    • Saves a lot of typing work
    • +
    • Most can be extended/overwritten by user
    • +
    • Differrent kind of templates:
        +
      • New files / file templates
      • +
      • Placeholder for common stubs like GPL/BSD licence notification
      • +
      • Code snippets
      • +

    File-Templates

    +
      +
    • Useful for often used tasks
    • +
    • Might include placeholders like it's own filename
    • +
    • Geany is offering a basic collection; own templates below ~/.config/geany/templates/files
    • +

    Snippets

    +
      +
    • Reoccuring text passages can be replaces with a "shortcut"
    • +
    • Geany is offering a list of generic and file type specific ones
    • +
    • Can be extended by user
    • +
    • Many examples inside Wiki
    • +

    Example RegEx

    @@ -126,12 +167,42 @@

    Autocompletion

    +
      +
    • Hybrid of static approach & information of current session
    • +
    • Configurable:
        +
      • ... how many letters to start showing suggestions
      • +
      • ... shall often used words be included (useful for LaTeX, HTML)
      • +
    • +
    • Static "Tages" are loaded from files
    • +
    • File type specific
    • +
    • Tag files are listed inside the Wiki
    • +
    • Tag-files can be generated by Geany from e.g. project
    • +

    Symbol browser & document browser

    +
      +
    • Located on side bar
    • +
    • Fast access to symboles (variable defintion, functions ...) with mouse
    • +
    • Fast access to open documents
    • +

    Plugin interface

    +
      +
    • Huge number of plugins
    • +
    • API/Binding for C, Python and Lua
    • +
    • Examples:
        +
      • GeanyVC
      • +
      • git-changebar
      • +
      • Projectorganizer
      • +
      • Addons
      • +
      • DevHelp
      • +
      • Webhelper
      • +
      • Scope & Debugger
      • +
    • +
    • More bindings coming soon
    • +
    -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 12:52:48 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 12:52:48 -0000 Subject: [geany/talks] e8b34a: EN: Adding Meta slide Message-ID: <20151004125255.B3E565C4215@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 12:52:48 UTC Commit: e8b34a4eae01d704b508e008d91a14b09120f760 https://github.com/geany/talks/commit/e8b34a4eae01d704b508e008d91a14b09120f760 Log Message: ----------- EN: Adding Meta slide Modified Paths: -------------- en/A_short_introduction/index.html Modified: en/A_short_introduction/index.html 9 lines changed, 9 insertions(+), 0 deletions(-) =================================================================== @@ -206,6 +206,15 @@
    +

    Meta

    + Wiki: wiki.geany.org
    + Homepage: geany.org
    + Manual: geany.org/manual
    + Sexy: geany.sexy
    + Github: github.com/geany
    + IRC: #geany @ freenode
    +
    +

    The End

    -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 12:54:13 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 12:54:13 -0000 Subject: [geany/talks] 2b69a0: Adding short README on how to get reveal.js into tree Message-ID: <20151004125419.EBE2E5C4215@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 12:54:13 UTC Commit: 2b69a0cde688cc5ce679facd420261c19e680346 https://github.com/geany/talks/commit/2b69a0cde688cc5ce679facd420261c19e680346 Log Message: ----------- Adding short README on how to get reveal.js into tree Modified Paths: -------------- en/A_short_introduction/README.md Modified: en/A_short_introduction/README.md 6 lines changed, 6 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,6 @@ +To get revel.js you will have to get the submodule. Do: + +$ git submodule init +$ git submodule update + +for it. Done. -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 12:57:42 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 12:57:42 -0000 Subject: [geany/geany] 4742ca: Merge branch 'master' of https://github.com/konsolebox/geany into konsolebox-master Message-ID: <20151004125818.DABA75C4215@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 12:57:42 UTC Commit: 4742cac866c97f61f081ee78a971dcca90e8909b https://github.com/geany/geany/commit/4742cac866c97f61f081ee78a971dcca90e8909b Log Message: ----------- Merge branch 'master' of https://github.com/konsolebox/geany into konsolebox-master Modified Paths: -------------- po/ar.po po/ast.po po/be.po po/bg.po po/ca.po po/cs.po po/de.po po/en_GB.po po/es.po po/et.po po/eu.po po/fa.po po/fi.po po/gl.po po/he.po po/hi.po po/hu.po po/id.po po/it.po po/ja.po po/kk.po po/ko.po po/lb.po po/lt.po po/mn.po po/nl.po po/nn.po po/pl.po po/pt.po po/pt_BR.po po/ro.po po/ru.po po/sk.po po/sl.po po/sr.po po/sv.po po/tr.po po/uk.po po/vi.po po/zh_CN.po po/zh_TW.po Modified: po/ar.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4058,7 +4058,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ast.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4313,7 +4313,7 @@ msgstr "" "carauteres de control correspondientes" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/be.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4197,7 +4197,7 @@ msgstr "" "???????? ???????" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "?????????????? ??????????? ?????" #: ../src/search.c:327 Modified: po/bg.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4522,7 +4522,7 @@ msgstr "" "????????? ???????." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ca.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4218,7 +4218,7 @@ msgstr "" "seq??ncies de control corresponents" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/cs.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4214,7 +4214,7 @@ msgstr "" "kontroln?mi znaky." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/de.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4277,7 +4277,7 @@ msgstr "" "entsprechenden Sonderzeichen" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "_Mehrzeilen-Erkennung (multi-line matching) benutzen" #: ../src/search.c:327 Modified: po/en_GB.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4207,7 +4207,7 @@ msgstr "" "corresponding control characters" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/es.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4255,7 +4255,7 @@ msgstr "" "de control correspondiente" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Usar coincidencias multi_l?neas" #: ../src/search.c:327 Modified: po/et.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4076,7 +4076,7 @@ msgid "" msgstr "Asenda \\\\, \\t, \\n, \\r ja \\uXXXX (Unicode s?mbolid) juhtm?rkidega" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/eu.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4087,7 +4087,7 @@ msgstr "" "kontrol-karaktereekin" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/fa.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4270,7 +4270,7 @@ msgstr "" "corresponding control characters" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/fi.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4235,7 +4235,7 @@ msgstr "" "ohjausmerkeill?" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/gl.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4254,7 +4254,7 @@ msgstr "" "correspondentes caracteres de control" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/he.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4113,7 +4113,7 @@ msgid "" msgstr "????? ?\\\\, \\t, \\n, \\r ?-?\\uXXXX (???? ???????) ????? ???? ???????" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/hi.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4023,7 +4023,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/hu.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4236,7 +4236,7 @@ msgstr "" "a megfelel? vez?rl?karakterekkel." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/id.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4249,7 +4249,7 @@ msgstr "" "kontrol penggantinya" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/it.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4306,7 +4306,7 @@ msgstr "" "corrispondenti caratteri di controllo" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ja.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4171,7 +4171,7 @@ msgstr "" "??" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/kk.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4072,7 +4072,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ko.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4333,7 +4333,7 @@ msgstr "" "?." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/lb.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4357,7 +4357,7 @@ msgstr "" "entspriechend Zeechen" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/lt.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4194,7 +4194,7 @@ msgstr "" "valdymo simboliais" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/mn.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4360,7 +4360,7 @@ msgstr "" #: ../src/search.c:322 #, fuzzy -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "??????? ????? ?????? _???????" #: ../src/search.c:327 Modified: po/nl.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4227,7 +4227,7 @@ msgstr "" "stuurtekens" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Gebruik _meerregelige matching" #: ../src/search.c:327 Modified: po/nn.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4030,7 +4030,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/pl.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4231,7 +4231,7 @@ msgstr "" "kontrolnymi." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/pt.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4229,7 +4229,7 @@ msgstr "" "caracteres de controlo correspondentes" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Usar compara??o multi_Linha" #: ../src/search.c:327 Modified: po/pt_BR.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4234,7 +4234,7 @@ msgstr "" "caracteres de controle correspondentes" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Utilizar casamento multi_linhas" #: ../src/search.c:327 Modified: po/ro.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4366,7 +4366,7 @@ msgstr "" "caracterele de control corespunz?toare." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ru.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4217,7 +4217,7 @@ msgstr "" "???????????? ?????????" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "_????????????? ?????" #: ../src/search.c:327 Modified: po/sk.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4183,7 +4183,7 @@ msgstr "" "znakom" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/sl.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4253,7 +4253,7 @@ msgstr "" "z ustreznimi kontrolnimi znaki" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/sr.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4104,7 +4104,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/sv.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4183,7 +4183,7 @@ msgstr "" "specialtecken." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Anv?nd flerraders matchning" #: ../src/search.c:327 Modified: po/tr.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4188,7 +4188,7 @@ msgstr "" "karakterleri ile yer de?i?tir" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/uk.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4424,7 +4424,7 @@ msgstr "" "????????? ?????????." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/vi.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4307,7 +4307,7 @@ msgstr "" "Unicode) b?ng k? t? ?i?u khi?n t??ng ?ng" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/zh_CN.po 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -4066,8 +4066,8 @@ msgid "" msgstr "?? \\\\?\\t?\\n?\\r ? \\uXXXX (Unicode???) ?????????" #: ../src/search.c:322 -msgid "Use multi-_line matching" -msgstr "??????(_L)" +msgid "Use multi-line matchin_g" +msgstr "??????(_G)" #: ../src/search.c:327 msgid "" Modified: po/zh_TW.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4140,7 +4140,7 @@ msgid "" msgstr "?????????? \\\\?\\t,\\n?\\r ? \\uXXXX (?????) " #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 4 12:58:03 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sun, 04 Oct 2015 12:58:03 -0000 Subject: [geany/geany] e7e5d5: Merge branch 'konsolebox-master' Message-ID: <20151004125819.A6E6E5C4217@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sun, 04 Oct 2015 12:58:03 UTC Commit: e7e5d51d0a239ea64fae9921d467272ea48fb27f https://github.com/geany/geany/commit/e7e5d51d0a239ea64fae9921d467272ea48fb27f Log Message: ----------- Merge branch 'konsolebox-master' Modified Paths: -------------- po/ar.po po/ast.po po/be.po po/bg.po po/ca.po po/cs.po po/de.po po/en_GB.po po/es.po po/et.po po/eu.po po/fa.po po/fi.po po/gl.po po/he.po po/hi.po po/hu.po po/id.po po/it.po po/ja.po po/kk.po po/ko.po po/lb.po po/lt.po po/mn.po po/nl.po po/nn.po po/pl.po po/pt.po po/pt_BR.po po/ro.po po/ru.po po/sk.po po/sl.po po/sr.po po/sv.po po/tr.po po/uk.po po/vi.po po/zh_CN.po po/zh_TW.po Modified: po/ar.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4058,7 +4058,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ast.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4313,7 +4313,7 @@ msgstr "" "carauteres de control correspondientes" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/be.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4197,7 +4197,7 @@ msgstr "" "???????? ???????" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "?????????????? ??????????? ?????" #: ../src/search.c:327 Modified: po/bg.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4522,7 +4522,7 @@ msgstr "" "????????? ???????." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ca.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4218,7 +4218,7 @@ msgstr "" "seq??ncies de control corresponents" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/cs.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4214,7 +4214,7 @@ msgstr "" "kontroln?mi znaky." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/de.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4277,7 +4277,7 @@ msgstr "" "entsprechenden Sonderzeichen" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "_Mehrzeilen-Erkennung (multi-line matching) benutzen" #: ../src/search.c:327 Modified: po/en_GB.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4207,7 +4207,7 @@ msgstr "" "corresponding control characters" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/es.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4255,7 +4255,7 @@ msgstr "" "de control correspondiente" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Usar coincidencias multi_l?neas" #: ../src/search.c:327 Modified: po/et.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4076,7 +4076,7 @@ msgid "" msgstr "Asenda \\\\, \\t, \\n, \\r ja \\uXXXX (Unicode s?mbolid) juhtm?rkidega" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/eu.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4087,7 +4087,7 @@ msgstr "" "kontrol-karaktereekin" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/fa.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4270,7 +4270,7 @@ msgstr "" "corresponding control characters" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/fi.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4235,7 +4235,7 @@ msgstr "" "ohjausmerkeill?" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/gl.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4254,7 +4254,7 @@ msgstr "" "correspondentes caracteres de control" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/he.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4113,7 +4113,7 @@ msgid "" msgstr "????? ?\\\\, \\t, \\n, \\r ?-?\\uXXXX (???? ???????) ????? ???? ???????" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/hi.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4023,7 +4023,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/hu.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4236,7 +4236,7 @@ msgstr "" "a megfelel? vez?rl?karakterekkel." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/id.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4249,7 +4249,7 @@ msgstr "" "kontrol penggantinya" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/it.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4306,7 +4306,7 @@ msgstr "" "corrispondenti caratteri di controllo" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ja.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4171,7 +4171,7 @@ msgstr "" "??" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/kk.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4072,7 +4072,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ko.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4333,7 +4333,7 @@ msgstr "" "?." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/lb.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4357,7 +4357,7 @@ msgstr "" "entspriechend Zeechen" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/lt.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4194,7 +4194,7 @@ msgstr "" "valdymo simboliais" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/mn.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4360,7 +4360,7 @@ msgstr "" #: ../src/search.c:322 #, fuzzy -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "??????? ????? ?????? _???????" #: ../src/search.c:327 Modified: po/nl.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4227,7 +4227,7 @@ msgstr "" "stuurtekens" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Gebruik _meerregelige matching" #: ../src/search.c:327 Modified: po/nn.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4030,7 +4030,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/pl.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4231,7 +4231,7 @@ msgstr "" "kontrolnymi." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/pt.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4229,7 +4229,7 @@ msgstr "" "caracteres de controlo correspondentes" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Usar compara??o multi_Linha" #: ../src/search.c:327 Modified: po/pt_BR.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4234,7 +4234,7 @@ msgstr "" "caracteres de controle correspondentes" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Utilizar casamento multi_linhas" #: ../src/search.c:327 Modified: po/ro.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4366,7 +4366,7 @@ msgstr "" "caracterele de control corespunz?toare." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/ru.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4217,7 +4217,7 @@ msgstr "" "???????????? ?????????" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "_????????????? ?????" #: ../src/search.c:327 Modified: po/sk.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4183,7 +4183,7 @@ msgstr "" "znakom" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/sl.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4253,7 +4253,7 @@ msgstr "" "z ustreznimi kontrolnimi znaki" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/sr.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4104,7 +4104,7 @@ msgid "" msgstr "" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/sv.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4183,7 +4183,7 @@ msgstr "" "specialtecken." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "Anv?nd flerraders matchning" #: ../src/search.c:327 Modified: po/tr.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4188,7 +4188,7 @@ msgstr "" "karakterleri ile yer de?i?tir" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/uk.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4424,7 +4424,7 @@ msgstr "" "????????? ?????????." #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/vi.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4307,7 +4307,7 @@ msgstr "" "Unicode) b?ng k? t? ?i?u khi?n t??ng ?ng" #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 Modified: po/zh_CN.po 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -4066,8 +4066,8 @@ msgid "" msgstr "?? \\\\?\\t?\\n?\\r ? \\uXXXX (Unicode???) ?????????" #: ../src/search.c:322 -msgid "Use multi-_line matching" -msgstr "??????(_L)" +msgid "Use multi-line matchin_g" +msgstr "??????(_G)" #: ../src/search.c:327 msgid "" Modified: po/zh_TW.po 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -4140,7 +4140,7 @@ msgid "" msgstr "?????????? \\\\?\\t,\\n?\\r ? \\uXXXX (?????) " #: ../src/search.c:322 -msgid "Use multi-_line matching" +msgid "Use multi-line matchin_g" msgstr "" #: ../src/search.c:327 -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 6 13:16:42 2015 From: git-noreply at xxxxx (=?utf-8?b?SmnFmcOtIFRlY2hldA==?=) Date: Tue, 06 Oct 2015 13:16:42 -0000 Subject: [geany/geany] 3495cf: Remove saved file's mtime check comparing it with the current time Message-ID: <20151006131641.1B9385C3CEC@mail.geany.org> Branch: refs/heads/master Author: Ji?? Techet Committer: Ji?? Techet Date: Tue, 18 Aug 2015 17:48:12 UTC Commit: 3495cf05abf42e5f08952a19d3798cc27a50b6e6 https://github.com/geany/geany/commit/3495cf05abf42e5f08952a19d3798cc27a50b6e6 Log Message: ----------- Remove saved file's mtime check comparing it with the current time As the edited file can be a remote file on a server with a different time zone, the mtime can actually be in the future. In this case the check not only shows the misleading warning but more importantly the doc->priv->mtime < st.st_mtime check never happens and the user doesn't get the modified file prompt. Setting doc->priv->mtime = time(NULL); to the current time on file creation isn't harmful in any way because the saved file's mtime is taken but it's a bit misleading so better to set it to 0. Modified Paths: -------------- src/document.c Modified: src/document.c 8 lines changed, 1 insertions(+), 7 deletions(-) =================================================================== @@ -885,7 +885,7 @@ GeanyDocument *document_new_file(const gchar *utf8_filename, GeanyFiletype *ft, #ifdef USE_GIO_FILEMON monitor_file_setup(doc); #else - doc->priv->mtime = time(NULL); + doc->priv->mtime = 0; #endif /* "the" SCI signal (connect after initial setup(i.e. adding text)) */ @@ -3617,12 +3617,6 @@ gboolean document_check_disk_status(GeanyDocument *doc, gboolean force) /* doc may be closed now */ ret = TRUE; } - else if (! use_gio_filemon && /* ignore check when using GIO */ - doc->priv->mtime > cur_time) - { - g_warning("%s: Something is wrong with the time stamps.", G_STRFUNC); - /* Note: on Windows st.st_mtime can be newer than cur_time */ - } else if (doc->priv->mtime < st.st_mtime) { /* make sure the user is not prompted again after he cancelled the "reload file?" message */ -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Thu Oct 1 10:09:45 2015 From: git-noreply at xxxxx (=?utf-8?b?SmnFmcOtIFRlY2hldA==?=) Date: Thu, 01 Oct 2015 10:09:45 -0000 Subject: [geany/geany] daf4dd: Don't mix POSIX/GIO operations when opening/saving/stat()ing files Message-ID: <20151006131641.935DF5C4221@mail.geany.org> Branch: refs/heads/master Author: Ji?? Techet Committer: Ji?? Techet Date: Thu, 01 Oct 2015 10:09:45 UTC Commit: daf4dd45b874f7d29e30f8eddd4fbb6cae40e687 https://github.com/geany/geany/commit/daf4dd45b874f7d29e30f8eddd4fbb6cae40e687 Log Message: ----------- Don't mix POSIX/GIO operations when opening/saving/stat()ing files GVFS uses different backends for "native" GIO operations and POSIX operations which use the FUSE backend. If the two kinds of operations are mixed, we may get races. The patch checks the value of file_prefs.use_gio_unsafe_file_saving and based on it either uses GIO operations or POSIX operations for file loading, saving and checking modification time. Modified Paths: -------------- src/document.c Modified: src/document.c 101 lines changed, 72 insertions(+), 29 deletions(-) =================================================================== @@ -80,6 +80,10 @@ #include + +#define USE_GIO_FILE_OPERATIONS (!file_prefs.use_safe_file_saving && file_prefs.use_gio_unsafe_file_saving) + + GeanyFilePrefs file_prefs; @@ -930,12 +934,60 @@ typedef struct } FileData; +static gboolean get_mtime(const gchar *locale_filename, time_t *time) +{ + GError *error = NULL; + const gchar *err_msg = NULL; + + if (USE_GIO_FILE_OPERATIONS) + { + GFile *file = g_file_new_for_path(locale_filename); + GFileInfo *info = g_file_query_info(file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, &error); + + if (info) + { + GTimeVal timeval; + + g_file_info_get_modification_time(info, &timeval); + g_object_unref(info); + *time = timeval.tv_sec; + } + else if (error) + err_msg = error->message; + + g_object_unref(file); + } + else + { + GStatBuf st; + + if (g_stat(locale_filename, &st) == 0) + *time = st.st_mtime; + else + err_msg = g_strerror(errno); + } + + if (err_msg) + { + gchar *utf8_filename = utils_get_utf8_from_locale(locale_filename); + + ui_set_statusbar(TRUE, _("Could not open file %s (%s)"), + utf8_filename, err_msg); + g_free(utf8_filename); + } + + if (error) + g_error_free(error); + + return err_msg == NULL; +} + + /* loads textfile data, verifies and converts to forced_enc or UTF-8. Also handles BOM. */ static gboolean load_text_file(const gchar *locale_filename, const gchar *display_filename, FileData *filedata, const gchar *forced_enc) { GError *err = NULL; - struct stat st; filedata->data = NULL; filedata->len = 0; @@ -943,23 +995,26 @@ static gboolean load_text_file(const gchar *locale_filename, const gchar *displa filedata->bom = FALSE; filedata->readonly = FALSE; - if (g_stat(locale_filename, &st) != 0) - { - ui_set_statusbar(TRUE, _("Could not open file %s (%s)"), - display_filename, g_strerror(errno)); + if (!get_mtime(locale_filename, &filedata->mtime)) return FALSE; - } - filedata->mtime = st.st_mtime; + if (USE_GIO_FILE_OPERATIONS) + { + GFile *file = g_file_new_for_path(locale_filename); + + g_file_load_contents(file, NULL, &filedata->data, &filedata->len, NULL, &err); + g_object_unref(file); + } + else + g_file_get_contents(locale_filename, &filedata->data, &filedata->len, &err); - if (! g_file_get_contents(locale_filename, &filedata->data, NULL, &err)) + if (err) { ui_set_statusbar(TRUE, "%s", err->message); g_error_free(err); return FALSE; } - filedata->len = (gsize) st.st_size; if (! encodings_convert_to_utf8_auto(&filedata->data, &filedata->len, forced_enc, &filedata->enc, &filedata->bom, &filedata->readonly)) { @@ -1583,25 +1638,13 @@ gboolean document_reload_prompt(GeanyDocument *doc, const gchar *forced_enc) } -static gboolean document_update_timestamp(GeanyDocument *doc, const gchar *locale_filename) +static void document_update_timestamp(GeanyDocument *doc, const gchar *locale_filename) { #ifndef USE_GIO_FILEMON - struct stat st; - - g_return_val_if_fail(doc != NULL, FALSE); - - /* stat the file to get the timestamp, otherwise on Windows the actual - * timestamp can be ahead of time(NULL) */ - if (g_stat(locale_filename, &st) != 0) - { - ui_set_statusbar(TRUE, _("Could not open file %s (%s)"), doc->file_name, - g_strerror(errno)); - return FALSE; - } + g_return_if_fail(doc != NULL); - doc->priv->mtime = st.st_mtime; /* get the modification time from file and keep it */ + get_mtime(locale_filename, &doc->priv->mtime); /* get the modification time from file and keep it */ #endif - return TRUE; } @@ -1867,7 +1910,7 @@ static gchar *write_data_to_disk(const gchar *locale_filename, if (g_file_set_contents(locale_filename, data, len, &error)) geany_debug("Wrote %s with g_file_set_contents().", locale_filename); } - else if (file_prefs.use_gio_unsafe_file_saving) + else if (USE_GIO_FILE_OPERATIONS) { GFile *fp; @@ -3583,7 +3626,7 @@ gboolean document_check_disk_status(GeanyDocument *doc, gboolean force) gboolean ret = FALSE; gboolean use_gio_filemon; time_t cur_time = 0; - struct stat st; + time_t mtime; gchar *locale_filename; FileDiskStatus old_status; @@ -3611,16 +3654,16 @@ gboolean document_check_disk_status(GeanyDocument *doc, gboolean force) } locale_filename = utils_get_locale_from_utf8(doc->file_name); - if (g_stat(locale_filename, &st) != 0) + if (!get_mtime(locale_filename, &mtime)) { monitor_resave_missing_file(doc); /* doc may be closed now */ ret = TRUE; } - else if (doc->priv->mtime < st.st_mtime) + else if (doc->priv->mtime < mtime) { /* make sure the user is not prompted again after he cancelled the "reload file?" message */ - doc->priv->mtime = st.st_mtime; + doc->priv->mtime = mtime; monitor_reload_file(doc); /* doc may be closed now */ ret = TRUE; -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 6 13:00:11 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Tue, 06 Oct 2015 13:00:11 -0000 Subject: [geany/geany] 25bd24: Merge pull request #621 from techee/remote_mtime Message-ID: <20151006131642.259E35C3D43@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Tue, 06 Oct 2015 13:00:11 UTC Commit: 25bd24187ba30497109e01938365e8a4ca2a12fd https://github.com/geany/geany/commit/25bd24187ba30497109e01938365e8a4ca2a12fd Log Message: ----------- Merge pull request #621 from techee/remote_mtime Fix the "source file has been modified" issue Closes #605. Modified Paths: -------------- src/document.c Modified: src/document.c 109 lines changed, 73 insertions(+), 36 deletions(-) =================================================================== @@ -80,6 +80,10 @@ #include + +#define USE_GIO_FILE_OPERATIONS (!file_prefs.use_safe_file_saving && file_prefs.use_gio_unsafe_file_saving) + + GeanyFilePrefs file_prefs; @@ -885,7 +889,7 @@ GeanyDocument *document_new_file(const gchar *utf8_filename, GeanyFiletype *ft, #ifdef USE_GIO_FILEMON monitor_file_setup(doc); #else - doc->priv->mtime = time(NULL); + doc->priv->mtime = 0; #endif /* "the" SCI signal (connect after initial setup(i.e. adding text)) */ @@ -930,12 +934,60 @@ typedef struct } FileData; +static gboolean get_mtime(const gchar *locale_filename, time_t *time) +{ + GError *error = NULL; + const gchar *err_msg = NULL; + + if (USE_GIO_FILE_OPERATIONS) + { + GFile *file = g_file_new_for_path(locale_filename); + GFileInfo *info = g_file_query_info(file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, &error); + + if (info) + { + GTimeVal timeval; + + g_file_info_get_modification_time(info, &timeval); + g_object_unref(info); + *time = timeval.tv_sec; + } + else if (error) + err_msg = error->message; + + g_object_unref(file); + } + else + { + GStatBuf st; + + if (g_stat(locale_filename, &st) == 0) + *time = st.st_mtime; + else + err_msg = g_strerror(errno); + } + + if (err_msg) + { + gchar *utf8_filename = utils_get_utf8_from_locale(locale_filename); + + ui_set_statusbar(TRUE, _("Could not open file %s (%s)"), + utf8_filename, err_msg); + g_free(utf8_filename); + } + + if (error) + g_error_free(error); + + return err_msg == NULL; +} + + /* loads textfile data, verifies and converts to forced_enc or UTF-8. Also handles BOM. */ static gboolean load_text_file(const gchar *locale_filename, const gchar *display_filename, FileData *filedata, const gchar *forced_enc) { GError *err = NULL; - struct stat st; filedata->data = NULL; filedata->len = 0; @@ -943,23 +995,26 @@ static gboolean load_text_file(const gchar *locale_filename, const gchar *displa filedata->bom = FALSE; filedata->readonly = FALSE; - if (g_stat(locale_filename, &st) != 0) - { - ui_set_statusbar(TRUE, _("Could not open file %s (%s)"), - display_filename, g_strerror(errno)); + if (!get_mtime(locale_filename, &filedata->mtime)) return FALSE; - } - filedata->mtime = st.st_mtime; + if (USE_GIO_FILE_OPERATIONS) + { + GFile *file = g_file_new_for_path(locale_filename); + + g_file_load_contents(file, NULL, &filedata->data, &filedata->len, NULL, &err); + g_object_unref(file); + } + else + g_file_get_contents(locale_filename, &filedata->data, &filedata->len, &err); - if (! g_file_get_contents(locale_filename, &filedata->data, NULL, &err)) + if (err) { ui_set_statusbar(TRUE, "%s", err->message); g_error_free(err); return FALSE; } - filedata->len = (gsize) st.st_size; if (! encodings_convert_to_utf8_auto(&filedata->data, &filedata->len, forced_enc, &filedata->enc, &filedata->bom, &filedata->readonly)) { @@ -1583,25 +1638,13 @@ gboolean document_reload_prompt(GeanyDocument *doc, const gchar *forced_enc) } -static gboolean document_update_timestamp(GeanyDocument *doc, const gchar *locale_filename) +static void document_update_timestamp(GeanyDocument *doc, const gchar *locale_filename) { #ifndef USE_GIO_FILEMON - struct stat st; - - g_return_val_if_fail(doc != NULL, FALSE); - - /* stat the file to get the timestamp, otherwise on Windows the actual - * timestamp can be ahead of time(NULL) */ - if (g_stat(locale_filename, &st) != 0) - { - ui_set_statusbar(TRUE, _("Could not open file %s (%s)"), doc->file_name, - g_strerror(errno)); - return FALSE; - } + g_return_if_fail(doc != NULL); - doc->priv->mtime = st.st_mtime; /* get the modification time from file and keep it */ + get_mtime(locale_filename, &doc->priv->mtime); /* get the modification time from file and keep it */ #endif - return TRUE; } @@ -1867,7 +1910,7 @@ static gchar *write_data_to_disk(const gchar *locale_filename, if (g_file_set_contents(locale_filename, data, len, &error)) geany_debug("Wrote %s with g_file_set_contents().", locale_filename); } - else if (file_prefs.use_gio_unsafe_file_saving) + else if (USE_GIO_FILE_OPERATIONS) { GFile *fp; @@ -3583,7 +3626,7 @@ gboolean document_check_disk_status(GeanyDocument *doc, gboolean force) gboolean ret = FALSE; gboolean use_gio_filemon; time_t cur_time = 0; - struct stat st; + time_t mtime; gchar *locale_filename; FileDiskStatus old_status; @@ -3611,22 +3654,16 @@ gboolean document_check_disk_status(GeanyDocument *doc, gboolean force) } locale_filename = utils_get_locale_from_utf8(doc->file_name); - if (g_stat(locale_filename, &st) != 0) + if (!get_mtime(locale_filename, &mtime)) { monitor_resave_missing_file(doc); /* doc may be closed now */ ret = TRUE; } - else if (! use_gio_filemon && /* ignore check when using GIO */ - doc->priv->mtime > cur_time) - { - g_warning("%s: Something is wrong with the time stamps.", G_STRFUNC); - /* Note: on Windows st.st_mtime can be newer than cur_time */ - } - else if (doc->priv->mtime < st.st_mtime) + else if (doc->priv->mtime < mtime) { /* make sure the user is not prompted again after he cancelled the "reload file?" message */ - doc->priv->mtime = st.st_mtime; + doc->priv->mtime = mtime; monitor_reload_file(doc); /* doc may be closed now */ ret = TRUE; -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 5 20:11:12 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 05 Oct 2015 20:11:12 -0000 Subject: [geany/geany] 203644: plugins: refactor GtkListStore population code into separate function Message-ID: <20151006135843.357475C4223@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Mon, 05 Oct 2015 20:11:12 UTC Commit: 203644a23397a0f4bca56eff820bac547208561b https://github.com/geany/geany/commit/203644a23397a0f4bca56eff820bac547208561b Log Message: ----------- plugins: refactor GtkListStore population code into separate function Modified Paths: -------------- src/plugins.c Modified: src/plugins.c 57 lines changed, 33 insertions(+), 24 deletions(-) =================================================================== @@ -1278,6 +1278,9 @@ static void pm_selection_changed(GtkTreeSelection *selection, gpointer user_data } +static void pm_populate(GtkListStore *store); + + static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data) { gboolean old_state, state; @@ -1339,6 +1342,33 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer g_free(file_name); } +static void pm_populate(GtkListStore *store) +{ + GtkTreeIter iter; + GList *list; + + gtk_list_store_clear(store); + list = g_list_first(plugin_list); + if (list == NULL) + { + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE, + PLUGIN_COLUMN_PLUGIN, NULL, -1); + } + else + { + for (; list != NULL; list = list->next) + { + Plugin *p = list->data; + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, + PLUGIN_COLUMN_CHECK, is_active_plugin(p), + PLUGIN_COLUMN_PLUGIN, p, + -1); + } + } +} static gboolean pm_treeview_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data) @@ -1504,8 +1534,6 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) GtkCellRenderer *text_renderer, *checkbox_renderer; GtkTreeViewColumn *column; GtkTreeModel *filter_model; - GtkTreeIter iter; - GList *list; GtkTreeSelection *sel; g_signal_connect(tree, "query-tooltip", G_CALLBACK(pm_treeview_query_tooltip), NULL); @@ -1539,27 +1567,6 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) g_signal_connect(tree, "button-press-event", G_CALLBACK(pm_treeview_button_press_cb), NULL); - list = g_list_first(plugin_list); - if (list == NULL) - { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE, - PLUGIN_COLUMN_PLUGIN, NULL, -1); - } - else - { - Plugin *p; - for (; list != NULL; list = list->next) - { - p = list->data; - - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, - PLUGIN_COLUMN_CHECK, is_active_plugin(p), - PLUGIN_COLUMN_PLUGIN, p, - -1); - } - } /* filter */ filter_model = gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL); gtk_tree_model_filter_set_visible_func( @@ -1567,8 +1574,9 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) /* set model to tree view */ gtk_tree_view_set_model(GTK_TREE_VIEW(tree), filter_model); - g_object_unref(store); g_object_unref(filter_model); + + pm_populate(store); } @@ -1679,6 +1687,7 @@ static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data) pm_widgets.store = gtk_list_store_new( PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_POINTER); pm_prepare_treeview(pm_widgets.tree, pm_widgets.store); + g_object_unref(pm_widgets.store); swin = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin), -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 5 20:09:36 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 05 Oct 2015 20:09:36 -0000 Subject: [geany/geany] d00867: plugins: introduce load and unload functions for plugins Message-ID: <20151006135842.11D2A5C4224@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Mon, 05 Oct 2015 20:09:36 UTC Commit: d008675b1bb1360f32360c98f54175783c5f0f2f https://github.com/geany/geany/commit/d008675b1bb1360f32360c98f54175783c5f0f2f Log Message: ----------- plugins: introduce load and unload functions for plugins Currently they encapsulate loading and unloading of standard plugins. In the future plugins can provide such functions to load their types of plugins. Such a dummy proxy plugin is implemented now to load standard plugins so that these aren't going to be specially handled. Modified Paths: -------------- src/plugindata.h src/pluginprivate.h src/plugins.c Modified: src/plugindata.h 9 lines changed, 9 insertions(+), 0 deletions(-) =================================================================== @@ -347,6 +347,15 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify fr geany_plugin_register_full((plugin), GEANY_API_VERSION, \ (min_api_version), GEANY_ABI_VERSION, (pdata), (free_func)) +/* Hooks that need to be implemented for every proxy */ +typedef struct _GeanyProxyFuncs +{ + void (*load) (GeanyPlugin *proxy, GeanyPlugin *subplugins, const gchar *filename, gpointer pdata); + void (*unload) (GeanyPlugin *proxy, GeanyPlugin *subplugins, gpointer pdata); +} +GeanyProxyFuncs; + + /* Deprecated aliases */ #ifndef GEANY_DISABLE_DEPRECATED Modified: src/pluginprivate.h 9 lines changed, 6 insertions(+), 3 deletions(-) =================================================================== @@ -46,6 +46,8 @@ typedef enum _LoadedFlags { } LoadedFlags; +typedef struct GeanyPluginPrivate Plugin; /* shorter alias */ + typedef struct GeanyPluginPrivate { GModule *module; @@ -66,6 +68,10 @@ typedef struct GeanyPluginPrivate gpointer cb_data; /* user data passed back to functions in GeanyPluginFuncs */ GDestroyNotify cb_data_destroy; /* called when the plugin is unloaded, for cb_data */ LoadedFlags flags; /* bit-or of LoadedFlags */ + + /* proxy plugin support */ + GeanyProxyFuncs proxy_cbs; + Plugin *proxy; /* The proxy that handles this plugin */ } GeanyPluginPrivate; @@ -73,9 +79,6 @@ GeanyPluginPrivate; #define PLUGIN_IS_LEGACY(p) (((p)->flags & IS_LEGACY) != 0) #define PLUGIN_HAS_LOAD_DATA(p) (((p)->flags & LOAD_DATA) != 0) -typedef GeanyPluginPrivate Plugin; /* shorter alias */ - - void plugin_watch_object(Plugin *plugin, gpointer object); G_END_DECLS Modified: src/plugins.c 237 lines changed, 162 insertions(+), 75 deletions(-) =================================================================== @@ -75,8 +75,31 @@ static GtkWidget *menu_separator = NULL; static gchar *get_plugin_path(void); static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data); -static GeanyData geany_data; +typedef struct { + gchar extension[8]; + Plugin *plugin; /* &builtin_so_proxy_plugin for native plugins */ +} PluginProxy; + + +static void plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *plugin, const gchar *filename, gpointer pdata); +static void plugin_unload_gmodule(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer pdata); + +static Plugin builtin_so_proxy_plugin = { + .proxy_cbs = { + .load = plugin_load_gmodule, + .unload = plugin_unload_gmodule, + }, + /* rest of Plugin can be NULL/0 */ +}; + +static PluginProxy builtin_so_proxy = { + .extension = G_MODULE_SUFFIX, + .plugin = &builtin_so_proxy_plugin, +}; +static GPtrArray *active_proxies = NULL; + +static GeanyData geany_data; static void geany_data_init(void) @@ -105,16 +128,15 @@ geany_data_init(void) /* Prevent the same plugin filename being loaded more than once. * Note: g_module_name always returns the .so name, even when Plugin::filename is a .la file. */ static gboolean -plugin_loaded(GModule *module) +plugin_loaded(Plugin *plugin) { gchar *basename_module, *basename_loaded; GList *item; - basename_module = g_path_get_basename(g_module_name(module)); + basename_module = g_path_get_basename(plugin->filename); for (item = plugin_list; item != NULL; item = g_list_next(item)) { - basename_loaded = g_path_get_basename( - g_module_name(((Plugin*)item->data)->module)); + basename_loaded = g_path_get_basename(((Plugin*)item->data)->filename); if (utils_str_equal(basename_module, basename_loaded)) { @@ -131,7 +153,7 @@ plugin_loaded(GModule *module) * would cause a crash. */ for (item = active_plugin_list; item != NULL; item = g_list_next(item)) { - basename_loaded = g_path_get_basename(g_module_name(((Plugin*)item->data)->module)); + basename_loaded = g_path_get_basename(((Plugin*)item->data)->filename); if (utils_str_equal(basename_module, basename_loaded)) { @@ -168,19 +190,19 @@ static Plugin *find_active_plugin_by_name(const gchar *filename) static gboolean plugin_check_version(Plugin *plugin, int plugin_version_code) { - GModule *module = plugin->module; + const gchar *name = g_module_name(plugin->module); if (plugin_version_code < 0) { msgwin_status_add(_("The plugin \"%s\" is not binary compatible with this " - "release of Geany - please recompile it."), g_module_name(module)); + "release of Geany - please recompile it."), name); geany_debug("Plugin \"%s\" is not binary compatible with this " - "release of Geany - recompile it.", g_module_name(module)); + "release of Geany - recompile it.", name); return FALSE; } - if (plugin_version_code > GEANY_API_VERSION) + else if (plugin_version_code > GEANY_API_VERSION) { geany_debug("Plugin \"%s\" requires a newer version of Geany (API >= v%d).", - g_module_name(module), plugin_version_code); + name, plugin_version_code); return FALSE; } return TRUE; @@ -425,7 +447,7 @@ static void register_legacy_plugin(Plugin *plugin, GModule *module) if (! g_module_symbol(module, "plugin_" #__x, (void *) (&p_##__x))) \ { \ geany_debug("Plugin \"%s\" has no plugin_" #__x "() function - ignoring plugin!", \ - g_module_name(plugin->module)); \ + g_module_name(module)); \ return; \ } CHECK_FUNC(version_check); @@ -481,8 +503,9 @@ static gboolean plugin_load(Plugin *plugin) { gboolean init_ok = TRUE; + /* Start the plugin. Legacy plugins require additional cruft. */ - if (PLUGIN_IS_LEGACY(plugin)) + if (PLUGIN_IS_LEGACY(plugin) && plugin->proxy == &builtin_so_proxy_plugin) { GeanyPlugin **p_geany_plugin; PluginInfo **p_info; @@ -531,20 +554,70 @@ plugin_load(Plugin *plugin) } +static void plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *fname, gpointer pdata) +{ + GModule *module; + void (*p_geany_load_module)(GeanyPlugin *); + + g_return_val_if_fail(g_module_supported(), NULL); + /* Don't use G_MODULE_BIND_LAZY otherwise we can get unresolved symbols at runtime, + * causing a segfault. Without that flag the module will safely fail to load. + * G_MODULE_BIND_LOCAL also helps find undefined symbols e.g. app when it would + * otherwise not be detected due to the shadowing of Geany's app variable. + * Also without G_MODULE_BIND_LOCAL calling public functions e.g. the old info() + * function from a plugin will be shadowed. */ + module = g_module_open(fname, G_MODULE_BIND_LOCAL); + if (!module) + { + geany_debug("Can't load plugin: %s", g_module_error()); + return; + } + + subplugin->priv->module = module; + /*geany_debug("Initializing plugin '%s'", plugin->info.name);*/ + g_module_symbol(module, "geany_load_module", (void *) &p_geany_load_module); + if (p_geany_load_module) + { + /* This is a new style plugin. It should fill in plugin->info and then call + * geany_plugin_register() in its geany_load_module() to successfully load. + * The ABI and API checks are performed by geany_plugin_register() (i.e. by us). + * We check the LOADED_OK flag separately to protect us against buggy plugins + * who ignore the result of geany_plugin_register() and register anyway */ + p_geany_load_module(subplugin); + } + else + { + /* This is the legacy / deprecated code path. It does roughly the same as + * geany_load_module() and geany_plugin_register() together for the new ones */ + register_legacy_plugin(subplugin->priv, module); + } + /* We actually check the LOADED_OK flag later */ +} + + +static void plugin_unload_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer pdata) +{ + GModule *module = subplugin->priv->module; + + g_return_if_fail(module); + + if (! g_module_close(module)) + g_warning("%s: %s", subplugin->priv->filename, g_module_error()); +} + + /* Load and optionally init a plugin. * load_plugin decides whether the plugin's plugin_init() function should be called or not. If it is * called, the plugin will be started, if not the plugin will be read only (for the list of * available plugins in the plugin manager). * When add_to_list is set, the plugin will be added to the plugin manager's plugin_list. */ static Plugin* -plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list) +plugin_new(Plugin *proxy, const gchar *fname, gboolean load_plugin, gboolean add_to_list) { Plugin *plugin; - GModule *module; - void (*p_geany_load_module)(GeanyPlugin *); g_return_val_if_fail(fname, NULL); - g_return_val_if_fail(g_module_supported(), NULL); + g_return_val_if_fail(proxy, NULL); /* find the plugin in the list of already loaded, active plugins and use it, otherwise * load the module */ @@ -563,64 +636,37 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list) return plugin; } - /* Don't use G_MODULE_BIND_LAZY otherwise we can get unresolved symbols at runtime, - * causing a segfault. Without that flag the module will safely fail to load. - * G_MODULE_BIND_LOCAL also helps find undefined symbols e.g. app when it would - * otherwise not be detected due to the shadowing of Geany's app variable. - * Also without G_MODULE_BIND_LOCAL calling public functions e.g. the old info() - * function from a plugin will be shadowed. */ - module = g_module_open(fname, G_MODULE_BIND_LOCAL); - if (! module) - { - geany_debug("Can't load plugin: %s", g_module_error()); - return NULL; - } - - if (plugin_loaded(module)) - { - geany_debug("Plugin \"%s\" already loaded.", fname); - - if (! g_module_close(module)) - g_warning("%s: %s", fname, g_module_error()); - return NULL; - } - plugin = g_new0(Plugin, 1); - plugin->module = module; plugin->filename = g_strdup(fname); + plugin->proxy = proxy; plugin->public.geany_data = &geany_data; plugin->public.priv = plugin; /* Fields of plugin->info/funcs must to be initialized by the plugin */ plugin->public.info = &plugin->info; plugin->public.funcs = &plugin->cbs; - g_module_symbol(module, "geany_load_module", (void *) &p_geany_load_module); - if (p_geany_load_module) - { - /* This is a new style plugin. It should fill in plugin->info and then call - * geany_plugin_register() in its geany_load_module() to successfully load. - * The ABI and API checks are performed by geany_plugin_register() (i.e. by us). - * We check the LOADED_OK flag separately to protect us against buggy plugins - * who ignore the result of geany_plugin_register() and register anyway */ - p_geany_load_module(&plugin->public); - } - else + if (plugin_loaded(plugin)) { - /* This is the legacy / deprecated code path. It does roughly the same as - * geany_load_module() and geany_plugin_register() together for the new ones */ - register_legacy_plugin(plugin, module); + geany_debug("Plugin \"%s\" already loaded.", fname); + goto err; } + /* Load plugin, this should read its name etc. It must also call + * geany_plugin_register() for the following PLUGIN_LOADED_OK condition */ + proxy->proxy_cbs.load(&proxy->public, &plugin->public, fname, proxy->cb_data); + if (! PLUGIN_LOADED_OK(plugin)) { geany_debug("Failed to load \"%s\" - ignoring plugin!", fname); goto err; } + /* The proxy assumes success, therefore we have to call unload from here + * on in case of errors */ if (EMPTY(plugin->info.name)) { geany_debug("No plugin name set for \"%s\" - ignoring plugin!", fname); - goto err; + goto err_unload; } if (load_plugin && !plugin_load(plugin)) @@ -628,7 +674,7 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list) /* Handle failing init same as failing to load for now. In future we * could present a informational UI or something */ geany_debug("Plugin failed to initialize \"%s\" - ignoring plugin!", fname); - goto err; + goto err_unload; } if (add_to_list) @@ -636,11 +682,11 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list) return plugin; -err: +err_unload: if (plugin->cb_data_destroy) plugin->cb_data_destroy(plugin->cb_data); - if (! g_module_close(module)) - g_warning("%s: %s", fname, g_module_error()); + proxy->proxy_cbs.unload(&proxy->public, &plugin->public, proxy->cb_data); +err: g_free(plugin->filename); g_free(plugin); return NULL; @@ -754,21 +800,19 @@ plugin_cleanup(Plugin *plugin) static void plugin_free(Plugin *plugin) { + Plugin *proxy; + g_return_if_fail(plugin); - g_return_if_fail(plugin->module); + g_return_if_fail(plugin->proxy); + proxy = plugin->proxy; if (is_active_plugin(plugin)) plugin_cleanup(plugin); active_plugin_list = g_list_remove(active_plugin_list, plugin); plugin_list = g_list_remove(plugin_list, plugin); - /* cb_data_destroy might be plugin code and must be called before unloading the module */ - if (plugin->cb_data_destroy) - plugin->cb_data_destroy(plugin->cb_data); - - if (! g_module_close(plugin->module)) - g_warning("%s: %s", plugin->filename, g_module_error()); + proxy->proxy_cbs.unload(&proxy->public, &plugin->public, proxy->cb_data); g_free(plugin->filename); g_free(plugin); @@ -830,6 +874,36 @@ static gboolean check_plugin_path(const gchar *fname) } +/* Retuns NULL if this ain't a plugin, + * otherwise it returns the appropriate PluginProxy instance to load it */ +static PluginProxy* is_plugin(const gchar *file) +{ + PluginProxy *proxy; + const gchar *ext; + guint i; + + /* extract file extension to avoid g_str_has_suffix() in the loop */ + ext = (const gchar *)strrchr(file, '.'); + if (ext == NULL) + return FALSE; + /* ensure the dot is really part of the filename */ + else if (strchr(ext, G_DIR_SEPARATOR) != NULL) + return FALSE; + + ext += 1; + /* O(n*m), (m being extensions per proxy) doesn't scale very well in theory + * but not a problem in practice yet */ + foreach_ptr_array(proxy, i, active_proxies) + { + if (utils_str_casecmp(ext, proxy->extension) == 0) + { + return proxy; + } + } + return NULL; +} + + /* load active plugins at startup */ static void load_active_plugins(void) @@ -843,9 +917,19 @@ load_active_plugins(void) { const gchar *fname = active_plugins_pref[i]; +#ifdef G_OS_WIN32 + /* ensure we have canonical paths */ + gchar *p = fname; + while ((p = strchr(p, '/')) != NULL) + *p = G_DIR_SEPARATOR; +#endif + if (!EMPTY(fname) && g_file_test(fname, G_FILE_TEST_EXISTS)) { - if (!check_plugin_path(fname) || plugin_new(fname, TRUE, FALSE) == NULL) + PluginProxy *proxy = NULL; + if (check_plugin_path(fname)) + proxy = is_plugin(fname); + if (proxy == NULL || plugin_new(proxy->plugin, fname, TRUE, FALSE) == NULL) failed_plugins_list = g_list_prepend(failed_plugins_list, g_strdup(fname)); } } @@ -856,20 +940,18 @@ static void load_plugins_from_path(const gchar *path) { GSList *list, *item; - gchar *fname, *tmp; gint count = 0; list = utils_get_file_list(path, NULL, NULL); for (item = list; item != NULL; item = g_slist_next(item)) { - tmp = strrchr(item->data, '.'); - if (tmp == NULL || utils_str_casecmp(tmp, "." G_MODULE_SUFFIX) != 0) - continue; + gchar *fname = g_build_filename(path, item->data, NULL); + PluginProxy *proxy = is_plugin(fname); - fname = g_build_filename(path, item->data, NULL); - if (plugin_new(fname, FALSE, TRUE)) + if (proxy != NULL && plugin_new(proxy->plugin, fname, FALSE, TRUE)) count++; + g_free(fname); } @@ -1028,6 +1110,9 @@ void plugins_init(void) g_signal_connect(geany_object, "save-settings", G_CALLBACK(update_active_plugins_pref), NULL); stash_group_add_string_vector(group, &active_plugins_pref, "active_plugins", NULL); + + active_proxies = g_ptr_array_sized_new(1); + g_ptr_array_add(active_proxies, &builtin_so_proxy); } @@ -1146,6 +1231,7 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer GtkTreePath *path = gtk_tree_path_new_from_string(pth); GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_widgets.tree)); Plugin *p; + Plugin *proxy; gtk_tree_model_get_iter(model, &iter, path); gtk_tree_path_free(path); @@ -1163,8 +1249,9 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer state = ! old_state; /* toggle the state */ - /* save the filename of the plugin */ + /* save the filename and proxy of the plugin */ file_name = g_strdup(p->filename); + proxy = p->proxy; /* unload plugin module */ if (!state) @@ -1174,7 +1261,7 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer plugin_free(p); /* reload plugin module and initialize it if item is checked */ - p = plugin_new(file_name, state, TRUE); + p = plugin_new(proxy, file_name, state, TRUE); if (!p) { /* plugin file may no longer be on disk, or is now incompatible */ -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 5 20:11:12 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 05 Oct 2015 20:11:12 -0000 Subject: [geany/geany] bdaab9: plugins: generic load_data instead of module pointer in Plugin struct Message-ID: <20151006135842.A577D5C4222@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Mon, 05 Oct 2015 20:11:12 UTC Commit: bdaab9c8378913016d8bc6c76d2100e6d6a9baab https://github.com/geany/geany/commit/bdaab9c8378913016d8bc6c76d2100e6d6a9baab Log Message: ----------- plugins: generic load_data instead of module pointer in Plugin struct Being a GModule is actually a detail of standard plugins. Future proxy plugins might need different handles. Therefore replace the module field with a more generic pointer and encapsulate the GModule detail further. This pointer shall be returned from GeanyProxyFuncs::load and is passed back to GeanyProxyFuncs::unload, and isn't interpreted by Geany. Modified Paths: -------------- src/plugindata.h src/pluginprivate.h src/plugins.c src/pluginutils.c Modified: src/plugindata.h 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -350,8 +350,8 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify fr /* Hooks that need to be implemented for every proxy */ typedef struct _GeanyProxyFuncs { - void (*load) (GeanyPlugin *proxy, GeanyPlugin *subplugins, const gchar *filename, gpointer pdata); - void (*unload) (GeanyPlugin *proxy, GeanyPlugin *subplugins, gpointer pdata); + gpointer (*load) (GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata); + void (*unload) (GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata); } GeanyProxyFuncs; Modified: src/pluginprivate.h 5 lines changed, 4 insertions(+), 1 deletions(-) =================================================================== @@ -50,7 +50,6 @@ typedef struct GeanyPluginPrivate Plugin; /* shorter alias */ typedef struct GeanyPluginPrivate { - GModule *module; gchar *filename; /* plugin filename (/path/libname.so) */ PluginInfo info; /* plugin name, description, etc */ GeanyPlugin public; /* fields the plugin can read */ @@ -72,6 +71,8 @@ typedef struct GeanyPluginPrivate /* proxy plugin support */ GeanyProxyFuncs proxy_cbs; Plugin *proxy; /* The proxy that handles this plugin */ + gpointer proxy_data; /* Data passed to the proxy hooks of above proxy, so + * this gives the proxy a pointer to each plugin */ } GeanyPluginPrivate; @@ -80,6 +81,8 @@ GeanyPluginPrivate; #define PLUGIN_HAS_LOAD_DATA(p) (((p)->flags & LOAD_DATA) != 0) void plugin_watch_object(Plugin *plugin, gpointer object); +void plugin_make_resident(Plugin *plugin); +gpointer plugin_get_module_symbol(Plugin *plugin, const gchar *sym); G_END_DECLS Modified: src/plugins.c 100 lines changed, 78 insertions(+), 22 deletions(-) =================================================================== @@ -81,8 +81,8 @@ typedef struct { } PluginProxy; -static void plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *plugin, const gchar *filename, gpointer pdata); -static void plugin_unload_gmodule(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer pdata); +static gpointer plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *plugin, const gchar *filename, gpointer pdata); +static void plugin_unload_gmodule(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer load_data, gpointer pdata); static Plugin builtin_so_proxy_plugin = { .proxy_cbs = { @@ -190,22 +190,27 @@ static Plugin *find_active_plugin_by_name(const gchar *filename) static gboolean plugin_check_version(Plugin *plugin, int plugin_version_code) { - const gchar *name = g_module_name(plugin->module); + gboolean ret = TRUE; if (plugin_version_code < 0) { + gchar *name = g_path_get_basename(plugin->filename); msgwin_status_add(_("The plugin \"%s\" is not binary compatible with this " "release of Geany - please recompile it."), name); geany_debug("Plugin \"%s\" is not binary compatible with this " "release of Geany - recompile it.", name); - return FALSE; + ret = FALSE; + g_free(name); } else if (plugin_version_code > GEANY_API_VERSION) { + gchar *name = g_path_get_basename(plugin->filename); geany_debug("Plugin \"%s\" requires a newer version of Geany (API >= v%d).", name, plugin_version_code); - return FALSE; + ret = FALSE; + g_free(name); } - return TRUE; + + return ret; } @@ -239,9 +244,10 @@ static void read_key_group(Plugin *plugin) { GeanyKeyGroupInfo *p_key_info; GeanyKeyGroup **p_key_group; + GModule *module = plugin->proxy_data; - g_module_symbol(plugin->module, "plugin_key_group_info", (void *) &p_key_info); - g_module_symbol(plugin->module, "plugin_key_group", (void *) &p_key_group); + g_module_symbol(module, "plugin_key_group_info", (void *) &p_key_info); + g_module_symbol(module, "plugin_key_group", (void *) &p_key_group); if (p_key_info && p_key_group) { GeanyKeyGroupInfo *key_info = p_key_info; @@ -329,8 +335,10 @@ gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_a /* Only init and cleanup callbacks are truly mandatory. */ if (! cbs->init || ! cbs->cleanup) { - geany_debug("Plugin '%s' has no %s function - ignoring plugin!", - g_module_name(p->module), cbs->init ? "cleanup" : "init"); + gchar *name = g_path_get_basename(p->filename); + geany_debug("Plugin '%s' has no %s function - ignoring plugin!", name, + cbs->init ? "cleanup" : "init"); + g_free(name); } else { @@ -510,15 +518,16 @@ plugin_load(Plugin *plugin) GeanyPlugin **p_geany_plugin; PluginInfo **p_info; PluginFields **plugin_fields; + GModule *module = plugin->proxy_data; /* set these symbols before plugin_init() is called * we don't set geany_data since it is set directly by plugin_new() */ - g_module_symbol(plugin->module, "geany_plugin", (void *) &p_geany_plugin); + g_module_symbol(module, "geany_plugin", (void *) &p_geany_plugin); if (p_geany_plugin) *p_geany_plugin = &plugin->public; - g_module_symbol(plugin->module, "plugin_info", (void *) &p_info); + g_module_symbol(module, "plugin_info", (void *) &p_info); if (p_info) *p_info = &plugin->info; - g_module_symbol(plugin->module, "plugin_fields", (void *) &plugin_fields); + g_module_symbol(module, "plugin_fields", (void *) &plugin_fields); if (plugin_fields) *plugin_fields = &plugin->fields; read_key_group(plugin); @@ -554,7 +563,7 @@ plugin_load(Plugin *plugin) } -static void plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *fname, gpointer pdata) +static gpointer plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *fname, gpointer pdata) { GModule *module; void (*p_geany_load_module)(GeanyPlugin *); @@ -570,10 +579,9 @@ static void plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, cons if (!module) { geany_debug("Can't load plugin: %s", g_module_error()); - return; + return NULL; } - subplugin->priv->module = module; /*geany_debug("Initializing plugin '%s'", plugin->info.name);*/ g_module_symbol(module, "geany_load_module", (void *) &p_geany_load_module); if (p_geany_load_module) @@ -592,14 +600,15 @@ static void plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, cons register_legacy_plugin(subplugin->priv, module); } /* We actually check the LOADED_OK flag later */ + return module; } -static void plugin_unload_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer pdata) +static void plugin_unload_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata) { - GModule *module = subplugin->priv->module; + GModule *module = (GModule *) load_data; - g_return_if_fail(module); + g_return_if_fail(module != NULL); if (! g_module_close(module)) g_warning("%s: %s", subplugin->priv->filename, g_module_error()); @@ -653,7 +662,7 @@ plugin_new(Plugin *proxy, const gchar *fname, gboolean load_plugin, gboolean add /* Load plugin, this should read its name etc. It must also call * geany_plugin_register() for the following PLUGIN_LOADED_OK condition */ - proxy->proxy_cbs.load(&proxy->public, &plugin->public, fname, proxy->cb_data); + plugin->proxy_data = proxy->proxy_cbs.load(&proxy->public, &plugin->public, fname, proxy->cb_data); if (! PLUGIN_LOADED_OK(plugin)) { @@ -669,6 +678,16 @@ plugin_new(Plugin *proxy, const gchar *fname, gboolean load_plugin, gboolean add goto err_unload; } + /* cb_data_destroy() frees plugin->cb_data. If that pointer also passed to unload() afterwards + * then that would become a use-after-free. Disallow this combination. If a proxy + * needs the same pointer it must not use a destroy func but free manually in its unload(). */ + if (plugin->proxy_data == proxy->cb_data && plugin->cb_data_destroy) + { + geany_debug("Proxy of plugin \"%s\" specified invalid data - ignoring plugin!", fname); + plugin->proxy_data = NULL; + goto err_unload; + } + if (load_plugin && !plugin_load(plugin)) { /* Handle failing init same as failing to load for now. In future we @@ -685,7 +704,7 @@ plugin_new(Plugin *proxy, const gchar *fname, gboolean load_plugin, gboolean add err_unload: if (plugin->cb_data_destroy) plugin->cb_data_destroy(plugin->cb_data); - proxy->proxy_cbs.unload(&proxy->public, &plugin->public, proxy->cb_data); + proxy->proxy_cbs.unload(&proxy->public, &plugin->public, plugin->proxy_data, proxy->cb_data); err: g_free(plugin->filename); g_free(plugin); @@ -758,6 +777,40 @@ static void remove_sources(Plugin *plugin) } +/* Make the GModule backing plugin resident (if it's GModule-backed at all) */ +void plugin_make_resident(Plugin *plugin) +{ + if (plugin->proxy == &builtin_so_proxy_plugin) + { + g_return_if_fail(plugin->proxy_data != NULL); + g_module_make_resident(plugin->proxy_data); + } + else + g_warning("Skipping g_module_make_resident() for non-native plugin"); +} + + +/* Retrieve the address of a symbol sym located in plugin, if it's GModule-backed */ +gpointer plugin_get_module_symbol(Plugin *plugin, const gchar *sym) +{ + gpointer symbol; + + if (plugin->proxy == &builtin_so_proxy_plugin) + { + g_return_val_if_fail(plugin->proxy_data != NULL, NULL); + if (g_module_symbol(plugin->proxy_data, sym, &symbol)) + return symbol; + else + g_warning("Failed to locate signal handler for '%s': %s", + sym, g_module_error()); + } + else /* TODO: Could possibly support this via a new proxy hook */ + g_warning("Failed to locate signal handler for '%s': Not supported for non-native plugins", + sym); + return NULL; +} + + static gboolean is_active_plugin(Plugin *plugin) { return (g_list_find(active_plugin_list, plugin) != NULL); @@ -812,7 +865,10 @@ plugin_free(Plugin *plugin) active_plugin_list = g_list_remove(active_plugin_list, plugin); plugin_list = g_list_remove(plugin_list, plugin); - proxy->proxy_cbs.unload(&proxy->public, &plugin->public, proxy->cb_data); + /* cb_data_destroy might be plugin code and must be called before unloading the module. */ + if (plugin->cb_data_destroy) + plugin->cb_data_destroy(plugin->cb_data); + proxy->proxy_cbs.unload(&proxy->public, &plugin->public, plugin->proxy_data, proxy->cb_data); g_free(plugin->filename); g_free(plugin); Modified: src/pluginutils.c 11 lines changed, 2 insertions(+), 9 deletions(-) =================================================================== @@ -96,8 +96,7 @@ GEANY_API_SYMBOL void plugin_module_make_resident(GeanyPlugin *plugin) { g_return_if_fail(plugin); - - g_module_make_resident(plugin->priv->module); + plugin_make_resident(plugin->priv); } @@ -444,12 +443,7 @@ static void connect_plugin_signals(GtkBuilder *builder, GObject *object, gpointer symbol = NULL; struct BuilderConnectData *data = user_data; - if (!g_module_symbol(data->plugin->priv->module, handler_name, &symbol)) - { - g_warning("Failed to locate signal handler for '%s': %s", - signal_name, g_module_error()); - return; - } + symbol = plugin_get_module_symbol(data->plugin->priv, handler_name); plugin_signal_connect(data->plugin, object, signal_name, FALSE, G_CALLBACK(symbol) /*ub?*/, data->user_data); @@ -503,7 +497,6 @@ void plugin_builder_connect_signals(GeanyPlugin *plugin, struct BuilderConnectData data = { NULL }; g_return_if_fail(plugin != NULL && plugin->priv != NULL); - g_return_if_fail(plugin->priv->module != NULL); g_return_if_fail(GTK_IS_BUILDER(builder)); data.user_data = user_data; -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 5 20:11:12 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 05 Oct 2015 20:11:12 -0000 Subject: [geany/geany] e5bb65: plugins: when loading active ones, loop until no more proxy plugins are added Message-ID: <20151006135843.BF8E85C3CEC@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Mon, 05 Oct 2015 20:11:12 UTC Commit: e5bb6571c60be2169c3d66ae38c72d72709bf10e https://github.com/geany/geany/commit/e5bb6571c60be2169c3d66ae38c72d72709bf10e Log Message: ----------- plugins: when loading active ones, loop until no more proxy plugins are added During the loading of the active plugins they are also initialized (done at startup). As a result, these plugins could be pluxys and make more plugins available, some of which may be active as well. Because of this the loop has to be restarted if pluxies become available to also load active plugins that depend on the pluxy. The loop is only restarted at the end so only nested pluxys could possibly cause the loop to be run more than twice. Modified Paths: -------------- src/plugins.c Modified: src/plugins.c 37 lines changed, 22 insertions(+), 15 deletions(-) =================================================================== @@ -964,31 +964,38 @@ static PluginProxy* is_plugin(const gchar *file) static void load_active_plugins(void) { - guint i, len; + guint i, len, proxies; if (active_plugins_pref == NULL || (len = g_strv_length(active_plugins_pref)) == 0) return; - for (i = 0; i < len; i++) + /* If proxys are loaded we have to restart to load plugins that sort before their proxy */ + do { - const gchar *fname = active_plugins_pref[i]; + proxies = active_proxies->len; + g_list_free_full(failed_plugins_list, (GDestroyNotify) g_free); + failed_plugins_list = NULL; + for (i = 0; i < len; i++) + { + gchar *fname = active_plugins_pref[i]; #ifdef G_OS_WIN32 - /* ensure we have canonical paths */ - gchar *p = fname; - while ((p = strchr(p, '/')) != NULL) - *p = G_DIR_SEPARATOR; + /* ensure we have canonical paths */ + gchar *p = fname; + while ((p = strchr(p, '/')) != NULL) + *p = G_DIR_SEPARATOR; #endif - if (!EMPTY(fname) && g_file_test(fname, G_FILE_TEST_EXISTS)) - { - PluginProxy *proxy = NULL; - if (check_plugin_path(fname)) - proxy = is_plugin(fname); - if (proxy == NULL || plugin_new(proxy->plugin, fname, TRUE, FALSE) == NULL) - failed_plugins_list = g_list_prepend(failed_plugins_list, g_strdup(fname)); + if (!EMPTY(fname) && g_file_test(fname, G_FILE_TEST_EXISTS)) + { + PluginProxy *proxy = NULL; + if (check_plugin_path(fname)) + proxy = is_plugin(fname); + if (proxy == NULL || plugin_new(proxy->plugin, fname, TRUE, FALSE) == NULL) + failed_plugins_list = g_list_prepend(failed_plugins_list, g_strdup(fname)); + } } - } + } while (proxies != active_proxies->len); } -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 5 20:11:12 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 05 Oct 2015 20:11:12 -0000 Subject: [geany/geany] 6e5ca6: plugins: add geany_plugin_register_proxy() to the plugin API Message-ID: <20151006135845.0834E5C4224@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Mon, 05 Oct 2015 20:11:12 UTC Commit: 6e5ca69e2e9ddbfc2cc44527a1aa5fca8db09167 https://github.com/geany/geany/commit/6e5ca69e2e9ddbfc2cc44527a1aa5fca8db09167 Log Message: ----------- plugins: add geany_plugin_register_proxy() to the plugin API This function finally allows plugins to register themselves as a proxy for one or more file extensions. Lots of documentation is added to doc/plugins.dox, please refer to that for more details. Modified Paths: -------------- doc/plugins.dox src/plugindata.h src/plugins.c Modified: doc/plugins.dox 397 lines changed, 397 insertions(+), 0 deletions(-) =================================================================== @@ -43,6 +43,7 @@ GeanyFuncs::cleanup functions). @section pluginsupport Plugin Support - @link howto Plugin HowTo @endlink - get started +- @ref proxy - @ref legacy - @link plugindata.h Plugin Datatypes and Macros @endlink - @link pluginsignals.c Plugin Signals @endlink @@ -725,4 +726,400 @@ void geany_load_module(GeanyPlugin *plugin) @endcode + at page proxy Proxy Plugin HowTo + + at section proxy_intro Introduction + +Geany has built-in support for plugins. These plugins can alter the way Geany operates in many +imaginable ways which leaves little to be desired. + +However, there is one significant short-coming. Due to the infrastructure, Geany's built-in support +only covers plugins written in C, perhaps C++ and Vala. Basically all languages which can be +compiled into native shared libraries and can link GTK libraries. This excludes dynamic languages +such as Python. + +Geany provides a mechanism to enable support for those languages. Native plugins can register as +proxy plugins by being a normal plugin to the Geany-side and by providing a bridge to write plugins +in another language on the other side. + +These plugins are also called sub-plugins. This refers to the relation to their proxy. +To Geany they are first-class citizens. + + at section proxy_protocol Writing a Proxy Plugin + +The basic idea is that a proxy plugin provides methods to match, load and unload one or more +sub-plugin plugins in an abstract manner: + + - Matching consists of providing a list of supported file extensions for the sub-plugins and + a mechanism to resolve file extension uncertainty or ambiguity. The matching makes the plugin + visible to the user within the Plugin Manager. + - Loading consists of loading the sub-plugin's file, passing the file to some form of interpreter + and calling GEANY_PLUGIN_REGISTER() or GEANY_PLUGIN_REGISTER_FULL() on behalf of the sub-plugin + at some point. + - Unloading simply reverses the effect of loading. + +For providing these methods, GeanyPlugin has a field GeanyProxyFuncs which contains three function +pointers which must be initialized proir to calling geany_plugin_register_proxy(). This should be +done in the GeanyPluginFuncs::init function of the proxy plugin. + + - In the call to geany_plugin_register_proxy() the proxy plugin passes a list of file extensions. + When Geany scans through its plugin directories as usual it will also look for files with + that extensions and consider found files as plugin candidate. + - GeanyProxyFuncs::probe may be implemented to probe if a plugin candidate (that has one of the + provided file extensions) is actually a plugin. This may depend on the plugin file itself in + case of ambiguity or availability of runtime dependencies or even configuration. + @ref PROXY_IGNORED or @ref PROXY_MATCHED should be returned, possibly in combination + with the @ref PROXY_NOLOAD flag. Not implementing GeanyProxyFuncs::probe at all is eqivalent to + always returning @ref PROXY_MATCHED. + - GeanyProxyFuncs::load must be implemented to actually load the plugin. It is called by Geany + when the user enables the sub-plugin. What "loading" means is entirely up to the proxy plugin and + probably depends on the interpreter of the dynamic language that shall be supported. After + setting everything up as necessary GEANY_PLUGIN_REGISTER() or GEANY_PLUGIN_REGISTER_FULL() must + be called to register the sub-plugin. + - GeanyProxyFuncs::unload must be implemented and is called when the user unchecks the sub-plugin + or when Geany exits. Here, the proxy should release any references or memory associated to the + sub-plugin. Note that if GeanyProxyFuncs::load didn't succeed, i.e. didn't successfully register + the sub-plugin, then this function won't be called. + +GeanyProxyFuncs::load and GeanyProxyFuncs::unload receive two GeanyPlugin pointers: One that +corresponds to the proxy itself and another that corresponds to the sub-plugin. The sub-plugin's +one may be used to call various API functions on behalf of the sub-plugin, including +GEANY_PLUGIN_REGISTER() and GEANY_PLUGIN_REGISTER_FULL(). + +GeanyProxyFuncs::load may return a pointer that is passed back to GeanyProxyFuncs::unload. This can +be used to store proxy-defined but sub-plugin-specific data required for unloading. However, this +pointer is not passed to the sub-plugin's GeanyPluginFuncs. To arrange for that, you want to call +GEANY_PLUGIN_REGISTER_FULL(). This method is the key to enable proxy plugins to wrap the +GeanyPluginFuncs of all sub-plugins and yet multiplex between multiple sub-plugin, for example by +storing a per-sub-plugin interpreter context. + + at note If the pointer returned from GeanyProxyFuncs::load is the same that is passed to +GEANY_PLUGIN_REGISTER_FULL() then you must pass NULL as free_func, because that would be invoked +prior to unloading. Insert the corresponding code into GeanyProxyFuncs::unload. + + at section proxy_compat_guideline Guideline for Checking Compatiblity + +Determining if a plugin candidate is compatible is not a single test. There are multiple levels and +each should be handled differently in order to give the user a consistent feedback. + +Consider the 5 basic cases: + +1) A candidate comes with a suitable file extension but is not a workable plugin file at all. For +example, your proxy supports plugins written in a shell script (.sh) but the shebang of that script +points to an incompatible shell (or even lacks a shebang). You should check for this in +GeanyProxyFuncs::probe() and return @ref PROXY_IGNORED which hides that script from the Plugin +Manager and allows other enabled proxy plugins to pick it up. GeanyProxyFuncs::probe() returning + at ref PROXY_IGNORED is an indication that the candidate is meant for another proxy, or the user +placed the file by accident in one of Geany's plugin directories. In other words the candidate +simply doesn't correspond to your proxy. Thus any noise by debug messages for this case is +undesirable. + +2) A proxy plugin provides its own, versioned API to sub-plugin. The API version of the sub-plugin +is not compatible with the API exposed by the proxy. GeanyProxyFuncs::probe() should never perform +a version check because its sole purpose is to indicate a proxy's correspondence to a given +candidate. It should return @ref PROXY_MATCHED instead. Later, Geany will invoke the +GeanyProxyFuncs::load(), and this function is the right place for a version check. If it fails then +you simply do not call GEANY_PLUGIN_REGISTER(), but rather print a debug message. The result is +that the sub-plugin is not shown in the Plugin Manager at all. This is consistent with the +treatment of native plugins by Geany. + +3) The sub-plugin is also depending on Geany's API version (whether it is or not depends on the +design of the proxy). In this case do not do anything special but forward the API version the +sub-plugin is written/compiled against to GEANY_PLUGIN_REGISTER(). Here, Geany will perform its own +compatiblity check, allowing for a consistent user feedback. The result is again that the +sub-plugin is hidden from the Plugin Manager, like in case 2. But Geany will print a debug message +so you can skip that. + + +If you have even more cases try to fit it into case 1 or 2, depending on whether other proxy +plugins should get a chance to load the candidate or not. + + at section proxy_dep_guideline Guideline for Runtime Errors + +A sub-plugin might not be able to run even if it's perfectly compatible with its proxy. This +includes the case when it lacks certain runtime dependencies such as programs or modules but also +syntactic problems or other errors. + +There are two basic classes: + +1) Runtime errors that can be determined at load time. For example, the shebang of a script +indicates a specific interpeter version but that version is not installed on the system. Your proxy +should respond the same way as for version-incompatible plugins: don't register the plugin at +all, but leave a message the user suggesting what has to be installed in order to work. Handle +syntax errors in the scripts of sub-plugins the same way if possible. + +2) Runtime errors that cannot be determined without actually running the plugin. An example would +be missing modules in Python scripts. If your proxy has no way of foreseeing the problem the plugin +will be registered normally. However, you can catch runtime errors by implementing +GeanyPluginFuncs::init() on the plugin's behalf. This is called after user activation and allows to +indicate errors by returning @c FALSE. However, allowing the user to enable a plugin and then +disabling anyway is a poor user experience. + +Therefore, if possible, try to fail fast and disallow registration. + + at section Proxy Plugin Example + +In this section a dumb example proxy plugin is shown in order to give a practical starting point. +The sub-plugin are not actually code but rather a ini-style description of one or more menu items +that are added to Geany's tools menu and a help dialog. Real world sub-plugins would contain actual +code, usually written in a scripting language. + +A sub-plugin file looks like this: + + at code{.ini} +#!!PROXY_MAGIC!! + +[Init] +item0 = Bam +item1 = Foo +item2 = Bar + +[Help] +text = I'm a simple test. Nothing to see! + +[Info] +name = Demo Proxy Tester +description = I'm a simple test. Nothing to see! +version = 0.1 +author = The Geany developer team + at endcode + +The first line acts as a verification that this file is truly a sub-plugin. Within the [Init] section +there is the menu items for Geany's tools menu. The [Help] section declares the sub-plugins help +text which is shown in its help dialog (via GeanyPluginFuncs::help). The [Info] section is +used as-is for filling the sub-plugins PluginInfo fields. + +That's it, this dumb format is purely declarative and contains no logic. Yet we will create plugins +from it. + +We start by registering the proxy plugin to Geany. There is nothing special to it compared to +normal plugins. A proxy plugin must also fill its own @ref PluginInfo and @ref GeanyPluginFuncs, +followed by registering through GEANY_PLUGIN_REGISTER(). + + + at code{.c} + +/* Called by Geany to initialize the plugin. */ +static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + // ... +} + + +/* Called by Geany before unloading the plugin. */ +static void demoproxy_cleanup(GeanyPlugin *plugin, gpointer data) +{ + // ... +} + + +G_MODULE_EXPORT +void geany_load_module(GeanyPlugin *plugin) +{ + plugin->info->name = _("Demo Proxy"); + plugin->info->description = _("Example Proxy."); + plugin->info->version = "0.1"; + plugin->info->author = _("The Geany developer team"); + + plugin->funcs->init = demoproxy_init; + plugin->funcs->cleanup = demoproxy_cleanup; + + GEANY_PLUGIN_REGISTER(plugin, 225); +} + + at endcode + +The next step is to actually register as a proxy plugin. This is done in demoproxy_init(). +As previously mentioned, it needs a list of accepted file extensions and a set of callback +functions. + + at code{.c} +static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + const gchar *extensions[] = { "ini", "px", NULL }; + + plugin->proxy_funcs->probe = demoproxy_probe; + plugin->proxy_funcs->load = demoproxy_load; + plugin->proxy_funcs->unload = demoproxy_unload; + + return geany_plugin_register_proxy(plugin, extensions); +} + + at endcode + +The callback functions deserve a closer look. + +As already mentioned the file format includes a magic first line which must be present. +GeanyProxyFuncs::probe() verifies that it's present and avoids showing the sub-plugin in the +Plugin Manager if not. + + at code{.c} +static gint demoproxy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata) +{ + /* We know the extension is right (Geany checks that). For demo purposes we perform an + * additional check. This is not necessary when the extension is unique enough. */ + gboolean match = FALSE; + gchar linebuf[128]; + FILE *f = fopen(filename, "r"); + if (f != NULL) + { + if (fgets(linebuf, sizeof(linebuf), f) != NULL) + match = utils_str_equal(linebuf, "#!!PROXY_MAGIC!!\n"); + fclose(f); + } + return match ? PROXY_MATCHED : PROXY_IGNORED; +} + at endcode + +GeanyProxyFuncs::load is a bit more complex. It reads the file, fills the sub-plugin's PluginInfo +fields and calls GEANY_PLUGIN_REGISTER_FULL(). Additionally, it creates a per-plugin context that +holds GKeyFile instance (a poor man's interpeter context). You can also see that it does not call +GEANY_PLUGIN_REGISTER_FULL() if g_key_file_load_from_file() found an error (probably a syntax +problem) which means the sub-plugin cannot be enabled. + +It also installs wrapper functions for the sub-plugin's GeanyPluginFuncs as ini files aren't code. +It's very likely that your proxy needs something similar because you can only install function +pointers to native code. + + at code{.c} +typedef struct { + GKeyFile *file; + gchar *help_text; + GSList *menu_items; +} +PluginContext; + + +static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata); +static void proxy_help(GeanyPlugin *plugin, gpointer pdata); +static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata); + + +static gpointer demoproxy_load(GeanyPlugin *proxy, GeanyPlugin *plugin, + const gchar *filename, gpointer pdata) +{ + GKeyFile *file; + gboolean result; + + file = g_key_file_new(); + result = g_key_file_load_from_file(file, filename, 0, NULL); + + if (result) + { + PluginContext *data = g_new0(PluginContext, 1); + data->file = file; + + plugin->info->name = g_key_file_get_locale_string(data->file, "Info", "name", NULL, NULL); + plugin->info->description = g_key_file_get_locale_string(data->file, "Info", "description", NULL, NULL); + plugin->info->version = g_key_file_get_locale_string(data->file, "Info", "version", NULL, NULL); + plugin->info->author = g_key_file_get_locale_string(data->file, "Info", "author", NULL, NULL); + + plugin->funcs->init = proxy_init; + plugin->funcs->help = proxy_help; + plugin->funcs->cleanup = proxy_cleanup; + + /* Cannot pass g_free as free_func be Geany calls it before unloading, and since + * demoproxy_unload() accesses the data this would be catastrophic */ + GEANY_PLUGIN_REGISTER_FULL(plugin, 225, data, NULL); + return data; + } + + g_key_file_free(file); + return NULL; +} + at endcode + +demoproxy_unload() simply releases all resources aquired in demoproxy_load(). It does not have to +do anything else in for unloading. + + at code{.c} +static void demoproxy_unload(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer load_data, gpointer pdata) +{ + PluginContext *data = load_data; + + g_free((gchar *)plugin->info->name); + g_free((gchar *)plugin->info->description); + g_free((gchar *)plugin->info->version); + g_free((gchar *)plugin->info->author); + + g_key_file_free(data->file); + g_free(data); +} + at endcode + +Finally the demo_proxy's wrapper GeanyPluginFuncs. They are called for each possible sub-plugin and +therefore have to multiplex between each using the plugin-defined data pointer. Each is called by +Geany as if it were an ordinary, native plugin. + +proxy_init() actually reads the sub-plugin's file using GKeyFile APIs. It prepares for the help +dialog and installs the menu items. proxy_help() is called when the user clicks the help button in +the Plugin Manager. Consequently, this fires up a suitable dialog, although with a dummy message. +proxy_cleanup() frees all memory allocated in proxy_init(). + + at code{.c} +static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data; + gint i = 0; + gchar *text; + + data = (PluginContext *) pdata; + + /* Normally, you would instruct the VM/interpreter to call into the actual plugin. The + * plugin would be identified by pdata. Because there is no interpreter for + * .ini files we do it inline, as this is just a demo */ + data->help_text = g_key_file_get_locale_string(data->file, "Help", "text", NULL, NULL); + while (TRUE) + { + GtkWidget *item; + gchar *key = g_strdup_printf("item%d", i++); + text = g_key_file_get_locale_string(data->file, "Init", key, NULL, NULL); + g_free(key); + + if (!text) + break; + + item = gtk_menu_item_new_with_label(text); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(plugin->geany_data->main_widgets->tools_menu), item); + gtk_widget_set_sensitive(item, FALSE); + data->menu_items = g_slist_prepend(data->menu_items, (gpointer) item); + g_free(text); + } + + return TRUE; +} + + +static void proxy_help(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data; + GtkWidget *dialog; + + data = (PluginContext *) pdata; + + dialog = gtk_message_dialog_new( + GTK_WINDOW(plugin->geany_data->main_widgets->window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_OK, + "%s", data->help_text); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + _("(From the %s plugin)"), plugin->info->name); + + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + + +static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data = (PluginContext *) pdata; + + g_slist_free_full(data->menu_items, (GDestroyNotify) gtk_widget_destroy); + g_free(data->help_text); +} + at endcode + + */ Modified: src/plugindata.h 43 lines changed, 38 insertions(+), 5 deletions(-) =================================================================== @@ -240,6 +240,7 @@ GeanyData; #define geany geany_data /**< Simple macro for @c geany_data that reduces typing. */ typedef struct GeanyPluginFuncs GeanyPluginFuncs; +typedef struct GeanyProxyFuncs GeanyProxyFuncs; /** Basic information for the plugin and identification. * @see geany_plugin. */ @@ -248,7 +249,8 @@ typedef struct GeanyPlugin PluginInfo *info; /**< Fields set in plugin_set_info(). */ GeanyData *geany_data; /**< Pointer to global GeanyData intance */ GeanyPluginFuncs *funcs; /**< Functions implemented by the plugin, set in geany_load_module() */ - + GeanyProxyFuncs *proxy_funcs; /**< Hooks implemented by the plugin if it wants to act as a proxy + Must be set prior to calling geany_plugin_register_proxy() */ struct GeanyPluginPrivate *priv; /* private */ } GeanyPlugin; @@ -347,24 +349,55 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify fr geany_plugin_register_full((plugin), GEANY_API_VERSION, \ (min_api_version), GEANY_ABI_VERSION, (pdata), (free_func)) +/** Return values for GeanyProxyHooks::probe() + * + * Only @c PROXY_IGNORED, @c PROXY_MATCHED or @c PROXY_MATCHED|PROXY_NOLOAD + * are valid return values. + * + * @see geany_plugin_register_proxy() for a full description of the proxy plugin mechanisms. + * + * @since 1.26 (API 226) + */ typedef enum { + /** The proxy is not responsible at all, and Geany or other plugins are free + * to probe it. + **/ PROXY_IGNORED, + /** The proxy is responsible for this file, and creates a plugin for it */ PROXY_MATCHED, + /** The proxy is does not directly load it, but it's still tied to the proxy + * + * This is for plugins that come in multiple files where only one of these + * files is relevant for the plugin creation (for the PM dialog). The other + * files should be ignored by Geany and other proxies. Example: libpeas has + * a .plugin and a .so per plugin. Geany should not process the .so file + * if there is a corresponding .plugin. + */ PROXY_NOLOAD = 0x100, } GeanyProxyProbeResults; -/* Hooks that need to be implemented for every proxy */ -typedef struct _GeanyProxyFuncs + +/** Hooks that need to be implemented by every proxy + * + * @see geany_plugin_register_proxy() for a full description of the proxy mechanism. + * + * @since 1.26 (API 226) + **/ +struct GeanyProxyFuncs { + /** Called to determine whether the proxy is truly responsible for the requested plugin. + * A NULL pointer assumes the probe() function would always return @ref PROXY_MATCHED */ gint (*probe) (GeanyPlugin *proxy, const gchar *filename, gpointer pdata); + /** Called after probe(), to perform the actual job of loading the plugin */ gpointer (*load) (GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata); + /** Called when the user initiates unloading of a plugin, e.g. on Geany exit */ void (*unload) (GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata); -} -GeanyProxyFuncs; +}; +gint geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensions); /* Deprecated aliases */ #ifndef GEANY_DISABLE_DEPRECATED Modified: src/plugins.c 52 lines changed, 52 insertions(+), 0 deletions(-) =================================================================== @@ -653,6 +653,7 @@ plugin_new(Plugin *proxy, const gchar *fname, gboolean load_plugin, gboolean add /* Fields of plugin->info/funcs must to be initialized by the plugin */ plugin->public.info = &plugin->info; plugin->public.funcs = &plugin->cbs; + plugin->public.proxy_funcs = &plugin->proxy_cbs; if (plugin_loaded(plugin)) { @@ -1758,4 +1759,55 @@ static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data) } +/** Register the plugin as a proxy for other plugins + * + * Proxy plugins register a list of file extensions and a set of callbacks that are called + * appropriately. A plugin can be a proxy for multiple types of sub-plugins by handling + * separate file extensions, however they must share the same set of hooks, because this + * function can only be called at most once per plugin. + * + * Each callback receives the plugin-defined data as parameter (see geany_plugin_register()). The + * callbacks must be set prior to calling this, by assigning to @a plugin->proxy_funcs. + * GeanyProxyFuncs::load and GeanyProxyFuncs::unload must be implemented. + * + * Nested proxies are unsupported at this point (TODO). + * + * @note It is entirely up to the proxy to provide access to Geany's plugin API. Native code + * can naturally call Geany's API directly, for interpreted languages the proxy has to + * implement some kind of bindings that the plugin can use. + * + * @see proxy for detailed documentation and an example. + * + * @param plugin The pointer to the plugin's GeanyPlugin instance + * @param extensions A @c NULL-terminated string array of file extensions, excluding the dot. + * @return @c TRUE if the proxy was successfully registered, otherwise @c FALSE + * + * @since 1.26 (API 226) + */ +GEANY_API_SYMBOL +gboolean geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensions) +{ + Plugin *p; + const gchar **ext; + + g_return_val_if_fail(plugin != NULL, FALSE); + g_return_val_if_fail(extensions != NULL, FALSE); + g_return_val_if_fail(*extensions != NULL, FALSE); + g_return_val_if_fail(plugin->proxy_funcs->load != NULL, FALSE); + g_return_val_if_fail(plugin->proxy_funcs->unload != NULL, FALSE); + + p = plugin->priv; + + foreach_strv(ext, extensions) + { + PluginProxy *proxy = g_new(PluginProxy, 1); + g_strlcpy(proxy->extension, *ext, sizeof(proxy->extension)); + proxy->plugin = p; + /* prepend, so that plugins automatically override core providers for a given extension */ + g_ptr_array_insert(active_proxies, 0, proxy); + } + + return TRUE; +} + #endif -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 5 20:11:12 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 05 Oct 2015 20:11:12 -0000 Subject: [geany/geany] 3ccf95: plugins: introduce probe() for proxy plugins Message-ID: <20151006135844.573BD5C4221@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Mon, 05 Oct 2015 20:11:12 UTC Commit: 3ccf959013edb6744ab58f6953a2b44dfb1abef3 https://github.com/geany/geany/commit/3ccf959013edb6744ab58f6953a2b44dfb1abef3 Log Message: ----------- plugins: introduce probe() for proxy plugins When a file extension alone is ambigious as to whether a potential plugin is really handled then the proxy should use the probe hook to find out. This can be especially helpful when two pluxies work on the same file extension. The proxy's probe() should return PROXY_IGNORED or PROXY_MATCHED accordingly. A special flag value, PROXY_NOLOAD, can be or'ed into PROXY_MATCHED to say that the file belongs to the proxy, but isn't directly loaded and should not be handled by any other proxy or geany itself. Example for PROXY_IGNORED: geanypy only supports python2 at the moment. So, scripts written for python3 aren't handled by it and should be skipped for the PM dialog. Or perhaps they are handled by another proxy that supports python3. Example for PROXY_NOLOAD: A pluxy registers for the metadata file extension (.plugin) where author etc is in. The actual implmentation is in a python script (.py). The .py file is tied to the .plugin and should not be processed by other pluxies. Thus, the pluxy also registers for the .py extension but returns PROXY_MATCHED|PROXY_NOLOAD for it (if it would return only PROXY_MATCHED the sub-plugin would show up twice in the PM dialog). Modified Paths: -------------- src/plugindata.h src/plugins.c Modified: src/plugindata.h 10 lines changed, 10 insertions(+), 0 deletions(-) =================================================================== @@ -347,9 +347,19 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify fr geany_plugin_register_full((plugin), GEANY_API_VERSION, \ (min_api_version), GEANY_ABI_VERSION, (pdata), (free_func)) +typedef enum +{ + PROXY_IGNORED, + PROXY_MATCHED, + + PROXY_NOLOAD = 0x100, +} +GeanyProxyProbeResults; + /* Hooks that need to be implemented for every proxy */ typedef struct _GeanyProxyFuncs { + gint (*probe) (GeanyPlugin *proxy, const gchar *filename, gpointer pdata); gpointer (*load) (GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata); void (*unload) (GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata); } Modified: src/plugins.c 17 lines changed, 16 insertions(+), 1 deletions(-) =================================================================== @@ -953,7 +953,22 @@ static PluginProxy* is_plugin(const gchar *file) { if (utils_str_casecmp(ext, proxy->extension) == 0) { - return proxy; + Plugin *p = proxy->plugin; + gint ret = PROXY_MATCHED; + + if (p->proxy_cbs.probe) + ret = p->proxy_cbs.probe(&p->public, file, p->cb_data); + switch (ret) + { + case PROXY_MATCHED: + return proxy; + case PROXY_MATCHED|PROXY_NOLOAD: + return NULL; + default: + if (ret != PROXY_IGNORED) + g_warning("Ignoring bogus return from proxy probe!\n"); + continue; + } } } return NULL; -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 5 20:11:12 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 05 Oct 2015 20:11:12 -0000 Subject: [geany/geany] 8ac9d5: plugins: reselect when toggling the current plugin Message-ID: <20151006135845.B20785C4222@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Mon, 05 Oct 2015 20:11:12 UTC Commit: 8ac9d56fff28a6a8b296bfde3f7c047bc8762e61 https://github.com/geany/geany/commit/8ac9d56fff28a6a8b296bfde3f7c047bc8762e61 Log Message: ----------- plugins: reselect when toggling the current plugin When enabling/disabling pluxys in the PM dialog the list of available plugins might change. If plugins before the pluxy go/come then the wrong plugin becomes selected (the selected row number stays the same). Re-apply the selection to the current one in the toggle callback to overcome this issue. Modified Paths: -------------- src/plugins.c Modified: src/plugins.c 53 lines changed, 53 insertions(+), 0 deletions(-) =================================================================== @@ -1301,6 +1301,41 @@ static void pm_selection_changed(GtkTreeSelection *selection, gpointer user_data } +static gboolean find_iter_for_plugin(Plugin *p, GtkTreeModel *model, GtkTreeIter *iter) +{ + Plugin *pp; + gboolean valid; + + for (valid = gtk_tree_model_get_iter_first(model, iter); + valid; + valid = gtk_tree_model_iter_next(model, iter)) + { + gtk_tree_model_get(model, iter, PLUGIN_COLUMN_PLUGIN, &pp, -1); + if (p == pp) + return TRUE; + } + + return FALSE; +} + + +static gboolean select_plugin(gpointer data) +{ + GtkTreeIter iter; + Plugin *p = data; + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_widgets.tree)); + + /* restore selection */ + if (find_iter_for_plugin(p, model, &iter)) + { + GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pm_widgets.tree)); + gtk_tree_selection_select_iter(sel, &iter); + } + + return G_SOURCE_REMOVE; +} + + static void pm_populate(GtkListStore *store); @@ -1314,6 +1349,7 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_widgets.tree)); Plugin *p; Plugin *proxy; + guint prev_num_proxies; gtk_tree_model_get_iter(model, &iter, path); gtk_tree_path_free(path); @@ -1334,6 +1370,7 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer /* save the filename and proxy of the plugin */ file_name = g_strdup(p->filename); proxy = p->proxy; + prev_num_proxies = active_proxies->len; /* unload plugin module */ if (!state) @@ -1362,6 +1399,19 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer /* set again the sensitiveness of the configure and help buttons */ pm_update_buttons(p); } + /* We need to find out if a proxy was added or removed because that affects the plugin list + * presented by the plugin manager. The current solution counts active_proxies twice, + * this suboptimal from an algorithmic POV, however most efficient for the extremely small + * number (at most 3) of pluxies we expect users to load */ + if (prev_num_proxies != active_proxies->len) + { + /* Rescan the plugin list as we now support more */ + if (prev_num_proxies < active_proxies->len) + load_all_plugins(); + pm_populate(pm_widgets.store); + /* restore selection. doesn't work if it's done immediately (same row keeps selected) */ + g_idle_add(select_plugin, p); + } g_free(file_name); } @@ -1524,6 +1574,9 @@ static gboolean pm_tree_filter_func(GtkTreeModel *model, GtkTreeIter *iter, gpoi gchar *haystack, *filename; gtk_tree_model_get(model, iter, PLUGIN_COLUMN_PLUGIN, &plugin, -1); + + if (!plugin) + return FALSE; key = gtk_entry_get_text(GTK_ENTRY(pm_widgets.filter_entry)); filename = g_path_get_basename(plugin->filename); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 6 13:40:34 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Tue, 06 Oct 2015 13:40:34 -0000 Subject: [geany/geany] bbf8e8: demoproxy: add a demo proxy showcasing how to create a proxy plugin Message-ID: <20151006135846.95F685C422D@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Colomban Wendling Date: Tue, 06 Oct 2015 13:40:34 UTC Commit: bbf8e882c2ea76c2d400decfbd6d5b44978eb3fa https://github.com/geany/geany/commit/bbf8e882c2ea76c2d400decfbd6d5b44978eb3fa Log Message: ----------- demoproxy: add a demo proxy showcasing how to create a proxy plugin This demo proxy does not actually do anything useful. It simply loads pseudo-plugins from an ini-style file. The point is that there will be a plugin in the PM dialog for each ini. Each ini-plugin also causes a menu item to be generated. Modified Paths: -------------- plugins/Makefile.am plugins/demoproxy.c plugins/demoproxytest.px po/POTFILES.skip Modified: plugins/Makefile.am 10 lines changed, 8 insertions(+), 2 deletions(-) =================================================================== @@ -1,7 +1,8 @@ # Adapted from Pidgin's plugins/Makefile.am, thanks EXTRA_DIST = \ - makefile.win32 + makefile.win32 \ + demoproxytest.px plugindir = $(libdir)/geany @@ -11,6 +12,7 @@ plugins_include_HEADERS = \ geanyplugin.h demoplugin_la_LDFLAGS = -module -avoid-version -no-undefined +demoproxy_la_LDFLAGS = -module -avoid-version -no-undefined classbuilder_la_LDFLAGS = -module -avoid-version -no-undefined htmlchars_la_LDFLAGS = -module -avoid-version -no-undefined export_la_LDFLAGS = -module -avoid-version -no-undefined @@ -30,9 +32,11 @@ plugin_LTLIBRARIES = \ # Plugins not to be installed noinst_LTLIBRARIES = \ - demoplugin.la + demoplugin.la \ + demoproxy.la demoplugin_la_SOURCES = demoplugin.c +demoproxy_la_SOURCES = demoproxy.c classbuilder_la_SOURCES = classbuilder.c htmlchars_la_SOURCES = htmlchars.c export_la_SOURCES = export.c @@ -41,6 +45,7 @@ filebrowser_la_SOURCES = filebrowser.c splitwindow_la_SOURCES = splitwindow.c demoplugin_la_CFLAGS = -DG_LOG_DOMAIN=\""Demoplugin"\" -DLOCALEDIR=\""$(LOCALEDIR)"\" +demoproxy_la_CFLAGS = -DG_LOG_DOMAIN=\""Demoproxy"\" classbuilder_la_CFLAGS = -DG_LOG_DOMAIN=\""Classbuilder"\" htmlchars_la_CFLAGS = -DG_LOG_DOMAIN=\""HTMLChars"\" export_la_CFLAGS = -DG_LOG_DOMAIN=\""Export"\" @@ -49,6 +54,7 @@ filebrowser_la_CFLAGS = -DG_LOG_DOMAIN=\""FileBrowser"\" splitwindow_la_CFLAGS = -DG_LOG_DOMAIN=\""SplitWindow"\" demoplugin_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) +demoproxy_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) classbuilder_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) htmlchars_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) export_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) -lm Modified: plugins/demoproxy.c 202 lines changed, 202 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,202 @@ +/* + * demoproxy.c - this file is part of Geany, a fast and lightweight IDE + * + * Copyright 2015 Thomas Martitz + * + * 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. + */ + +/** + * Demo proxy - example of a basic proxy plugin for Geany. Sub-plugins add menu items to the + * Tools menu and have a help dialog. + * + * Note: This is compiled but not installed by default. On Unix, you can install it by compiling + * Geany and then copying (or symlinking) to the plugins/demoproxy.so and + * plugins/demoproxytest.px files to ~/.config/geany/plugins + * - it will be loaded at next startup. + */ + +/* plugin API, always comes first */ +#include "geanyplugin.h" + +typedef struct { + GKeyFile *file; + gchar *help_text; + GSList *menu_items; +} +PluginContext; + + +static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data; + gint i = 0; + gchar *text; + + data = (PluginContext *) pdata; + + /* Normally, you would instruct the VM/interpreter to call into the actual plugin. The + * plugin would be identified by pdata. Because there is no interpreter for + * .ini files we do it inline, as this is just a demo */ + data->help_text = g_key_file_get_locale_string(data->file, "Help", "text", NULL, NULL); + while (TRUE) + { + GtkWidget *item; + gchar *key = g_strdup_printf("item%d", i++); + text = g_key_file_get_locale_string(data->file, "Init", key, NULL, NULL); + g_free(key); + + if (!text) + break; + + item = gtk_menu_item_new_with_label(text); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(plugin->geany_data->main_widgets->tools_menu), item); + gtk_widget_set_sensitive(item, FALSE); + data->menu_items = g_slist_prepend(data->menu_items, (gpointer) item); + g_free(text); + } + + return TRUE; +} + + +static void proxy_help(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data; + GtkWidget *dialog; + + data = (PluginContext *) pdata; + + dialog = gtk_message_dialog_new( + GTK_WINDOW(plugin->geany_data->main_widgets->window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_OK, + "%s", data->help_text); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + _("(From the %s plugin)"), plugin->info->name); + + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + + +static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data = (PluginContext *) pdata; + + g_slist_free_full(data->menu_items, (GDestroyNotify) gtk_widget_destroy); + g_free(data->help_text); +} + + +static gint demoproxy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata) +{ + /* We know the extension is right (Geany checks that). For demo purposes we perform an + * additional check. This is not necessary when the extension is unique enough. */ + gboolean match = FALSE; + gchar linebuf[128]; + FILE *f = fopen(filename, "r"); + if (f != NULL) + { + if (fgets(linebuf, sizeof(linebuf), f) != NULL) + match = utils_str_equal(linebuf, "#!!PROXY_MAGIC!!\n"); + fclose(f); + } + return match ? PROXY_MATCHED : PROXY_IGNORED; +} + + +static gpointer demoproxy_load(GeanyPlugin *proxy, GeanyPlugin *plugin, + const gchar *filename, gpointer pdata) +{ + GKeyFile *file; + gboolean result; + + file = g_key_file_new(); + result = g_key_file_load_from_file(file, filename, 0, NULL); + + if (result) + { + PluginContext *data = g_new0(PluginContext, 1); + data->file = file; + + plugin->info->name = g_key_file_get_locale_string(data->file, "Info", "name", NULL, NULL); + plugin->info->description = g_key_file_get_locale_string(data->file, "Info", "description", NULL, NULL); + plugin->info->version = g_key_file_get_locale_string(data->file, "Info", "version", NULL, NULL); + plugin->info->author = g_key_file_get_locale_string(data->file, "Info", "author", NULL, NULL); + + plugin->funcs->init = proxy_init; + plugin->funcs->help = proxy_help; + plugin->funcs->cleanup = proxy_cleanup; + + /* Cannot pass g_free as free_func be Geany calls it before unloading, and since + * demoproxy_unload() accesses the data this would be catastrophic */ + GEANY_PLUGIN_REGISTER_FULL(plugin, 225, data, NULL); + return data; + } + + g_key_file_free(file); + return NULL; +} + + +static void demoproxy_unload(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer load_data, gpointer pdata) +{ + PluginContext *data = load_data; + + g_free((gchar *)plugin->info->name); + g_free((gchar *)plugin->info->description); + g_free((gchar *)plugin->info->version); + g_free((gchar *)plugin->info->author); + + g_key_file_free(data->file); + g_free(data); +} + + +/* Called by Geany to initialize the plugin. */ +static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + const gchar *extensions[] = { "ini", "px", NULL }; + + plugin->proxy_funcs->probe = demoproxy_probe; + plugin->proxy_funcs->load = demoproxy_load; + plugin->proxy_funcs->unload = demoproxy_unload; + + return geany_plugin_register_proxy(plugin, extensions); +} + + +/* Called by Geany before unloading the plugin. */ +static void demoproxy_cleanup(GeanyPlugin *plugin, gpointer data) +{ +} + + +G_MODULE_EXPORT +void geany_load_module(GeanyPlugin *plugin) +{ + plugin->info->name = _("Demo Proxy"); + plugin->info->description = _("Example Proxy."); + plugin->info->version = "0.1"; + plugin->info->author = _("The Geany developer team"); + + plugin->funcs->init = demoproxy_init; + plugin->funcs->cleanup = demoproxy_cleanup; + + GEANY_PLUGIN_REGISTER(plugin, 225); +} Modified: plugins/demoproxytest.px 15 lines changed, 15 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,15 @@ +#!!PLUXY_MAGIC!! + +[Init] +item0 = Bam +item1 = Foo +item2 = Bar + +[Help] +text = I'm a simple test. Nothing to see! + +[Info] +name = Demo Pluxy Tester +description = I'm a simple test. Nothing to see! +version = 0.1 +author = The Geany developer team Modified: po/POTFILES.skip 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -6,5 +6,6 @@ geany.desktop.in geany.glade # no need to translate these files plugins/demoplugin.c +plugins/demoproxy.c doc/stash-example.c doc/stash-gui-example.c -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 6 13:40:57 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Tue, 06 Oct 2015 13:40:57 -0000 Subject: [geany/geany] 6dfe5c: plugins: use GQueue to restore GLib compatibility Message-ID: <20151006135847.CCB9B5C4226@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Colomban Wendling Date: Tue, 06 Oct 2015 13:40:57 UTC Commit: 6dfe5ce9420c3674e2e779339e72ce5d79ae70b9 https://github.com/geany/geany/commit/6dfe5ce9420c3674e2e779339e72ce5d79ae70b9 Log Message: ----------- plugins: use GQueue to restore GLib compatibility g_ptr_array_insert() is too recent (2.40), but prepending is required. GQueue is a fine replacement with better old-glib support, at the expense of working with a doubly-linked list instead of plain array. Modified Paths: -------------- src/plugins.c src/utils.h Modified: src/plugins.c 32 lines changed, 15 insertions(+), 17 deletions(-) =================================================================== @@ -97,7 +97,7 @@ static PluginProxy builtin_so_proxy = { .plugin = &builtin_so_proxy_plugin, }; -static GPtrArray *active_proxies = NULL; +static GQueue active_proxies = G_QUEUE_INIT; static void plugin_free(Plugin *plugin); @@ -901,19 +901,18 @@ static void free_subplugins(Plugin *proxy) /* Returns true if the removal was successful (=> never for non-proxies) */ static gboolean unregister_proxy(Plugin *proxy) { - PluginProxy *p; - guint i; gboolean is_proxy = FALSE; + GList *node; /* Remove the proxy from the proxy list first. It might appear more than once (once * for each extension), but if it doesn't appear at all it's not actually a proxy */ - foreach_ptr_array(p, i, active_proxies) + foreach_list_safe(node, active_proxies.head) { + PluginProxy *p = node->data; if (p->plugin == proxy) { is_proxy = TRUE; - g_ptr_array_remove(active_proxies, p); - i -= 1; + g_queue_delete_link(&active_proxies, node); } } return is_proxy; @@ -1014,9 +1013,8 @@ static gboolean check_plugin_path(const gchar *fname) * otherwise it returns the appropriate PluginProxy instance to load it */ static PluginProxy* is_plugin(const gchar *file) { - PluginProxy *proxy; + GList *node; const gchar *ext; - guint i; /* extract file extension to avoid g_str_has_suffix() in the loop */ ext = (const gchar *)strrchr(file, '.'); @@ -1029,8 +1027,9 @@ static PluginProxy* is_plugin(const gchar *file) ext += 1; /* O(n*m), (m being extensions per proxy) doesn't scale very well in theory * but not a problem in practice yet */ - foreach_ptr_array(proxy, i, active_proxies) + foreach_list(node, active_proxies.head) { + PluginProxy *proxy = node->data; if (utils_str_casecmp(ext, proxy->extension) == 0) { Plugin *p = proxy->plugin; @@ -1067,7 +1066,7 @@ load_active_plugins(void) /* If proxys are loaded we have to restart to load plugins that sort before their proxy */ do { - proxies = active_proxies->len; + proxies = active_proxies.length; g_list_free_full(failed_plugins_list, (GDestroyNotify) g_free); failed_plugins_list = NULL; for (i = 0; i < len; i++) @@ -1090,7 +1089,7 @@ load_active_plugins(void) failed_plugins_list = g_list_prepend(failed_plugins_list, g_strdup(fname)); } } - } while (proxies != active_proxies->len); + } while (proxies != active_proxies.length); } @@ -1298,8 +1297,7 @@ void plugins_init(void) g_signal_connect(geany_object, "save-settings", G_CALLBACK(update_active_plugins_pref), NULL); stash_group_add_string_vector(group, &active_plugins_pref, "active_plugins", NULL); - active_proxies = g_ptr_array_sized_new(1); - g_ptr_array_add(active_proxies, &builtin_so_proxy); + g_queue_push_head(&active_proxies, &builtin_so_proxy); } @@ -1473,7 +1471,7 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer /* save the filename and proxy of the plugin */ file_name = g_strdup(p->filename); proxy = p->proxy; - prev_num_proxies = active_proxies->len; + prev_num_proxies = active_proxies.length; /* unload plugin module */ if (!state) @@ -1529,11 +1527,11 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer } /* We need to find out if a proxy was added or removed because that affects the plugin list * presented by the plugin manager */ - if (prev_num_proxies != active_proxies->len) + if (prev_num_proxies != active_proxies.length) { /* Rescan the plugin list as we now support more. Gives some "already loaded" warnings * they are unproblematic */ - if (prev_num_proxies < active_proxies->len) + if (prev_num_proxies < active_proxies.length) load_all_plugins(); pm_populate(pm_widgets.store); @@ -2002,7 +2000,7 @@ gboolean geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensio g_strlcpy(proxy->extension, *ext, sizeof(proxy->extension)); proxy->plugin = p; /* prepend, so that plugins automatically override core providers for a given extension */ - g_ptr_array_insert(active_proxies, 0, proxy); + g_queue_push_head(&active_proxies, proxy); } return TRUE; Modified: src/utils.h 8 lines changed, 8 insertions(+), 0 deletions(-) =================================================================== @@ -116,6 +116,14 @@ G_BEGIN_DECLS #define foreach_slist(node, list) \ foreach_list(node, list) +/* Iterates all the nodes in @a list. Safe against removal during iteration + * @param node should be a (@c GList*). + * @param list @c GList to traverse. */ +#define foreach_list_safe(node, list) \ + for (GList *_node = (list), *_next = (list) ? (list)->next : NULL; \ + (node = _node) != NULL; \ + _node = _next, _next = _next ? _next->next : NULL) + /** Iterates through each unsorted filename in a @c GDir. * @param filename (@c const @c gchar*) locale-encoded filename, without path. Do not modify or free. * @param dir @c GDir created with @c g_dir_open(). Call @c g_dir_close() afterwards. -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 6 13:43:40 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Tue, 06 Oct 2015 13:43:40 -0000 Subject: [geany/geany] d0f944: Bump plugin API version for proxy plugins support Message-ID: <20151006135849.9B2DB5C4223@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Tue, 06 Oct 2015 13:43:40 UTC Commit: d0f94460eacfc555683187e7055ef5dec7f279b8 https://github.com/geany/geany/commit/d0f94460eacfc555683187e7055ef5dec7f279b8 Log Message: ----------- Bump plugin API version for proxy plugins support Modified Paths: -------------- src/plugindata.h Modified: src/plugindata.h 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -58,7 +58,7 @@ G_BEGIN_DECLS * @warning You should not test for values below 200 as previously * @c GEANY_API_VERSION was defined as an enum value, not a macro. */ -#define GEANY_API_VERSION 225 +#define GEANY_API_VERSION 226 /* hack to have a different ABI when built with GTK3 because loading GTK2-linked plugins * with GTK3-linked Geany leads to crash */ -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 6 13:40:57 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Tue, 06 Oct 2015 13:40:57 -0000 Subject: [geany/geany] 6cb443: plugins: enforce geany_plugin_register_proxy() can be called once Message-ID: <20151006135848.7E0C85C4232@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Colomban Wendling Date: Tue, 06 Oct 2015 13:40:57 UTC Commit: 6cb443e8631681c7b74ccef25c7656bdbc9f9305 https://github.com/geany/geany/commit/6cb443e8631681c7b74ccef25c7656bdbc9f9305 Log Message: ----------- plugins: enforce geany_plugin_register_proxy() can be called once In the future we might want to enable calling it again to set new supported plugin types/extensions. This is not implemented yet, but in order to allow this in the future we have to prevent it now, otherwise we'd need to break the API. Modified Paths: -------------- src/plugins.c Modified: src/plugins.c 11 lines changed, 10 insertions(+), 1 deletions(-) =================================================================== @@ -1985,6 +1985,8 @@ gboolean geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensio { Plugin *p; const gchar **ext; + PluginProxy *proxy; + GList *node; g_return_val_if_fail(plugin != NULL, FALSE); g_return_val_if_fail(extensions != NULL, FALSE); @@ -1993,10 +1995,17 @@ gboolean geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensio g_return_val_if_fail(plugin->proxy_funcs->unload != NULL, FALSE); p = plugin->priv; + /* Check if this was called aready. We want to reserve for the use case of calling + * this again to set new supported extensions (for example, based on proxy configuration). */ + foreach_list(node, active_proxies.head) + { + proxy = node->data; + g_return_if_fail(p != proxy->plugin); + } foreach_strv(ext, extensions) { - PluginProxy *proxy = g_new(PluginProxy, 1); + proxy = g_new(PluginProxy, 1); g_strlcpy(proxy->extension, *ext, sizeof(proxy->extension)); proxy->plugin = p; /* prepend, so that plugins automatically override core providers for a given extension */ -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 6 13:40:57 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Tue, 06 Oct 2015 13:40:57 -0000 Subject: [geany/geany] 7ac89d: plugins: improve PM dialog for proxy and sub-plugins Message-ID: <20151006135847.4AFD35C4236@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Colomban Wendling Date: Tue, 06 Oct 2015 13:40:57 UTC Commit: 7ac89deebdaf4052ed795495cfbbb8c35043a29f https://github.com/geany/geany/commit/7ac89deebdaf4052ed795495cfbbb8c35043a29f Log Message: ----------- plugins: improve PM dialog for proxy and sub-plugins Geany now remembers how many plugins depend on a pluxy. It uses this information to disable the "Active" checkbox in the PM dialog. Additionally, the PM dialog displays plugins in a hierarchical manner, so that sub-plugins are shown next to pluxy. This is espcially handy since it makes the sub-plugin <-> pluxy relationship really obvious, and it's easier to spot which plugins need to be disabled before the pluxy can be disabled. This allows to remove code to re-select the plugin because the row (respective to the hierarchy level) does not change anymore. Modified Paths: -------------- src/pluginprivate.h src/plugins.c Modified: src/pluginprivate.h 2 lines changed, 2 insertions(+), 0 deletions(-) =================================================================== @@ -73,6 +73,8 @@ typedef struct GeanyPluginPrivate Plugin *proxy; /* The proxy that handles this plugin */ gpointer proxy_data; /* Data passed to the proxy hooks of above proxy, so * this gives the proxy a pointer to each plugin */ + gint proxied_count; /* count of active plugins this provides a proxy for + * (a count because of possibly nested proxies) */ } GeanyPluginPrivate; Modified: src/plugins.c 243 lines changed, 194 insertions(+), 49 deletions(-) =================================================================== @@ -99,6 +99,8 @@ static PluginProxy builtin_so_proxy = { static GPtrArray *active_proxies = NULL; +static void plugin_free(Plugin *plugin); + static GeanyData geany_data; static void @@ -125,6 +127,30 @@ geany_data_init(void) } +/* In order to have nested proxies work the count of dependent plugins must propagate up. + * This prevents that any plugin in the tree is unloaded while a leaf plugin is active. */ +static void proxied_count_inc(Plugin *proxy) +{ + do + { + proxy->proxied_count += 1; + proxy = proxy->proxy; + } while (proxy != NULL); +} + + +static void proxied_count_dec(Plugin *proxy) +{ + g_warn_if_fail(proxy->proxied_count > 0); + + do + { + proxy->proxied_count -= 1; + proxy = proxy->proxy; + } while (proxy != NULL); +} + + /* Prevent the same plugin filename being loaded more than once. * Note: g_module_name always returns the .so name, even when Plugin::filename is a .la file. */ static gboolean @@ -557,6 +583,7 @@ plugin_load(Plugin *plugin) * keep list sorted so tools menu items and plugin preference tabs are * sorted by plugin name */ active_plugin_list = g_list_insert_sorted(active_plugin_list, plugin, cmp_plugin_names); + proxied_count_inc(plugin->proxy); geany_debug("Loaded: %s (%s)", plugin->filename, plugin->info.name); return TRUE; @@ -847,10 +874,56 @@ plugin_cleanup(Plugin *plugin) plugin->cb_data_destroy = NULL; } + proxied_count_dec(plugin->proxy); geany_debug("Unloaded: %s", plugin->filename); } +/* Remove all plugins that proxy is a proxy for from plugin_list (and free) */ +static void free_subplugins(Plugin *proxy) +{ + GList *item; + + item = plugin_list; + while (item) + { + GList *next = g_list_next(item); + if (proxy == ((Plugin *) item->data)->proxy) + { + /* plugin_free modifies plugin_list */ + plugin_free((Plugin *) item->data); + } + item = next; + } +} + + +/* Returns true if the removal was successful (=> never for non-proxies) */ +static gboolean unregister_proxy(Plugin *proxy) +{ + PluginProxy *p; + guint i; + gboolean is_proxy = FALSE; + + /* Remove the proxy from the proxy list first. It might appear more than once (once + * for each extension), but if it doesn't appear at all it's not actually a proxy */ + foreach_ptr_array(p, i, active_proxies) + { + if (p->plugin == proxy) + { + is_proxy = TRUE; + g_ptr_array_remove(active_proxies, p); + i -= 1; + } + } + return is_proxy; +} + + +/* Cleanup a plugin and free all resources allocated on behalf of it. + * + * If the plugin is a proxy then this also takes special care to unload all + * subplugin loaded through it (make sure none of them is active!) */ static void plugin_free(Plugin *plugin) { @@ -858,10 +931,17 @@ plugin_free(Plugin *plugin) g_return_if_fail(plugin); g_return_if_fail(plugin->proxy); + g_return_if_fail(plugin->proxied_count == 0); proxy = plugin->proxy; + /* If this a proxy remove all depending subplugins. We can assume none of them is *activated* + * (but potentially loaded). Note that free_subplugins() might call us through recursion */ if (is_active_plugin(plugin)) + { + if (unregister_proxy(plugin)) + free_subplugins(plugin); plugin_cleanup(plugin); + } active_plugin_list = g_list_remove(active_plugin_list, plugin); plugin_list = g_list_remove(plugin_list, plugin); @@ -873,7 +953,6 @@ plugin_free(Plugin *plugin) g_free(plugin->filename); g_free(plugin); - plugin = NULL; } @@ -1048,6 +1127,28 @@ static gchar *get_plugin_path(void) } +/* See load_all_plugins(), this simply sorts items with lower hierarchy level first + * (where hierarchy level == number of intermediate proxies before the builtin so loader) */ +static gint cmp_plugin_by_proxy(gconstpointer a, gconstpointer b) +{ + const Plugin *pa = a; + const Plugin *pb = b; + + while (TRUE) + { + if (pa->proxy == pb->proxy) + return 0; + else if (pa->proxy == &builtin_so_proxy_plugin) + return -1; + else if (pb->proxy == &builtin_so_proxy_plugin) + return 1; + + pa = pa->proxy; + pb = pb->proxy; + } +} + + /* Load (but don't initialize) all plugins for the Plugin Manager dialog */ static void load_all_plugins(void) { @@ -1072,6 +1173,13 @@ static void load_all_plugins(void) /* finally load plugins from $prefix/lib/geany */ load_plugins_from_path(plugin_path_system); + /* It is important to sort any plugins that are proxied after their proxy because + * pm_populate() needs the proxy to be loaded and active (if selected by user) in order + * to properly set the value for the PLUGIN_COLUMN_CAN_UNCHECK column. The order between + * sub-plugins does not matter, only between sub-plugins and their proxy, thus + * sorting by hierarchy level is perfectly sufficient */ + plugin_list = g_list_sort(plugin_list, cmp_plugin_by_proxy); + g_free(plugin_path_config); g_free(plugin_path_system); } @@ -1195,6 +1303,15 @@ void plugins_init(void) } +/* Same as plugin_free(), except it does nothing for proxies-in-use, to be called on + * finalize in a loop */ +static void plugin_free_leaf(Plugin *p) +{ + if (p->proxied_count == 0) + plugin_free(p); +} + + /* called even if plugin support is disabled */ void plugins_finalize(void) { @@ -1203,11 +1320,11 @@ void plugins_finalize(void) g_list_foreach(failed_plugins_list, (GFunc) g_free, NULL); g_list_free(failed_plugins_list); } - if (active_plugin_list != NULL) - { - g_list_foreach(active_plugin_list, (GFunc) plugin_free, NULL); - g_list_free(active_plugin_list); - } + /* Have to loop because proxys cannot be unloaded until after all their + * plugins are unloaded as well (the second loop should should catch all the remaining ones) */ + while (active_plugin_list != NULL) + g_list_foreach(active_plugin_list, (GFunc) plugin_free_leaf, NULL); + g_strfreev(active_plugins_pref); } @@ -1236,6 +1353,7 @@ gboolean plugins_have_preferences(void) enum { PLUGIN_COLUMN_CHECK = 0, + PLUGIN_COLUMN_CAN_UNCHECK, PLUGIN_COLUMN_PLUGIN, PLUGIN_N_COLUMNS, PM_BUTTON_KEYBINDINGS, @@ -1247,7 +1365,7 @@ typedef struct { GtkWidget *dialog; GtkWidget *tree; - GtkListStore *store; + GtkTreeStore *store; GtkWidget *filter_entry; GtkWidget *configure_button; GtkWidget *keybindings_button; @@ -1319,24 +1437,7 @@ static gboolean find_iter_for_plugin(Plugin *p, GtkTreeModel *model, GtkTreeIter } -static gboolean select_plugin(gpointer data) -{ - GtkTreeIter iter; - Plugin *p = data; - GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_widgets.tree)); - - /* restore selection */ - if (find_iter_for_plugin(p, model, &iter)) - { - GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pm_widgets.tree)); - gtk_tree_selection_select_iter(sel, &iter); - } - - return G_SOURCE_REMOVE; -} - - -static void pm_populate(GtkListStore *store); +static void pm_populate(GtkTreeStore *store); static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data) @@ -1352,7 +1453,6 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer guint prev_num_proxies; gtk_tree_model_get_iter(model, &iter, path); - gtk_tree_path_free(path); gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_CHECK, &old_state, @@ -1360,7 +1460,10 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer /* no plugins item */ if (p == NULL) + { + gtk_tree_path_free(path); return; + } gtk_tree_model_filter_convert_iter_to_child_iter( GTK_TREE_MODEL_FILTER(model), &store_iter, &iter); @@ -1384,7 +1487,7 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer if (!p) { /* plugin file may no longer be on disk, or is now incompatible */ - gtk_list_store_remove(pm_widgets.store, &store_iter); + gtk_tree_store_remove(pm_widgets.store, &store_iter); } else { @@ -1392,40 +1495,66 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer keybindings_load_keyfile(); /* load shortcuts */ /* update model */ - gtk_list_store_set(pm_widgets.store, &store_iter, + gtk_tree_store_set(pm_widgets.store, &store_iter, PLUGIN_COLUMN_CHECK, state, PLUGIN_COLUMN_PLUGIN, p, -1); /* set again the sensitiveness of the configure and help buttons */ pm_update_buttons(p); + + /* Depending on the state disable the checkbox for the proxy of this plugin, and + * only re-enable if the proxy is not used by any other plugin */ + if (p->proxy != &builtin_so_proxy_plugin) + { + GtkTreeIter parent; + gboolean can_uncheck; + GtkTreePath *store_path = gtk_tree_model_filter_convert_path_to_child_path( + GTK_TREE_MODEL_FILTER(model), path); + + g_warn_if_fail(store_path != NULL); + if (gtk_tree_path_up(store_path)) + { + gtk_tree_model_get_iter(GTK_TREE_MODEL(pm_widgets.store), &parent, store_path); + + if (state) + can_uncheck = FALSE; + else + can_uncheck = p->proxy->proxied_count == 0; + + gtk_tree_store_set(pm_widgets.store, &parent, + PLUGIN_COLUMN_CAN_UNCHECK, can_uncheck, -1); + } + gtk_tree_path_free(store_path); + } } /* We need to find out if a proxy was added or removed because that affects the plugin list - * presented by the plugin manager. The current solution counts active_proxies twice, - * this suboptimal from an algorithmic POV, however most efficient for the extremely small - * number (at most 3) of pluxies we expect users to load */ + * presented by the plugin manager */ if (prev_num_proxies != active_proxies->len) { - /* Rescan the plugin list as we now support more */ + /* Rescan the plugin list as we now support more. Gives some "already loaded" warnings + * they are unproblematic */ if (prev_num_proxies < active_proxies->len) load_all_plugins(); + pm_populate(pm_widgets.store); - /* restore selection. doesn't work if it's done immediately (same row keeps selected) */ - g_idle_add(select_plugin, p); + gtk_tree_view_expand_row(GTK_TREE_VIEW(pm_widgets.tree), path, FALSE); } + + gtk_tree_path_free(path); g_free(file_name); } -static void pm_populate(GtkListStore *store) +static void pm_populate(GtkTreeStore *store) { GtkTreeIter iter; GList *list; - gtk_list_store_clear(store); + gtk_tree_store_clear(store); list = g_list_first(plugin_list); if (list == NULL) { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE, + gtk_tree_store_append(store, &iter, NULL); + gtk_tree_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE, PLUGIN_COLUMN_PLUGIN, NULL, -1); } else @@ -1433,11 +1562,18 @@ static void pm_populate(GtkListStore *store) for (; list != NULL; list = list->next) { Plugin *p = list->data; + GtkTreeIter parent; + + if (p->proxy != &builtin_so_proxy_plugin + && find_iter_for_plugin(p->proxy, GTK_TREE_MODEL(pm_widgets.store), &parent)) + gtk_tree_store_append(store, &iter, &parent); + else + gtk_tree_store_append(store, &iter, NULL); - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, + gtk_tree_store_set(store, &iter, PLUGIN_COLUMN_CHECK, is_active_plugin(p), PLUGIN_COLUMN_PLUGIN, p, + PLUGIN_COLUMN_CAN_UNCHECK, (p->proxied_count == 0), -1); } } @@ -1450,26 +1586,33 @@ static gboolean pm_treeview_query_tooltip(GtkWidget *widget, gint x, gint y, GtkTreeIter iter; GtkTreePath *path; Plugin *p = NULL; + gboolean can_uncheck = TRUE; if (! gtk_tree_view_get_tooltip_context(GTK_TREE_VIEW(widget), &x, &y, keyboard_mode, &model, &path, &iter)) return FALSE; - gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, -1); + gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, PLUGIN_COLUMN_CAN_UNCHECK, &can_uncheck, -1); if (p != NULL) { - gchar *markup; - gchar *details; + gchar *prefix, *suffix, *details, *markup; + const gchar *uchk; + uchk = can_uncheck ? + "" : _("\nOther plugins depend on this. Disable them first to allow deactivation.\n"); + /* Four allocations is less than ideal but meh */ details = g_strdup_printf(_("Version:\t%s\nAuthor(s):\t%s\nFilename:\t%s"), p->info.version, p->info.author, p->filename); - markup = g_markup_printf_escaped("%s\n%s\n\n%s", - p->info.name, p->info.description, details); + prefix = g_markup_printf_escaped("%s\n%s\n", p->info.name, p->info.description); + suffix = g_markup_printf_escaped("\n%s", details); + markup = g_strconcat(prefix, uchk, suffix, NULL); gtk_tooltip_set_markup(tooltip, markup); gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, path); g_free(details); + g_free(suffix); + g_free(prefix); g_free(markup); } gtk_tree_path_free(path); @@ -1605,7 +1748,7 @@ static void on_pm_tree_filter_entry_icon_release_cb(GtkEntry *entry, GtkEntryIco } -static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) +static void pm_prepare_treeview(GtkWidget *tree, GtkTreeStore *store) { GtkCellRenderer *text_renderer, *checkbox_renderer; GtkTreeViewColumn *column; @@ -1618,7 +1761,8 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) checkbox_renderer = gtk_cell_renderer_toggle_new(); column = gtk_tree_view_column_new_with_attributes( - _("Active"), checkbox_renderer, "active", PLUGIN_COLUMN_CHECK, NULL); + _("Active"), checkbox_renderer, + "active", PLUGIN_COLUMN_CHECK, "activatable", PLUGIN_COLUMN_CAN_UNCHECK, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); g_signal_connect(checkbox_renderer, "toggled", G_CALLBACK(pm_plugin_toggled), NULL); @@ -1760,9 +1904,10 @@ static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data) /* prepare treeview */ pm_widgets.tree = gtk_tree_view_new(); - pm_widgets.store = gtk_list_store_new( - PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_POINTER); + pm_widgets.store = gtk_tree_store_new( + PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_POINTER); pm_prepare_treeview(pm_widgets.tree, pm_widgets.store); + gtk_tree_view_expand_all(GTK_TREE_VIEW(pm_widgets.tree)); g_object_unref(pm_widgets.store); swin = gtk_scrolled_window_new(NULL, NULL); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 6 13:53:14 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Tue, 06 Oct 2015 13:53:14 -0000 Subject: [geany/geany] c6952c: Merge pull request #629 from kugel-/pluxy Message-ID: <20151006135850.86BD85C4226@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Tue, 06 Oct 2015 13:53:14 UTC Commit: c6952c75999c3775a5d7342bc3892c5c9451fb39 https://github.com/geany/geany/commit/c6952c75999c3775a5d7342bc3892c5c9451fb39 Log Message: ----------- Merge pull request #629 from kugel-/pluxy Add support for plugins acting as proxies for foreign plugins, promoting foreign plugins to first-class citizen. Modified Paths: -------------- doc/plugins.dox plugins/Makefile.am plugins/demoproxy.c plugins/demoproxytest.px po/POTFILES.skip src/plugindata.h src/pluginprivate.h src/plugins.c src/pluginutils.c src/utils.h Modified: doc/plugins.dox 397 lines changed, 397 insertions(+), 0 deletions(-) =================================================================== @@ -43,6 +43,7 @@ GeanyFuncs::cleanup functions). @section pluginsupport Plugin Support - @link howto Plugin HowTo @endlink - get started +- @ref proxy - @ref legacy - @link plugindata.h Plugin Datatypes and Macros @endlink - @link pluginsignals.c Plugin Signals @endlink @@ -725,4 +726,400 @@ void geany_load_module(GeanyPlugin *plugin) @endcode + at page proxy Proxy Plugin HowTo + + at section proxy_intro Introduction + +Geany has built-in support for plugins. These plugins can alter the way Geany operates in many +imaginable ways which leaves little to be desired. + +However, there is one significant short-coming. Due to the infrastructure, Geany's built-in support +only covers plugins written in C, perhaps C++ and Vala. Basically all languages which can be +compiled into native shared libraries and can link GTK libraries. This excludes dynamic languages +such as Python. + +Geany provides a mechanism to enable support for those languages. Native plugins can register as +proxy plugins by being a normal plugin to the Geany-side and by providing a bridge to write plugins +in another language on the other side. + +These plugins are also called sub-plugins. This refers to the relation to their proxy. +To Geany they are first-class citizens. + + at section proxy_protocol Writing a Proxy Plugin + +The basic idea is that a proxy plugin provides methods to match, load and unload one or more +sub-plugin plugins in an abstract manner: + + - Matching consists of providing a list of supported file extensions for the sub-plugins and + a mechanism to resolve file extension uncertainty or ambiguity. The matching makes the plugin + visible to the user within the Plugin Manager. + - Loading consists of loading the sub-plugin's file, passing the file to some form of interpreter + and calling GEANY_PLUGIN_REGISTER() or GEANY_PLUGIN_REGISTER_FULL() on behalf of the sub-plugin + at some point. + - Unloading simply reverses the effect of loading. + +For providing these methods, GeanyPlugin has a field GeanyProxyFuncs which contains three function +pointers which must be initialized proir to calling geany_plugin_register_proxy(). This should be +done in the GeanyPluginFuncs::init function of the proxy plugin. + + - In the call to geany_plugin_register_proxy() the proxy plugin passes a list of file extensions. + When Geany scans through its plugin directories as usual it will also look for files with + that extensions and consider found files as plugin candidate. + - GeanyProxyFuncs::probe may be implemented to probe if a plugin candidate (that has one of the + provided file extensions) is actually a plugin. This may depend on the plugin file itself in + case of ambiguity or availability of runtime dependencies or even configuration. + @ref PROXY_IGNORED or @ref PROXY_MATCHED should be returned, possibly in combination + with the @ref PROXY_NOLOAD flag. Not implementing GeanyProxyFuncs::probe at all is eqivalent to + always returning @ref PROXY_MATCHED. + - GeanyProxyFuncs::load must be implemented to actually load the plugin. It is called by Geany + when the user enables the sub-plugin. What "loading" means is entirely up to the proxy plugin and + probably depends on the interpreter of the dynamic language that shall be supported. After + setting everything up as necessary GEANY_PLUGIN_REGISTER() or GEANY_PLUGIN_REGISTER_FULL() must + be called to register the sub-plugin. + - GeanyProxyFuncs::unload must be implemented and is called when the user unchecks the sub-plugin + or when Geany exits. Here, the proxy should release any references or memory associated to the + sub-plugin. Note that if GeanyProxyFuncs::load didn't succeed, i.e. didn't successfully register + the sub-plugin, then this function won't be called. + +GeanyProxyFuncs::load and GeanyProxyFuncs::unload receive two GeanyPlugin pointers: One that +corresponds to the proxy itself and another that corresponds to the sub-plugin. The sub-plugin's +one may be used to call various API functions on behalf of the sub-plugin, including +GEANY_PLUGIN_REGISTER() and GEANY_PLUGIN_REGISTER_FULL(). + +GeanyProxyFuncs::load may return a pointer that is passed back to GeanyProxyFuncs::unload. This can +be used to store proxy-defined but sub-plugin-specific data required for unloading. However, this +pointer is not passed to the sub-plugin's GeanyPluginFuncs. To arrange for that, you want to call +GEANY_PLUGIN_REGISTER_FULL(). This method is the key to enable proxy plugins to wrap the +GeanyPluginFuncs of all sub-plugins and yet multiplex between multiple sub-plugin, for example by +storing a per-sub-plugin interpreter context. + + at note If the pointer returned from GeanyProxyFuncs::load is the same that is passed to +GEANY_PLUGIN_REGISTER_FULL() then you must pass NULL as free_func, because that would be invoked +prior to unloading. Insert the corresponding code into GeanyProxyFuncs::unload. + + at section proxy_compat_guideline Guideline for Checking Compatiblity + +Determining if a plugin candidate is compatible is not a single test. There are multiple levels and +each should be handled differently in order to give the user a consistent feedback. + +Consider the 5 basic cases: + +1) A candidate comes with a suitable file extension but is not a workable plugin file at all. For +example, your proxy supports plugins written in a shell script (.sh) but the shebang of that script +points to an incompatible shell (or even lacks a shebang). You should check for this in +GeanyProxyFuncs::probe() and return @ref PROXY_IGNORED which hides that script from the Plugin +Manager and allows other enabled proxy plugins to pick it up. GeanyProxyFuncs::probe() returning + at ref PROXY_IGNORED is an indication that the candidate is meant for another proxy, or the user +placed the file by accident in one of Geany's plugin directories. In other words the candidate +simply doesn't correspond to your proxy. Thus any noise by debug messages for this case is +undesirable. + +2) A proxy plugin provides its own, versioned API to sub-plugin. The API version of the sub-plugin +is not compatible with the API exposed by the proxy. GeanyProxyFuncs::probe() should never perform +a version check because its sole purpose is to indicate a proxy's correspondence to a given +candidate. It should return @ref PROXY_MATCHED instead. Later, Geany will invoke the +GeanyProxyFuncs::load(), and this function is the right place for a version check. If it fails then +you simply do not call GEANY_PLUGIN_REGISTER(), but rather print a debug message. The result is +that the sub-plugin is not shown in the Plugin Manager at all. This is consistent with the +treatment of native plugins by Geany. + +3) The sub-plugin is also depending on Geany's API version (whether it is or not depends on the +design of the proxy). In this case do not do anything special but forward the API version the +sub-plugin is written/compiled against to GEANY_PLUGIN_REGISTER(). Here, Geany will perform its own +compatiblity check, allowing for a consistent user feedback. The result is again that the +sub-plugin is hidden from the Plugin Manager, like in case 2. But Geany will print a debug message +so you can skip that. + + +If you have even more cases try to fit it into case 1 or 2, depending on whether other proxy +plugins should get a chance to load the candidate or not. + + at section proxy_dep_guideline Guideline for Runtime Errors + +A sub-plugin might not be able to run even if it's perfectly compatible with its proxy. This +includes the case when it lacks certain runtime dependencies such as programs or modules but also +syntactic problems or other errors. + +There are two basic classes: + +1) Runtime errors that can be determined at load time. For example, the shebang of a script +indicates a specific interpeter version but that version is not installed on the system. Your proxy +should respond the same way as for version-incompatible plugins: don't register the plugin at +all, but leave a message the user suggesting what has to be installed in order to work. Handle +syntax errors in the scripts of sub-plugins the same way if possible. + +2) Runtime errors that cannot be determined without actually running the plugin. An example would +be missing modules in Python scripts. If your proxy has no way of foreseeing the problem the plugin +will be registered normally. However, you can catch runtime errors by implementing +GeanyPluginFuncs::init() on the plugin's behalf. This is called after user activation and allows to +indicate errors by returning @c FALSE. However, allowing the user to enable a plugin and then +disabling anyway is a poor user experience. + +Therefore, if possible, try to fail fast and disallow registration. + + at section Proxy Plugin Example + +In this section a dumb example proxy plugin is shown in order to give a practical starting point. +The sub-plugin are not actually code but rather a ini-style description of one or more menu items +that are added to Geany's tools menu and a help dialog. Real world sub-plugins would contain actual +code, usually written in a scripting language. + +A sub-plugin file looks like this: + + at code{.ini} +#!!PROXY_MAGIC!! + +[Init] +item0 = Bam +item1 = Foo +item2 = Bar + +[Help] +text = I'm a simple test. Nothing to see! + +[Info] +name = Demo Proxy Tester +description = I'm a simple test. Nothing to see! +version = 0.1 +author = The Geany developer team + at endcode + +The first line acts as a verification that this file is truly a sub-plugin. Within the [Init] section +there is the menu items for Geany's tools menu. The [Help] section declares the sub-plugins help +text which is shown in its help dialog (via GeanyPluginFuncs::help). The [Info] section is +used as-is for filling the sub-plugins PluginInfo fields. + +That's it, this dumb format is purely declarative and contains no logic. Yet we will create plugins +from it. + +We start by registering the proxy plugin to Geany. There is nothing special to it compared to +normal plugins. A proxy plugin must also fill its own @ref PluginInfo and @ref GeanyPluginFuncs, +followed by registering through GEANY_PLUGIN_REGISTER(). + + + at code{.c} + +/* Called by Geany to initialize the plugin. */ +static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + // ... +} + + +/* Called by Geany before unloading the plugin. */ +static void demoproxy_cleanup(GeanyPlugin *plugin, gpointer data) +{ + // ... +} + + +G_MODULE_EXPORT +void geany_load_module(GeanyPlugin *plugin) +{ + plugin->info->name = _("Demo Proxy"); + plugin->info->description = _("Example Proxy."); + plugin->info->version = "0.1"; + plugin->info->author = _("The Geany developer team"); + + plugin->funcs->init = demoproxy_init; + plugin->funcs->cleanup = demoproxy_cleanup; + + GEANY_PLUGIN_REGISTER(plugin, 225); +} + + at endcode + +The next step is to actually register as a proxy plugin. This is done in demoproxy_init(). +As previously mentioned, it needs a list of accepted file extensions and a set of callback +functions. + + at code{.c} +static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + const gchar *extensions[] = { "ini", "px", NULL }; + + plugin->proxy_funcs->probe = demoproxy_probe; + plugin->proxy_funcs->load = demoproxy_load; + plugin->proxy_funcs->unload = demoproxy_unload; + + return geany_plugin_register_proxy(plugin, extensions); +} + + at endcode + +The callback functions deserve a closer look. + +As already mentioned the file format includes a magic first line which must be present. +GeanyProxyFuncs::probe() verifies that it's present and avoids showing the sub-plugin in the +Plugin Manager if not. + + at code{.c} +static gint demoproxy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata) +{ + /* We know the extension is right (Geany checks that). For demo purposes we perform an + * additional check. This is not necessary when the extension is unique enough. */ + gboolean match = FALSE; + gchar linebuf[128]; + FILE *f = fopen(filename, "r"); + if (f != NULL) + { + if (fgets(linebuf, sizeof(linebuf), f) != NULL) + match = utils_str_equal(linebuf, "#!!PROXY_MAGIC!!\n"); + fclose(f); + } + return match ? PROXY_MATCHED : PROXY_IGNORED; +} + at endcode + +GeanyProxyFuncs::load is a bit more complex. It reads the file, fills the sub-plugin's PluginInfo +fields and calls GEANY_PLUGIN_REGISTER_FULL(). Additionally, it creates a per-plugin context that +holds GKeyFile instance (a poor man's interpeter context). You can also see that it does not call +GEANY_PLUGIN_REGISTER_FULL() if g_key_file_load_from_file() found an error (probably a syntax +problem) which means the sub-plugin cannot be enabled. + +It also installs wrapper functions for the sub-plugin's GeanyPluginFuncs as ini files aren't code. +It's very likely that your proxy needs something similar because you can only install function +pointers to native code. + + at code{.c} +typedef struct { + GKeyFile *file; + gchar *help_text; + GSList *menu_items; +} +PluginContext; + + +static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata); +static void proxy_help(GeanyPlugin *plugin, gpointer pdata); +static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata); + + +static gpointer demoproxy_load(GeanyPlugin *proxy, GeanyPlugin *plugin, + const gchar *filename, gpointer pdata) +{ + GKeyFile *file; + gboolean result; + + file = g_key_file_new(); + result = g_key_file_load_from_file(file, filename, 0, NULL); + + if (result) + { + PluginContext *data = g_new0(PluginContext, 1); + data->file = file; + + plugin->info->name = g_key_file_get_locale_string(data->file, "Info", "name", NULL, NULL); + plugin->info->description = g_key_file_get_locale_string(data->file, "Info", "description", NULL, NULL); + plugin->info->version = g_key_file_get_locale_string(data->file, "Info", "version", NULL, NULL); + plugin->info->author = g_key_file_get_locale_string(data->file, "Info", "author", NULL, NULL); + + plugin->funcs->init = proxy_init; + plugin->funcs->help = proxy_help; + plugin->funcs->cleanup = proxy_cleanup; + + /* Cannot pass g_free as free_func be Geany calls it before unloading, and since + * demoproxy_unload() accesses the data this would be catastrophic */ + GEANY_PLUGIN_REGISTER_FULL(plugin, 225, data, NULL); + return data; + } + + g_key_file_free(file); + return NULL; +} + at endcode + +demoproxy_unload() simply releases all resources aquired in demoproxy_load(). It does not have to +do anything else in for unloading. + + at code{.c} +static void demoproxy_unload(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer load_data, gpointer pdata) +{ + PluginContext *data = load_data; + + g_free((gchar *)plugin->info->name); + g_free((gchar *)plugin->info->description); + g_free((gchar *)plugin->info->version); + g_free((gchar *)plugin->info->author); + + g_key_file_free(data->file); + g_free(data); +} + at endcode + +Finally the demo_proxy's wrapper GeanyPluginFuncs. They are called for each possible sub-plugin and +therefore have to multiplex between each using the plugin-defined data pointer. Each is called by +Geany as if it were an ordinary, native plugin. + +proxy_init() actually reads the sub-plugin's file using GKeyFile APIs. It prepares for the help +dialog and installs the menu items. proxy_help() is called when the user clicks the help button in +the Plugin Manager. Consequently, this fires up a suitable dialog, although with a dummy message. +proxy_cleanup() frees all memory allocated in proxy_init(). + + at code{.c} +static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data; + gint i = 0; + gchar *text; + + data = (PluginContext *) pdata; + + /* Normally, you would instruct the VM/interpreter to call into the actual plugin. The + * plugin would be identified by pdata. Because there is no interpreter for + * .ini files we do it inline, as this is just a demo */ + data->help_text = g_key_file_get_locale_string(data->file, "Help", "text", NULL, NULL); + while (TRUE) + { + GtkWidget *item; + gchar *key = g_strdup_printf("item%d", i++); + text = g_key_file_get_locale_string(data->file, "Init", key, NULL, NULL); + g_free(key); + + if (!text) + break; + + item = gtk_menu_item_new_with_label(text); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(plugin->geany_data->main_widgets->tools_menu), item); + gtk_widget_set_sensitive(item, FALSE); + data->menu_items = g_slist_prepend(data->menu_items, (gpointer) item); + g_free(text); + } + + return TRUE; +} + + +static void proxy_help(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data; + GtkWidget *dialog; + + data = (PluginContext *) pdata; + + dialog = gtk_message_dialog_new( + GTK_WINDOW(plugin->geany_data->main_widgets->window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_OK, + "%s", data->help_text); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + _("(From the %s plugin)"), plugin->info->name); + + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + + +static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data = (PluginContext *) pdata; + + g_slist_free_full(data->menu_items, (GDestroyNotify) gtk_widget_destroy); + g_free(data->help_text); +} + at endcode + + */ Modified: plugins/Makefile.am 10 lines changed, 8 insertions(+), 2 deletions(-) =================================================================== @@ -1,7 +1,8 @@ # Adapted from Pidgin's plugins/Makefile.am, thanks EXTRA_DIST = \ - makefile.win32 + makefile.win32 \ + demoproxytest.px plugindir = $(libdir)/geany @@ -11,6 +12,7 @@ plugins_include_HEADERS = \ geanyplugin.h demoplugin_la_LDFLAGS = -module -avoid-version -no-undefined +demoproxy_la_LDFLAGS = -module -avoid-version -no-undefined classbuilder_la_LDFLAGS = -module -avoid-version -no-undefined htmlchars_la_LDFLAGS = -module -avoid-version -no-undefined export_la_LDFLAGS = -module -avoid-version -no-undefined @@ -30,9 +32,11 @@ plugin_LTLIBRARIES = \ # Plugins not to be installed noinst_LTLIBRARIES = \ - demoplugin.la + demoplugin.la \ + demoproxy.la demoplugin_la_SOURCES = demoplugin.c +demoproxy_la_SOURCES = demoproxy.c classbuilder_la_SOURCES = classbuilder.c htmlchars_la_SOURCES = htmlchars.c export_la_SOURCES = export.c @@ -41,6 +45,7 @@ filebrowser_la_SOURCES = filebrowser.c splitwindow_la_SOURCES = splitwindow.c demoplugin_la_CFLAGS = -DG_LOG_DOMAIN=\""Demoplugin"\" -DLOCALEDIR=\""$(LOCALEDIR)"\" +demoproxy_la_CFLAGS = -DG_LOG_DOMAIN=\""Demoproxy"\" classbuilder_la_CFLAGS = -DG_LOG_DOMAIN=\""Classbuilder"\" htmlchars_la_CFLAGS = -DG_LOG_DOMAIN=\""HTMLChars"\" export_la_CFLAGS = -DG_LOG_DOMAIN=\""Export"\" @@ -49,6 +54,7 @@ filebrowser_la_CFLAGS = -DG_LOG_DOMAIN=\""FileBrowser"\" splitwindow_la_CFLAGS = -DG_LOG_DOMAIN=\""SplitWindow"\" demoplugin_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) +demoproxy_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) classbuilder_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) htmlchars_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) export_la_LIBADD = $(top_builddir)/src/libgeany.la $(GTK_LIBS) -lm Modified: plugins/demoproxy.c 202 lines changed, 202 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,202 @@ +/* + * demoproxy.c - this file is part of Geany, a fast and lightweight IDE + * + * Copyright 2015 Thomas Martitz + * + * 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. + */ + +/** + * Demo proxy - example of a basic proxy plugin for Geany. Sub-plugins add menu items to the + * Tools menu and have a help dialog. + * + * Note: This is compiled but not installed by default. On Unix, you can install it by compiling + * Geany and then copying (or symlinking) to the plugins/demoproxy.so and + * plugins/demoproxytest.px files to ~/.config/geany/plugins + * - it will be loaded at next startup. + */ + +/* plugin API, always comes first */ +#include "geanyplugin.h" + +typedef struct { + GKeyFile *file; + gchar *help_text; + GSList *menu_items; +} +PluginContext; + + +static gboolean proxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data; + gint i = 0; + gchar *text; + + data = (PluginContext *) pdata; + + /* Normally, you would instruct the VM/interpreter to call into the actual plugin. The + * plugin would be identified by pdata. Because there is no interpreter for + * .ini files we do it inline, as this is just a demo */ + data->help_text = g_key_file_get_locale_string(data->file, "Help", "text", NULL, NULL); + while (TRUE) + { + GtkWidget *item; + gchar *key = g_strdup_printf("item%d", i++); + text = g_key_file_get_locale_string(data->file, "Init", key, NULL, NULL); + g_free(key); + + if (!text) + break; + + item = gtk_menu_item_new_with_label(text); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(plugin->geany_data->main_widgets->tools_menu), item); + gtk_widget_set_sensitive(item, FALSE); + data->menu_items = g_slist_prepend(data->menu_items, (gpointer) item); + g_free(text); + } + + return TRUE; +} + + +static void proxy_help(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data; + GtkWidget *dialog; + + data = (PluginContext *) pdata; + + dialog = gtk_message_dialog_new( + GTK_WINDOW(plugin->geany_data->main_widgets->window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_OK, + "%s", data->help_text); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + _("(From the %s plugin)"), plugin->info->name); + + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + + +static void proxy_cleanup(GeanyPlugin *plugin, gpointer pdata) +{ + PluginContext *data = (PluginContext *) pdata; + + g_slist_free_full(data->menu_items, (GDestroyNotify) gtk_widget_destroy); + g_free(data->help_text); +} + + +static gint demoproxy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata) +{ + /* We know the extension is right (Geany checks that). For demo purposes we perform an + * additional check. This is not necessary when the extension is unique enough. */ + gboolean match = FALSE; + gchar linebuf[128]; + FILE *f = fopen(filename, "r"); + if (f != NULL) + { + if (fgets(linebuf, sizeof(linebuf), f) != NULL) + match = utils_str_equal(linebuf, "#!!PROXY_MAGIC!!\n"); + fclose(f); + } + return match ? PROXY_MATCHED : PROXY_IGNORED; +} + + +static gpointer demoproxy_load(GeanyPlugin *proxy, GeanyPlugin *plugin, + const gchar *filename, gpointer pdata) +{ + GKeyFile *file; + gboolean result; + + file = g_key_file_new(); + result = g_key_file_load_from_file(file, filename, 0, NULL); + + if (result) + { + PluginContext *data = g_new0(PluginContext, 1); + data->file = file; + + plugin->info->name = g_key_file_get_locale_string(data->file, "Info", "name", NULL, NULL); + plugin->info->description = g_key_file_get_locale_string(data->file, "Info", "description", NULL, NULL); + plugin->info->version = g_key_file_get_locale_string(data->file, "Info", "version", NULL, NULL); + plugin->info->author = g_key_file_get_locale_string(data->file, "Info", "author", NULL, NULL); + + plugin->funcs->init = proxy_init; + plugin->funcs->help = proxy_help; + plugin->funcs->cleanup = proxy_cleanup; + + /* Cannot pass g_free as free_func be Geany calls it before unloading, and since + * demoproxy_unload() accesses the data this would be catastrophic */ + GEANY_PLUGIN_REGISTER_FULL(plugin, 225, data, NULL); + return data; + } + + g_key_file_free(file); + return NULL; +} + + +static void demoproxy_unload(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer load_data, gpointer pdata) +{ + PluginContext *data = load_data; + + g_free((gchar *)plugin->info->name); + g_free((gchar *)plugin->info->description); + g_free((gchar *)plugin->info->version); + g_free((gchar *)plugin->info->author); + + g_key_file_free(data->file); + g_free(data); +} + + +/* Called by Geany to initialize the plugin. */ +static gboolean demoproxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + const gchar *extensions[] = { "ini", "px", NULL }; + + plugin->proxy_funcs->probe = demoproxy_probe; + plugin->proxy_funcs->load = demoproxy_load; + plugin->proxy_funcs->unload = demoproxy_unload; + + return geany_plugin_register_proxy(plugin, extensions); +} + + +/* Called by Geany before unloading the plugin. */ +static void demoproxy_cleanup(GeanyPlugin *plugin, gpointer data) +{ +} + + +G_MODULE_EXPORT +void geany_load_module(GeanyPlugin *plugin) +{ + plugin->info->name = _("Demo Proxy"); + plugin->info->description = _("Example Proxy."); + plugin->info->version = "0.1"; + plugin->info->author = _("The Geany developer team"); + + plugin->funcs->init = demoproxy_init; + plugin->funcs->cleanup = demoproxy_cleanup; + + GEANY_PLUGIN_REGISTER(plugin, 225); +} Modified: plugins/demoproxytest.px 15 lines changed, 15 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,15 @@ +#!!PLUXY_MAGIC!! + +[Init] +item0 = Bam +item1 = Foo +item2 = Bar + +[Help] +text = I'm a simple test. Nothing to see! + +[Info] +name = Demo Pluxy Tester +description = I'm a simple test. Nothing to see! +version = 0.1 +author = The Geany developer team Modified: po/POTFILES.skip 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -6,5 +6,6 @@ geany.desktop.in geany.glade # no need to translate these files plugins/demoplugin.c +plugins/demoproxy.c doc/stash-example.c doc/stash-gui-example.c Modified: src/plugindata.h 56 lines changed, 54 insertions(+), 2 deletions(-) =================================================================== @@ -58,7 +58,7 @@ G_BEGIN_DECLS * @warning You should not test for values below 200 as previously * @c GEANY_API_VERSION was defined as an enum value, not a macro. */ -#define GEANY_API_VERSION 225 +#define GEANY_API_VERSION 226 /* hack to have a different ABI when built with GTK3 because loading GTK2-linked plugins * with GTK3-linked Geany leads to crash */ @@ -240,6 +240,7 @@ GeanyData; #define geany geany_data /**< Simple macro for @c geany_data that reduces typing. */ typedef struct GeanyPluginFuncs GeanyPluginFuncs; +typedef struct GeanyProxyFuncs GeanyProxyFuncs; /** Basic information for the plugin and identification. * @see geany_plugin. */ @@ -248,7 +249,8 @@ typedef struct GeanyPlugin PluginInfo *info; /**< Fields set in plugin_set_info(). */ GeanyData *geany_data; /**< Pointer to global GeanyData intance */ GeanyPluginFuncs *funcs; /**< Functions implemented by the plugin, set in geany_load_module() */ - + GeanyProxyFuncs *proxy_funcs; /**< Hooks implemented by the plugin if it wants to act as a proxy + Must be set prior to calling geany_plugin_register_proxy() */ struct GeanyPluginPrivate *priv; /* private */ } GeanyPlugin; @@ -347,6 +349,56 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify fr geany_plugin_register_full((plugin), GEANY_API_VERSION, \ (min_api_version), GEANY_ABI_VERSION, (pdata), (free_func)) +/** Return values for GeanyProxyHooks::probe() + * + * Only @c PROXY_IGNORED, @c PROXY_MATCHED or @c PROXY_MATCHED|PROXY_NOLOAD + * are valid return values. + * + * @see geany_plugin_register_proxy() for a full description of the proxy plugin mechanisms. + * + * @since 1.26 (API 226) + */ +typedef enum +{ + /** The proxy is not responsible at all, and Geany or other plugins are free + * to probe it. + **/ + PROXY_IGNORED, + /** The proxy is responsible for this file, and creates a plugin for it */ + PROXY_MATCHED, + + /** The proxy is does not directly load it, but it's still tied to the proxy + * + * This is for plugins that come in multiple files where only one of these + * files is relevant for the plugin creation (for the PM dialog). The other + * files should be ignored by Geany and other proxies. Example: libpeas has + * a .plugin and a .so per plugin. Geany should not process the .so file + * if there is a corresponding .plugin. + */ + PROXY_NOLOAD = 0x100, +} +GeanyProxyProbeResults; + + +/** Hooks that need to be implemented by every proxy + * + * @see geany_plugin_register_proxy() for a full description of the proxy mechanism. + * + * @since 1.26 (API 226) + **/ +struct GeanyProxyFuncs +{ + /** Called to determine whether the proxy is truly responsible for the requested plugin. + * A NULL pointer assumes the probe() function would always return @ref PROXY_MATCHED */ + gint (*probe) (GeanyPlugin *proxy, const gchar *filename, gpointer pdata); + /** Called after probe(), to perform the actual job of loading the plugin */ + gpointer (*load) (GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata); + /** Called when the user initiates unloading of a plugin, e.g. on Geany exit */ + void (*unload) (GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata); +}; + +gint geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensions); + /* Deprecated aliases */ #ifndef GEANY_DISABLE_DEPRECATED Modified: src/pluginprivate.h 16 lines changed, 12 insertions(+), 4 deletions(-) =================================================================== @@ -46,9 +46,10 @@ typedef enum _LoadedFlags { } LoadedFlags; +typedef struct GeanyPluginPrivate Plugin; /* shorter alias */ + typedef struct GeanyPluginPrivate { - GModule *module; gchar *filename; /* plugin filename (/path/libname.so) */ PluginInfo info; /* plugin name, description, etc */ GeanyPlugin public; /* fields the plugin can read */ @@ -66,6 +67,14 @@ typedef struct GeanyPluginPrivate gpointer cb_data; /* user data passed back to functions in GeanyPluginFuncs */ GDestroyNotify cb_data_destroy; /* called when the plugin is unloaded, for cb_data */ LoadedFlags flags; /* bit-or of LoadedFlags */ + + /* proxy plugin support */ + GeanyProxyFuncs proxy_cbs; + Plugin *proxy; /* The proxy that handles this plugin */ + gpointer proxy_data; /* Data passed to the proxy hooks of above proxy, so + * this gives the proxy a pointer to each plugin */ + gint proxied_count; /* count of active plugins this provides a proxy for + * (a count because of possibly nested proxies) */ } GeanyPluginPrivate; @@ -73,10 +82,9 @@ GeanyPluginPrivate; #define PLUGIN_IS_LEGACY(p) (((p)->flags & IS_LEGACY) != 0) #define PLUGIN_HAS_LOAD_DATA(p) (((p)->flags & LOAD_DATA) != 0) -typedef GeanyPluginPrivate Plugin; /* shorter alias */ - - void plugin_watch_object(Plugin *plugin, gpointer object); +void plugin_make_resident(Plugin *plugin); +gpointer plugin_get_module_symbol(Plugin *plugin, const gchar *sym); G_END_DECLS Modified: src/plugins.c 697 lines changed, 564 insertions(+), 133 deletions(-) =================================================================== @@ -75,8 +75,33 @@ static GtkWidget *menu_separator = NULL; static gchar *get_plugin_path(void); static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data); -static GeanyData geany_data; +typedef struct { + gchar extension[8]; + Plugin *plugin; /* &builtin_so_proxy_plugin for native plugins */ +} PluginProxy; + + +static gpointer plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *plugin, const gchar *filename, gpointer pdata); +static void plugin_unload_gmodule(GeanyPlugin *proxy, GeanyPlugin *plugin, gpointer load_data, gpointer pdata); + +static Plugin builtin_so_proxy_plugin = { + .proxy_cbs = { + .load = plugin_load_gmodule, + .unload = plugin_unload_gmodule, + }, + /* rest of Plugin can be NULL/0 */ +}; +static PluginProxy builtin_so_proxy = { + .extension = G_MODULE_SUFFIX, + .plugin = &builtin_so_proxy_plugin, +}; + +static GQueue active_proxies = G_QUEUE_INIT; + +static void plugin_free(Plugin *plugin); + +static GeanyData geany_data; static void geany_data_init(void) @@ -102,19 +127,42 @@ geany_data_init(void) } +/* In order to have nested proxies work the count of dependent plugins must propagate up. + * This prevents that any plugin in the tree is unloaded while a leaf plugin is active. */ +static void proxied_count_inc(Plugin *proxy) +{ + do + { + proxy->proxied_count += 1; + proxy = proxy->proxy; + } while (proxy != NULL); +} + + +static void proxied_count_dec(Plugin *proxy) +{ + g_warn_if_fail(proxy->proxied_count > 0); + + do + { + proxy->proxied_count -= 1; + proxy = proxy->proxy; + } while (proxy != NULL); +} + + /* Prevent the same plugin filename being loaded more than once. * Note: g_module_name always returns the .so name, even when Plugin::filename is a .la file. */ static gboolean -plugin_loaded(GModule *module) +plugin_loaded(Plugin *plugin) { gchar *basename_module, *basename_loaded; GList *item; - basename_module = g_path_get_basename(g_module_name(module)); + basename_module = g_path_get_basename(plugin->filename); for (item = plugin_list; item != NULL; item = g_list_next(item)) { - basename_loaded = g_path_get_basename( - g_module_name(((Plugin*)item->data)->module)); + basename_loaded = g_path_get_basename(((Plugin*)item->data)->filename); if (utils_str_equal(basename_module, basename_loaded)) { @@ -131,7 +179,7 @@ plugin_loaded(GModule *module) * would cause a crash. */ for (item = active_plugin_list; item != NULL; item = g_list_next(item)) { - basename_loaded = g_path_get_basename(g_module_name(((Plugin*)item->data)->module)); + basename_loaded = g_path_get_basename(((Plugin*)item->data)->filename); if (utils_str_equal(basename_module, basename_loaded)) { @@ -168,22 +216,27 @@ static Plugin *find_active_plugin_by_name(const gchar *filename) static gboolean plugin_check_version(Plugin *plugin, int plugin_version_code) { - GModule *module = plugin->module; + gboolean ret = TRUE; if (plugin_version_code < 0) { + gchar *name = g_path_get_basename(plugin->filename); msgwin_status_add(_("The plugin \"%s\" is not binary compatible with this " - "release of Geany - please recompile it."), g_module_name(module)); + "release of Geany - please recompile it."), name); geany_debug("Plugin \"%s\" is not binary compatible with this " - "release of Geany - recompile it.", g_module_name(module)); - return FALSE; + "release of Geany - recompile it.", name); + ret = FALSE; + g_free(name); } - if (plugin_version_code > GEANY_API_VERSION) + else if (plugin_version_code > GEANY_API_VERSION) { + gchar *name = g_path_get_basename(plugin->filename); geany_debug("Plugin \"%s\" requires a newer version of Geany (API >= v%d).", - g_module_name(module), plugin_version_code); - return FALSE; + name, plugin_version_code); + ret = FALSE; + g_free(name); } - return TRUE; + + return ret; } @@ -217,9 +270,10 @@ static void read_key_group(Plugin *plugin) { GeanyKeyGroupInfo *p_key_info; GeanyKeyGroup **p_key_group; + GModule *module = plugin->proxy_data; - g_module_symbol(plugin->module, "plugin_key_group_info", (void *) &p_key_info); - g_module_symbol(plugin->module, "plugin_key_group", (void *) &p_key_group); + g_module_symbol(module, "plugin_key_group_info", (void *) &p_key_info); + g_module_symbol(module, "plugin_key_group", (void *) &p_key_group); if (p_key_info && p_key_group) { GeanyKeyGroupInfo *key_info = p_key_info; @@ -307,8 +361,10 @@ gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_a /* Only init and cleanup callbacks are truly mandatory. */ if (! cbs->init || ! cbs->cleanup) { - geany_debug("Plugin '%s' has no %s function - ignoring plugin!", - g_module_name(p->module), cbs->init ? "cleanup" : "init"); + gchar *name = g_path_get_basename(p->filename); + geany_debug("Plugin '%s' has no %s function - ignoring plugin!", name, + cbs->init ? "cleanup" : "init"); + g_free(name); } else { @@ -425,7 +481,7 @@ static void register_legacy_plugin(Plugin *plugin, GModule *module) if (! g_module_symbol(module, "plugin_" #__x, (void *) (&p_##__x))) \ { \ geany_debug("Plugin \"%s\" has no plugin_" #__x "() function - ignoring plugin!", \ - g_module_name(plugin->module)); \ + g_module_name(module)); \ return; \ } CHECK_FUNC(version_check); @@ -481,21 +537,23 @@ static gboolean plugin_load(Plugin *plugin) { gboolean init_ok = TRUE; + /* Start the plugin. Legacy plugins require additional cruft. */ - if (PLUGIN_IS_LEGACY(plugin)) + if (PLUGIN_IS_LEGACY(plugin) && plugin->proxy == &builtin_so_proxy_plugin) { GeanyPlugin **p_geany_plugin; PluginInfo **p_info; PluginFields **plugin_fields; + GModule *module = plugin->proxy_data; /* set these symbols before plugin_init() is called * we don't set geany_data since it is set directly by plugin_new() */ - g_module_symbol(plugin->module, "geany_plugin", (void *) &p_geany_plugin); + g_module_symbol(module, "geany_plugin", (void *) &p_geany_plugin); if (p_geany_plugin) *p_geany_plugin = &plugin->public; - g_module_symbol(plugin->module, "plugin_info", (void *) &p_info); + g_module_symbol(module, "plugin_info", (void *) &p_info); if (p_info) *p_info = &plugin->info; - g_module_symbol(plugin->module, "plugin_fields", (void *) &plugin_fields); + g_module_symbol(module, "plugin_fields", (void *) &plugin_fields); if (plugin_fields) *plugin_fields = &plugin->fields; read_key_group(plugin); @@ -525,26 +583,77 @@ plugin_load(Plugin *plugin) * keep list sorted so tools menu items and plugin preference tabs are * sorted by plugin name */ active_plugin_list = g_list_insert_sorted(active_plugin_list, plugin, cmp_plugin_names); + proxied_count_inc(plugin->proxy); geany_debug("Loaded: %s (%s)", plugin->filename, plugin->info.name); return TRUE; } +static gpointer plugin_load_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *fname, gpointer pdata) +{ + GModule *module; + void (*p_geany_load_module)(GeanyPlugin *); + + g_return_val_if_fail(g_module_supported(), NULL); + /* Don't use G_MODULE_BIND_LAZY otherwise we can get unresolved symbols at runtime, + * causing a segfault. Without that flag the module will safely fail to load. + * G_MODULE_BIND_LOCAL also helps find undefined symbols e.g. app when it would + * otherwise not be detected due to the shadowing of Geany's app variable. + * Also without G_MODULE_BIND_LOCAL calling public functions e.g. the old info() + * function from a plugin will be shadowed. */ + module = g_module_open(fname, G_MODULE_BIND_LOCAL); + if (!module) + { + geany_debug("Can't load plugin: %s", g_module_error()); + return NULL; + } + + /*geany_debug("Initializing plugin '%s'", plugin->info.name);*/ + g_module_symbol(module, "geany_load_module", (void *) &p_geany_load_module); + if (p_geany_load_module) + { + /* This is a new style plugin. It should fill in plugin->info and then call + * geany_plugin_register() in its geany_load_module() to successfully load. + * The ABI and API checks are performed by geany_plugin_register() (i.e. by us). + * We check the LOADED_OK flag separately to protect us against buggy plugins + * who ignore the result of geany_plugin_register() and register anyway */ + p_geany_load_module(subplugin); + } + else + { + /* This is the legacy / deprecated code path. It does roughly the same as + * geany_load_module() and geany_plugin_register() together for the new ones */ + register_legacy_plugin(subplugin->priv, module); + } + /* We actually check the LOADED_OK flag later */ + return module; +} + + +static void plugin_unload_gmodule(GeanyPlugin *proxy, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata) +{ + GModule *module = (GModule *) load_data; + + g_return_if_fail(module != NULL); + + if (! g_module_close(module)) + g_warning("%s: %s", subplugin->priv->filename, g_module_error()); +} + + /* Load and optionally init a plugin. * load_plugin decides whether the plugin's plugin_init() function should be called or not. If it is * called, the plugin will be started, if not the plugin will be read only (for the list of * available plugins in the plugin manager). * When add_to_list is set, the plugin will be added to the plugin manager's plugin_list. */ static Plugin* -plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list) +plugin_new(Plugin *proxy, const gchar *fname, gboolean load_plugin, gboolean add_to_list) { Plugin *plugin; - GModule *module; - void (*p_geany_load_module)(GeanyPlugin *); g_return_val_if_fail(fname, NULL); - g_return_val_if_fail(g_module_supported(), NULL); + g_return_val_if_fail(proxy, NULL); /* find the plugin in the list of already loaded, active plugins and use it, otherwise * load the module */ @@ -563,64 +672,48 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list) return plugin; } - /* Don't use G_MODULE_BIND_LAZY otherwise we can get unresolved symbols at runtime, - * causing a segfault. Without that flag the module will safely fail to load. - * G_MODULE_BIND_LOCAL also helps find undefined symbols e.g. app when it would - * otherwise not be detected due to the shadowing of Geany's app variable. - * Also without G_MODULE_BIND_LOCAL calling public functions e.g. the old info() - * function from a plugin will be shadowed. */ - module = g_module_open(fname, G_MODULE_BIND_LOCAL); - if (! module) - { - geany_debug("Can't load plugin: %s", g_module_error()); - return NULL; - } - - if (plugin_loaded(module)) - { - geany_debug("Plugin \"%s\" already loaded.", fname); - - if (! g_module_close(module)) - g_warning("%s: %s", fname, g_module_error()); - return NULL; - } - plugin = g_new0(Plugin, 1); - plugin->module = module; plugin->filename = g_strdup(fname); + plugin->proxy = proxy; plugin->public.geany_data = &geany_data; plugin->public.priv = plugin; /* Fields of plugin->info/funcs must to be initialized by the plugin */ plugin->public.info = &plugin->info; plugin->public.funcs = &plugin->cbs; + plugin->public.proxy_funcs = &plugin->proxy_cbs; - g_module_symbol(module, "geany_load_module", (void *) &p_geany_load_module); - if (p_geany_load_module) + if (plugin_loaded(plugin)) { - /* This is a new style plugin. It should fill in plugin->info and then call - * geany_plugin_register() in its geany_load_module() to successfully load. - * The ABI and API checks are performed by geany_plugin_register() (i.e. by us). - * We check the LOADED_OK flag separately to protect us against buggy plugins - * who ignore the result of geany_plugin_register() and register anyway */ - p_geany_load_module(&plugin->public); - } - else - { - /* This is the legacy / deprecated code path. It does roughly the same as - * geany_load_module() and geany_plugin_register() together for the new ones */ - register_legacy_plugin(plugin, module); + geany_debug("Plugin \"%s\" already loaded.", fname); + goto err; } + /* Load plugin, this should read its name etc. It must also call + * geany_plugin_register() for the following PLUGIN_LOADED_OK condition */ + plugin->proxy_data = proxy->proxy_cbs.load(&proxy->public, &plugin->public, fname, proxy->cb_data); + if (! PLUGIN_LOADED_OK(plugin)) { geany_debug("Failed to load \"%s\" - ignoring plugin!", fname); goto err; } + /* The proxy assumes success, therefore we have to call unload from here + * on in case of errors */ if (EMPTY(plugin->info.name)) { geany_debug("No plugin name set for \"%s\" - ignoring plugin!", fname); - goto err; + goto err_unload; + } + + /* cb_data_destroy() frees plugin->cb_data. If that pointer also passed to unload() afterwards + * then that would become a use-after-free. Disallow this combination. If a proxy + * needs the same pointer it must not use a destroy func but free manually in its unload(). */ + if (plugin->proxy_data == proxy->cb_data && plugin->cb_data_destroy) + { + geany_debug("Proxy of plugin \"%s\" specified invalid data - ignoring plugin!", fname); + plugin->proxy_data = NULL; + goto err_unload; } if (load_plugin && !plugin_load(plugin)) @@ -628,7 +721,7 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list) /* Handle failing init same as failing to load for now. In future we * could present a informational UI or something */ geany_debug("Plugin failed to initialize \"%s\" - ignoring plugin!", fname); - goto err; + goto err_unload; } if (add_to_list) @@ -636,11 +729,11 @@ plugin_new(const gchar *fname, gboolean load_plugin, gboolean add_to_list) return plugin; -err: +err_unload: if (plugin->cb_data_destroy) plugin->cb_data_destroy(plugin->cb_data); - if (! g_module_close(module)) - g_warning("%s: %s", fname, g_module_error()); + proxy->proxy_cbs.unload(&proxy->public, &plugin->public, plugin->proxy_data, proxy->cb_data); +err: g_free(plugin->filename); g_free(plugin); return NULL; @@ -712,6 +805,40 @@ static void remove_sources(Plugin *plugin) } +/* Make the GModule backing plugin resident (if it's GModule-backed at all) */ +void plugin_make_resident(Plugin *plugin) +{ + if (plugin->proxy == &builtin_so_proxy_plugin) + { + g_return_if_fail(plugin->proxy_data != NULL); + g_module_make_resident(plugin->proxy_data); + } + else + g_warning("Skipping g_module_make_resident() for non-native plugin"); +} + + +/* Retrieve the address of a symbol sym located in plugin, if it's GModule-backed */ +gpointer plugin_get_module_symbol(Plugin *plugin, const gchar *sym) +{ + gpointer symbol; + + if (plugin->proxy == &builtin_so_proxy_plugin) + { + g_return_val_if_fail(plugin->proxy_data != NULL, NULL); + if (g_module_symbol(plugin->proxy_data, sym, &symbol)) + return symbol; + else + g_warning("Failed to locate signal handler for '%s': %s", + sym, g_module_error()); + } + else /* TODO: Could possibly support this via a new proxy hook */ + g_warning("Failed to locate signal handler for '%s': Not supported for non-native plugins", + sym); + return NULL; +} + + static gboolean is_active_plugin(Plugin *plugin) { return (g_list_find(active_plugin_list, plugin) != NULL); @@ -747,32 +874,84 @@ plugin_cleanup(Plugin *plugin) plugin->cb_data_destroy = NULL; } + proxied_count_dec(plugin->proxy); geany_debug("Unloaded: %s", plugin->filename); } +/* Remove all plugins that proxy is a proxy for from plugin_list (and free) */ +static void free_subplugins(Plugin *proxy) +{ + GList *item; + + item = plugin_list; + while (item) + { + GList *next = g_list_next(item); + if (proxy == ((Plugin *) item->data)->proxy) + { + /* plugin_free modifies plugin_list */ + plugin_free((Plugin *) item->data); + } + item = next; + } +} + + +/* Returns true if the removal was successful (=> never for non-proxies) */ +static gboolean unregister_proxy(Plugin *proxy) +{ + gboolean is_proxy = FALSE; + GList *node; + + /* Remove the proxy from the proxy list first. It might appear more than once (once + * for each extension), but if it doesn't appear at all it's not actually a proxy */ + foreach_list_safe(node, active_proxies.head) + { + PluginProxy *p = node->data; + if (p->plugin == proxy) + { + is_proxy = TRUE; + g_queue_delete_link(&active_proxies, node); + } + } + return is_proxy; +} + + +/* Cleanup a plugin and free all resources allocated on behalf of it. + * + * If the plugin is a proxy then this also takes special care to unload all + * subplugin loaded through it (make sure none of them is active!) */ static void plugin_free(Plugin *plugin) { + Plugin *proxy; + g_return_if_fail(plugin); - g_return_if_fail(plugin->module); + g_return_if_fail(plugin->proxy); + g_return_if_fail(plugin->proxied_count == 0); + proxy = plugin->proxy; + /* If this a proxy remove all depending subplugins. We can assume none of them is *activated* + * (but potentially loaded). Note that free_subplugins() might call us through recursion */ if (is_active_plugin(plugin)) + { + if (unregister_proxy(plugin)) + free_subplugins(plugin); plugin_cleanup(plugin); + } active_plugin_list = g_list_remove(active_plugin_list, plugin); plugin_list = g_list_remove(plugin_list, plugin); - /* cb_data_destroy might be plugin code and must be called before unloading the module */ + /* cb_data_destroy might be plugin code and must be called before unloading the module. */ if (plugin->cb_data_destroy) plugin->cb_data_destroy(plugin->cb_data); - - if (! g_module_close(plugin->module)) - g_warning("%s: %s", plugin->filename, g_module_error()); + proxy->proxy_cbs.unload(&proxy->public, &plugin->public, plugin->proxy_data, proxy->cb_data); g_free(plugin->filename); g_free(plugin); - plugin = NULL; } @@ -830,25 +1009,87 @@ static gboolean check_plugin_path(const gchar *fname) } +/* Retuns NULL if this ain't a plugin, + * otherwise it returns the appropriate PluginProxy instance to load it */ +static PluginProxy* is_plugin(const gchar *file) +{ + GList *node; + const gchar *ext; + + /* extract file extension to avoid g_str_has_suffix() in the loop */ + ext = (const gchar *)strrchr(file, '.'); + if (ext == NULL) + return FALSE; + /* ensure the dot is really part of the filename */ + else if (strchr(ext, G_DIR_SEPARATOR) != NULL) + return FALSE; + + ext += 1; + /* O(n*m), (m being extensions per proxy) doesn't scale very well in theory + * but not a problem in practice yet */ + foreach_list(node, active_proxies.head) + { + PluginProxy *proxy = node->data; + if (utils_str_casecmp(ext, proxy->extension) == 0) + { + Plugin *p = proxy->plugin; + gint ret = PROXY_MATCHED; + + if (p->proxy_cbs.probe) + ret = p->proxy_cbs.probe(&p->public, file, p->cb_data); + switch (ret) + { + case PROXY_MATCHED: + return proxy; + case PROXY_MATCHED|PROXY_NOLOAD: + return NULL; + default: + if (ret != PROXY_IGNORED) + g_warning("Ignoring bogus return from proxy probe!\n"); + continue; + } + } + } + return NULL; +} + + /* load active plugins at startup */ static void load_active_plugins(void) { - guint i, len; + guint i, len, proxies; if (active_plugins_pref == NULL || (len = g_strv_length(active_plugins_pref)) == 0) return; - for (i = 0; i < len; i++) + /* If proxys are loaded we have to restart to load plugins that sort before their proxy */ + do { - const gchar *fname = active_plugins_pref[i]; - - if (!EMPTY(fname) && g_file_test(fname, G_FILE_TEST_EXISTS)) + proxies = active_proxies.length; + g_list_free_full(failed_plugins_list, (GDestroyNotify) g_free); + failed_plugins_list = NULL; + for (i = 0; i < len; i++) { - if (!check_plugin_path(fname) || plugin_new(fname, TRUE, FALSE) == NULL) - failed_plugins_list = g_list_prepend(failed_plugins_list, g_strdup(fname)); + gchar *fname = active_plugins_pref[i]; + +#ifdef G_OS_WIN32 + /* ensure we have canonical paths */ + gchar *p = fname; + while ((p = strchr(p, '/')) != NULL) + *p = G_DIR_SEPARATOR; +#endif + + if (!EMPTY(fname) && g_file_test(fname, G_FILE_TEST_EXISTS)) + { + PluginProxy *proxy = NULL; + if (check_plugin_path(fname)) + proxy = is_plugin(fname); + if (proxy == NULL || plugin_new(proxy->plugin, fname, TRUE, FALSE) == NULL) + failed_plugins_list = g_list_prepend(failed_plugins_list, g_strdup(fname)); + } } - } + } while (proxies != active_proxies.length); } @@ -856,20 +1097,18 @@ static void load_plugins_from_path(const gchar *path) { GSList *list, *item; - gchar *fname, *tmp; gint count = 0; list = utils_get_file_list(path, NULL, NULL); for (item = list; item != NULL; item = g_slist_next(item)) { - tmp = strrchr(item->data, '.'); - if (tmp == NULL || utils_str_casecmp(tmp, "." G_MODULE_SUFFIX) != 0) - continue; + gchar *fname = g_build_filename(path, item->data, NULL); + PluginProxy *proxy = is_plugin(fname); - fname = g_build_filename(path, item->data, NULL); - if (plugin_new(fname, FALSE, TRUE)) + if (proxy != NULL && plugin_new(proxy->plugin, fname, FALSE, TRUE)) count++; + g_free(fname); } @@ -887,6 +1126,28 @@ static gchar *get_plugin_path(void) } +/* See load_all_plugins(), this simply sorts items with lower hierarchy level first + * (where hierarchy level == number of intermediate proxies before the builtin so loader) */ +static gint cmp_plugin_by_proxy(gconstpointer a, gconstpointer b) +{ + const Plugin *pa = a; + const Plugin *pb = b; + + while (TRUE) + { + if (pa->proxy == pb->proxy) + return 0; + else if (pa->proxy == &builtin_so_proxy_plugin) + return -1; + else if (pb->proxy == &builtin_so_proxy_plugin) + return 1; + + pa = pa->proxy; + pb = pb->proxy; + } +} + + /* Load (but don't initialize) all plugins for the Plugin Manager dialog */ static void load_all_plugins(void) { @@ -911,6 +1172,13 @@ static void load_all_plugins(void) /* finally load plugins from $prefix/lib/geany */ load_plugins_from_path(plugin_path_system); + /* It is important to sort any plugins that are proxied after their proxy because + * pm_populate() needs the proxy to be loaded and active (if selected by user) in order + * to properly set the value for the PLUGIN_COLUMN_CAN_UNCHECK column. The order between + * sub-plugins does not matter, only between sub-plugins and their proxy, thus + * sorting by hierarchy level is perfectly sufficient */ + plugin_list = g_list_sort(plugin_list, cmp_plugin_by_proxy); + g_free(plugin_path_config); g_free(plugin_path_system); } @@ -1028,6 +1296,17 @@ void plugins_init(void) g_signal_connect(geany_object, "save-settings", G_CALLBACK(update_active_plugins_pref), NULL); stash_group_add_string_vector(group, &active_plugins_pref, "active_plugins", NULL); + + g_queue_push_head(&active_proxies, &builtin_so_proxy); +} + + +/* Same as plugin_free(), except it does nothing for proxies-in-use, to be called on + * finalize in a loop */ +static void plugin_free_leaf(Plugin *p) +{ + if (p->proxied_count == 0) + plugin_free(p); } @@ -1039,11 +1318,11 @@ void plugins_finalize(void) g_list_foreach(failed_plugins_list, (GFunc) g_free, NULL); g_list_free(failed_plugins_list); } - if (active_plugin_list != NULL) - { - g_list_foreach(active_plugin_list, (GFunc) plugin_free, NULL); - g_list_free(active_plugin_list); - } + /* Have to loop because proxys cannot be unloaded until after all their + * plugins are unloaded as well (the second loop should should catch all the remaining ones) */ + while (active_plugin_list != NULL) + g_list_foreach(active_plugin_list, (GFunc) plugin_free_leaf, NULL); + g_strfreev(active_plugins_pref); } @@ -1072,6 +1351,7 @@ gboolean plugins_have_preferences(void) enum { PLUGIN_COLUMN_CHECK = 0, + PLUGIN_COLUMN_CAN_UNCHECK, PLUGIN_COLUMN_PLUGIN, PLUGIN_N_COLUMNS, PM_BUTTON_KEYBINDINGS, @@ -1083,7 +1363,7 @@ typedef struct { GtkWidget *dialog; GtkWidget *tree; - GtkListStore *store; + GtkTreeStore *store; GtkWidget *filter_entry; GtkWidget *configure_button; GtkWidget *keybindings_button; @@ -1137,6 +1417,27 @@ static void pm_selection_changed(GtkTreeSelection *selection, gpointer user_data } +static gboolean find_iter_for_plugin(Plugin *p, GtkTreeModel *model, GtkTreeIter *iter) +{ + Plugin *pp; + gboolean valid; + + for (valid = gtk_tree_model_get_iter_first(model, iter); + valid; + valid = gtk_tree_model_iter_next(model, iter)) + { + gtk_tree_model_get(model, iter, PLUGIN_COLUMN_PLUGIN, &pp, -1); + if (p == pp) + return TRUE; + } + + return FALSE; +} + + +static void pm_populate(GtkTreeStore *store); + + static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data) { gboolean old_state, state; @@ -1146,9 +1447,10 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer GtkTreePath *path = gtk_tree_path_new_from_string(pth); GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_widgets.tree)); Plugin *p; + Plugin *proxy; + guint prev_num_proxies; gtk_tree_model_get_iter(model, &iter, path); - gtk_tree_path_free(path); gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_CHECK, &old_state, @@ -1156,15 +1458,20 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer /* no plugins item */ if (p == NULL) + { + gtk_tree_path_free(path); return; + } gtk_tree_model_filter_convert_iter_to_child_iter( GTK_TREE_MODEL_FILTER(model), &store_iter, &iter); state = ! old_state; /* toggle the state */ - /* save the filename of the plugin */ + /* save the filename and proxy of the plugin */ file_name = g_strdup(p->filename); + proxy = p->proxy; + prev_num_proxies = active_proxies.length; /* unload plugin module */ if (!state) @@ -1174,11 +1481,11 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer plugin_free(p); /* reload plugin module and initialize it if item is checked */ - p = plugin_new(file_name, state, TRUE); + p = plugin_new(proxy, file_name, state, TRUE); if (!p) { /* plugin file may no longer be on disk, or is now incompatible */ - gtk_list_store_remove(pm_widgets.store, &store_iter); + gtk_tree_store_remove(pm_widgets.store, &store_iter); } else { @@ -1186,16 +1493,89 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer keybindings_load_keyfile(); /* load shortcuts */ /* update model */ - gtk_list_store_set(pm_widgets.store, &store_iter, + gtk_tree_store_set(pm_widgets.store, &store_iter, PLUGIN_COLUMN_CHECK, state, PLUGIN_COLUMN_PLUGIN, p, -1); /* set again the sensitiveness of the configure and help buttons */ pm_update_buttons(p); + + /* Depending on the state disable the checkbox for the proxy of this plugin, and + * only re-enable if the proxy is not used by any other plugin */ + if (p->proxy != &builtin_so_proxy_plugin) + { + GtkTreeIter parent; + gboolean can_uncheck; + GtkTreePath *store_path = gtk_tree_model_filter_convert_path_to_child_path( + GTK_TREE_MODEL_FILTER(model), path); + + g_warn_if_fail(store_path != NULL); + if (gtk_tree_path_up(store_path)) + { + gtk_tree_model_get_iter(GTK_TREE_MODEL(pm_widgets.store), &parent, store_path); + + if (state) + can_uncheck = FALSE; + else + can_uncheck = p->proxy->proxied_count == 0; + + gtk_tree_store_set(pm_widgets.store, &parent, + PLUGIN_COLUMN_CAN_UNCHECK, can_uncheck, -1); + } + gtk_tree_path_free(store_path); + } } + /* We need to find out if a proxy was added or removed because that affects the plugin list + * presented by the plugin manager */ + if (prev_num_proxies != active_proxies.length) + { + /* Rescan the plugin list as we now support more. Gives some "already loaded" warnings + * they are unproblematic */ + if (prev_num_proxies < active_proxies.length) + load_all_plugins(); + + pm_populate(pm_widgets.store); + gtk_tree_view_expand_row(GTK_TREE_VIEW(pm_widgets.tree), path, FALSE); + } + + gtk_tree_path_free(path); g_free(file_name); } +static void pm_populate(GtkTreeStore *store) +{ + GtkTreeIter iter; + GList *list; + + gtk_tree_store_clear(store); + list = g_list_first(plugin_list); + if (list == NULL) + { + gtk_tree_store_append(store, &iter, NULL); + gtk_tree_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE, + PLUGIN_COLUMN_PLUGIN, NULL, -1); + } + else + { + for (; list != NULL; list = list->next) + { + Plugin *p = list->data; + GtkTreeIter parent; + + if (p->proxy != &builtin_so_proxy_plugin + && find_iter_for_plugin(p->proxy, GTK_TREE_MODEL(pm_widgets.store), &parent)) + gtk_tree_store_append(store, &iter, &parent); + else + gtk_tree_store_append(store, &iter, NULL); + + gtk_tree_store_set(store, &iter, + PLUGIN_COLUMN_CHECK, is_active_plugin(p), + PLUGIN_COLUMN_PLUGIN, p, + PLUGIN_COLUMN_CAN_UNCHECK, (p->proxied_count == 0), + -1); + } + } +} static gboolean pm_treeview_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data) @@ -1204,26 +1584,33 @@ static gboolean pm_treeview_query_tooltip(GtkWidget *widget, gint x, gint y, GtkTreeIter iter; GtkTreePath *path; Plugin *p = NULL; + gboolean can_uncheck = TRUE; if (! gtk_tree_view_get_tooltip_context(GTK_TREE_VIEW(widget), &x, &y, keyboard_mode, &model, &path, &iter)) return FALSE; - gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, -1); + gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, PLUGIN_COLUMN_CAN_UNCHECK, &can_uncheck, -1); if (p != NULL) { - gchar *markup; - gchar *details; + gchar *prefix, *suffix, *details, *markup; + const gchar *uchk; + uchk = can_uncheck ? + "" : _("\nOther plugins depend on this. Disable them first to allow deactivation.\n"); + /* Four allocations is less than ideal but meh */ details = g_strdup_printf(_("Version:\t%s\nAuthor(s):\t%s\nFilename:\t%s"), p->info.version, p->info.author, p->filename); - markup = g_markup_printf_escaped("%s\n%s\n\n%s", - p->info.name, p->info.description, details); + prefix = g_markup_printf_escaped("%s\n%s\n", p->info.name, p->info.description); + suffix = g_markup_printf_escaped("\n%s", details); + markup = g_strconcat(prefix, uchk, suffix, NULL); gtk_tooltip_set_markup(tooltip, markup); gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, path); g_free(details); + g_free(suffix); + g_free(prefix); g_free(markup); } gtk_tree_path_free(path); @@ -1328,6 +1715,9 @@ static gboolean pm_tree_filter_func(GtkTreeModel *model, GtkTreeIter *iter, gpoi gchar *haystack, *filename; gtk_tree_model_get(model, iter, PLUGIN_COLUMN_PLUGIN, &plugin, -1); + + if (!plugin) + return FALSE; key = gtk_entry_get_text(GTK_ENTRY(pm_widgets.filter_entry)); filename = g_path_get_basename(plugin->filename); @@ -1356,13 +1746,11 @@ static void on_pm_tree_filter_entry_icon_release_cb(GtkEntry *entry, GtkEntryIco } -static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) +static void pm_prepare_treeview(GtkWidget *tree, GtkTreeStore *store) { GtkCellRenderer *text_renderer, *checkbox_renderer; GtkTreeViewColumn *column; GtkTreeModel *filter_model; - GtkTreeIter iter; - GList *list; GtkTreeSelection *sel; g_signal_connect(tree, "query-tooltip", G_CALLBACK(pm_treeview_query_tooltip), NULL); @@ -1371,7 +1759,8 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) checkbox_renderer = gtk_cell_renderer_toggle_new(); column = gtk_tree_view_column_new_with_attributes( - _("Active"), checkbox_renderer, "active", PLUGIN_COLUMN_CHECK, NULL); + _("Active"), checkbox_renderer, + "active", PLUGIN_COLUMN_CHECK, "activatable", PLUGIN_COLUMN_CAN_UNCHECK, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); g_signal_connect(checkbox_renderer, "toggled", G_CALLBACK(pm_plugin_toggled), NULL); @@ -1396,27 +1785,6 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) g_signal_connect(tree, "button-press-event", G_CALLBACK(pm_treeview_button_press_cb), NULL); - list = g_list_first(plugin_list); - if (list == NULL) - { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE, - PLUGIN_COLUMN_PLUGIN, NULL, -1); - } - else - { - Plugin *p; - for (; list != NULL; list = list->next) - { - p = list->data; - - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, - PLUGIN_COLUMN_CHECK, is_active_plugin(p), - PLUGIN_COLUMN_PLUGIN, p, - -1); - } - } /* filter */ filter_model = gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL); gtk_tree_model_filter_set_visible_func( @@ -1424,8 +1792,9 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) /* set model to tree view */ gtk_tree_view_set_model(GTK_TREE_VIEW(tree), filter_model); - g_object_unref(store); g_object_unref(filter_model); + + pm_populate(store); } @@ -1533,9 +1902,11 @@ static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data) /* prepare treeview */ pm_widgets.tree = gtk_tree_view_new(); - pm_widgets.store = gtk_list_store_new( - PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_POINTER); + pm_widgets.store = gtk_tree_store_new( + PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_POINTER); pm_prepare_treeview(pm_widgets.tree, pm_widgets.store); + gtk_tree_view_expand_all(GTK_TREE_VIEW(pm_widgets.tree)); + g_object_unref(pm_widgets.store); swin = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin), @@ -1584,4 +1955,64 @@ static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data) } +/** Register the plugin as a proxy for other plugins + * + * Proxy plugins register a list of file extensions and a set of callbacks that are called + * appropriately. A plugin can be a proxy for multiple types of sub-plugins by handling + * separate file extensions, however they must share the same set of hooks, because this + * function can only be called at most once per plugin. + * + * Each callback receives the plugin-defined data as parameter (see geany_plugin_register()). The + * callbacks must be set prior to calling this, by assigning to @a plugin->proxy_funcs. + * GeanyProxyFuncs::load and GeanyProxyFuncs::unload must be implemented. + * + * Nested proxies are unsupported at this point (TODO). + * + * @note It is entirely up to the proxy to provide access to Geany's plugin API. Native code + * can naturally call Geany's API directly, for interpreted languages the proxy has to + * implement some kind of bindings that the plugin can use. + * + * @see proxy for detailed documentation and an example. + * + * @param plugin The pointer to the plugin's GeanyPlugin instance + * @param extensions A @c NULL-terminated string array of file extensions, excluding the dot. + * @return @c TRUE if the proxy was successfully registered, otherwise @c FALSE + * + * @since 1.26 (API 226) + */ +GEANY_API_SYMBOL +gboolean geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensions) +{ + Plugin *p; + const gchar **ext; + PluginProxy *proxy; + GList *node; + + g_return_val_if_fail(plugin != NULL, FALSE); + g_return_val_if_fail(extensions != NULL, FALSE); + g_return_val_if_fail(*extensions != NULL, FALSE); + g_return_val_if_fail(plugin->proxy_funcs->load != NULL, FALSE); + g_return_val_if_fail(plugin->proxy_funcs->unload != NULL, FALSE); + + p = plugin->priv; + /* Check if this was called aready. We want to reserve for the use case of calling + * this again to set new supported extensions (for example, based on proxy configuration). */ + foreach_list(node, active_proxies.head) + { + proxy = node->data; + g_return_if_fail(p != proxy->plugin); + } + + foreach_strv(ext, extensions) + { + proxy = g_new(PluginProxy, 1); + g_strlcpy(proxy->extension, *ext, sizeof(proxy->extension)); + proxy->plugin = p; + /* prepend, so that plugins automatically override core providers for a given extension */ + g_queue_push_head(&active_proxies, proxy); + } + + return TRUE; +} + #endif Modified: src/pluginutils.c 11 lines changed, 2 insertions(+), 9 deletions(-) =================================================================== @@ -96,8 +96,7 @@ GEANY_API_SYMBOL void plugin_module_make_resident(GeanyPlugin *plugin) { g_return_if_fail(plugin); - - g_module_make_resident(plugin->priv->module); + plugin_make_resident(plugin->priv); } @@ -444,12 +443,7 @@ static void connect_plugin_signals(GtkBuilder *builder, GObject *object, gpointer symbol = NULL; struct BuilderConnectData *data = user_data; - if (!g_module_symbol(data->plugin->priv->module, handler_name, &symbol)) - { - g_warning("Failed to locate signal handler for '%s': %s", - signal_name, g_module_error()); - return; - } + symbol = plugin_get_module_symbol(data->plugin->priv, handler_name); plugin_signal_connect(data->plugin, object, signal_name, FALSE, G_CALLBACK(symbol) /*ub?*/, data->user_data); @@ -503,7 +497,6 @@ void plugin_builder_connect_signals(GeanyPlugin *plugin, struct BuilderConnectData data = { NULL }; g_return_if_fail(plugin != NULL && plugin->priv != NULL); - g_return_if_fail(plugin->priv->module != NULL); g_return_if_fail(GTK_IS_BUILDER(builder)); data.user_data = user_data; Modified: src/utils.h 8 lines changed, 8 insertions(+), 0 deletions(-) =================================================================== @@ -116,6 +116,14 @@ G_BEGIN_DECLS #define foreach_slist(node, list) \ foreach_list(node, list) +/* Iterates all the nodes in @a list. Safe against removal during iteration + * @param node should be a (@c GList*). + * @param list @c GList to traverse. */ +#define foreach_list_safe(node, list) \ + for (GList *_node = (list), *_next = (list) ? (list)->next : NULL; \ + (node = _node) != NULL; \ + _node = _next, _next = _next ? _next->next : NULL) + /** Iterates through each unsorted filename in a @c GDir. * @param filename (@c const @c gchar*) locale-encoded filename, without path. Do not modify or free. * @param dir @c GDir created with @c g_dir_open(). Call @c g_dir_close() afterwards. -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Wed Oct 7 16:27:56 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Wed, 07 Oct 2015 16:27:56 -0000 Subject: [geany/talks] ea99b8: Fix merkdown syntax Message-ID: <20151007162803.DE2355C2048@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Wed, 07 Oct 2015 16:27:56 UTC Commit: ea99b8105ed78995e7e6ce4f6b7272d1b4cb76c3 https://github.com/geany/talks/commit/ea99b8105ed78995e7e6ce4f6b7272d1b4cb76c3 Log Message: ----------- Fix merkdown syntax Modified Paths: -------------- en/A_short_introduction/README.md Modified: en/A_short_introduction/README.md 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -1,6 +1,6 @@ To get revel.js you will have to get the submodule. Do: -$ git submodule init -$ git submodule update + $ git submodule init + $ git submodule update for it. Done. -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Thu Oct 8 00:16:09 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Thu, 08 Oct 2015 00:16:09 -0000 Subject: [geany/geany] d6e94c: Use GStatBuf instead of plain `struct stat` Message-ID: <20151008001829.9C4AD5C22B2@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Matthew Brush Date: Thu, 08 Oct 2015 00:16:09 UTC Commit: d6e94cf9d4b41b55f06bafe185b1b0a7fc61bd30 https://github.com/geany/geany/commit/d6e94cf9d4b41b55f06bafe185b1b0a7fc61bd30 Log Message: ----------- Use GStatBuf instead of plain `struct stat` Especially under Windows, there are 32-bit and 64-bit stat, and g_[l]stat may use the non-default one. Closes #677 Modified Paths: -------------- src/dialogs.c src/socket.c tagmanager/ctags/ctags.c tagmanager/src/tm_source_file.c tagmanager/src/tm_workspace.c Modified: src/dialogs.c 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -1152,7 +1152,7 @@ void dialogs_show_file_properties(GeanyDocument *doc) gchar *file_size, *title, *base_name, *time_changed, *time_modified, *time_accessed, *enctext; gchar *short_name; #ifdef HAVE_SYS_TYPES_H - struct stat st; + GStatBuf st; off_t filesize; mode_t mode; gchar *locale_filename; Modified: src/socket.c 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -225,7 +225,7 @@ static void socket_get_document_list(gint sock) #ifndef G_OS_WIN32 static void check_socket_permissions(void) { - struct stat socket_stat; + GStatBuf socket_stat; if (g_lstat(socket_info.file_name, &socket_stat) == 0) { /* If the user id of the process is not the same as the owner of the socket Modified: tagmanager/ctags/ctags.c 16 lines changed, 8 insertions(+), 8 deletions(-) =================================================================== @@ -421,7 +421,7 @@ extern char* newUpperString (const char* str) extern long unsigned int getFileSize (const char *const name) { - struct stat fileStatus; + GStatBuf fileStatus; unsigned long size = 0; if (g_stat (name, &fileStatus) == 0) @@ -436,7 +436,7 @@ static boolean isSymbolicLink (const char *const name) #if defined (MSDOS) || defined (WIN32) || defined (VMS) || defined (__EMX__) || defined (AMIGA) return FALSE; #else - struct stat fileStatus; + GStatBuf fileStatus; boolean result = FALSE; if (g_lstat (name, &fileStatus) == 0) @@ -448,7 +448,7 @@ static boolean isSymbolicLink (const char *const name) static boolean isNormalFile (const char *const name) { - struct stat fileStatus; + GStatBuf fileStatus; boolean result = FALSE; if (g_stat (name, &fileStatus) == 0) @@ -460,7 +460,7 @@ static boolean isNormalFile (const char *const name) extern boolean isExecutable (const char *const name) { - struct stat fileStatus; + GStatBuf fileStatus; boolean result = FALSE; if (g_stat (name, &fileStatus) == 0) @@ -473,7 +473,7 @@ extern boolean isSameFile (const char *const name1, const char *const name2) { boolean result = FALSE; #ifdef HAVE_STAT_ST_INO - struct stat stat1, stat2; + GStatBuf stat1, stat2; if (g_stat (name1, &stat1) == 0 && g_stat (name2, &stat2) == 0) result = (boolean) (stat1.st_ino == stat2.st_ino); @@ -488,7 +488,7 @@ static boolean isSetUID (const char *const name) #if defined (VMS) || defined (MSDOS) || defined (WIN32) || defined (__EMX__) || defined (AMIGA) return FALSE; #else - struct stat fileStatus; + GStatBuf fileStatus; boolean result = FALSE; if (g_stat (name, &fileStatus) == 0) @@ -520,7 +520,7 @@ static boolean isDirectory (const char *const name) eFree (fib); } #else - struct stat fileStatus; + GStatBuf fileStatus; if (g_stat (name, &fileStatus) == 0) result = (boolean) S_ISDIR (fileStatus.st_mode); @@ -531,7 +531,7 @@ static boolean isDirectory (const char *const name) extern boolean doesFileExist (const char *const fileName) { - struct stat fileStatus; + GStatBuf fileStatus; return (boolean) (g_stat (fileName, &fileStatus) == 0); } Modified: tagmanager/src/tm_source_file.c 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -142,7 +142,7 @@ static void tm_source_file_set_tag_arglist(const char *tag_name, const char *arg static gboolean tm_source_file_init(TMSourceFile *source_file, const char *file_name, const char* name) { - struct stat s; + GStatBuf s; int status; #ifdef TM_DEBUG @@ -269,7 +269,7 @@ gboolean tm_source_file_parse(TMSourceFile *source_file, guchar* text_buf, gsize if (!use_buffer) { - struct stat s; + GStatBuf s; /* load file to memory and parse it from memory unless the file is too big */ if (g_stat(file_name, &s) != 0 || s.st_size > 10*1024*1024) Modified: tagmanager/src/tm_workspace.c 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -391,7 +391,7 @@ gboolean tm_workspace_load_global_tags(const char *tags_file, gint mode) static guint tm_file_inode_hash(gconstpointer key) { - struct stat file_stat; + GStatBuf file_stat; const char *filename = (const char*)key; if (g_stat(filename, &file_stat) == 0) { -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sat Oct 10 12:33:55 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sat, 10 Oct 2015 12:33:55 -0000 Subject: [geany/talks] bf453e: EN: Fix typos Message-ID: <20151010123402.0D91C5C323E@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sat, 10 Oct 2015 12:33:55 UTC Commit: bf453e329c16933594ad9994a1d0a9d5072c77de https://github.com/geany/talks/commit/bf453e329c16933594ad9994a1d0a9d5072c77de Log Message: ----------- EN: Fix typos Modified Paths: -------------- en/A_short_introduction/index.html Modified: en/A_short_introduction/index.html 28 lines changed, 14 insertions(+), 14 deletions(-) =================================================================== @@ -98,13 +98,13 @@

    This is just a selection

    -

    Syntaxhighlighting

    +

    Syntax highlighting

    • More than 60 file types
    • -
    • Based upon Scintilla-Projekt
    • -
    • Done with C++ classes -- so called Lexxer
    • -
    • Automatic detection and manual set-able
    • -
    • Self-defined file types (using shippid Lexxer)
    • +
    • Based upon Scintilla project
    • +
    • Done with C++ classes -- so called Lexer
    • +
    • Automatic detection, can also be set manually
    • +
    • Self-defined file types (using shipped Lexer)
    @@ -122,7 +122,7 @@

    Keyboard shortcuts

      -
    • Many functions can be controlled via keybaord short cur
    • +
    • Many functions can be controlled via keybaord short cuts
    • Can be reconfigured either via config file or via dialog
    • Pluings can register own keyboard shortcuts
    @@ -142,17 +142,17 @@

    File-Templates

    • Useful for often used tasks
    • -
    • Might include placeholders like it's own filename
    • +
    • Might include placeholders like its own filename
    • Geany is offering a basic collection; own templates below ~/.config/geany/templates/files

    Snippets

      -
    • Reoccuring text passages can be replaces with a "shortcut"
    • +
    • Reoccurring text passages can be replaced with a "shortcut"
    • Geany is offering a list of generic and file type specific ones
    • Can be extended by user
    • -
    • Many examples inside Wiki
    • +
    • Many examples in the Wiki
    @@ -173,17 +173,17 @@
  • ... how many letters to start showing suggestions
  • ... shall often used words be included (useful for LaTeX, HTML)
  • -
  • Static "Tages" are loaded from files
  • +
  • Static "Tags" are loaded from files
  • File type specific
  • -
  • Tag files are listed inside the Wiki
  • -
  • Tag-files can be generated by Geany from e.g. project
  • +
  • Tag files are listed in the Wiki
  • +
  • Tag files can be generated by Geany from e.g. project
  • Symbol browser & document browser

    • Located on side bar
    • -
    • Fast access to symboles (variable defintion, functions ...) with mouse
    • +
    • Fast access to symbols (variable defintions, classes, functions ...) with mouse
    • Fast access to open documents
    @@ -194,7 +194,7 @@
  • API/Binding for C, Python and Lua
  • Examples:
    • GeanyVC
    • -
    • git-changebar
    • +
    • Git Changebar
    • Projectorganizer
    • Addons
    • DevHelp
    • -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sat Oct 10 12:39:44 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sat, 10 Oct 2015 12:39:44 -0000 Subject: [geany/talks] ae6f63: EN: Fix position of Geany logo Message-ID: <20151010124544.7CAED5C323E@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sat, 10 Oct 2015 12:39:44 UTC Commit: ae6f63067c33db8c3d100ad8ec95e29d7146c62b https://github.com/geany/talks/commit/ae6f63067c33db8c3d100ad8ec95e29d7146c62b Log Message: ----------- EN: Fix position of Geany logo At least on my local setup with Firefox the logo was missplaced in such a way it was not completly on the display. Modified Paths: -------------- en/A_short_introduction/index.html Modified: en/A_short_introduction/index.html 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -36,7 +36,7 @@ -
      Logo von Geany
      +
      Logo von Geany
      -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sat Oct 10 12:45:37 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Sat, 10 Oct 2015 12:45:37 -0000 Subject: [geany/talks] dbb1fc: EN: Some minor updates Message-ID: <20151010124545.0407E5C3259@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Sat, 10 Oct 2015 12:45:37 UTC Commit: dbb1fc154db8a1ae350048fe6bf3b1a3e0decbdd https://github.com/geany/talks/commit/dbb1fc154db8a1ae350048fe6bf3b1a3e0decbdd Log Message: ----------- EN: Some minor updates Modified Paths: -------------- en/A_short_introduction/index.html Modified: en/A_short_introduction/index.html 9 lines changed, 5 insertions(+), 4 deletions(-) =================================================================== @@ -63,7 +63,7 @@

      About me

        -
      • Professional nerd with background in economics working for a Operations-as-a-Service-company
      • +
      • Professional nerd with background in economics working for an Operations-as-a-Service-company
      • 10 years user of Geany
      • Maintainer of geany-plugins
      • Coordinator of translation work
      • @@ -89,6 +89,7 @@
      • Written in C and C++
      • Based upon Scintilla and GTK2
      • Licence: GPLv2+
      • +
      • Available for Linux, Windows and MacOS X
  • @@ -133,7 +134,7 @@
  • Saves a lot of typing work
  • Most can be extended/overwritten by user
  • Differrent kind of templates:
      -
    • New files / file templates
    • +
    • File templates for new files
    • Placeholder for common stubs like GPL/BSD licence notification
    • Code snippets
    @@ -152,7 +153,7 @@
  • Reoccurring text passages can be replaced with a "shortcut"
  • Geany is offering a list of generic and file type specific ones
  • Can be extended by user
  • -
  • Many examples in the Wiki
  • +
  • Many examples at the Wiki
  • @@ -175,7 +176,7 @@
  • Static "Tags" are loaded from files
  • File type specific
  • -
  • Tag files are listed in the Wiki
  • +
  • Tag files are listed at the Wiki
  • Tag files can be generated by Geany from e.g. project
  • -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 12 21:06:49 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 12 Oct 2015 21:06:49 -0000 Subject: [geany/geany] 1d08d3: plugin api: introduce GeanyKeyGroupFunc and GeanyKeyBindingFunc Message-ID: <20151012210649.207B45C402D@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Wed, 26 Aug 2015 21:46:45 UTC Commit: 1d08d3db4a0c7f6dc91309f3986d232b26cffb08 https://github.com/geany/geany/commit/1d08d3db4a0c7f6dc91309f3986d232b26cffb08 Log Message: ----------- plugin api: introduce GeanyKeyGroupFunc and GeanyKeyBindingFunc These are new keybinding callback functions that take a few more parameters. Most importantly they have pdata pointer which allows plugins to store context information. This is especially useful for future plugins in OOP languages to store an instance pointer there, or interpreted ones to store interpreter context. Modified Paths: -------------- src/keybindings.c src/keybindings.h src/keybindingsprivate.h Modified: src/keybindings.c 58 lines changed, 33 insertions(+), 25 deletions(-) =================================================================== @@ -194,6 +194,8 @@ GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id, kb->default_key = key; kb->default_mods = mod; kb->callback = callback; + kb->cb_func = NULL; + kb->cb_data = NULL; kb->menu_item = menu_item; kb->id = key_id; return kb; @@ -208,6 +210,8 @@ static void add_kb_group(GeanyKeyGroup *group, group->name = name; group->label = label; group->callback = callback; + group->cb_func = NULL; + group->cb_data = NULL; group->plugin = plugin; group->key_items = g_ptr_array_new(); } @@ -1207,6 +1211,30 @@ gboolean keybindings_check_event(GdkEventKey *ev, GeanyKeyBinding *kb) } +static gboolean run_kb(GeanyKeyBinding *kb, GeanyKeyGroup *group) +{ + gboolean handled = TRUE; + /* call the corresponding handler/callback functions for this shortcut. + * Check the individual keybindings first (handler first, callback second) and + * group second (again handler first, callback second) */ + if (kb->cb_func) + handled = kb->cb_func(kb, kb->id, kb->cb_data); + else if (kb->callback) + kb->callback(kb->id); + else if (group->cb_func) + handled = group->cb_func(group, kb->id, group->cb_data); + else if (group->callback) + handled = group->callback(kb->id); + else + { + g_warning("No callback or handler for keybinding %s: %s!", group->name, kb->name); + return FALSE; + } + + return handled; +} + + /* central keypress event handler, almost all keypress events go to this function */ static gboolean on_key_press_event(GtkWidget *widget, GdkEventKey *ev, gpointer user_data) { @@ -1249,20 +1277,8 @@ static gboolean on_key_press_event(GtkWidget *widget, GdkEventKey *ev, gpointer { if (keyval == kb->key && state == kb->mods) { - /* call the corresponding callback function for this shortcut */ - if (kb->callback) - { - kb->callback(kb->id); + if (run_kb(kb, group)) return TRUE; - } - else if (group->callback) - { - if (group->callback(kb->id)) - return TRUE; - else - continue; /* not handled */ - } - g_warning("No callback for keybinding %s: %s!", group->name, kb->name); } } } @@ -1296,20 +1312,12 @@ GEANY_API_SYMBOL void keybindings_send_command(guint group_id, guint key_id) { GeanyKeyBinding *kb; + GeanyKeyGroup *group; kb = keybindings_lookup_item(group_id, key_id); - if (kb) - { - if (kb->callback) - kb->callback(key_id); - else - { - GeanyKeyGroup *group = keybindings_get_core_group(group_id); - - if (group->callback) - group->callback(key_id); - } - } + group = keybindings_get_core_group(group_id); + if (kb && group) + run_kb(kb, group); } Modified: src/keybindings.h 48 lines changed, 34 insertions(+), 14 deletions(-) =================================================================== @@ -37,12 +37,42 @@ G_BEGIN_DECLS #define GEANY_PRIMARY_MOD_MASK GDK_CONTROL_MASK #endif +/** A collection of keybindings grouped together. */ +typedef struct GeanyKeyGroup GeanyKeyGroup; +typedef struct GeanyKeyBinding GeanyKeyBinding; + +/** Function pointer type used for keybinding group callbacks. + * + * You should return @c TRUE to indicate handling the callback. (Occasionally, if the keybinding + * cannot apply in the current situation, it is useful to return @c FALSE to allow a later keybinding + * with the same key combination to handle it). */ +typedef gboolean (*GeanyKeyGroupCallback) (guint key_id); + +/** Function pointer type used for keybinding group callbacks, with userdata for passing context. + * + * You should return @c TRUE to indicate handling the callback. (Occasionally, if the keybinding + * cannot apply in the current situation, it is useful to return @c FALSE to allow a later keybinding + * with the same key combination to handle it). + * + * @since 1.26 (API 226) */ +typedef gboolean (*GeanyKeyGroupFunc)(GeanyKeyGroup *group, guint key_id, gpointer pdata); + /** Function pointer type used for keybinding callbacks. */ typedef void (*GeanyKeyCallback) (guint key_id); +/** Function pointer type used for keybinding callbacks, with userdata for passing context + * + * You should return @c TRUE to indicate handling the callback. (Occasionally, if the keybinding + * cannot apply in the current situation, it is useful to return @c FALSE to allow a later keybinding + * with the same key combination to handle it). + * + * @since 1.26 (API 226) */ +typedef gboolean (*GeanyKeyBindingFunc)(GeanyKeyBinding *key, guint key_id, gpointer pdata); + /** Represents a single keybinding action. + * * Use keybindings_set_item() to set. */ -typedef struct GeanyKeyBinding +struct GeanyKeyBinding { guint key; /**< Key value in lower-case, such as @c GDK_a or 0 */ GdkModifierType mods; /**< Modifier keys, such as @c GDK_CONTROL_MASK or 0 */ @@ -57,19 +87,9 @@ typedef struct GeanyKeyBinding guint id; guint default_key; GdkModifierType default_mods; -} -GeanyKeyBinding; - - -/** Function pointer type used for keybinding group callbacks. - * You should return @c TRUE to indicate handling the callback. (Occasionally, if the keybinding - * cannot apply in the current situation, it is useful to return @c FALSE to allow a later keybinding - * with the same key combination to handle it). */ -typedef gboolean (*GeanyKeyGroupCallback) (guint key_id); - -/** A collection of keybindings grouped together. */ -typedef struct GeanyKeyGroup GeanyKeyGroup; - + GeanyKeyBindingFunc cb_func; + gpointer cb_data; +}; /* Note: we don't need to break the plugin ABI when appending keybinding or keygroup IDs, * just make sure to insert immediately before the _COUNT item, so Modified: src/keybindingsprivate.h 2 lines changed, 2 insertions(+), 0 deletions(-) =================================================================== @@ -38,6 +38,8 @@ struct GeanyKeyGroup GPtrArray *key_items; /* pointers to GeanyKeyBinding structs */ gsize plugin_key_count; /* number of keybindings the group holds */ GeanyKeyBinding *plugin_keys; /* array of GeanyKeyBinding structs */ + GeanyKeyGroupFunc cb_func; /* use this or individual keybinding callbacks (new style) */ + gpointer cb_data; }; G_END_DECLS -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 12 21:06:50 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 12 Oct 2015 21:06:50 -0000 Subject: [geany/geany] d3f623: plugin api: introduce keybindings_set_item_full and plugin_set_key_group_full Message-ID: <20151012210649.A65A95C402D@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Wed, 26 Aug 2015 21:49:37 UTC Commit: d3f6237505f1627d5b12c9ae5bed6dc736249878 https://github.com/geany/geany/commit/d3f6237505f1627d5b12c9ae5bed6dc736249878 Log Message: ----------- plugin api: introduce keybindings_set_item_full and plugin_set_key_group_full These function actually set the new GeanyKeyGroupFunc and GeanyKeyBindingFunc and are exported for plugins Modified Paths: -------------- src/keybindings.c src/keybindings.h src/pluginutils.c src/pluginutils.h Modified: src/keybindings.c 39 lines changed, 39 insertions(+), 0 deletions(-) =================================================================== @@ -202,6 +202,45 @@ GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id, } +/** Creates a new keybinding using a GeanyKeyBindingFunc and attaches is to a keybinding group + * + * If given the callback should return @c TRUE if the keybinding was handled, otherwise @c FALSE + * to allow other callbacks to be run. This allows for multiplexing keybindings on the same keys, + * depending on the focused widget (or context). If the callback is NULL the group's callback will + * be invoked, but the same rule applies. + * + * @param group Group. + * @param key_id Keybinding index for the group. + * @param cb New-style callback to be called when activated, or @c NULL to use the group callback. + * @param pdata Plugin-specific data passed back to the callback. + * @param key (Lower case) default key, e.g. @c GDK_j, but usually 0 for unset. + * @param mod Default modifier, e.g. @c GDK_CONTROL_MASK, but usually 0 for unset. + * @param kf_name Key name for the configuration file, such as @c "menu_new". + * @param label Label used in the preferences dialog keybindings tab. May contain + * underscores - these won't be displayed. + * @param menu_item Optional widget to set an accelerator for, or @c NULL. + * @return The keybinding - normally this is ignored. + * + * @since 1.26 (API 226) + * @see See plugin_set_key_group_full + **/ +GEANY_API_SYMBOL +GeanyKeyBinding *keybindings_set_item_full(GeanyKeyGroup *group, gsize key_id, + GeanyKeyBindingFunc cb, gpointer pdata, guint key, GdkModifierType mod, + const gchar *kf_name, const gchar *label, GtkWidget *menu_item) +{ + GeanyKeyBinding *kb; + + /* For now, this is intended for plugins only */ + g_assert(group->plugin); + + kb = keybindings_set_item(group, key_id, NULL, key, mod, kf_name, label, menu_item); + kb->cb_func = cb; + kb->cb_data = pdata; + return kb; +} + + static void add_kb_group(GeanyKeyGroup *group, const gchar *name, const gchar *label, GeanyKeyGroupCallback callback, gboolean plugin) { Modified: src/keybindings.h 4 lines changed, 4 insertions(+), 0 deletions(-) =================================================================== @@ -275,6 +275,10 @@ GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id, GeanyKeyCallback callback, guint key, GdkModifierType mod, const gchar *name, const gchar *label, GtkWidget *menu_item); +GeanyKeyBinding *keybindings_set_item_full(GeanyKeyGroup *group, gsize key_id, + GeanyKeyBindingFunc cb, gpointer pdata, guint key, GdkModifierType mod, + const gchar *kf_name, const gchar *label, GtkWidget *menu_item); + GeanyKeyBinding *keybindings_get_item(GeanyKeyGroup *group, gsize key_id); GdkModifierType keybindings_get_modifiers(GdkModifierType mods); Modified: src/pluginutils.c 30 lines changed, 30 insertions(+), 0 deletions(-) =================================================================== @@ -33,6 +33,8 @@ #include "app.h" #include "geanyobject.h" +#include "keybindings.h" +#include "keybindingsprivate.h" #include "plugindata.h" #include "pluginprivate.h" #include "plugins.h" @@ -307,6 +309,34 @@ GeanyKeyGroup *plugin_set_key_group(GeanyPlugin *plugin, return priv->key_group; } +/** Sets up or resizes a keybinding group for the plugin + * + * You should then call keybindings_set_item() or keybindings_set_item_full() for each + * keybinding in the group. + * @param plugin Must be @ref geany_plugin. + * @param section_name Name used in the configuration file, such as @c "html_chars". + * @param count Number of keybindings for the group. + * @param cb New-style group callback, or @c NULL if you only want individual keybinding callbacks. + * @param pdata Plugin specific data, passed to the group callback. + * @return The plugin's keybinding group. + * + * @since 1.26 (API 226) + * @see See keybindings_set_item + * @see See keybindings_set_item_full + **/ +GEANY_API_SYMBOL +GeanyKeyGroup *plugin_set_key_group_full(GeanyPlugin *plugin, + const gchar *section_name, gsize count, GeanyKeyGroupFunc cb, gpointer pdata) +{ + GeanyKeyGroup *group; + + group = plugin_set_key_group(plugin, section_name, count, NULL); + group->cb_func = cb; + group->cb_data = pdata; + + return group; +} + static void on_pref_btn_clicked(gpointer btn, Plugin *p) { Modified: src/pluginutils.h 3 lines changed, 3 insertions(+), 0 deletions(-) =================================================================== @@ -54,6 +54,9 @@ guint plugin_idle_add(struct GeanyPlugin *plugin, GSourceFunc function, gpointer struct GeanyKeyGroup *plugin_set_key_group(struct GeanyPlugin *plugin, const gchar *section_name, gsize count, GeanyKeyGroupCallback callback); +GeanyKeyGroup *plugin_set_key_group_full(struct GeanyPlugin *plugin, + const gchar *section_name, gsize count, GeanyKeyGroupFunc cb, gpointer pdata); + void plugin_show_configure(struct GeanyPlugin *plugin); void plugin_builder_connect_signals(struct GeanyPlugin *plugin, -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 12 21:06:51 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 12 Oct 2015 21:06:51 -0000 Subject: [geany/geany] 7c2c9d: plugin api: add destroy_notify to the new keybinding APIs Message-ID: <20151012210650.386BF5C402D@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Wed, 26 Aug 2015 21:49:45 UTC Commit: 7c2c9dc27a909129007d74bb40ac67a643f6a2f4 https://github.com/geany/geany/commit/7c2c9dc27a909129007d74bb40ac67a643f6a2f4 Log Message: ----------- plugin api: add destroy_notify to the new keybinding APIs The destroy_notify can be used to make Geany automatically free the per-KeyGroup or per-KeyBinding user_data. This is particularly useful for vala-based plugins or other (future) language bindings. The destroy functions can be conviniently hooked into the destroy_notify of the underlying GPtrArrays, therefore this commit also implements such notifies internally. Modified Paths: -------------- src/keybindings.c src/keybindings.h src/keybindingsprivate.h src/pluginutils.c src/pluginutils.h Modified: src/keybindings.c 61 lines changed, 40 insertions(+), 21 deletions(-) =================================================================== @@ -202,7 +202,7 @@ GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id, } -/** Creates a new keybinding using a GeanyKeyBindingFunc and attaches is to a keybinding group +/** Creates a new keybinding using a GeanyKeyBindingFunc and attaches it to a keybinding group * * If given the callback should return @c TRUE if the keybinding was handled, otherwise @c FALSE * to allow other callbacks to be run. This allows for multiplexing keybindings on the same keys, @@ -211,14 +211,15 @@ GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id, * * @param group Group. * @param key_id Keybinding index for the group. - * @param cb New-style callback to be called when activated, or @c NULL to use the group callback. - * @param pdata Plugin-specific data passed back to the callback. * @param key (Lower case) default key, e.g. @c GDK_j, but usually 0 for unset. * @param mod Default modifier, e.g. @c GDK_CONTROL_MASK, but usually 0 for unset. * @param kf_name Key name for the configuration file, such as @c "menu_new". * @param label Label used in the preferences dialog keybindings tab. May contain * underscores - these won't be displayed. * @param menu_item Optional widget to set an accelerator for, or @c NULL. + * @param cb New-style callback to be called when activated, or @c NULL to use the group callback. + * @param pdata Plugin-specific data passed back to the callback. + * @param destroy_notify Function that is invoked to free the plugin data when not needed anymore. * @return The keybinding - normally this is ignored. * * @since 1.26 (API 226) @@ -226,8 +227,9 @@ GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id, **/ GEANY_API_SYMBOL GeanyKeyBinding *keybindings_set_item_full(GeanyKeyGroup *group, gsize key_id, - GeanyKeyBindingFunc cb, gpointer pdata, guint key, GdkModifierType mod, - const gchar *kf_name, const gchar *label, GtkWidget *menu_item) + guint key, GdkModifierType mod, const gchar *kf_name, const gchar *label, + GtkWidget *menu_item, GeanyKeyBindingFunc cb, gpointer pdata, + GDestroyNotify destroy_notify) { GeanyKeyBinding *kb; @@ -237,10 +239,23 @@ GeanyKeyBinding *keybindings_set_item_full(GeanyKeyGroup *group, gsize key_id, kb = keybindings_set_item(group, key_id, NULL, key, mod, kf_name, label, menu_item); kb->cb_func = cb; kb->cb_data = pdata; + kb->cb_data_destroy = destroy_notify; return kb; } +static void free_key_binding(gpointer item) +{ + GeanyKeyBinding *kb = item; + + g_free(kb->name); + g_free(kb->label); + + if (kb->cb_data_destroy) + kb->cb_data_destroy(kb->cb_data); +} + + static void add_kb_group(GeanyKeyGroup *group, const gchar *name, const gchar *label, GeanyKeyGroupCallback callback, gboolean plugin) { @@ -252,7 +267,8 @@ static void add_kb_group(GeanyKeyGroup *group, group->cb_func = NULL; group->cb_data = NULL; group->plugin = plugin; - group->key_items = g_ptr_array_new(); + /* Only plugins use the destroy notify thus far */ + group->key_items = g_ptr_array_new_with_free_func(plugin ? free_key_binding : NULL); } @@ -680,10 +696,27 @@ static void init_default_kb(void) } +static void free_key_group(gpointer item) +{ + GeanyKeyGroup *group = item; + + g_ptr_array_free(group->key_items, TRUE); + + if (group->plugin) + { + if (group->cb_data_destroy) + group->cb_data_destroy(group->cb_data); + g_free(group->plugin_keys); + g_free(group); + } +} + + void keybindings_init(void) { memset(binding_ids, 0, sizeof binding_ids); keybinding_groups = g_ptr_array_sized_new(GEANY_KEY_GROUP_COUNT); + g_ptr_array_set_free_func(keybinding_groups, free_key_group); kb_accel_group = gtk_accel_group_new(); init_default_kb(); @@ -2591,19 +2624,5 @@ GeanyKeyGroup *keybindings_set_group(GeanyKeyGroup *group, const gchar *section_ void keybindings_free_group(GeanyKeyGroup *group) { - GeanyKeyBinding *kb; - - g_ptr_array_free(group->key_items, TRUE); - - if (group->plugin) - { - foreach_c_array(kb, group->plugin_keys, group->plugin_key_count) - { - g_free(kb->name); - g_free(kb->label); - } - g_free(group->plugin_keys); - g_ptr_array_remove_fast(keybinding_groups, group); - g_free(group); - } + g_ptr_array_remove_fast(keybinding_groups, group); } Modified: src/keybindings.h 6 lines changed, 4 insertions(+), 2 deletions(-) =================================================================== @@ -89,6 +89,7 @@ struct GeanyKeyBinding GdkModifierType default_mods; GeanyKeyBindingFunc cb_func; gpointer cb_data; + GDestroyNotify cb_data_destroy; }; /* Note: we don't need to break the plugin ABI when appending keybinding or keygroup IDs, @@ -276,8 +277,9 @@ GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id, const gchar *name, const gchar *label, GtkWidget *menu_item); GeanyKeyBinding *keybindings_set_item_full(GeanyKeyGroup *group, gsize key_id, - GeanyKeyBindingFunc cb, gpointer pdata, guint key, GdkModifierType mod, - const gchar *kf_name, const gchar *label, GtkWidget *menu_item); + guint key, GdkModifierType mod, const gchar *kf_name, const gchar *label, + GtkWidget *menu_item, GeanyKeyBindingFunc func, gpointer pdata, + GDestroyNotify destroy_notify); GeanyKeyBinding *keybindings_get_item(GeanyKeyGroup *group, gsize key_id); Modified: src/keybindingsprivate.h 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -40,6 +40,7 @@ struct GeanyKeyGroup GeanyKeyBinding *plugin_keys; /* array of GeanyKeyBinding structs */ GeanyKeyGroupFunc cb_func; /* use this or individual keybinding callbacks (new style) */ gpointer cb_data; + GDestroyNotify cb_data_destroy; /* used to destroy handler_data */ }; G_END_DECLS Modified: src/pluginutils.c 5 lines changed, 4 insertions(+), 1 deletions(-) =================================================================== @@ -318,6 +318,7 @@ GeanyKeyGroup *plugin_set_key_group(GeanyPlugin *plugin, * @param count Number of keybindings for the group. * @param cb New-style group callback, or @c NULL if you only want individual keybinding callbacks. * @param pdata Plugin specific data, passed to the group callback. + * @param destroy_notify Function that is invoked to free the plugin data when not needed anymore. * @return The plugin's keybinding group. * * @since 1.26 (API 226) @@ -326,13 +327,15 @@ GeanyKeyGroup *plugin_set_key_group(GeanyPlugin *plugin, **/ GEANY_API_SYMBOL GeanyKeyGroup *plugin_set_key_group_full(GeanyPlugin *plugin, - const gchar *section_name, gsize count, GeanyKeyGroupFunc cb, gpointer pdata) + const gchar *section_name, gsize count, + GeanyKeyGroupFunc cb, gpointer pdata, GDestroyNotify destroy_notify) { GeanyKeyGroup *group; group = plugin_set_key_group(plugin, section_name, count, NULL); group->cb_func = cb; group->cb_data = pdata; + group->cb_data_destroy = destroy_notify; return group; } Modified: src/pluginutils.h 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -55,7 +55,7 @@ struct GeanyKeyGroup *plugin_set_key_group(struct GeanyPlugin *plugin, const gchar *section_name, gsize count, GeanyKeyGroupCallback callback); GeanyKeyGroup *plugin_set_key_group_full(struct GeanyPlugin *plugin, - const gchar *section_name, gsize count, GeanyKeyGroupFunc cb, gpointer pdata); + const gchar *section_name, gsize count, GeanyKeyGroupFunc cb, gpointer pdata, GDestroyNotify destroy_notify); void plugin_show_configure(struct GeanyPlugin *plugin); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 12 21:06:51 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Mon, 12 Oct 2015 21:06:51 -0000 Subject: [geany/geany] d4f26f: plugin api: bump API number for new keybindings APIs Message-ID: <20151012210650.BFED65C412B@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Wed, 26 Aug 2015 21:51:33 UTC Commit: d4f26fdb13ff7e76728397c19b6dc8677ce7efaa https://github.com/geany/geany/commit/d4f26fdb13ff7e76728397c19b6dc8677ce7efaa Log Message: ----------- plugin api: bump API number for new keybindings APIs Modified Paths: -------------- src/plugindata.h Modified: src/plugindata.h 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -58,7 +58,7 @@ G_BEGIN_DECLS * @warning You should not test for values below 200 as previously * @c GEANY_API_VERSION was defined as an enum value, not a macro. */ -#define GEANY_API_VERSION 225 +#define GEANY_API_VERSION 226 /* hack to have a different ABI when built with GTK3 because loading GTK2-linked plugins * with GTK3-linked Geany leads to crash */ -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 12 17:20:02 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Mon, 12 Oct 2015 17:20:02 -0000 Subject: [geany/geany] b2879e: tagmanager: Fix handling of scopes starting with a non-ASCII character Message-ID: <20151012210651.ED4F45C4063@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Mon, 12 Oct 2015 17:20:02 UTC Commit: b2879e9fca00f544105029b45974bd808cbb9b7b https://github.com/geany/geany/commit/b2879e9fca00f544105029b45974bd808cbb9b7b Log Message: ----------- tagmanager: Fix handling of scopes starting with a non-ASCII character Fix handling of scopes starting with a non-ASCII character. Actually, just drop the check on the first byte of the scope, as it doesn't seem to serve any purpose as it only checks the first byte (so isn't any kind of real validation; and as it predates Geany it's impossible to know the real reason behind this check), and breaks support for non-ASCII scopes. Modified Paths: -------------- tagmanager/src/tm_tag.c tests/ctags/Makefile.am tests/ctags/non-ascii-ident1.php tests/ctags/non-ascii-ident1.php.tags Modified: tagmanager/src/tm_tag.c 4 lines changed, 1 insertions(+), 3 deletions(-) =================================================================== @@ -244,9 +244,7 @@ static gboolean tm_tag_init(TMTag *tag, TMSourceFile *file, const tagEntryInfo * if (NULL != tag_entry->extensionFields.arglist) tag->arglist = g_strdup(tag_entry->extensionFields.arglist); if ((NULL != tag_entry->extensionFields.scope[1]) && - (isalpha(tag_entry->extensionFields.scope[1][0]) || - tag_entry->extensionFields.scope[1][0] == '_' || - tag_entry->extensionFields.scope[1][0] == '$')) + (0 != tag_entry->extensionFields.scope[1][0])) tag->scope = g_strdup(tag_entry->extensionFields.scope[1]); if (tag_entry->extensionFields.inheritance != NULL) tag->inheritance = g_strdup(tag_entry->extensionFields.inheritance); Modified: tests/ctags/Makefile.am 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -226,6 +226,7 @@ test_sources = \ namespaces2.php \ namespaces.php \ no_terminator.js \ + non-ascii-ident1.php \ numlib.f90 \ objectivec_implementation.mm \ objectivec_interface.mm \ Modified: tests/ctags/non-ascii-ident1.php 12 lines changed, 12 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,12 @@ +r = $a * $b; + } + public function __toString() { + return (string) $this->r; + } +} + +echo new ?(2, 2); Modified: tests/ctags/non-ascii-ident1.php.tags 5 lines changed, 5 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,5 @@ +# format=tagmanager +__construct?16?($a, $b)???0 +__toString?16?()???0 +r?16384???0 +??1?0 -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sat Oct 10 11:57:47 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Sat, 10 Oct 2015 11:57:47 -0000 Subject: [geany/geany] 152103: Merge pull request #376 from kugel-/keybindings-rework3 Message-ID: <20151012210651.6A4E85C412B@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Sat, 10 Oct 2015 11:57:47 UTC Commit: 152103392c04827a38e9486e3b55888effbb5372 https://github.com/geany/geany/commit/152103392c04827a38e9486e3b55888effbb5372 Log Message: ----------- Merge pull request #376 from kugel-/keybindings-rework3 Keybindings rework Modified Paths: -------------- src/keybindings.c src/keybindings.h src/keybindingsprivate.h src/pluginutils.c src/pluginutils.h Modified: src/keybindings.c 148 lines changed, 107 insertions(+), 41 deletions(-) =================================================================== @@ -194,12 +194,68 @@ GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id, kb->default_key = key; kb->default_mods = mod; kb->callback = callback; + kb->cb_func = NULL; + kb->cb_data = NULL; kb->menu_item = menu_item; kb->id = key_id; return kb; } +/** Creates a new keybinding using a GeanyKeyBindingFunc and attaches it to a keybinding group + * + * If given the callback should return @c TRUE if the keybinding was handled, otherwise @c FALSE + * to allow other callbacks to be run. This allows for multiplexing keybindings on the same keys, + * depending on the focused widget (or context). If the callback is NULL the group's callback will + * be invoked, but the same rule applies. + * + * @param group Group. + * @param key_id Keybinding index for the group. + * @param key (Lower case) default key, e.g. @c GDK_j, but usually 0 for unset. + * @param mod Default modifier, e.g. @c GDK_CONTROL_MASK, but usually 0 for unset. + * @param kf_name Key name for the configuration file, such as @c "menu_new". + * @param label Label used in the preferences dialog keybindings tab. May contain + * underscores - these won't be displayed. + * @param menu_item Optional widget to set an accelerator for, or @c NULL. + * @param cb New-style callback to be called when activated, or @c NULL to use the group callback. + * @param pdata Plugin-specific data passed back to the callback. + * @param destroy_notify Function that is invoked to free the plugin data when not needed anymore. + * @return The keybinding - normally this is ignored. + * + * @since 1.26 (API 226) + * @see See plugin_set_key_group_full + **/ +GEANY_API_SYMBOL +GeanyKeyBinding *keybindings_set_item_full(GeanyKeyGroup *group, gsize key_id, + guint key, GdkModifierType mod, const gchar *kf_name, const gchar *label, + GtkWidget *menu_item, GeanyKeyBindingFunc cb, gpointer pdata, + GDestroyNotify destroy_notify) +{ + GeanyKeyBinding *kb; + + /* For now, this is intended for plugins only */ + g_assert(group->plugin); + + kb = keybindings_set_item(group, key_id, NULL, key, mod, kf_name, label, menu_item); + kb->cb_func = cb; + kb->cb_data = pdata; + kb->cb_data_destroy = destroy_notify; + return kb; +} + + +static void free_key_binding(gpointer item) +{ + GeanyKeyBinding *kb = item; + + g_free(kb->name); + g_free(kb->label); + + if (kb->cb_data_destroy) + kb->cb_data_destroy(kb->cb_data); +} + + static void add_kb_group(GeanyKeyGroup *group, const gchar *name, const gchar *label, GeanyKeyGroupCallback callback, gboolean plugin) { @@ -208,8 +264,11 @@ static void add_kb_group(GeanyKeyGroup *group, group->name = name; group->label = label; group->callback = callback; + group->cb_func = NULL; + group->cb_data = NULL; group->plugin = plugin; - group->key_items = g_ptr_array_new(); + /* Only plugins use the destroy notify thus far */ + group->key_items = g_ptr_array_new_with_free_func(plugin ? free_key_binding : NULL); } @@ -637,10 +696,27 @@ static void init_default_kb(void) } +static void free_key_group(gpointer item) +{ + GeanyKeyGroup *group = item; + + g_ptr_array_free(group->key_items, TRUE); + + if (group->plugin) + { + if (group->cb_data_destroy) + group->cb_data_destroy(group->cb_data); + g_free(group->plugin_keys); + g_free(group); + } +} + + void keybindings_init(void) { memset(binding_ids, 0, sizeof binding_ids); keybinding_groups = g_ptr_array_sized_new(GEANY_KEY_GROUP_COUNT); + g_ptr_array_set_free_func(keybinding_groups, free_key_group); kb_accel_group = gtk_accel_group_new(); init_default_kb(); @@ -1207,6 +1283,30 @@ gboolean keybindings_check_event(GdkEventKey *ev, GeanyKeyBinding *kb) } +static gboolean run_kb(GeanyKeyBinding *kb, GeanyKeyGroup *group) +{ + gboolean handled = TRUE; + /* call the corresponding handler/callback functions for this shortcut. + * Check the individual keybindings first (handler first, callback second) and + * group second (again handler first, callback second) */ + if (kb->cb_func) + handled = kb->cb_func(kb, kb->id, kb->cb_data); + else if (kb->callback) + kb->callback(kb->id); + else if (group->cb_func) + handled = group->cb_func(group, kb->id, group->cb_data); + else if (group->callback) + handled = group->callback(kb->id); + else + { + g_warning("No callback or handler for keybinding %s: %s!", group->name, kb->name); + return FALSE; + } + + return handled; +} + + /* central keypress event handler, almost all keypress events go to this function */ static gboolean on_key_press_event(GtkWidget *widget, GdkEventKey *ev, gpointer user_data) { @@ -1249,20 +1349,8 @@ static gboolean on_key_press_event(GtkWidget *widget, GdkEventKey *ev, gpointer { if (keyval == kb->key && state == kb->mods) { - /* call the corresponding callback function for this shortcut */ - if (kb->callback) - { - kb->callback(kb->id); + if (run_kb(kb, group)) return TRUE; - } - else if (group->callback) - { - if (group->callback(kb->id)) - return TRUE; - else - continue; /* not handled */ - } - g_warning("No callback for keybinding %s: %s!", group->name, kb->name); } } } @@ -1296,20 +1384,12 @@ GEANY_API_SYMBOL void keybindings_send_command(guint group_id, guint key_id) { GeanyKeyBinding *kb; + GeanyKeyGroup *group; kb = keybindings_lookup_item(group_id, key_id); - if (kb) - { - if (kb->callback) - kb->callback(key_id); - else - { - GeanyKeyGroup *group = keybindings_get_core_group(group_id); - - if (group->callback) - group->callback(key_id); - } - } + group = keybindings_get_core_group(group_id); + if (kb && group) + run_kb(kb, group); } @@ -2544,19 +2624,5 @@ GeanyKeyGroup *keybindings_set_group(GeanyKeyGroup *group, const gchar *section_ void keybindings_free_group(GeanyKeyGroup *group) { - GeanyKeyBinding *kb; - - g_ptr_array_free(group->key_items, TRUE); - - if (group->plugin) - { - foreach_c_array(kb, group->plugin_keys, group->plugin_key_count) - { - g_free(kb->name); - g_free(kb->label); - } - g_free(group->plugin_keys); - g_ptr_array_remove_fast(keybinding_groups, group); - g_free(group); - } + g_ptr_array_remove_fast(keybinding_groups, group); } Modified: src/keybindings.h 54 lines changed, 40 insertions(+), 14 deletions(-) =================================================================== @@ -37,12 +37,42 @@ G_BEGIN_DECLS #define GEANY_PRIMARY_MOD_MASK GDK_CONTROL_MASK #endif +/** A collection of keybindings grouped together. */ +typedef struct GeanyKeyGroup GeanyKeyGroup; +typedef struct GeanyKeyBinding GeanyKeyBinding; + +/** Function pointer type used for keybinding group callbacks. + * + * You should return @c TRUE to indicate handling the callback. (Occasionally, if the keybinding + * cannot apply in the current situation, it is useful to return @c FALSE to allow a later keybinding + * with the same key combination to handle it). */ +typedef gboolean (*GeanyKeyGroupCallback) (guint key_id); + +/** Function pointer type used for keybinding group callbacks, with userdata for passing context. + * + * You should return @c TRUE to indicate handling the callback. (Occasionally, if the keybinding + * cannot apply in the current situation, it is useful to return @c FALSE to allow a later keybinding + * with the same key combination to handle it). + * + * @since 1.26 (API 226) */ +typedef gboolean (*GeanyKeyGroupFunc)(GeanyKeyGroup *group, guint key_id, gpointer pdata); + /** Function pointer type used for keybinding callbacks. */ typedef void (*GeanyKeyCallback) (guint key_id); +/** Function pointer type used for keybinding callbacks, with userdata for passing context + * + * You should return @c TRUE to indicate handling the callback. (Occasionally, if the keybinding + * cannot apply in the current situation, it is useful to return @c FALSE to allow a later keybinding + * with the same key combination to handle it). + * + * @since 1.26 (API 226) */ +typedef gboolean (*GeanyKeyBindingFunc)(GeanyKeyBinding *key, guint key_id, gpointer pdata); + /** Represents a single keybinding action. + * * Use keybindings_set_item() to set. */ -typedef struct GeanyKeyBinding +struct GeanyKeyBinding { guint key; /**< Key value in lower-case, such as @c GDK_a or 0 */ GdkModifierType mods; /**< Modifier keys, such as @c GDK_CONTROL_MASK or 0 */ @@ -57,19 +87,10 @@ typedef struct GeanyKeyBinding guint id; guint default_key; GdkModifierType default_mods; -} -GeanyKeyBinding; - - -/** Function pointer type used for keybinding group callbacks. - * You should return @c TRUE to indicate handling the callback. (Occasionally, if the keybinding - * cannot apply in the current situation, it is useful to return @c FALSE to allow a later keybinding - * with the same key combination to handle it). */ -typedef gboolean (*GeanyKeyGroupCallback) (guint key_id); - -/** A collection of keybindings grouped together. */ -typedef struct GeanyKeyGroup GeanyKeyGroup; - + GeanyKeyBindingFunc cb_func; + gpointer cb_data; + GDestroyNotify cb_data_destroy; +}; /* Note: we don't need to break the plugin ABI when appending keybinding or keygroup IDs, * just make sure to insert immediately before the _COUNT item, so @@ -255,6 +276,11 @@ GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id, GeanyKeyCallback callback, guint key, GdkModifierType mod, const gchar *name, const gchar *label, GtkWidget *menu_item); +GeanyKeyBinding *keybindings_set_item_full(GeanyKeyGroup *group, gsize key_id, + guint key, GdkModifierType mod, const gchar *kf_name, const gchar *label, + GtkWidget *menu_item, GeanyKeyBindingFunc func, gpointer pdata, + GDestroyNotify destroy_notify); + GeanyKeyBinding *keybindings_get_item(GeanyKeyGroup *group, gsize key_id); GdkModifierType keybindings_get_modifiers(GdkModifierType mods); Modified: src/keybindingsprivate.h 3 lines changed, 3 insertions(+), 0 deletions(-) =================================================================== @@ -38,6 +38,9 @@ struct GeanyKeyGroup GPtrArray *key_items; /* pointers to GeanyKeyBinding structs */ gsize plugin_key_count; /* number of keybindings the group holds */ GeanyKeyBinding *plugin_keys; /* array of GeanyKeyBinding structs */ + GeanyKeyGroupFunc cb_func; /* use this or individual keybinding callbacks (new style) */ + gpointer cb_data; + GDestroyNotify cb_data_destroy; /* used to destroy handler_data */ }; G_END_DECLS Modified: src/pluginutils.c 33 lines changed, 33 insertions(+), 0 deletions(-) =================================================================== @@ -33,6 +33,8 @@ #include "app.h" #include "geanyobject.h" +#include "keybindings.h" +#include "keybindingsprivate.h" #include "plugindata.h" #include "pluginprivate.h" #include "plugins.h" @@ -306,6 +308,37 @@ GeanyKeyGroup *plugin_set_key_group(GeanyPlugin *plugin, return priv->key_group; } +/** Sets up or resizes a keybinding group for the plugin + * + * You should then call keybindings_set_item() or keybindings_set_item_full() for each + * keybinding in the group. + * @param plugin Must be @ref geany_plugin. + * @param section_name Name used in the configuration file, such as @c "html_chars". + * @param count Number of keybindings for the group. + * @param cb New-style group callback, or @c NULL if you only want individual keybinding callbacks. + * @param pdata Plugin specific data, passed to the group callback. + * @param destroy_notify Function that is invoked to free the plugin data when not needed anymore. + * @return The plugin's keybinding group. + * + * @since 1.26 (API 226) + * @see See keybindings_set_item + * @see See keybindings_set_item_full + **/ +GEANY_API_SYMBOL +GeanyKeyGroup *plugin_set_key_group_full(GeanyPlugin *plugin, + const gchar *section_name, gsize count, + GeanyKeyGroupFunc cb, gpointer pdata, GDestroyNotify destroy_notify) +{ + GeanyKeyGroup *group; + + group = plugin_set_key_group(plugin, section_name, count, NULL); + group->cb_func = cb; + group->cb_data = pdata; + group->cb_data_destroy = destroy_notify; + + return group; +} + static void on_pref_btn_clicked(gpointer btn, Plugin *p) { Modified: src/pluginutils.h 3 lines changed, 3 insertions(+), 0 deletions(-) =================================================================== @@ -54,6 +54,9 @@ guint plugin_idle_add(struct GeanyPlugin *plugin, GSourceFunc function, gpointer struct GeanyKeyGroup *plugin_set_key_group(struct GeanyPlugin *plugin, const gchar *section_name, gsize count, GeanyKeyGroupCallback callback); +GeanyKeyGroup *plugin_set_key_group_full(struct GeanyPlugin *plugin, + const gchar *section_name, gsize count, GeanyKeyGroupFunc cb, gpointer pdata, GDestroyNotify destroy_notify); + void plugin_show_configure(struct GeanyPlugin *plugin); void plugin_builder_connect_signals(struct GeanyPlugin *plugin, -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 13 20:36:51 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Tue, 13 Oct 2015 20:36:51 -0000 Subject: [geany/geany] 6db80a: Don't open more than one document for non-existing paths from the CLI Message-ID: <20151013203651.8D0C85C2F81@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Tue, 08 Sep 2015 14:23:57 UTC Commit: 6db80a247f6969ffc14272189da449d566a745c0 https://github.com/geany/geany/commit/6db80a247f6969ffc14272189da449d566a745c0 Log Message: ----------- Don't open more than one document for non-existing paths from the CLI When creating a new document for a non-existing file from the command line, check if we don't already have opened it and simply show the existing one if we do. This avoids creating new documents that will be saved to the same location again and again. Closes https://bugs.launchpad.net/linuxmint/+bug/1482558 Modified Paths: -------------- src/libmain.c Modified: src/libmain.c 6 lines changed, 5 insertions(+), 1 deletions(-) =================================================================== @@ -804,7 +804,11 @@ gboolean main_handle_filename(const gchar *locale_filename) { /* create new file with the given filename */ gchar *utf8_filename = utils_get_utf8_from_locale(filename); - doc = document_new_file(utf8_filename, NULL, NULL); + doc = document_find_by_filename(utf8_filename); + if (doc) + document_show_tab(doc); + else + doc = document_new_file(utf8_filename, NULL, NULL); if (doc != NULL) ui_add_recent_document(doc); g_free(utf8_filename); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 13 20:35:16 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Tue, 13 Oct 2015 20:35:16 -0000 Subject: [geany/geany] 7215c5: Merge pull request #646 from b4n/new-cl-files-once Message-ID: <20151013203652.245F05C2F81@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Tue, 13 Oct 2015 20:35:16 UTC Commit: 7215c5027711546cbe899fe4ccf828bd945c1d66 https://github.com/geany/geany/commit/7215c5027711546cbe899fe4ccf828bd945c1d66 Log Message: ----------- Merge pull request #646 from b4n/new-cl-files-once Don't open more than one document for non-existing paths from the CLI Modified Paths: -------------- src/libmain.c Modified: src/libmain.c 6 lines changed, 5 insertions(+), 1 deletions(-) =================================================================== @@ -804,7 +804,11 @@ gboolean main_handle_filename(const gchar *locale_filename) { /* create new file with the given filename */ gchar *utf8_filename = utils_get_utf8_from_locale(filename); - doc = document_new_file(utf8_filename, NULL, NULL); + doc = document_find_by_filename(utf8_filename); + if (doc) + document_show_tab(doc); + else + doc = document_new_file(utf8_filename, NULL, NULL); if (doc != NULL) ui_add_recent_document(doc); g_free(utf8_filename); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Fri Oct 16 13:22:41 2015 From: git-noreply at xxxxx (=?utf-8?q?Enrico_Tr=C3=B6ger?=) Date: Fri, 16 Oct 2015 13:22:41 -0000 Subject: [geany/infrastructure] 614978: Add scripts to backup all non-repository data from Github Message-ID: <20151016132316.893E35C3704@mail.geany.org> Branch: refs/heads/master Author: Enrico Tr?ger Committer: Enrico Tr?ger Date: Fri, 16 Oct 2015 13:22:41 UTC Commit: 6149781705e146ae2f47877a705592f062dd5fd3 https://github.com/geany/infrastructure/commit/6149781705e146ae2f47877a705592f062dd5fd3 Log Message: ----------- Add scripts to backup all non-repository data from Github Modified Paths: -------------- github-backup/backup.sh github-backup/create_or_update_virtualenv.sh Modified: github-backup/backup.sh 22 lines changed, 22 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,22 @@ +#!/bin/sh + +# backup almost everything from Github except the GIT repositories itself as we have git.geany.org + +# read the token +. /home/geany/.github-token + +# start the backup +/home/geany/github-backup/venv/bin/github-backup \ + --token "${GITHUB_TOKEN}" \ + --issues \ + --issue-comments \ + --issue-events \ + --pulls \ + --pull-comments \ + --pull-commits \ + --wikis \ + --labels \ + --organization \ + --milestones \ + --output-directory=/home/geany/github-backup + geany Modified: github-backup/create_or_update_virtualenv.sh 16 lines changed, 16 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,16 @@ +#!/bin/sh + +# initial setup of the Python virtualenv for github-backup (github.com/josegonzalez/python-github-backup) +# or just update it if it already exists + +BASE_DIR="/home/geany/github-backup" +mkdir -p "${BASE_DIR}" +cd "${BASE_DIR}" +if [ ! -d "${BASE_DIR}/venv" ]; then + virtualenv venv +fi + +# update +venv/bin/pip install -U pip setuptools +venv/bin/pip install -U "git+git://github.com/eht16/yolk#egg=yolk" +venv/bin/pip install -U "git+git://github.com/josegonzalez/python-github-backup.git#egg=github-backup" -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Fri Oct 16 13:24:39 2015 From: git-noreply at xxxxx (=?utf-8?q?Enrico_Tr=C3=B6ger?=) Date: Fri, 16 Oct 2015 13:24:39 -0000 Subject: [geany/infrastructure] 9a25cd: Fix syntax, oops Message-ID: <20151016132445.1F6025C3704@mail.geany.org> Branch: refs/heads/master Author: Enrico Tr?ger Committer: Enrico Tr?ger Date: Fri, 16 Oct 2015 13:24:39 UTC Commit: 9a25cde90ab7c295bda17faae9c317d73d4b9776 https://github.com/geany/infrastructure/commit/9a25cde90ab7c295bda17faae9c317d73d4b9776 Log Message: ----------- Fix syntax, oops Modified Paths: -------------- github-backup/backup.sh Modified: github-backup/backup.sh 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -18,5 +18,5 @@ --labels \ --organization \ --milestones \ - --output-directory=/home/geany/github-backup + --output-directory=/home/geany/github-backup \ geany -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Fri Oct 16 14:05:29 2015 From: git-noreply at xxxxx (=?utf-8?q?Enrico_Tr=C3=B6ger?=) Date: Fri, 16 Oct 2015 14:05:29 -0000 Subject: [geany/infrastructure] 93e518: Add missing repositories of the Github geany organization Message-ID: <20151016140535.C64815C3704@mail.geany.org> Branch: refs/heads/master Author: Enrico Tr?ger Committer: Enrico Tr?ger Date: Fri, 16 Oct 2015 14:05:29 UTC Commit: 93e5185388f2f9f508e30a8498c4ecf9e8ccb76f https://github.com/geany/infrastructure/commit/93e5185388f2f9f508e30a8498c4ecf9e8ccb76f Log Message: ----------- Add missing repositories of the Github geany organization Modified Paths: -------------- scripts/git_hooks/github_commit_mail.py scripts/git_hooks/post_commit_hook.py Modified: scripts/git_hooks/github_commit_mail.py 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -66,6 +66,7 @@ 'geany/infrastructure': 'commits at lists.geany.org', 'geany/www.geany.org': 'commits at lists.geany.org', 'geany/geany-themes': 'commits at lists.geany.org', + 'geany/geany-osx': 'commits at lists.geany.org', # plugins 'geany/geany-plugins': 'plugins-commits at lists.geany.org', 'geany/plugins.geany.org': 'plugins-commits at lists.geany.org', Modified: scripts/git_hooks/post_commit_hook.py 11 lines changed, 10 insertions(+), 1 deletions(-) =================================================================== @@ -24,7 +24,16 @@ LOG_FILENAME = u'/var/log/git_mirror.log' -VALID_UPDATE_REPOSITORIES = ('geany', 'geany-plugins', 'infrastructure', 'newsletter', 'talks', 'geany-themes') +VALID_UPDATE_REPOSITORIES = ( + 'geany', + 'geany-plugins', + 'infrastructure', + 'newsletter', + 'plugins.geany.org', + 'www.geany.org', + 'geany-osx', + 'talks', + 'geany-themes') REPOSITORY_BASE_PATH = u'/srv/www/git.geany.org/repos/%s.git' UPDATE_LOCK_FILE = u'%s/_geany/.update_lock' UPDATE_NOTIFY_FILE = u'%s/_geany/.update_required' -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Fri Oct 16 14:20:16 2015 From: git-noreply at xxxxx (=?utf-8?q?Enrico_Tr=C3=B6ger?=) Date: Fri, 16 Oct 2015 14:20:16 -0000 Subject: [geany/infrastructure] 38eb61: Update instructions to set up a new repository Message-ID: <20151016142028.9E7B15C3704@mail.geany.org> Branch: refs/heads/master Author: Enrico Tr?ger Committer: Enrico Tr?ger Date: Fri, 16 Oct 2015 14:20:16 UTC Commit: 38eb610202b7e5af804229977a5f7283cafec3e4 https://github.com/geany/infrastructure/commit/38eb610202b7e5af804229977a5f7283cafec3e4 Log Message: ----------- Update instructions to set up a new repository Modified Paths: -------------- README.md Modified: README.md 9 lines changed, 6 insertions(+), 3 deletions(-) =================================================================== @@ -34,13 +34,16 @@ If you want to add or remove a repository maintained by these scripts, follow th cd /srv/www/git.geany.org/repos/ git init --bare geany-themes.git cp /srv/www/git.geany.org/repos/geany.git/config /srv/www/git.geany.org/repos/geany-themes.git/ - touch /srv/www/git.geany.org/repos/geany-themes.git/.update_required + mkdir /srv/www/git.geany.org/repos/geany-themes.git/_geany + chmod 775 /srv/www/git.geany.org/repos/geany-themes.git/_geany + chown geany:www-data /srv/www/git.geany.org/repos/geany-themes.git/_geany + touch /srv/www/git.geany.org/repos/geany-themes.git/_geany/.update_required ln -s /srv/www/git.geany.org/repos/geany-themes.git/ /srv/www/git.geany.org/git/geany-themes chown -R www-data:www-data /srv/www/git.geany.org/repos/geany-themes.git chown www-data:geany /srv/www/git.geany.org/repos/geany-themes.git chmod 775 /srv/www/git.geany.org/repos/geany-themes.git - chown www-data:geany /srv/www/git.geany.org/repos/geany-themes.git/.update_required - chmod 664 /srv/www/git.geany.org/repos/geany-themes.git/.update_required + chown geany:www-data /srv/www/git.geany.org/repos/geany-themes.git/_geany/.update_required + chmod 664 /srv/www/git.geany.org/repos/geany-themes.git/_geany/.update_required * Edit /srv/www/git.geany.org/repos/geany-themes.git/config and adjust remote URL -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sat Oct 17 18:00:56 2015 From: git-noreply at xxxxx (=?utf-8?q?Matthew_Brush?=) Date: Sat, 17 Oct 2015 18:00:56 -0000 Subject: [geany/geany] d5e484: Update manual for changes to view menu/color schemes dialog Message-ID: <20151017180247.41DF95C1E66@mail.geany.org> Branch: refs/heads/master Author: Matthew Brush Committer: Matthew Brush Date: Sat, 17 Oct 2015 18:00:56 UTC Commit: d5e484cadb9275e5f6600c4834299fc766cf3a24 https://github.com/geany/geany/commit/d5e484cadb9275e5f6600c4834299fc766cf3a24 Log Message: ----------- Update manual for changes to view menu/color schemes dialog Closes #699 Modified Paths: -------------- doc/geany.txt Modified: doc/geany.txt 12 lines changed, 6 insertions(+), 6 deletions(-) =================================================================== @@ -1572,12 +1572,12 @@ View menu The View menu allows various elements of the main window to be shown or hidden, and also provides various display-related editor options. -Color schemes menu -^^^^^^^^^^^^^^^^^^ -The Color schemes menu is available under the *View->Editor* submenu. -It lists various color schemes for editor highlighting styles, -including the default scheme first. Other items are available based -on what color scheme files Geany found at startup. +Color schemes dialog +^^^^^^^^^^^^^^^^^^^^ +The Color Schemes dialog is available under the *View->Change Color Scheme* +menu item. It lists various color schemes for editor highlighting +styles, including the default scheme first. Other items are available +based on what color scheme files Geany found at startup. Color scheme files are read from the `Configuration file paths`_ under the ``colorschemes`` subdirectory. They should have the extension -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Thu Oct 15 18:52:08 2015 From: git-noreply at xxxxx (=?utf-8?b?SmlyzIxpzIEgVGVjaGV0?=) Date: Thu, 15 Oct 2015 18:52:08 -0000 Subject: [geany/geany] f9e6ce: Use g_return_val_if_fail() to fix build on OS X Message-ID: <20151019134646.CD2AC5C42A2@mail.geany.org> Branch: refs/heads/master Author: Jir?i? Techet Committer: Jir?i? Techet Date: Thu, 15 Oct 2015 18:52:08 UTC Commit: f9e6ceaceef1ca01961b045801c88149987f2f96 https://github.com/geany/geany/commit/f9e6ceaceef1ca01961b045801c88149987f2f96 Log Message: ----------- Use g_return_val_if_fail() to fix build on OS X Modified Paths: -------------- src/plugins.c Modified: src/plugins.c 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -2000,7 +2000,7 @@ gboolean geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensio foreach_list(node, active_proxies.head) { proxy = node->data; - g_return_if_fail(p != proxy->plugin); + g_return_val_if_fail(p != proxy->plugin, FALSE); } foreach_strv(ext, extensions) -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 19 13:45:28 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Mon, 19 Oct 2015 13:45:28 -0000 Subject: [geany/geany] 9420ec: Merge pull request #697 from techee/val_if_fail Message-ID: <20151019134647.797415C42A4@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Mon, 19 Oct 2015 13:45:28 UTC Commit: 9420ec84939ed8625611047b6764da5488c09537 https://github.com/geany/geany/commit/9420ec84939ed8625611047b6764da5488c09537 Log Message: ----------- Merge pull request #697 from techee/val_if_fail Use g_return_val_if_fail() to fix build on OS X Modified Paths: -------------- src/plugins.c Modified: src/plugins.c 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -2000,7 +2000,7 @@ gboolean geany_plugin_register_proxy(GeanyPlugin *plugin, const gchar **extensio foreach_list(node, active_proxies.head) { proxy = node->data; - g_return_if_fail(p != proxy->plugin); + g_return_val_if_fail(p != proxy->plugin, FALSE); } foreach_strv(ext, extensions) -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 20 20:42:38 2015 From: git-noreply at xxxxx (=?utf-8?q?Frank_Lanitz?=) Date: Tue, 20 Oct 2015 20:42:38 -0000 Subject: [geany/geany] 459983: Update of Portuguese translation Message-ID: <20151020204246.154515C42BD@mail.geany.org> Branch: refs/heads/master Author: Frank Lanitz Committer: Frank Lanitz Date: Tue, 20 Oct 2015 20:42:38 UTC Commit: 459983ed15a68a25e829f166af7a59a6b79d24eb https://github.com/geany/geany/commit/459983ed15a68a25e829f166af7a59a6b79d24eb Log Message: ----------- Update of Portuguese translation Modified Paths: -------------- NEWS po/pt.po Modified: NEWS 3 lines changed, 2 insertions(+), 1 deletions(-) =================================================================== @@ -1,7 +1,8 @@ Geany 1.26 (unreleased) Internationalization - * Update translations: sv, el + * Update translations: sv, el, pt + Geany 1.25 (July 12, 2015) Modified: po/pt.po 849 lines changed, 432 insertions(+), 417 deletions(-) =================================================================== No diff available, check online -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Wed Oct 21 23:38:15 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Wed, 21 Oct 2015 23:38:15 -0000 Subject: [geany/geany] 1c6b90: waf: Check and enable C99 compiler support Message-ID: <20151022195454.7A7165C09B0@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Wed, 21 Oct 2015 23:38:15 UTC Commit: 1c6b908c9e7f4d6d1ca0754de4c6564f3c8ac7a7 https://github.com/geany/geany/commit/1c6b908c9e7f4d6d1ca0754de4c6564f3c8ac7a7 Log Message: ----------- waf: Check and enable C99 compiler support Closes #679. Modified Paths: -------------- wscript Modified: wscript 65 lines changed, 60 insertions(+), 5 deletions(-) =================================================================== @@ -45,7 +45,7 @@ import tempfile from waflib import Logs, Options, Scripting, Utils from waflib.Build import BuildContext from waflib.Configure import ConfigurationContext -from waflib.Errors import WafError +from waflib.Errors import ConfigurationError, WafError from waflib.TaskGen import feature, before_method from waflib.Tools.compiler_c import c_compiler from waflib.Tools.compiler_cxx import cxx_compiler @@ -188,6 +188,7 @@ def configure(conf): conf.check_waf_version(mini='1.6.1') conf.load('compiler_c') + _check_c99(conf) is_win32 = _target_is_win32(conf) visibility_hidden_supported = conf.check_cc(cflags=['-Werror', '-fvisibility=hidden'], mandatory=False) @@ -410,7 +411,7 @@ def build(bld): includes = ['.', 'src/', 'scintilla/include', 'tagmanager/src'], defines = 'G_LOG_DOMAIN="%s"' % plugin_name, target = plugin_name, - uselib = ['GTK', 'GLIB', 'GMODULE'] + uselib_add, + uselib = ['GTK', 'GLIB', 'GMODULE', 'C99'] + uselib_add, use = ['geany'], install_path = instpath) @@ -422,7 +423,7 @@ def build(bld): target = 'ctags', includes = ['.', 'tagmanager', 'tagmanager/ctags'], defines = 'G_LOG_DOMAIN="CTags"', - uselib = ['cshlib', 'GLIB', 'geanyexport']) + uselib = ['cshlib', 'GLIB', 'geanyexport', 'C99']) # Tagmanager bld.objects( @@ -432,7 +433,7 @@ def build(bld): target = 'tagmanager', includes = ['.', 'tagmanager', 'tagmanager/ctags'], defines = ['GEANY_PRIVATE', 'G_LOG_DOMAIN="Tagmanager"'], - uselib = ['cshlib', 'GTK', 'GLIB', 'geanyexport']) + uselib = ['cshlib', 'GTK', 'GLIB', 'geanyexport', 'C99']) # MIO bld.objects( @@ -486,7 +487,7 @@ def build(bld): name = 'signallist.i', rule = gen_signallist) - base_uselibs = ['GTK', 'GLIB', 'GMODULE', 'GIO', 'GTHREAD', 'WIN32', 'MAC_INTEGRATION', 'SUNOS_SOCKET', 'M'] + base_uselibs = ['GTK', 'GLIB', 'GMODULE', 'GIO', 'GTHREAD', 'WIN32', 'MAC_INTEGRATION', 'SUNOS_SOCKET', 'M', 'C99'] # libgeany bld.shlib( @@ -914,3 +915,57 @@ def _uc_first(string, ctx): if _target_is_win32(ctx): return string.title() return string + + +# Copied from Geany-Plugins +def _check_c99(conf): + # FIXME: improve some checks? + # TODO: look at Autoconf's C99 checks? + fragment = ''' + // single-line comments + + #include + + struct s { int a, b; }; + + // inlines + static inline void fun_inline(struct s param) {} + + int main(void) { + _Bool b = false; + + // variable declaration in for body + for (int i = 0; i < 2; i++); + + // compound literals + fun_inline((struct s) { 1, 2 }); + + // mixed declarations and code + int mixed = 0; + + // named initializers + struct s name_inited = { + .a = 42, + .b = 64 + }; + + return (b || mixed || ! name_inited.a); + } + ''' + + exc = None + # list of flags is stolen from Autoconf 2.69 + flags = ['', '-std=gnu99', '-std=c99', '-c99', '-AC99', + '-D_STDC_C99=', '-qlanglvl=extc99'] + for flag in flags: + try: + desc = ['with flag %s' % flag, 'with no flags'][not flag] + conf.check_cc(fragment=fragment, uselib_store='C99', cflags=flag, + msg="Checking for C99 support (%s)" % desc) + exc = None + break + except ConfigurationError as e: + exc = e + if exc: + raise exc + return True -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Thu Oct 22 19:53:32 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Thu, 22 Oct 2015 19:53:32 -0000 Subject: [geany/geany] 7fbe99: Merge pull request #707 from b4n/waf/c99 Message-ID: <20151022195455.0942A5C09B0@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Thu, 22 Oct 2015 19:53:32 UTC Commit: 7fbe99fd8a0f140ebd037128e181570a12f0713a https://github.com/geany/geany/commit/7fbe99fd8a0f140ebd037128e181570a12f0713a Log Message: ----------- Merge pull request #707 from b4n/waf/c99 waf: Check and enable C99 compiler support Modified Paths: -------------- wscript Modified: wscript 65 lines changed, 60 insertions(+), 5 deletions(-) =================================================================== @@ -45,7 +45,7 @@ import tempfile from waflib import Logs, Options, Scripting, Utils from waflib.Build import BuildContext from waflib.Configure import ConfigurationContext -from waflib.Errors import WafError +from waflib.Errors import ConfigurationError, WafError from waflib.TaskGen import feature, before_method from waflib.Tools.compiler_c import c_compiler from waflib.Tools.compiler_cxx import cxx_compiler @@ -188,6 +188,7 @@ def configure(conf): conf.check_waf_version(mini='1.6.1') conf.load('compiler_c') + _check_c99(conf) is_win32 = _target_is_win32(conf) visibility_hidden_supported = conf.check_cc(cflags=['-Werror', '-fvisibility=hidden'], mandatory=False) @@ -410,7 +411,7 @@ def build(bld): includes = ['.', 'src/', 'scintilla/include', 'tagmanager/src'], defines = 'G_LOG_DOMAIN="%s"' % plugin_name, target = plugin_name, - uselib = ['GTK', 'GLIB', 'GMODULE'] + uselib_add, + uselib = ['GTK', 'GLIB', 'GMODULE', 'C99'] + uselib_add, use = ['geany'], install_path = instpath) @@ -422,7 +423,7 @@ def build(bld): target = 'ctags', includes = ['.', 'tagmanager', 'tagmanager/ctags'], defines = 'G_LOG_DOMAIN="CTags"', - uselib = ['cshlib', 'GLIB', 'geanyexport']) + uselib = ['cshlib', 'GLIB', 'geanyexport', 'C99']) # Tagmanager bld.objects( @@ -432,7 +433,7 @@ def build(bld): target = 'tagmanager', includes = ['.', 'tagmanager', 'tagmanager/ctags'], defines = ['GEANY_PRIVATE', 'G_LOG_DOMAIN="Tagmanager"'], - uselib = ['cshlib', 'GTK', 'GLIB', 'geanyexport']) + uselib = ['cshlib', 'GTK', 'GLIB', 'geanyexport', 'C99']) # MIO bld.objects( @@ -486,7 +487,7 @@ def build(bld): name = 'signallist.i', rule = gen_signallist) - base_uselibs = ['GTK', 'GLIB', 'GMODULE', 'GIO', 'GTHREAD', 'WIN32', 'MAC_INTEGRATION', 'SUNOS_SOCKET', 'M'] + base_uselibs = ['GTK', 'GLIB', 'GMODULE', 'GIO', 'GTHREAD', 'WIN32', 'MAC_INTEGRATION', 'SUNOS_SOCKET', 'M', 'C99'] # libgeany bld.shlib( @@ -914,3 +915,57 @@ def _uc_first(string, ctx): if _target_is_win32(ctx): return string.title() return string + + +# Copied from Geany-Plugins +def _check_c99(conf): + # FIXME: improve some checks? + # TODO: look at Autoconf's C99 checks? + fragment = ''' + // single-line comments + + #include + + struct s { int a, b; }; + + // inlines + static inline void fun_inline(struct s param) {} + + int main(void) { + _Bool b = false; + + // variable declaration in for body + for (int i = 0; i < 2; i++); + + // compound literals + fun_inline((struct s) { 1, 2 }); + + // mixed declarations and code + int mixed = 0; + + // named initializers + struct s name_inited = { + .a = 42, + .b = 64 + }; + + return (b || mixed || ! name_inited.a); + } + ''' + + exc = None + # list of flags is stolen from Autoconf 2.69 + flags = ['', '-std=gnu99', '-std=c99', '-c99', '-AC99', + '-D_STDC_C99=', '-qlanglvl=extc99'] + for flag in flags: + try: + desc = ['with flag %s' % flag, 'with no flags'][not flag] + conf.check_cc(fragment=fragment, uselib_store='C99', cflags=flag, + msg="Checking for C99 support (%s)" % desc) + exc = None + break + except ConfigurationError as e: + exc = e + if exc: + raise exc + return True -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Fri Oct 23 06:34:25 2015 From: git-noreply at xxxxx (=?utf-8?q?elextr?=) Date: Fri, 23 Oct 2015 06:34:25 -0000 Subject: [geany/geany] 06f2cb: Add ".adoc" extension to asciidoc filetype Message-ID: <20151023063427.B85E75C42CF@mail.geany.org> Branch: refs/heads/elextr-patch-1 Author: elextr Committer: elextr Date: Fri, 23 Oct 2015 06:34:25 UTC Commit: 06f2cbe6f6a25ebdcc8a48ee6e5be91a69785ca3 https://github.com/geany/geany/commit/06f2cbe6f6a25ebdcc8a48ee6e5be91a69785ca3 Log Message: ----------- Add ".adoc" extension to asciidoc filetype Closes #708 Modified Paths: -------------- data/filetype_extensions.conf Modified: data/filetype_extensions.conf 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -6,7 +6,7 @@ Abaqus=*.inp; Abc=*.abc;*.abp; ActionScript=*.as; Ada=*.adb;*.ads; -Asciidoc=*.asciidoc; +Asciidoc=*.asciidoc;*.adoc; ASM=*.asm; Batch=*.bat;*.cmd;*.nt; CAML=*.ml;*.mli; -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Fri Oct 23 06:34:25 2015 From: git-noreply at xxxxx (=?utf-8?q?elextr?=) Date: Fri, 23 Oct 2015 06:34:25 -0000 Subject: [geany/geany] 06f2cb: Add ".adoc" extension to asciidoc filetype Message-ID: <20151025154257.B19C65C428D@mail.geany.org> Branch: refs/heads/master Author: elextr Committer: elextr Date: Fri, 23 Oct 2015 06:34:25 UTC Commit: 06f2cbe6f6a25ebdcc8a48ee6e5be91a69785ca3 https://github.com/geany/geany/commit/06f2cbe6f6a25ebdcc8a48ee6e5be91a69785ca3 Log Message: ----------- Add ".adoc" extension to asciidoc filetype Closes #708 Modified Paths: -------------- data/filetype_extensions.conf Modified: data/filetype_extensions.conf 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -6,7 +6,7 @@ Abaqus=*.inp; Abc=*.abc;*.abp; ActionScript=*.as; Ada=*.adb;*.ads; -Asciidoc=*.asciidoc; +Asciidoc=*.asciidoc;*.adoc; ASM=*.asm; Batch=*.bat;*.cmd;*.nt; CAML=*.ml;*.mli; -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sat Oct 24 18:15:58 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Sat, 24 Oct 2015 18:15:58 -0000 Subject: [geany/geany] cbc85b: Improve memory backend of mio_read() Message-ID: <20151025154258.453335C428E@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Sat, 24 Oct 2015 18:15:58 UTC Commit: cbc85b7444da9e5210f1a7ac18a1e223af784af9 https://github.com/geany/geany/commit/cbc85b7444da9e5210f1a7ac18a1e223af784af9 Log Message: ----------- Improve memory backend of mio_read() Drop the loop in mem_read() in favor of a single memcpy() call. This greatly improves performances when nmemb > 1, for a small loss for some values of size when nmemb == 1. Gain can theoretically be infinite since swapping nmemb and size parameters changes almost nothing while it had a dramatic performance impact previously. Loss is up to about 25% in the worst case for some values of size when nmemb is 1. Also, now the function always copies as much data as possible, not only whole blocks. This follows the glibc implementation of fread() and simplifies the code. Doing so also fixes the position after a partial read to be at the last readable character rather than the end of the last read block. Modified Paths: -------------- tagmanager/mio/mio-memory.c Modified: tagmanager/mio/mio-memory.c 40 lines changed, 20 insertions(+), 20 deletions(-) =================================================================== @@ -73,34 +73,34 @@ mem_free (MIO *mio) static gsize mem_read (MIO *mio, - void *ptr, + void *ptr_, gsize size, gsize nmemb) { gsize n_read = 0; if (size != 0 && nmemb != 0) { - if (mio->impl.mem.ungetch != EOF) { - *((guchar *)ptr) = (guchar)mio->impl.mem.ungetch; - mio->impl.mem.ungetch = EOF; - mio->impl.mem.pos++; - if (size == 1) { - n_read++; - } else if (mio->impl.mem.pos + (size - 1) <= mio->impl.mem.size) { - memcpy (&(((guchar *)ptr)[1]), - &mio->impl.mem.buf[mio->impl.mem.pos], size - 1); - mio->impl.mem.pos += size - 1; - n_read++; - } + gsize size_avail = mio->impl.mem.size - mio->impl.mem.pos; + gsize copy_bytes = size * nmemb; + guchar *ptr = ptr_; + + if (size_avail < copy_bytes) { + copy_bytes = size_avail; } - for (; n_read < nmemb; n_read++) { - if (mio->impl.mem.pos + size > mio->impl.mem.size) { - break; - } else { - memcpy (&(((guchar *)ptr)[n_read * size]), - &mio->impl.mem.buf[mio->impl.mem.pos], size); - mio->impl.mem.pos += size; + + if (copy_bytes > 0) { + n_read = copy_bytes / size; + + if (mio->impl.mem.ungetch != EOF) { + *ptr = (guchar) mio->impl.mem.ungetch; + mio->impl.mem.ungetch = EOF; + copy_bytes--; + mio->impl.mem.pos++; + ptr++; } + + memcpy (ptr, &mio->impl.mem.buf[mio->impl.mem.pos], copy_bytes); + mio->impl.mem.pos += copy_bytes; } if (mio->impl.mem.pos >= mio->impl.mem.size) { mio->impl.mem.eof = TRUE; -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sat Oct 24 18:16:44 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Sat, 24 Oct 2015 18:16:44 -0000 Subject: [geany/geany] 326c39: Add printf attribute to vprintf() functions Message-ID: <20151025154258.C14D85C42B4@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Sat, 24 Oct 2015 18:16:44 UTC Commit: 326c39f8d59769237b80463631d57bef58a5a162 https://github.com/geany/geany/commit/326c39f8d59769237b80463631d57bef58a5a162 Log Message: ----------- Add printf attribute to vprintf() functions This allows GCC to check vprintf() format, and makes `-Wsuggest-attribute=format` happy. Modified Paths: -------------- tagmanager/mio/mio-file.c tagmanager/mio/mio-memory.c tagmanager/mio/mio.h Modified: tagmanager/mio/mio-file.c 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -92,6 +92,7 @@ file_puts (MIO *mio, return fputs (s, mio->impl.file.fp); } +G_GNUC_PRINTF (2, 0) static gint file_vprintf (MIO *mio, const gchar *format, Modified: tagmanager/mio/mio-memory.c 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -241,6 +241,7 @@ mem_puts (MIO *mio, return rv; } +G_GNUC_PRINTF (2, 0) static gint mem_vprintf (MIO *mio, const gchar *format, Modified: tagmanager/mio/mio.h 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -145,7 +145,7 @@ struct _MIO { const gchar *s); gint (*v_vprintf) (MIO *mio, const gchar *format, - va_list ap); + va_list ap) G_GNUC_PRINTF (2, 0); void (*v_clearerr) (MIO *mio); gint (*v_eof) (MIO *mio); gint (*v_error) (MIO *mio); @@ -198,7 +198,7 @@ gint mio_puts (MIO *mio, gint mio_vprintf (MIO *mio, const gchar *format, - va_list ap); + va_list ap) G_GNUC_PRINTF (2, 0); gint mio_printf (MIO *mio, const gchar *format, ...) G_GNUC_PRINTF (2, 3); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 25 13:05:37 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Sun, 25 Oct 2015 13:05:37 -0000 Subject: [geany/geany] 5dea35: Respect Smart Home Key setting in Go To Start of Display Line Message-ID: <20151025154259.4274B5C428E@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Sun, 25 Oct 2015 13:05:37 UTC Commit: 5dea35ada9afe202c5507fc35729a91d03175b9e https://github.com/geany/geany/commit/5dea35ada9afe202c5507fc35729a91d03175b9e Log Message: ----------- Respect Smart Home Key setting in Go To Start of Display Line Modified Paths: -------------- src/keybindings.c Modified: src/keybindings.c 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -2036,7 +2036,7 @@ static gboolean cb_func_goto_action(guint key_id) sci_send_command(doc->editor->sci, SCI_LINEEND); break; case GEANY_KEYS_GOTO_LINESTARTVISUAL: - sci_send_command(doc->editor->sci, SCI_HOMEDISPLAY); + sci_send_command(doc->editor->sci, editor_prefs.smart_home_key ? SCI_VCHOMEDISPLAY : SCI_HOMEDISPLAY); break; case GEANY_KEYS_GOTO_LINEENDVISUAL: sci_send_command(doc->editor->sci, SCI_LINEENDDISPLAY); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 25 15:37:34 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Sun, 25 Oct 2015 15:37:34 -0000 Subject: [geany/geany] 4c6bd0: Merge pull request #711 from geany/elextr-patch-1 Message-ID: <20151025154300.3F98B5C42B3@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Sun, 25 Oct 2015 15:37:34 UTC Commit: 4c6bd09b5a67064a92c36c77776246d18b20f40d https://github.com/geany/geany/commit/4c6bd09b5a67064a92c36c77776246d18b20f40d Log Message: ----------- Merge pull request #711 from geany/elextr-patch-1 Add ".adoc" extension to asciidoc filetype Modified Paths: -------------- data/filetype_extensions.conf Modified: data/filetype_extensions.conf 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -6,7 +6,7 @@ Abaqus=*.inp; Abc=*.abc;*.abp; ActionScript=*.as; Ada=*.adb;*.ads; -Asciidoc=*.asciidoc; +Asciidoc=*.asciidoc;*.adoc; ASM=*.asm; Batch=*.bat;*.cmd;*.nt; CAML=*.ml;*.mli; -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 25 15:09:21 2015 From: git-noreply at xxxxx (=?utf-8?q?Colomban_Wendling?=) Date: Sun, 25 Oct 2015 15:09:21 -0000 Subject: [geany/geany] 433580: Merge branch 'mio/update' Message-ID: <20151025154259.B7B575C428D@mail.geany.org> Branch: refs/heads/master Author: Colomban Wendling Committer: Colomban Wendling Date: Sun, 25 Oct 2015 15:09:21 UTC Commit: 4335804d236bd43d7545a48272fd313adc36ce1f https://github.com/geany/geany/commit/4335804d236bd43d7545a48272fd313adc36ce1f Log Message: ----------- Merge branch 'mio/update' Manually import some upstream MIO changes. Modified Paths: -------------- tagmanager/mio/mio-file.c tagmanager/mio/mio-memory.c tagmanager/mio/mio.h Modified: tagmanager/mio/mio-file.c 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -92,6 +92,7 @@ file_puts (MIO *mio, return fputs (s, mio->impl.file.fp); } +G_GNUC_PRINTF (2, 0) static gint file_vprintf (MIO *mio, const gchar *format, Modified: tagmanager/mio/mio-memory.c 41 lines changed, 21 insertions(+), 20 deletions(-) =================================================================== @@ -73,34 +73,34 @@ mem_free (MIO *mio) static gsize mem_read (MIO *mio, - void *ptr, + void *ptr_, gsize size, gsize nmemb) { gsize n_read = 0; if (size != 0 && nmemb != 0) { - if (mio->impl.mem.ungetch != EOF) { - *((guchar *)ptr) = (guchar)mio->impl.mem.ungetch; - mio->impl.mem.ungetch = EOF; - mio->impl.mem.pos++; - if (size == 1) { - n_read++; - } else if (mio->impl.mem.pos + (size - 1) <= mio->impl.mem.size) { - memcpy (&(((guchar *)ptr)[1]), - &mio->impl.mem.buf[mio->impl.mem.pos], size - 1); - mio->impl.mem.pos += size - 1; - n_read++; - } + gsize size_avail = mio->impl.mem.size - mio->impl.mem.pos; + gsize copy_bytes = size * nmemb; + guchar *ptr = ptr_; + + if (size_avail < copy_bytes) { + copy_bytes = size_avail; } - for (; n_read < nmemb; n_read++) { - if (mio->impl.mem.pos + size > mio->impl.mem.size) { - break; - } else { - memcpy (&(((guchar *)ptr)[n_read * size]), - &mio->impl.mem.buf[mio->impl.mem.pos], size); - mio->impl.mem.pos += size; + + if (copy_bytes > 0) { + n_read = copy_bytes / size; + + if (mio->impl.mem.ungetch != EOF) { + *ptr = (guchar) mio->impl.mem.ungetch; + mio->impl.mem.ungetch = EOF; + copy_bytes--; + mio->impl.mem.pos++; + ptr++; } + + memcpy (ptr, &mio->impl.mem.buf[mio->impl.mem.pos], copy_bytes); + mio->impl.mem.pos += copy_bytes; } if (mio->impl.mem.pos >= mio->impl.mem.size) { mio->impl.mem.eof = TRUE; @@ -241,6 +241,7 @@ mem_puts (MIO *mio, return rv; } +G_GNUC_PRINTF (2, 0) static gint mem_vprintf (MIO *mio, const gchar *format, Modified: tagmanager/mio/mio.h 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -145,7 +145,7 @@ struct _MIO { const gchar *s); gint (*v_vprintf) (MIO *mio, const gchar *format, - va_list ap); + va_list ap) G_GNUC_PRINTF (2, 0); void (*v_clearerr) (MIO *mio); gint (*v_eof) (MIO *mio); gint (*v_error) (MIO *mio); @@ -198,7 +198,7 @@ gint mio_puts (MIO *mio, gint mio_vprintf (MIO *mio, const gchar *format, - va_list ap); + va_list ap) G_GNUC_PRINTF (2, 0); gint mio_printf (MIO *mio, const gchar *format, ...) G_GNUC_PRINTF (2, 3); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 27 21:27:40 2015 From: git-noreply at xxxxx (=?utf-8?q?Enrico_Tr=C3=B6ger?=) Date: Tue, 27 Oct 2015 21:27:40 -0000 Subject: [geany/infrastructure] 02811e: Enable backup of Github web hooks Message-ID: <20151027212753.C23A65C093C@mail.geany.org> Branch: refs/heads/master Author: Enrico Tr?ger Committer: Enrico Tr?ger Date: Tue, 27 Oct 2015 21:27:40 UTC Commit: 02811e43aff91b5735dac2ef96078439958ab8e3 https://github.com/geany/infrastructure/commit/02811e43aff91b5735dac2ef96078439958ab8e3 Log Message: ----------- Enable backup of Github web hooks Modified Paths: -------------- github-backup/backup.sh Modified: github-backup/backup.sh 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -16,6 +16,7 @@ --pull-commits \ --wikis \ --labels \ + --hooks \ --organization \ --milestones \ --output-directory=/home/geany/github-backup \ -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 25 21:51:11 2015 From: git-noreply at xxxxx (=?utf-8?q?Enrico_Tr=C3=B6ger?=) Date: Sun, 25 Oct 2015 21:51:11 -0000 Subject: [geany/www.geany.org] d3b2a9: Migrate Pygments CSS update script into a management command Message-ID: <20151028225409.B1AFB5C431E@mail.geany.org> Branch: refs/heads/master Author: Enrico Tr?ger Committer: Enrico Tr?ger Date: Sun, 25 Oct 2015 21:51:11 UTC Commit: d3b2a97510fdceb8aeed486d571cc2e3feadc614 https://github.com/geany/www.geany.org/commit/d3b2a97510fdceb8aeed486d571cc2e3feadc614 Log Message: ----------- Migrate Pygments CSS update script into a management command Same behavior but now cleanly integrated into Django as management command: python manage.py generate_snippets_css Modified Paths: -------------- pastebin/management/commands/generate_snippets_css.py pastebin/static/css/pygments.css Modified: pastebin/management/commands/generate_snippets_css.py 21 lines changed, 12 insertions(+), 9 deletions(-) =================================================================== @@ -1,25 +1,28 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- -# # LICENCE: This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 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 Affero General Public License for more details. -# +# # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from django.core.management.base import BaseCommand from pygments.formatters import HtmlFormatter -f = open('pastebin/static/css/pygments.css', 'w') - -# You can change style and the html class here: -f.write(HtmlFormatter(style='colorful').get_style_defs('.code')) +######################################################################## +class Command(BaseCommand): + help = "Regenerate CSS for snippet sxntax highlighting py Pygments" + requires_system_checks = False -f.close() + #---------------------------------------------------------------------- + def handle(self, *args, **options): + with open('pastebin/static/css/pygments.css', 'w') as css_file: + # You can change style and the html class here: + css_file.write(HtmlFormatter(style='colorful').get_style_defs('.code')) Modified: pastebin/static/css/pygments.css 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -40,6 +40,7 @@ .code .nv { color: #996633 } /* Name.Variable */ .code .ow { color: #000000; font-weight: bold } /* Operator.Word */ .code .w { color: #bbbbbb } /* Text.Whitespace */ +.code .mb { color: #6600EE; font-weight: bold } /* Literal.Number.Bin */ .code .mf { color: #6600EE; font-weight: bold } /* Literal.Number.Float */ .code .mh { color: #005588; font-weight: bold } /* Literal.Number.Hex */ .code .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sun Oct 25 22:20:27 2015 From: git-noreply at xxxxx (=?utf-8?q?Enrico_Tr=C3=B6ger?=) Date: Sun, 25 Oct 2015 22:20:27 -0000 Subject: [geany/www.geany.org] 0c85e3: Minor CSS tweaks for Pastebin Message-ID: <20151028225410.3D58A5C4320@mail.geany.org> Branch: refs/heads/master Author: Enrico Tr?ger Committer: Enrico Tr?ger Date: Sun, 25 Oct 2015 22:20:27 UTC Commit: 0c85e381610d748eb1b6a5891b24492b04e0f1f8 https://github.com/geany/www.geany.org/commit/0c85e381610d748eb1b6a5891b24492b04e0f1f8 Log Message: ----------- Minor CSS tweaks for Pastebin Modified Paths: -------------- pastebin/static/css/pastebin.css pastebin/templates/pastebin/snippet_details.html Modified: pastebin/static/css/pastebin.css 30 lines changed, 22 insertions(+), 8 deletions(-) =================================================================== @@ -47,6 +47,7 @@ form.new-snippet-form { .new-snippet-form ul#id_expire_options li label { float: left; + text-align: center; margin-right: 10px; padding-right: 15px; width: auto; @@ -55,6 +56,11 @@ form.new-snippet-form { .new-snippet-form ul#id_expire_options li input[type="radio"] { margin: 0px; } + +.new-snippet-form .form-group input[type="text"], +.new-snippet-form .form-group select { + max-width: 40em; +} /* * Snippet form */ @@ -73,13 +79,7 @@ div.snippet_list { padding: 3px; } -div.snippet table { - margin: 0; - padding: 0; - border-collapse: collapse; -} - -.snippet-plain { +textarea.snippet-plain { font-family: monospace; font-size: 12px; height: 300px; @@ -126,9 +126,23 @@ div.snippet { margin-left: 25px; } +div.tab-pane { + border-left: 1px solid rgb(220, 220, 220); + border-right: 1px solid rgb(220, 220, 220); + border-bottom: 1px solid rgb(220, 220, 220); + background-color: rgb(250, 250, 250); +} + +div.snippet-plain { + padding: 5px; +} + div.snippet ol { padding: 1px; - border: 1px solid rgb(220, 220, 220); + margin-bottom: 5px; + border-left: 1px solid rgb(220, 220, 220); + border-bottom: 1px solid rgb(220, 220, 220); + border-top: 1px solid rgb(220, 220, 220); -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; Modified: pastebin/templates/pastebin/snippet_details.html 4 lines changed, 3 insertions(+), 1 deletions(-) =================================================================== @@ -50,7 +50,9 @@
    - +
    + +
    -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Wed Oct 28 22:53:57 2015 From: git-noreply at xxxxx (=?utf-8?q?Enrico_Tr=C3=B6ger?=) Date: Wed, 28 Oct 2015 22:53:57 -0000 Subject: [geany/www.geany.org] aab231: Add 'dump_database' management command Message-ID: <20151028225411.501875C431E@mail.geany.org> Branch: refs/heads/master Author: Enrico Tr?ger Committer: Enrico Tr?ger Date: Wed, 28 Oct 2015 22:53:57 UTC Commit: aab231c0c77d0612d9fe7752b26ba985e044bf80 https://github.com/geany/www.geany.org/commit/aab231c0c77d0612d9fe7752b26ba985e044bf80 Log Message: ----------- Add 'dump_database' management command Modified Paths: -------------- geany/management/commands/dump_database.py Modified: geany/management/commands/dump_database.py 31 lines changed, 31 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# LICENCE: This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.core.management import BaseCommand, call_command + + +######################################################################## +class Command(BaseCommand): + help = "Dump the database (excluding users, sessions and logs)" + + #---------------------------------------------------------------------- + def handle(self, *args, **options): + call_command( + 'dumpdata', + '--exclude', 'auth.user', + '--exclude', 'sessions.session', + '--exclude', 'admin.logentry', + '--exclude', 'pastebin.snippet', + '--indent', '2', + '--output', 'database.json') -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Wed Oct 28 22:21:37 2015 From: git-noreply at xxxxx (=?utf-8?q?Enrico_Tr=C3=B6ger?=) Date: Wed, 28 Oct 2015 22:21:37 -0000 Subject: [geany/www.geany.org] dcefe7: Silence warning of deprecated TEMPLATE_CONTEXT_PROCESSORS setting Message-ID: <20151028225410.C37C95C4321@mail.geany.org> Branch: refs/heads/master Author: Enrico Tr?ger Committer: Enrico Tr?ger Date: Wed, 28 Oct 2015 22:21:37 UTC Commit: dcefe7dd1e9eadbf93f154b93bee598e53632139 https://github.com/geany/www.geany.org/commit/dcefe7dd1e9eadbf93f154b93bee598e53632139 Log Message: ----------- Silence warning of deprecated TEMPLATE_CONTEXT_PROCESSORS setting While this setting is deprecated since Django 1.8, we still need to define it for the current version of Mezzanine. See https://github.com/stephenmcd/mezzanine/issues/1364. Modified Paths: -------------- geany/settings.py Modified: geany/settings.py 9 lines changed, 9 insertions(+), 0 deletions(-) =================================================================== @@ -273,6 +273,7 @@ }, }, ] +# compability for Mezzanine which still requires this setting TEMPLATE_CONTEXT_PROCESSORS = TEMPLATES[0]['OPTIONS']['context_processors'] # List of finder classes that know how to find static files in @@ -463,6 +464,14 @@ } +################### +# IGNORE WARNINGS # +################### +SILENCED_SYSTEM_CHECKS = ( + '1_8.W001' # TEMPLATE_CONTEXT_PROCESSORS setting required for Mezzanine +) + + ################## # LOCAL SETTINGS # ################## -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Thu Oct 29 22:37:29 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Thu, 29 Oct 2015 22:37:29 -0000 Subject: [geany/geany] e44e00: Fix configdir encoding Message-ID: <20151029231030.547395C4233@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Colomban Wendling Date: Thu, 29 Oct 2015 22:37:29 UTC Commit: e44e00afe02c980068cbca0ca3870b0b2a663397 https://github.com/geany/geany/commit/e44e00afe02c980068cbca0ca3870b0b2a663397 Log Message: ----------- Fix configdir encoding configdir is initially in locale (glib) encoding. Converting it from UTF-8 is wrong, and it must be converted _to_ UTF-8 when used in geany_debug() - otherwise, Help -> Debug Messages fails assertion. POSIX only, under Windows the glib encoding is also UTF-8. Closes #658. Modified Paths: -------------- src/libmain.c Modified: src/libmain.c 12 lines changed, 6 insertions(+), 6 deletions(-) =================================================================== @@ -568,7 +568,7 @@ static void parse_command_line_options(gint *argc, gchar ***argv) if (alternate_config) { - geany_debug("alternate config: %s", alternate_config); + geany_debug("Using alternate configuration directory"); app->configdir = alternate_config; } else @@ -668,7 +668,7 @@ static gint create_config_dir(void) g_free(old_dir); } #endif - geany_debug("creating config directory %s", app->configdir); + geany_debug("Creating configuration directory"); saved_errno = utils_mkdir(app->configdir, TRUE); } @@ -738,9 +738,6 @@ static gint setup_config_dir(void) { gint mkdir_result = 0; - /* convert configdir to locale encoding to avoid troubles */ - SETPTR(app->configdir, utils_get_locale_from_utf8(app->configdir)); - mkdir_result = create_config_dir(); if (mkdir_result != 0) { @@ -1013,6 +1010,7 @@ gint main_lib(gint argc, gchar **argv) GeanyDocument *doc; gint config_dir_result; const gchar *locale; + gchar *utf8_configdir; #if ! GLIB_CHECK_VERSION(2, 36, 0) g_type_init(); @@ -1101,7 +1099,9 @@ gint main_lib(gint argc, gchar **argv) gtk_major_version, gtk_minor_version, gtk_micro_version, glib_major_version, glib_minor_version, glib_micro_version); geany_debug("System data dir: %s", app->datadir); - geany_debug("User config dir: %s", app->configdir); + utf8_configdir = utils_get_utf8_from_locale(app->configdir); + geany_debug("User config dir: %s", utf8_configdir); + g_free(utf8_configdir); /* create the object so Geany signals can be connected in init() functions */ geany_object = geany_object_new(); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Fri Oct 30 14:23:44 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Fri, 30 Oct 2015 14:23:44 -0000 Subject: [geany/geany] b1e9c4: document: show informational doc message after first reload Message-ID: <20151031014748.2656D5C4344@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Fri, 30 Oct 2015 14:23:44 UTC Commit: b1e9c4f3b6655517f0ebf6eccabfc75cd7090f96 https://github.com/geany/geany/commit/b1e9c4f3b6655517f0ebf6eccabfc75cd7090f96 Log Message: ----------- document: show informational doc message after first reload 6f5d5db and d6d4728 disabled "maintain history on reload" by default, with the intention to reenable it when we have a better method to make it discoverable for the user. This was necessary since it became enabled by default but could be surprising given Geany warned about losing data before. This commit tries to resolve the discoverability, by providing an informational doc message that is shown once to the user, after the first reload. The doc message also gives the option to disable this feature. Modified Paths: -------------- src/document.c src/document.h src/documentprivate.h src/keyfile.c Modified: src/document.c 34 lines changed, 34 insertions(+), 0 deletions(-) =================================================================== @@ -1575,6 +1575,24 @@ void document_open_files(const GSList *filenames, gboolean readonly, GeanyFilety } +static void on_keep_edit_history_on_reload_response(GtkWidget *bar, gint response_id, GeanyDocument *doc) +{ + if (response_id == GTK_RESPONSE_NO) + { + file_prefs.keep_edit_history_on_reload = FALSE; + document_reload_force(doc, doc->encoding); + } + else if (response_id == GTK_RESPONSE_CANCEL) + { + /* this condition cannot be reached via info bar buttons, but by our code + * to replace this bar with a higher priority one */ + file_prefs.show_keep_edit_history_on_reload_msg = TRUE; + } + doc->priv->info_bars[MSG_TYPE_POST_RELOAD] = NULL; + gtk_widget_destroy(bar); +} + + /** * Reloads the document with the specified file encoding. * @a forced_enc or @c NULL to auto-detect the file encoding. @@ -1589,6 +1607,7 @@ gboolean document_reload_force(GeanyDocument *doc, const gchar *forced_enc) { gint pos = 0; GeanyDocument *new_doc; + GtkWidget *bar; g_return_val_if_fail(doc != NULL, FALSE); @@ -1600,6 +1619,21 @@ gboolean document_reload_force(GeanyDocument *doc, const gchar *forced_enc) pos = sci_get_current_position(doc->editor->sci); new_doc = document_open_file_full(doc, NULL, pos, doc->readonly, doc->file_type, forced_enc); + if (file_prefs.keep_edit_history_on_reload && file_prefs.show_keep_edit_history_on_reload_msg) + { + bar = document_show_message(doc, GTK_MESSAGE_INFO, + on_keep_edit_history_on_reload_response, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + _("Discard history"), GTK_RESPONSE_NO, + NULL, 0, _("The buffer's previous state is stored in the history and " + "undoing restores it. You can disable this by discarding the history upon " + "reload. This message will not be displayed again but " + "Your choice can be changed in the various preferences."), + _("The file has been reloaded.")); + doc->priv->info_bars[MSG_TYPE_POST_RELOAD] = bar; + file_prefs.show_keep_edit_history_on_reload_msg = FALSE; + } + return (new_doc != NULL); } Modified: src/document.h 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -65,6 +65,7 @@ typedef struct GeanyFilePrefs gchar *extract_filetype_regex; /* regex to extract filetype on opening */ gboolean tab_close_switch_to_mru; gboolean keep_edit_history_on_reload; /* Keep undo stack upon, and allow undoing of, document reloading. */ + gboolean show_keep_edit_history_on_reload_msg; /* whether to show the message introducing the above feature */ } GeanyFilePrefs; Modified: src/documentprivate.h 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -65,6 +65,7 @@ enum { MSG_TYPE_RELOAD, MSG_TYPE_RESAVE, + MSG_TYPE_POST_RELOAD, NUM_MSG_TYPES }; Modified: src/keyfile.c 2 lines changed, 2 insertions(+), 0 deletions(-) =================================================================== @@ -235,6 +235,8 @@ static void init_pref_groups(void) "use_gio_unsafe_file_saving", TRUE); stash_group_add_boolean(group, &file_prefs.keep_edit_history_on_reload, "keep_edit_history_on_reload_125", FALSE); + stash_group_add_boolean(group, &file_prefs.show_keep_edit_history_on_reload_msg, + "show_keep_edit_history_on_reload_msg", TRUE); /* for backwards-compatibility */ stash_group_add_integer(group, &editor_prefs.indentation->hard_tab_width, "indent_hard_tab_width", 8); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Fri Oct 30 14:30:14 2015 From: git-noreply at xxxxx (=?utf-8?q?Thomas_Martitz?=) Date: Fri, 30 Oct 2015 14:30:14 -0000 Subject: [geany/geany] 9b458b: document: Revert 6f5d5db and d6d4728 Message-ID: <20151031014748.DDCA65C4344@mail.geany.org> Branch: refs/heads/master Author: Thomas Martitz Committer: Thomas Martitz Date: Fri, 30 Oct 2015 14:30:14 UTC Commit: 9b458b9fd5332570e9d94a21059180dbaa2d8fd4 https://github.com/geany/geany/commit/9b458b9fd5332570e9d94a21059180dbaa2d8fd4 Log Message: ----------- document: Revert 6f5d5db and d6d4728 Now that there is a proper user indication for the "maintain history on reload" feature we can toggle it on by default. The setting is also renamed so that the default is effective for everyone (this was the plan). Modified Paths: -------------- doc/geany.txt src/keyfile.c Modified: doc/geany.txt 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -2652,7 +2652,7 @@ use_gio_unsafe_file_saving Whether to use GIO as the unsafe file t correctly on some complex setups. gio_unsafe_save_backup Make a backup when using GIO unsafe file false immediately saving. Backup is named `filename~`. -keep_edit_history_on_reload_125 Whether to maintain the edit history when false immediately +keep_edit_history_on_reload Whether to maintain the edit history when true immediately reloading a file, and allow the operation to be reverted. **Filetype related** Modified: src/keyfile.c 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -234,7 +234,7 @@ static void init_pref_groups(void) stash_group_add_boolean(group, &file_prefs.use_gio_unsafe_file_saving, "use_gio_unsafe_file_saving", TRUE); stash_group_add_boolean(group, &file_prefs.keep_edit_history_on_reload, - "keep_edit_history_on_reload_125", FALSE); + "keep_edit_history_on_reload", TRUE); stash_group_add_boolean(group, &file_prefs.show_keep_edit_history_on_reload_msg, "show_keep_edit_history_on_reload_msg", TRUE); /* for backwards-compatibility */ -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Sat Oct 31 01:47:43 2015 From: git-noreply at xxxxx (=?utf-8?q?elextr?=) Date: Sat, 31 Oct 2015 01:47:43 -0000 Subject: [geany/geany] f50a1a: Merge pull request #672 from kugel-/reload-docmsg Message-ID: <20151031014749.B0E6E5C4344@mail.geany.org> Branch: refs/heads/master Author: elextr Committer: elextr Date: Sat, 31 Oct 2015 01:47:43 UTC Commit: f50a1a9fc4b3b83e2caf0a55f7d2400582b31ec7 https://github.com/geany/geany/commit/f50a1a9fc4b3b83e2caf0a55f7d2400582b31ec7 Log Message: ----------- Merge pull request #672 from kugel-/reload-docmsg document: show informational doc message after first reload Closes #562 Modified Paths: -------------- doc/geany.txt src/document.c src/document.h src/documentprivate.h src/keyfile.c Modified: doc/geany.txt 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -2652,7 +2652,7 @@ use_gio_unsafe_file_saving Whether to use GIO as the unsafe file t correctly on some complex setups. gio_unsafe_save_backup Make a backup when using GIO unsafe file false immediately saving. Backup is named `filename~`. -keep_edit_history_on_reload_125 Whether to maintain the edit history when false immediately +keep_edit_history_on_reload Whether to maintain the edit history when true immediately reloading a file, and allow the operation to be reverted. **Filetype related** Modified: src/document.c 34 lines changed, 34 insertions(+), 0 deletions(-) =================================================================== @@ -1575,6 +1575,24 @@ void document_open_files(const GSList *filenames, gboolean readonly, GeanyFilety } +static void on_keep_edit_history_on_reload_response(GtkWidget *bar, gint response_id, GeanyDocument *doc) +{ + if (response_id == GTK_RESPONSE_NO) + { + file_prefs.keep_edit_history_on_reload = FALSE; + document_reload_force(doc, doc->encoding); + } + else if (response_id == GTK_RESPONSE_CANCEL) + { + /* this condition cannot be reached via info bar buttons, but by our code + * to replace this bar with a higher priority one */ + file_prefs.show_keep_edit_history_on_reload_msg = TRUE; + } + doc->priv->info_bars[MSG_TYPE_POST_RELOAD] = NULL; + gtk_widget_destroy(bar); +} + + /** * Reloads the document with the specified file encoding. * @a forced_enc or @c NULL to auto-detect the file encoding. @@ -1589,6 +1607,7 @@ gboolean document_reload_force(GeanyDocument *doc, const gchar *forced_enc) { gint pos = 0; GeanyDocument *new_doc; + GtkWidget *bar; g_return_val_if_fail(doc != NULL, FALSE); @@ -1600,6 +1619,21 @@ gboolean document_reload_force(GeanyDocument *doc, const gchar *forced_enc) pos = sci_get_current_position(doc->editor->sci); new_doc = document_open_file_full(doc, NULL, pos, doc->readonly, doc->file_type, forced_enc); + if (file_prefs.keep_edit_history_on_reload && file_prefs.show_keep_edit_history_on_reload_msg) + { + bar = document_show_message(doc, GTK_MESSAGE_INFO, + on_keep_edit_history_on_reload_response, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + _("Discard history"), GTK_RESPONSE_NO, + NULL, 0, _("The buffer's previous state is stored in the history and " + "undoing restores it. You can disable this by discarding the history upon " + "reload. This message will not be displayed again but " + "Your choice can be changed in the various preferences."), + _("The file has been reloaded.")); + doc->priv->info_bars[MSG_TYPE_POST_RELOAD] = bar; + file_prefs.show_keep_edit_history_on_reload_msg = FALSE; + } + return (new_doc != NULL); } Modified: src/document.h 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -65,6 +65,7 @@ typedef struct GeanyFilePrefs gchar *extract_filetype_regex; /* regex to extract filetype on opening */ gboolean tab_close_switch_to_mru; gboolean keep_edit_history_on_reload; /* Keep undo stack upon, and allow undoing of, document reloading. */ + gboolean show_keep_edit_history_on_reload_msg; /* whether to show the message introducing the above feature */ } GeanyFilePrefs; Modified: src/documentprivate.h 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -65,6 +65,7 @@ enum { MSG_TYPE_RELOAD, MSG_TYPE_RESAVE, + MSG_TYPE_POST_RELOAD, NUM_MSG_TYPES }; Modified: src/keyfile.c 4 lines changed, 3 insertions(+), 1 deletions(-) =================================================================== @@ -234,7 +234,9 @@ static void init_pref_groups(void) stash_group_add_boolean(group, &file_prefs.use_gio_unsafe_file_saving, "use_gio_unsafe_file_saving", TRUE); stash_group_add_boolean(group, &file_prefs.keep_edit_history_on_reload, - "keep_edit_history_on_reload_125", FALSE); + "keep_edit_history_on_reload", TRUE); + stash_group_add_boolean(group, &file_prefs.show_keep_edit_history_on_reload_msg, + "show_keep_edit_history_on_reload_msg", TRUE); /* for backwards-compatibility */ stash_group_add_integer(group, &editor_prefs.indentation->hard_tab_width, "indent_hard_tab_width", 8); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 19 17:00:36 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Mon, 19 Oct 2015 17:00:36 -0000 Subject: [geany/geany] 716c78: Alter spawn to return the error message only in error->message Message-ID: <20151101180229.DF31F5C3D8B@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Dimitar Zhekov Date: Mon, 19 Oct 2015 17:00:36 UTC Commit: 716c785e34f9b016ddf200048e7642f6eb1ffab2 https://github.com/geany/geany/commit/716c785e34f9b016ddf200048e7642f6eb1ffab2 Log Message: ----------- Alter spawn to return the error message only in error->message That is, do not cite the original text, program name, or the failed OS function name (except when the testing program is compiled). Also cut glib citing of the original text on bad quoting. Modified Paths: -------------- src/spawn.c Modified: src/spawn.c 54 lines changed, 35 insertions(+), 19 deletions(-) =================================================================== @@ -74,6 +74,23 @@ # define DEFAULT_IO_LENGTH 2048 #else # define DEFAULT_IO_LENGTH 4096 + +/* helper function that cuts glib citing of the original text on bad quoting: it may be long, + and only the caller knows whether it's UTF-8. Thought we lose the ' or " failed info. */ +static gboolean spawn_parse_argv(const gchar *command_line, gint *argcp, gchar ***argvp, + GError **error) +{ + GError *gerror = NULL; + + if (g_shell_parse_argv(command_line, argcp, argvp, &gerror)) + return TRUE; + + g_set_error_literal(error, gerror->domain, gerror->code, + gerror->code == G_SHELL_ERROR_BAD_QUOTING ? + _("Text ended before matching quote was found") : gerror->message); + g_error_free(gerror); + return FALSE; +} #endif #define G_IO_FAILURE (G_IO_ERR | G_IO_HUP | G_IO_NVAL) /* always used together */ @@ -104,7 +121,7 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) if (!*command_line) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING, + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING, /* TL note: from glib */ _("Text was empty (or contained only whitespace)")); return FALSE; @@ -119,16 +136,14 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) /* Windows allows "foo.exe, but we may have extra arguments */ if ((s = strchr(command_line, '"')) == NULL) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, - /* TL note: from glib */ - _("Text ended before matching quote was found for %c." - " (The text was '%s')"), '"', command_line); + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + _("Text ended before matching quote was found")); return FALSE; } if (!strchr(" \t", s[1])) /* strchr() catches s[1] == '\0' */ { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, _("A quoted Windows program name must be entirely inside the quotes")); return FALSE; } @@ -142,7 +157,7 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) if (quote && quote < s) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, _("A quoted Windows program name must be entirely inside the quotes")); return FALSE; } @@ -165,10 +180,8 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) if (open_quote) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, - /* TL note: from glib */ - _("Text ended before matching quote was found for %c." - " (The text was '%s')"), '"', command_line); + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_BAD_QUOTING, + _("Text ended before matching quote was found")); g_free(program); return FALSE; } @@ -176,7 +189,7 @@ static gchar *spawn_get_program_name(const gchar *command_line, GError **error) int argc; char **argv; - if (!g_shell_parse_argv(command_line, &argc, &argv, error)) + if (!spawn_parse_argv(command_line, &argc, &argv, error)) return FALSE; /* empty string results in parse error, so argv[0] is not NULL */ @@ -237,8 +250,8 @@ gboolean spawn_check_command(const gchar *command_line, gboolean execute, GError if (!executable) { - g_set_error(error, G_SHELL_ERROR, G_SHELL_ERROR_FAILED, /* or SPAWN error? */ - _("Program '%s' not found"), program); + g_set_error_literal(error, G_SHELL_ERROR, G_SHELL_ERROR_FAILED, + _("Program not found")); g_free(program); return FALSE; } @@ -274,15 +287,14 @@ gboolean spawn_kill_process(GPid pid, GError **error) { gchar *message = g_win32_error_message(GetLastError()); - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, - _("TerminateProcess() failed: %s"), message); + g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, message); g_free(message); return FALSE; } #else if (kill(pid, SIGTERM)) { - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", g_strerror(errno)); + g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, g_strerror(errno)); return FALSE; } #endif @@ -379,8 +391,12 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo if (!message) message = g_win32_error_message(GetLastError()); + #ifdef SPAWN_TEST failure = g_strdup_printf("%s() failed: %s", failed, message); g_free(message); + #else + failure = message; + #endif } if (pipe_io) @@ -546,7 +562,7 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch if (failure) { - g_set_error(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, "%s", failure); + g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED, failure); g_free(failure); return FALSE; } @@ -562,7 +578,7 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch int argc = 0; char **cl_argv; - if (!g_shell_parse_argv(command_line, &cl_argc, &cl_argv, error)) + if (!spawn_parse_argv(command_line, &cl_argc, &cl_argv, error)) return FALSE; if (argv) -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Mon Oct 19 17:43:23 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Mon, 19 Oct 2015 17:43:23 -0000 Subject: [geany/geany] d60a4b: Alter spawn to cut glib citing of the program name or working dir Message-ID: <20151101180230.8C5EC5C3D8B@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Dimitar Zhekov Date: Mon, 19 Oct 2015 17:43:23 UTC Commit: d60a4b789d28dabc97801d8579e74cd1fdfee524 https://github.com/geany/geany/commit/d60a4b789d28dabc97801d8579e74cd1fdfee524 Log Message: ----------- Alter spawn to cut glib citing of the program name or working dir Mostly translate the G_SPAWN error codes back to errno, and get the curresponding g_strerror() message. Modified Paths: -------------- src/spawn.c Modified: src/spawn.c 79 lines changed, 78 insertions(+), 1 deletions(-) =================================================================== @@ -572,6 +572,7 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch int cl_argc; char **full_argv; gboolean spawned; + GError *gerror = NULL; if (command_line) { @@ -593,7 +594,83 @@ static gboolean spawn_async_with_pipes(const gchar *working_directory, const gch spawned = g_spawn_async_with_pipes(working_directory, full_argv, envp, G_SPAWN_SEARCH_PATH | (child_pid ? G_SPAWN_DO_NOT_REAP_CHILD : 0), NULL, NULL, - child_pid, stdin_fd, stdout_fd, stderr_fd, error); + child_pid, stdin_fd, stdout_fd, stderr_fd, &gerror); + + if (!spawned) + { + gint en = 0; + const gchar *message = gerror->message; + + /* try to cut glib citing of the program name or working directory: they may be long, + and only the caller knows whether they're UTF-8. We lose the exact chdir error. */ + switch (gerror->code) + { + #ifdef EACCES + case G_SPAWN_ERROR_ACCES : en = EACCES; break; + #endif + #ifdef EPERM + case G_SPAWN_ERROR_PERM : en = EPERM; break; + #endif + #ifdef E2BIG + case G_SPAWN_ERROR_TOO_BIG : en = E2BIG; break; + #endif + #ifdef ENOEXEC + case G_SPAWN_ERROR_NOEXEC : en = ENOEXEC; break; + #endif + #ifdef ENAMETOOLONG + case G_SPAWN_ERROR_NAMETOOLONG : en = ENAMETOOLONG; break; + #endif + #ifdef ENOENT + case G_SPAWN_ERROR_NOENT : en = ENOENT; break; + #endif + #ifdef ENOMEM + case G_SPAWN_ERROR_NOMEM : en = ENOMEM; break; + #endif + #ifdef ENOTDIR + case G_SPAWN_ERROR_NOTDIR : en = ENOTDIR; break; + #endif + #ifdef ELOOP + case G_SPAWN_ERROR_LOOP : en = ELOOP; break; + #endif + #ifdef ETXTBUSY + case G_SPAWN_ERROR_TXTBUSY : en = ETXTBUSY; break; + #endif + #ifdef EIO + case G_SPAWN_ERROR_IO : en = EIO; break; + #endif + #ifdef ENFILE + case G_SPAWN_ERROR_NFILE : en = ENFILE; break; + #endif + #ifdef EMFILE + case G_SPAWN_ERROR_MFILE : en = EMFILE; break; + #endif + #ifdef EINVAL + case G_SPAWN_ERROR_INVAL : en = EINVAL; break; + #endif + #ifdef EISDIR + case G_SPAWN_ERROR_ISDIR : en = EISDIR; break; + #endif + #ifdef ELIBBAD + case G_SPAWN_ERROR_LIBBAD : en = ELIBBAD; break; + #endif + case G_SPAWN_ERROR_CHDIR : + { + message = _("Failed to change to the working directory"); + break; + } + case G_SPAWN_ERROR_FAILED : + { + message = _("Unknown error executing child process"); + break; + } + } + + if (en) + message = g_strerror(en); + + g_set_error_literal(error, gerror->domain, gerror->code, message); + g_error_free(gerror); + } if (full_argv != argv) { -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 20 17:22:51 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Tue, 20 Oct 2015 17:22:51 -0000 Subject: [geany/geany] 5aa0b0: Sync spawn glib/unix and Windows messages Message-ID: <20151101180231.2F9615C3D8B@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Dimitar Zhekov Date: Tue, 20 Oct 2015 17:22:51 UTC Commit: 5aa0b02f00e84c6412be8632432726c5894e1ff3 https://github.com/geany/geany/commit/5aa0b02f00e84c6412be8632432726c5894e1ff3 Log Message: ----------- Sync spawn glib/unix and Windows messages In particular, the exec() and CreateProcess() errors are reported directly, but failures in other any functions, which are extremely rare, include some descriptive text, such as "Failed to set pipe handle to inheritable (Access denied)". The example is artificial. Modified Paths: -------------- src/spawn.c Modified: src/spawn.c 32 lines changed, 21 insertions(+), 11 deletions(-) =================================================================== @@ -334,7 +334,7 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo if (!CreatePipe(&hpipe[pindex[i][0]], &hpipe[pindex[i][1]], NULL, 0)) { hpipe[pindex[i][0]] = hpipe[pindex[i][1]] = NULL; - failed = "CreatePipe"; + failed = "create pipe"; goto leave; } @@ -344,21 +344,21 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo if ((*fd[i] = _open_osfhandle((intptr_t) hpipe[i], mode[i])) == -1) { - failed = "_open_osfhandle"; + failed = "convert pipe handle to file descriptor"; message = g_strdup(g_strerror(errno)); goto leave; } } else if (!CloseHandle(hpipe[i])) { - failed = "CloseHandle"; + failed = "close pipe"; goto leave; } if (!SetHandleInformation(hpipe[i + 3], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) { - failed = "SetHandleInformation"; + failed = "set pipe handle to inheritable"; goto leave; } } @@ -371,7 +371,7 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo if (!CreateProcess(NULL, command_line, NULL, NULL, TRUE, pipe_io ? CREATE_NO_WINDOW : 0, environment, working_directory, &startup, &process)) { - failed = "CreateProcess"; + failed = ""; /* report the message only */ /* further errors will not be reported */ } else @@ -389,14 +389,24 @@ static gchar *spawn_create_process_with_pipes(char *command_line, const char *wo if (failed) { if (!message) + { + size_t len; + message = g_win32_error_message(GetLastError()); + len = strlen(message); - #ifdef SPAWN_TEST - failure = g_strdup_printf("%s() failed: %s", failed, message); - g_free(message); - #else - failure = message; - #endif + /* unlike g_strerror(), the g_win32_error messages may include a final '.' */ + if (len > 0 && message[len - 1] == '.') + message[len - 1] = '\0'; + } + + if (*failed == '\0') + failure = message; + else + { + failure = g_strdup_printf("Failed to %s (%s)", failed, message); + g_free(message); + } } if (pipe_io) -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 20 17:30:00 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Tue, 20 Oct 2015 17:30:00 -0000 Subject: [geany/geany] e74dc4: Unify the spawn callers error messages: grep tool Message-ID: <20151101180231.D871B5C42EB@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Dimitar Zhekov Date: Tue, 20 Oct 2015 17:30:00 UTC Commit: e74dc40e1134e3bb7df9e45bc95f9e3c71507841 https://github.com/geany/geany/commit/e74dc40e1134e3bb7df9e45bc95f9e3c71507841 Log Message: ----------- Unify the spawn callers error messages: grep tool Modified Paths: -------------- src/search.c Modified: src/search.c 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -1715,8 +1715,8 @@ search_find_in_files(const gchar *utf8_search_text, const gchar *utf8_dir, const } else { - geany_debug("%s: spawn_with_callbacks() failed: %s", G_STRFUNC, error->message); - ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message); + ui_set_statusbar(TRUE, _("Cannot execute grep tool \"%s\": %s. " + "Check the path setting in Preferences."), tool_prefs.grep_cmd, error->message); g_error_free(error); } -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 20 17:31:37 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Tue, 20 Oct 2015 17:31:37 -0000 Subject: [geany/geany] 421f8a: Unify the spawn callers error messages: terminal command Message-ID: <20151101180232.820275C407F@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Dimitar Zhekov Date: Tue, 20 Oct 2015 17:31:37 UTC Commit: 421f8a0ce08178bc5d76fd7922c253994492bf1d https://github.com/geany/geany/commit/421f8a0ce08178bc5d76fd7922c253994492bf1d Log Message: ----------- Unify the spawn callers error messages: terminal command Modified Paths: -------------- src/build.c Modified: src/build.c 4 lines changed, 2 insertions(+), 2 deletions(-) =================================================================== @@ -909,8 +909,8 @@ static void build_run_cmd(GeanyDocument *doc, guint cmdindex) } else { - geany_debug("spawn_async() failed: %s", error->message); - ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message); + ui_set_statusbar(TRUE, _("Cannot execute terminal command \"%s\": %s. " + "Check the path setting in Preferences."), tool_prefs.term_cmd, error->message); g_error_free(error); g_unlink(run_cmd); run_info[cmdindex].pid = (GPid) 0; -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 20 17:33:41 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Tue, 20 Oct 2015 17:33:41 -0000 Subject: [geany/geany] 897197: Unify the spawn callers error messages: context action Message-ID: <20151101180233.1B4425C430C@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Dimitar Zhekov Date: Tue, 20 Oct 2015 17:33:41 UTC Commit: 897197ae51cb51c7f07281ef7c6e118b27f25718 https://github.com/geany/geany/commit/897197ae51cb51c7f07281ef7c6e118b27f25718 Log Message: ----------- Unify the spawn callers error messages: context action Modified Paths: -------------- src/callbacks.c Modified: src/callbacks.c 14 lines changed, 11 insertions(+), 3 deletions(-) =================================================================== @@ -1452,6 +1452,7 @@ void on_context_action1_activate(GtkMenuItem *menuitem, gpointer user_data) gchar *word, *command; GError *error = NULL; GeanyDocument *doc = document_get_current(); + const gchar *format; g_return_if_fail(doc != NULL); @@ -1469,22 +1470,29 @@ void on_context_action1_activate(GtkMenuItem *menuitem, gpointer user_data) !EMPTY(doc->file_type->context_action_cmd)) { command = g_strdup(doc->file_type->context_action_cmd); + format = _("Cannot execute context action command \"%s\": %s. " + "Check the path setting in Preferences."); } else { command = g_strdup(tool_prefs.context_action_cmd); + format = _("Cannot execute context action command \"%s\": %s. " + "Check the path setting in Filetype configuration."); } /* substitute the wildcard %s and run the command if it is non empty */ if (G_LIKELY(!EMPTY(command))) { - utils_str_replace_all(&command, "%s", word); + gchar *command_line = g_strdup(command); - if (!spawn_async(NULL, command, NULL, NULL, NULL, &error)) + utils_str_replace_all(&command_line, "%s", word); + + if (!spawn_async(NULL, command_line, NULL, NULL, NULL, &error)) { - ui_set_statusbar(TRUE, "Context action command failed: %s", error->message); + ui_set_statusbar(TRUE, format, command, error->message); g_error_free(error); } + g_free(command_line); } g_free(word); g_free(command); -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 20 17:35:12 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Tue, 20 Oct 2015 17:35:12 -0000 Subject: [geany/geany] 389c0a: Unify the spawn callers error messages: print command Message-ID: <20151101180233.AFF375C423D@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Dimitar Zhekov Date: Tue, 20 Oct 2015 17:35:12 UTC Commit: 389c0a223dc391a1a2a84891ab2411e76a92d64b https://github.com/geany/geany/commit/389c0a223dc391a1a2a84891ab2411e76a92d64b Log Message: ----------- Unify the spawn callers error messages: print command Modified Paths: -------------- src/printing.c Modified: src/printing.c 5 lines changed, 3 insertions(+), 2 deletions(-) =================================================================== @@ -612,8 +612,9 @@ static void print_external(GeanyDocument *doc) #endif { dialogs_show_msgbox(GTK_MESSAGE_ERROR, - _("Printing of \"%s\" failed (return code: %s)."), - doc->file_name, error->message); + _("Cannot execute print command \"%s\": %s. " + "Check the path setting in Preferences."), + printing_prefs.external_print_cmd, error->message); g_error_free(error); } else -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 20 17:37:23 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Tue, 20 Oct 2015 17:37:23 -0000 Subject: [geany/geany] d8318b: Unify the spawn callers error messages: template replacement command Message-ID: <20151101180234.5494F5C3D8B@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Dimitar Zhekov Date: Tue, 20 Oct 2015 17:37:23 UTC Commit: d8318b6366369b6c94eb2e709c3e5d0d9b4efd3d https://github.com/geany/geany/commit/d8318b6366369b6c94eb2e709c3e5d0d9b4efd3d Log Message: ----------- Unify the spawn callers error messages: template replacement command Modified Paths: -------------- src/templates.c Modified: src/templates.c 3 lines changed, 2 insertions(+), 1 deletions(-) =================================================================== @@ -617,7 +617,8 @@ static gchar *run_command(const gchar *command, const gchar *file_name, } else { - g_warning("templates_replace_command: %s", error->message); + g_warning(_("Cannot execute template replacement command \"%s\": %s. " + "Check the path setting in template."), command, error->message); g_error_free(error); } -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). From git-noreply at xxxxx Tue Oct 20 17:38:37 2015 From: git-noreply at xxxxx (=?utf-8?q?Dimitar_Zhekov?=) Date: Tue, 20 Oct 2015 17:38:37 -0000 Subject: [geany/geany] 748f56: Unify the spawn callers error messages: custom command Message-ID: <20151101180235.084A15C435F@mail.geany.org> Branch: refs/heads/master Author: Dimitar Zhekov Committer: Dimitar Zhekov Date: Tue, 20 Oct 2015 17:38:37 UTC Commit: 748f5689ccf58b5172d3ec80b67bbde4cc3ea1a5 https://github.com/geany/geany/commit/748f5689ccf58b5172d3ec80b67bbde4cc3ea1a5 Log Message: ----------- Unify the spawn callers error messages: custom command Modified Paths: -------------- src/tools.c Modified: src/tools.c 5 lines changed, 3 insertions(+), 2 deletions(-) =================================================================== @@ -239,8 +239,9 @@ void tools_execute_custom_command(GeanyDocument *doc, const gchar *command) } else { - geany_debug("spawn_sync() failed: %s", error->message); - ui_set_statusbar(TRUE, _("Custom command failed: %s"), error->message); + ui_set_statusbar(TRUE, _("Cannot execute custom command \"%s\": %s. " + "Check the path setting in Preferences."), command, error->message); + g_error_free(error); g_error_free(error); } -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).