From da4cb81c8f16f332a4c4c516ee9b955d10fe77a5 Mon Sep 17 00:00:00 2001 From: Yevgen Muntyan <17531749+muntyan@users.noreply.github.com> Date: Mon, 6 May 2013 23:57:17 -0700 Subject: [PATCH 01/26] Starting medit-1.1 branch at 1.1.0 tag --- .hgignore | 50 + AUTHORS | 2 + COPYING | 504 ++ COPYING.GPL | 339 + INSTALL | 24 + LICENSE | 85 + Makefile.am | 43 + NEWS | 622 ++ README | 48 + THANKS | 40 + api/Makefile.am | 72 + api/gendefs.py | 10 + api/gendocbook.py | 31 + api/gendocs.py | 22 + api/genlua.py | 23 + api/mdp/__init__.py | 1 + api/mdp/docparser.py | 691 ++ api/mdp/module.py | 631 ++ api/mdp/xmlwriter.py | 201 + api/mpi/__init__.py | 1 + api/mpi/defswriter.py | 193 + api/mpi/docbookwriter.py | 496 ++ api/mpi/luawriter.py | 547 ++ api/mpi/module.py | 553 ++ api/mpi/util.py | 12 + api/parsedocs.py | 55 + api/sourcefiles.mak | 290 + autogen.sh | 37 + build/.empty | 0 checkle.py | 22 + configure.ac | 184 + doc/Makefile.am | 153 + doc/genhelpsectionsh.py | 71 + doc/img/prefs-file-filters.png | Bin 0 -> 31939 bytes doc/img/prefs-file-selector.png | Bin 0 -> 32720 bytes doc/license.docbook | 87 + doc/man-medit.t2t.in | 99 + doc/medit-common.xsl | 8 + doc/medit-defines.ent.in | 6 + doc/medit-single.xsl | 9 + doc/medit.css | 141 + doc/medit.docbook | 19 + doc/medit.xsl | 12 + doc/prefs.docbook | 441 ++ doc/regex.docbook | 23 + doc/script-book.xsl | 34 + doc/script-lua-gtk.tmpl.docbook | 27 + doc/script-lua.tmpl.docbook | 79 + doc/script-python.tmpl.docbook | 28 + doc/script.docbook | 18 + doc/script.xsl | 26 + doc/user-tools.docbook | 406 + hgrc | 7 + m4/moo-flags.m4 | 356 + m4/moo-gtk.m4 | 96 + m4/moo-intltool.m4 | 31 + m4/moo-os.m4 | 64 + m4/moo-pygtk.m4 | 57 + m4/moo-python.m4 | 146 + moo/Makefile.am | 85 + moo/eggsmclient/Makefile.incl | 18 + moo/eggsmclient/eggdesktopfile.c | 1520 ++++ moo/eggsmclient/eggdesktopfile.h | 163 + moo/eggsmclient/eggsmclient-dbus.c | 294 + moo/eggsmclient/eggsmclient-dummy.c | 37 + moo/eggsmclient/eggsmclient-mangle.h | 31 + moo/eggsmclient/eggsmclient-osx.c | 235 + moo/eggsmclient/eggsmclient-private.h | 62 + moo/eggsmclient/eggsmclient-win32.c | 351 + moo/eggsmclient/eggsmclient-xsmp.c | 1389 ++++ moo/eggsmclient/eggsmclient.c | 604 ++ moo/eggsmclient/eggsmclient.h | 118 + moo/gtksourceview/Makefile.incl | 32 + moo/gtksourceview/gtksourcebuffer.h | 1 + moo/gtksourceview/gtksourcecontextengine.c | 6797 +++++++++++++++++ moo/gtksourceview/gtksourcecontextengine.h | 124 + moo/gtksourceview/gtksourceengine.c | 106 + moo/gtksourceview/gtksourceengine.h | 87 + moo/gtksourceview/gtksourceiter.c | 801 ++ moo/gtksourceview/gtksourceiter.h | 53 + .../gtksourcelanguage-parser-1.c | 793 ++ .../gtksourcelanguage-parser-2.c | 1770 +++++ moo/gtksourceview/gtksourcelanguage-private.h | 94 + moo/gtksourceview/gtksourcelanguage.c | 906 +++ moo/gtksourceview/gtksourcelanguage.h | 83 + moo/gtksourceview/gtksourcelanguagemanager.c | 409 + moo/gtksourceview/gtksourcelanguagemanager.h | 81 + moo/gtksourceview/gtksourcestyle-private.h | 65 + moo/gtksourceview/gtksourcestyle.c | 447 ++ moo/gtksourceview/gtksourcestyle.h | 41 + moo/gtksourceview/gtksourcestylescheme.c | 1208 +++ moo/gtksourceview/gtksourcestylescheme.h | 88 + .../gtksourcestyleschememanager.c | 577 ++ .../gtksourcestyleschememanager.h | 90 + moo/gtksourceview/gtksourceview-api.h | 10 + moo/gtksourceview/gtksourceview-i18n.h | 20 + moo/gtksourceview/gtksourceview-marshal.h | 6 + moo/gtksourceview/gtksourceview-utils.c | 107 + moo/gtksourceview/gtksourceview-utils.h | 36 + moo/gtksourceview/gtksourceview.h | 1 + moo/gtksourceview/gtktextregion.c | 647 ++ moo/gtksourceview/gtktextregion.h | 87 + moo/marshals.list | 44 + moo/medit-app/Makefile.incl | 96 + moo/medit-app/data/encodings/ASCII.1 | 0 moo/medit-app/data/encodings/ASCII.2 | 6 + moo/medit-app/data/encodings/GB2312 | 1 + moo/medit-app/data/encodings/KOI8-R.1 | 3 + moo/medit-app/data/encodings/UCS-4.1 | 0 moo/medit-app/data/encodings/UCS-4.2 | Bin 0 -> 492 bytes moo/medit-app/data/encodings/UCS-4.3 | Bin 0 -> 188 bytes moo/medit-app/data/encodings/UCS-4.4 | Bin 0 -> 324 bytes moo/medit-app/data/encodings/UTF-16.1 | 0 moo/medit-app/data/encodings/UTF-16.2 | Bin 0 -> 248 bytes moo/medit-app/data/encodings/UTF-16.3 | Bin 0 -> 96 bytes moo/medit-app/data/encodings/UTF-16.4 | Bin 0 -> 166 bytes moo/medit-app/data/encodings/UTF-8.1 | 0 moo/medit-app/data/encodings/UTF-8.2 | 6 + moo/medit-app/data/encodings/UTF-8.3 | 4 + moo/medit-app/data/encodings/UTF-8.4 | 1 + moo/medit-app/data/encodings/UTF-8.5 | 10 + moo/medit-app/data/encodings/Windows-1251.1 | 10 + moo/medit-app/data/test-lua/lua/munit.lua | 70 + moo/medit-app/data/test-lua/testbasic.lua | 6 + moo/medit-app/data/test-lua/testedit.lua | 122 + moo/medit-app/data/test-lua/testeditor.lua | 119 + moo/medit-app/data/test-lua/testfileinfo.lua | 56 + moo/medit-app/data/test-lua/testgfile.lua | 93 + moo/medit-app/data/test-lua/testlineends.lua | 118 + moo/medit-app/data/test-lua/testmedit.lua | 69 + moo/medit-app/data/test-lua/testmoo.lua | 168 + moo/medit-app/data/test-lua/testmooeditor.lua | 353 + moo/medit-app/data/test-lua/testobject.lua | 7 + moo/medit-app/data/test-lua/testprefs.lua | 69 + moo/medit-app/data/test-lua/testtext.lua | 186 + moo/medit-app/data/test-lua/testunicode.lua | 216 + moo/medit-app/data/test-lua/testustring.lua | 34 + moo/medit-app/data/test-lua/testwindow.lua | 89 + moo/medit-app/data/test-python/munit.py | 14 + moo/medit-app/data/test-python/testmoo.py | 20 + moo/medit-app/main.c | 732 ++ moo/medit-app/medit-project.desktop.in | 11 + moo/medit-app/medit.desktop.in | 10 + moo/medit-app/medit.rc.in | 32 + moo/medit-app/mem-debug.h | 97 + moo/medit-app/parse.h | 240 + moo/medit-app/print-functions.py | 33 + moo/medit-app/run-tests-installed.sh | 9 + moo/medit-app/run-tests-uninstalled.sh | 4 + moo/medit-app/run-tests.h | 60 + moo/medit-app/run-tests.sh | 92 + moo/medit-module/Makefile.incl | 36 + moo/medit-module/medit-module.cpp | 7 + moo/medit-module/moo.rc.in | 30 + moo/moo-config.h | 63 + moo/mooapp/Makefile.incl | 26 + moo/mooapp/glade/mooappabout-credits.glade | 137 + moo/mooapp/glade/mooappabout-dialog.glade | 176 + moo/mooapp/glade/mooappabout-license.glade | 57 + moo/mooapp/mooapp-accels.h | 33 + moo/mooapp/mooapp-info.h | 26 + moo/mooapp/mooapp-private.h | 43 + moo/mooapp/mooapp.c | 1602 ++++ moo/mooapp/mooapp.h | 100 + moo/mooapp/mooappabout.c | 270 + moo/mooapp/mooappabout.h | 129 + moo/mooapp/moohtml.c | 2417 ++++++ moo/mooapp/moohtml.h | 96 + moo/mooapp/moolinklabel.c | 391 + moo/mooapp/moolinklabel.h | 61 + moo/mooedit/Makefile.incl | 159 + moo/mooedit/glade/mooeditprefs-file.glade | 323 + moo/mooedit/glade/mooeditprefs-filters.glade | 167 + moo/mooedit/glade/mooeditprefs-general.glade | 310 + moo/mooedit/glade/mooeditprefs-langs.glade | 169 + moo/mooedit/glade/mooeditprefs-view.glade | 344 + moo/mooedit/glade/mooeditprogress.glade | 46 + moo/mooedit/glade/mooeditsavemult.glade | 114 + moo/mooedit/glade/moopluginprefs.glade | 161 + moo/mooedit/glade/mooprint.glade | 545 ++ moo/mooedit/glade/mooquicksearch.glade | 116 + moo/mooedit/glade/moostatusbar.glade | 55 + moo/mooedit/glade/mootextfind-prompt.glade | 85 + moo/mooedit/glade/mootextfind.glade | 357 + moo/mooedit/glade/mootextgotoline.glade | 101 + moo/mooedit/glade/mootryencoding.glade | 99 + moo/mooedit/langs/CMakeLists.txt | 88 + moo/mooedit/langs/Makefile.incl | 109 + moo/mooedit/langs/R.lang | 129 + moo/mooedit/langs/ada.lang | 221 + moo/mooedit/langs/asp.lang | 1097 +++ moo/mooedit/langs/awk.lang | 138 + moo/mooedit/langs/bennugd.lang | 1 + moo/mooedit/langs/bibtex.lang | 93 + moo/mooedit/langs/boo.lang | 257 + moo/mooedit/langs/c.lang | 304 + moo/mooedit/langs/cg.lang | 232 + moo/mooedit/langs/changelog.lang | 92 + moo/mooedit/langs/chdr.lang | 43 + moo/mooedit/langs/check.sh | 41 + moo/mooedit/langs/classic.xml | 99 + moo/mooedit/langs/cmake.lang | 408 + moo/mooedit/langs/cobalt.xml | 134 + moo/mooedit/langs/cobol.lang | 456 ++ moo/mooedit/langs/convert.py | 517 ++ moo/mooedit/langs/convert.sh | 17 + moo/mooedit/langs/cpp.lang | 114 + moo/mooedit/langs/csharp.lang | 281 + moo/mooedit/langs/css.lang | 517 ++ moo/mooedit/langs/cuda.lang | 291 + moo/mooedit/langs/d.lang | 296 + moo/mooedit/langs/def.lang | 269 + moo/mooedit/langs/desktop.lang | 300 + moo/mooedit/langs/diff.lang | 85 + moo/mooedit/langs/docbook.lang | 476 ++ moo/mooedit/langs/dosbatch.lang | 167 + moo/mooedit/langs/dot.lang | 140 + moo/mooedit/langs/dpatch.lang | 48 + moo/mooedit/langs/dtd.lang | 140 + moo/mooedit/langs/eiffel.lang | 239 + moo/mooedit/langs/erlang.lang | 249 + moo/mooedit/langs/fcl.lang | 115 + moo/mooedit/langs/forth.lang | 235 + moo/mooedit/langs/fortran.lang | 512 ++ moo/mooedit/langs/fsharp.lang | 324 + moo/mooedit/langs/gap.lang | 85 + moo/mooedit/langs/gdb-log.lang | 152 + moo/mooedit/langs/glsl.lang | 483 ++ moo/mooedit/langs/go.lang | 215 + moo/mooedit/langs/gtk-doc.lang | 91 + moo/mooedit/langs/gtkrc.lang | 116 + moo/mooedit/langs/haddock.lang | 162 + moo/mooedit/langs/haskell-literate.lang | 59 + moo/mooedit/langs/haskell.lang | 228 + moo/mooedit/langs/html.lang | 161 + moo/mooedit/langs/idl.lang | 154 + moo/mooedit/langs/ini.lang | 95 + moo/mooedit/langs/java.lang | 209 + moo/mooedit/langs/javascript.lang | 335 + moo/mooedit/langs/kate.xml | 156 + moo/mooedit/langs/language2.rng | 491 ++ moo/mooedit/langs/latex.lang | 299 + moo/mooedit/langs/libtool.lang | 56 + moo/mooedit/langs/lua.lang | 157 + moo/mooedit/langs/m4.lang | 804 ++ moo/mooedit/langs/makefile.lang | 196 + moo/mooedit/langs/mallard.lang | 124 + moo/mooedit/langs/medit.xml | 155 + moo/mooedit/langs/msil.lang | 490 ++ moo/mooedit/langs/nemerle.lang | 263 + moo/mooedit/langs/nsis.lang | 88 + moo/mooedit/langs/objc.lang | 120 + moo/mooedit/langs/oblivion.xml | 112 + moo/mooedit/langs/ocaml.lang | 302 + moo/mooedit/langs/ocl.lang | 139 + moo/mooedit/langs/octave.lang | 239 + moo/mooedit/langs/ooc.lang | 265 + moo/mooedit/langs/opal.lang | 343 + moo/mooedit/langs/pascal.lang | 266 + moo/mooedit/langs/perl.lang | 1019 +++ moo/mooedit/langs/php.lang | 331 + moo/mooedit/langs/pkgconfig.lang | 62 + moo/mooedit/langs/po.lang | 95 + moo/mooedit/langs/prolog.lang | 249 + moo/mooedit/langs/protobuf.lang | 104 + moo/mooedit/langs/python-console.lang | 20 + moo/mooedit/langs/python.lang | 391 + moo/mooedit/langs/rpmspec.lang | 205 + moo/mooedit/langs/ruby.lang | 590 ++ moo/mooedit/langs/scheme.lang | 302 + moo/mooedit/langs/scilab.lang | 209 + moo/mooedit/langs/sh.lang | 500 ++ moo/mooedit/langs/sparql.lang | 448 ++ moo/mooedit/langs/sql.lang | 618 ++ moo/mooedit/langs/styles.rng | 138 + moo/mooedit/langs/systemverilog.lang | 263 + moo/mooedit/langs/t2t.lang | 150 + moo/mooedit/langs/tango.xml | 116 + moo/mooedit/langs/tcl.lang | 146 + moo/mooedit/langs/testfiles.sh | 736 ++ moo/mooedit/langs/testv1.lang | 57 + moo/mooedit/langs/texinfo.lang | 448 ++ moo/mooedit/langs/vala.lang | 227 + moo/mooedit/langs/vbnet.lang | 245 + moo/mooedit/langs/verilog.lang | 464 ++ moo/mooedit/langs/vhdl.lang | 248 + moo/mooedit/langs/xml.lang | 220 + moo/mooedit/langs/xslt.lang | 108 + moo/mooedit/langs/yacc.lang | 173 + moo/mooedit/medit.xml | 180 + moo/mooedit/mooedit-accels.h | 79 + moo/mooedit/mooedit-enum-types.c | 279 + moo/mooedit/mooedit-enum-types.c.tmpl | 40 + moo/mooedit/mooedit-enum-types.h | 64 + moo/mooedit/mooedit-enum-types.h.tmpl | 22 + moo/mooedit/mooedit-enums.h | 137 + moo/mooedit/mooedit-fileops.c | 1349 ++++ moo/mooedit/mooedit-fileops.h | 72 + moo/mooedit/mooedit-impl.h | 128 + moo/mooedit/mooedit-misc.c | 3 + moo/mooedit/mooedit-private.h | 75 + moo/mooedit/mooedit-script.c | 841 ++ moo/mooedit/mooedit-script.h | 90 + moo/mooedit/mooedit.c | 1868 +++++ moo/mooedit/mooedit.h | 128 + moo/mooedit/mooedit.xml | 22 + moo/mooedit/mooeditaction-factory.c | 799 ++ moo/mooedit/mooeditaction-factory.h | 43 + moo/mooedit/mooeditaction.c | 461 ++ moo/mooedit/mooeditaction.h | 63 + moo/mooedit/mooeditbookmark.c | 513 ++ moo/mooedit/mooeditbookmark.h | 77 + moo/mooedit/mooeditconfig.c | 925 +++ moo/mooedit/mooeditconfig.h | 98 + moo/mooedit/mooeditdialogs.c | 767 ++ moo/mooedit/mooeditdialogs.h | 68 + moo/mooedit/mooeditfileinfo-impl.h | 49 + moo/mooedit/mooeditfileinfo.c | 481 ++ moo/mooedit/mooeditfileinfo.h | 104 + moo/mooedit/mooeditfiltersettings.c | 620 ++ moo/mooedit/mooeditfiltersettings.h | 55 + moo/mooedit/mooedithistoryitem.c | 50 + moo/mooedit/mooedithistoryitem.h | 17 + moo/mooedit/mooeditor-impl.h | 28 + moo/mooedit/mooeditor-private.h | 50 + moo/mooedit/mooeditor-tests.c | 297 + moo/mooedit/mooeditor-tests.h | 28 + moo/mooedit/mooeditor.c | 3050 ++++++++ moo/mooedit/mooeditor.h | 186 + moo/mooedit/mooeditprefs.c | 290 + moo/mooedit/mooeditprefs.h | 92 + moo/mooedit/mooeditprefspage.c | 1104 +++ moo/mooedit/mooeditprogress.c | 161 + moo/mooedit/mooeditprogress.h | 24 + moo/mooedit/mooedittab-impl.h | 27 + moo/mooedit/mooedittab.c | 456 ++ moo/mooedit/mooedittab.h | 15 + moo/mooedit/mooedittypes.h | 41 + moo/mooedit/mooeditview-impl.h | 28 + moo/mooedit/mooeditview-priv.h | 18 + moo/mooedit/mooeditview-script.c | 2 + moo/mooedit/mooeditview-script.h | 12 + moo/mooedit/mooeditview.c | 479 ++ moo/mooedit/mooeditview.h | 54 + moo/mooedit/mooeditwindow-impl.h | 24 + moo/mooedit/mooeditwindow.c | 4798 ++++++++++++ moo/mooedit/mooeditwindow.h | 138 + moo/mooedit/moofold.c | 815 ++ moo/mooedit/moofold.h | 90 + moo/mooedit/mooindenter.c | 490 ++ moo/mooedit/mooindenter.h | 96 + moo/mooedit/moolang-private.h | 52 + moo/mooedit/moolang.c | 175 + moo/mooedit/moolang.h | 57 + moo/mooedit/moolangmgr-private.h | 61 + moo/mooedit/moolangmgr.c | 1105 +++ moo/mooedit/moolangmgr.h | 82 + moo/mooedit/moolinebuffer.c | 344 + moo/mooedit/moolinebuffer.h | 73 + moo/mooedit/moolinemark.c | 735 ++ moo/mooedit/moolinemark.h | 94 + moo/mooedit/mooplugin-loader.c | 485 ++ moo/mooedit/mooplugin-loader.h | 55 + moo/mooedit/mooplugin-macro.h | 223 + moo/mooedit/mooplugin.c | 1803 +++++ moo/mooedit/mooplugin.h | 305 + moo/mooedit/mootext-private.h | 67 + moo/mooedit/mootextbtree.c | 613 ++ moo/mooedit/mootextbtree.h | 96 + moo/mooedit/mootextbuffer.c | 2170 ++++++ moo/mooedit/mootextbuffer.h | 127 + moo/mooedit/mootextfind.c | 1381 ++++ moo/mooedit/mootextfind.h | 79 + moo/mooedit/mootextiter.h | 53 + moo/mooedit/mootextprint-private.h | 79 + moo/mooedit/mootextprint.c | 2114 +++++ moo/mooedit/mootextprint.h | 64 + moo/mooedit/mootextsearch-private.h | 99 + moo/mooedit/mootextsearch.c | 936 +++ moo/mooedit/mootextsearch.h | 47 + moo/mooedit/mootextstylescheme.c | 152 + moo/mooedit/mootextstylescheme.h | 58 + moo/mooedit/mootextview-input.c | 1622 ++++ moo/mooedit/mootextview-private.h | 212 + moo/mooedit/mootextview.c | 4115 ++++++++++ moo/mooedit/mootextview.h | 164 + moo/moofileview/Makefile.incl | 51 + .../glade/moobookmark-editor.glade | 275 + moo/moofileview/glade/moocreatefolder.glade | 85 + moo/moofileview/glade/moofileprops.glade | 134 + moo/moofileview/glade/moofileview-drop.glade | 87 + moo/moofileview/moobookmarkmgr.c | 1721 +++++ moo/moofileview/moobookmarkmgr.h | 97 + moo/moofileview/moobookmarkview.c | 312 + moo/moofileview/moobookmarkview.h | 67 + moo/moofileview/moofile-private.h | 100 + moo/moofileview/moofile.c | 654 ++ moo/moofileview/moofile.h | 68 + moo/moofileview/moofileentry.c | 1710 +++++ moo/moofileview/moofileentry.h | 89 + moo/moofileview/moofilesystem.c | 1206 +++ moo/moofileview/moofilesystem.h | 152 + moo/moofileview/moofileview-accels.h | 66 + moo/moofileview/moofileview-aux.h | 216 + moo/moofileview/moofileview-dialogs.c | 486 ++ moo/moofileview/moofileview-dialogs.h | 73 + moo/moofileview/moofileview-impl.h | 86 + moo/moofileview/moofileview-private.h | 42 + moo/moofileview/moofileview-tools.c | 441 ++ moo/moofileview/moofileview-tools.h | 30 + moo/moofileview/moofileview.c | 6268 +++++++++++++++ moo/moofileview/moofileview.h | 52 + moo/moofileview/moofileview.xml | 49 + moo/moofileview/moofolder-private.h | 114 + moo/moofileview/moofolder.c | 1237 +++ moo/moofileview/moofolder.h | 57 + moo/moofileview/moofoldermodel-private.h | 608 ++ moo/moofileview/moofoldermodel.c | 961 +++ moo/moofileview/moofoldermodel.h | 127 + moo/moofileview/mooiconview.c | 4007 ++++++++++ moo/moofileview/mooiconview.h | 154 + moo/moofileview/mootreeview.c | 733 ++ moo/moofileview/mootreeview.h | 146 + moo/moolua/Makefile.incl | 68 + moo/moolua/gtk-api.c | 481 ++ moo/moolua/gtk-api.h | 218 + moo/moolua/gtk-lua-api.h | 15 + moo/moolua/lua-default-init.lua | 15 + moo/moolua/lua-module-init.lua | 0 moo/moolua/lua-plugin-init.lua | 0 moo/moolua/lua/COPYRIGHT | 34 + moo/moolua/lua/Makefile.incl | 87 + moo/moolua/lua/README | 4 + moo/moolua/lua/README.lfs | 14 + moo/moolua/lua/_moo/_string.lua | 20 + moo/moolua/lua/_moo/_table.lua | 38 + moo/moolua/lua/_moo/_util.lua | 20 + moo/moolua/lua/_moo/builtin.lua | 157 + moo/moolua/lua/_moo/os.lua | 40 + moo/moolua/lua/_moo/path.lua | 34 + moo/moolua/lua/lapi.c | 1085 +++ moo/moolua/lua/lapi.h | 16 + moo/moolua/lua/lauxlib.c | 652 ++ moo/moolua/lua/lauxlib.h | 174 + moo/moolua/lua/lbaselib.c | 652 ++ moo/moolua/lua/lcode.c | 839 ++ moo/moolua/lua/lcode.h | 76 + moo/moolua/lua/ldblib.c | 397 + moo/moolua/lua/ldebug.c | 622 ++ moo/moolua/lua/ldebug.h | 33 + moo/moolua/lua/ldo.c | 527 ++ moo/moolua/lua/ldo.h | 59 + moo/moolua/lua/ldump.c | 164 + moo/moolua/lua/lfs.cpp | 586 ++ moo/moolua/lua/lfs.h | 16 + moo/moolua/lua/lfunc.c | 174 + moo/moolua/lua/lfunc.h | 34 + moo/moolua/lua/lgc.c | 711 ++ moo/moolua/lua/lgc.h | 110 + moo/moolua/lua/linit.c | 43 + moo/moolua/lua/liolib.c | 553 ++ moo/moolua/lua/llex.c | 461 ++ moo/moolua/lua/llex.h | 81 + moo/moolua/lua/llimits.h | 128 + moo/moolua/lua/lmathlib.c | 263 + moo/moolua/lua/lmem.c | 86 + moo/moolua/lua/lmem.h | 49 + moo/moolua/lua/loadlib.c | 664 ++ moo/moolua/lua/lobject.c | 214 + moo/moolua/lua/lobject.h | 381 + moo/moolua/lua/lopcodes.c | 102 + moo/moolua/lua/lopcodes.h | 268 + moo/moolua/lua/loslib.c | 243 + moo/moolua/lua/lparser.c | 1339 ++++ moo/moolua/lua/lparser.h | 82 + moo/moolua/lua/lstate.c | 214 + moo/moolua/lua/lstate.h | 169 + moo/moolua/lua/lstring.c | 111 + moo/moolua/lua/lstring.h | 31 + moo/moolua/lua/ltable.c | 588 ++ moo/moolua/lua/ltable.h | 40 + moo/moolua/lua/ltablib.c | 279 + moo/moolua/lua/ltm.c | 75 + moo/moolua/lua/ltm.h | 54 + moo/moolua/lua/lua.h | 388 + moo/moolua/lua/luaall.cpp | 36 + moo/moolua/lua/luaconf.h | 798 ++ moo/moolua/lua/lualib.h | 53 + moo/moolua/lua/lundump.c | 226 + moo/moolua/lua/lundump.h | 36 + moo/moolua/lua/lvm.c | 763 ++ moo/moolua/lua/lvm.h | 36 + moo/moolua/lua/lzio.c | 82 + moo/moolua/lua/lzio.h | 67 + moo/moolua/lua/moolua.cpp | 214 + moo/moolua/lua/moolua.h | 50 + moo/moolua/lua/slnudata.c | 946 +++ moo/moolua/lua/slnunico.c | 1366 ++++ moo/moolua/medit-lua.cpp | 356 + moo/moolua/medit-lua.h | 51 + moo/moolua/moo-lua-api-util.cpp | 1885 +++++ moo/moolua/moo-lua-api-util.h | 250 + moo/moolua/moo-lua-api.h | 48 + moo/moolua/moolua-tests.cpp | 142 + moo/moolua/moolua-tests.h | 27 + moo/moolua/mooluaplugin.cpp | 130 + moo/moopython/Makefile.incl | 49 + moo/moopython/codegen/README | 4 + moo/moopython/codegen/__init__.py | 15 + moo/moopython/codegen/argtypes.py | 1031 +++ moo/moopython/codegen/argtypes_m.py | 150 + moo/moopython/codegen/codegen.py | 1776 +++++ moo/moopython/codegen/definitions.py | 551 ++ moo/moopython/codegen/defsparser.py | 153 + moo/moopython/codegen/docgen.py | 751 ++ moo/moopython/codegen/mergedefs.py | 26 + moo/moopython/codegen/mkskel.py | 89 + moo/moopython/codegen/override.py | 281 + moo/moopython/codegen/reversewrapper.py | 867 +++ moo/moopython/codegen/scmexpr.py | 143 + moo/moopython/medit-python-init.py | 3 + moo/moopython/medit-python.c | 266 + moo/moopython/medit-python.h | 27 + moo/moopython/moopython-api.h | 544 ++ moo/moopython/moopython-builtin.c | 91 + moo/moopython/moopython-builtin.h | 29 + moo/moopython/moopython-loader.c | 213 + moo/moopython/moopython-loader.h | 32 + moo/moopython/moopython-pygtkmod.h | 123 + moo/moopython/moopython-tests.c | 52 + moo/moopython/moopython-tests.h | 12 + moo/moopython/moopython-utils.c | 641 ++ moo/moopython/moopython-utils.h | 100 + moo/moopython/plugins/Makefile.incl | 46 + moo/moopython/plugins/autosave.ini | 11 + moo/moopython/plugins/autosave.py | 56 + .../plugins/lib/insert_date_and_time.py | 104 + moo/moopython/plugins/lib/pyconsole.py | 674 ++ moo/moopython/plugins/medit/__init__.py | 14 + moo/moopython/plugins/medit/runpython.py | 85 + moo/moopython/plugins/pyproject/Makefile.ug | 85 + .../plugins/pyproject/medit-mprj.xml.in | 8 + .../plugins/pyproject/mprj/__init__.py | 18 + .../plugins/pyproject/mprj/config/__init__.py | 21 + .../plugins/pyproject/mprj/config/_config.py | 71 + .../plugins/pyproject/mprj/config/_dict.py | 198 + .../plugins/pyproject/mprj/config/_group.py | 168 + .../plugins/pyproject/mprj/config/_item.py | 207 + .../plugins/pyproject/mprj/config/_setting.py | 136 + .../plugins/pyproject/mprj/config/_utils.py | 26 + .../plugins/pyproject/mprj/config/_xml.py | 234 + .../plugins/pyproject/mprj/config/test.py | 322 + .../plugins/pyproject/mprj/config/view.py | 456 ++ .../plugins/pyproject/mprj/factory.glade | 252 + .../plugins/pyproject/mprj/factory.py | 180 + .../plugins/pyproject/mprj/manager.py | 338 + .../plugins/pyproject/mprj/optdialog.py | 89 + .../plugins/pyproject/mprj/project.py | 102 + .../plugins/pyproject/mprj/session.py | 96 + .../plugins/pyproject/mprj/settings.py | 110 + .../plugins/pyproject/mprj/simple.glade | 169 + .../plugins/pyproject/mprj/simple.py | 196 + moo/moopython/plugins/pyproject/mprj/test.py | 15 + .../plugins/pyproject/mprj/utils.glade | 87 + moo/moopython/plugins/pyproject/mprj/utils.py | 213 + .../pyproject/project-plugin.ini.in.in | 13 + .../plugins/pyproject/project-plugin.py | 43 + moo/moopython/plugins/pyproject/projects/c.py | 291 + .../pyproject/projects/cproj/__init__.py | 15 + .../pyproject/projects/cproj/config.py | 342 + .../pyproject/projects/cproj/optdialog.py | 240 + .../pyproject/projects/cproj/options.glade | 553 ++ .../pyproject/projects/cproj/parser.py | 49 + .../pyproject/projects/pyproj/__init__.py | 15 + .../pyproject/projects/pyproj/config.py | 65 + .../pyproject/projects/pyproj/optdialog.py | 77 + .../pyproject/projects/pyproj/options.glade | 139 + .../plugins/pyproject/projects/python.py | 151 + moo/moopython/plugins/pytest.py | 67 + moo/moopython/plugins/python.ini.in.in | 11 + moo/moopython/plugins/python.py | 121 + moo/moopython/plugins/simple.ini | 14 + moo/moopython/plugins/simple.py | 57 + moo/moopython/plugins/terminal.ini.in.in | 11 + moo/moopython/plugins/terminal.py | 263 + moo/moopython/pygtk/Makefile.incl | 66 + moo/moopython/pygtk/codeafter.c | 0 moo/moopython/pygtk/codebefore.c | 3 + moo/moopython/pygtk/moo-pygtk.c | 134 + moo/moopython/pygtk/moo-pygtk.h | 34 + moo/moopython/pygtk/moo.override | 122 + moo/moopython/pygtk/moo.py | 74 + moo/moopython/pygtk/mooedit.override | 630 ++ moo/moopython/pygtk/moopaned.override | 46 + moo/moopython/pygtk/mooutils.override | 579 ++ moo/moopython/pymoo.ini.in | 4 + moo/mooutils/Makefile.incl | 213 + moo/mooutils/glade/mooaccelbutton.glade | 148 + moo/mooutils/glade/mooaccelprefs.glade | 185 + moo/mooutils/glade/moologwindow.glade | 23 + moo/mooutils/moo-environ.h | 28 + moo/mooutils/moo-intltool-merge | 47 + moo/mooutils/moo-intltool-merge.cmake | 38 + moo/mooutils/moo-mime.c | 166 + moo/mooutils/moo-mime.h | 44 + moo/mooutils/moo-test-macros.h | 339 + moo/mooutils/moo-test-utils.c | 632 ++ moo/mooutils/moo-test-utils.h | 93 + moo/mooutils/mooaccel.c | 992 +++ moo/mooutils/mooaccel.h | 91 + moo/mooutils/mooaccelbutton.c | 360 + moo/mooutils/mooaccelbutton.h | 59 + moo/mooutils/mooaccelprefs.c | 847 ++ moo/mooutils/mooaccelprefs.h | 30 + moo/mooutils/mooaction-private.h | 54 + moo/mooutils/mooaction.c | 775 ++ moo/mooutils/mooaction.h | 94 + moo/mooutils/mooactionbase-private.h | 78 + moo/mooutils/mooactionbase.c | 655 ++ moo/mooutils/mooactionbase.h | 47 + moo/mooutils/mooactioncollection.c | 317 + moo/mooutils/mooactioncollection.h | 73 + moo/mooutils/mooactionfactory.c | 388 + moo/mooutils/mooactionfactory.h | 78 + moo/mooutils/mooactiongroup.c | 226 + moo/mooutils/mooactiongroup.h | 62 + moo/mooutils/mooapp-ipc.c | 416 + moo/mooutils/mooapp-ipc.h | 45 + moo/mooutils/mooappinput.c | 1186 +++ moo/mooutils/mooappinput.h | 47 + moo/mooutils/mooarray.h | 233 + moo/mooutils/mooatom.h | 51 + moo/mooutils/moobigpaned.c | 1869 +++++ moo/mooutils/moobigpaned.h | 104 + moo/mooutils/mooclosure.c | 362 + moo/mooutils/mooclosure.h | 83 + moo/mooutils/moocombo.c | 1508 ++++ moo/mooutils/moocombo.h | 123 + moo/mooutils/moocompat.h | 143 + moo/mooutils/moodialogs.c | 600 ++ moo/mooutils/moodialogs.h | 75 + moo/mooutils/mooeditops.c | 452 ++ moo/mooutils/mooeditops.h | 95 + moo/mooutils/mooencodings-data.h | 238 + moo/mooutils/mooencodings.c | 1260 +++ moo/mooutils/mooencodings.h | 65 + moo/mooutils/mooentry.c | 978 +++ moo/mooutils/mooentry.h | 67 + moo/mooutils/moofiledialog.c | 887 +++ moo/mooutils/moofiledialog.h | 113 + moo/mooutils/moofileicon.c | 806 ++ moo/mooutils/moofileicon.h | 70 + moo/mooutils/moofilewatch.c | 1491 ++++ moo/mooutils/moofilewatch.h | 79 + moo/mooutils/moofilewriter-private.h | 85 + moo/mooutils/moofilewriter.c | 730 ++ moo/mooutils/moofilewriter.h | 75 + moo/mooutils/moofiltermgr.c | 1150 +++ moo/mooutils/moofiltermgr.h | 84 + moo/mooutils/moofontsel.c | 2383 ++++++ moo/mooutils/moofontsel.h | 259 + moo/mooutils/mooglade.c | 2932 +++++++ moo/mooutils/mooglade.h | 126 + moo/mooutils/moohelp.c | 269 + moo/mooutils/moohelp.h | 47 + moo/mooutils/moohistorycombo.c | 650 ++ moo/mooutils/moohistorycombo.h | 68 + moo/mooutils/moohistorylist.c | 989 +++ moo/mooutils/moohistorylist.h | 135 + moo/mooutils/moohistorymgr.c | 1916 +++++ moo/mooutils/moohistorymgr.h | 99 + moo/mooutils/mooi18n.c | 197 + moo/mooutils/mooi18n.h | 81 + moo/mooutils/moolist.h | 269 + moo/mooutils/moomarkup.c | 1342 ++++ moo/mooutils/moomarkup.h | 216 + moo/mooutils/moomenu.c | 100 + moo/mooutils/moomenu.h | 50 + moo/mooutils/moomenuaction.c | 290 + moo/mooutils/moomenuaction.h | 71 + moo/mooutils/moomenumgr.c | 997 +++ moo/mooutils/moomenumgr.h | 115 + moo/mooutils/moomenutoolbutton.c | 173 + moo/mooutils/moomenutoolbutton.h | 55 + moo/mooutils/moonotebook.c | 3690 +++++++++ moo/mooutils/moonotebook.h | 119 + moo/mooutils/mooonce.h | 18 + moo/mooutils/moopane.c | 1861 +++++ moo/mooutils/moopane.h | 154 + moo/mooutils/moopaned.c | 3481 +++++++++ moo/mooutils/moopaned.h | 143 + moo/mooutils/mooprefs.c | 1220 +++ moo/mooutils/mooprefs.h | 94 + moo/mooutils/mooprefsdialog.c | 560 ++ moo/mooutils/mooprefsdialog.h | 74 + moo/mooutils/mooprefspage.c | 489 ++ moo/mooutils/mooprefspage.h | 77 + moo/mooutils/moospawn.c | 785 ++ moo/mooutils/moospawn.h | 97 + moo/mooutils/moostat.h | 52 + moo/mooutils/moostock.c | 232 + moo/mooutils/moostock.h | 71 + moo/mooutils/mootype-macros.h | 132 + moo/mooutils/mootypedecl-macros.h | 81 + moo/mooutils/moouixml.c | 2689 +++++++ moo/mooutils/moouixml.h | 187 + moo/mooutils/mooundo.c | 604 ++ moo/mooutils/mooundo.h | 106 + moo/mooutils/mooutils-debug.h | 113 + moo/mooutils/mooutils-enums.c | 142 + moo/mooutils/mooutils-enums.h | 41 + moo/mooutils/mooutils-enums.tmpl.c | 40 + moo/mooutils/mooutils-enums.tmpl.h | 23 + moo/mooutils/mooutils-file.c | 32 + moo/mooutils/mooutils-file.h | 24 + moo/mooutils/mooutils-fs.c | 1510 ++++ moo/mooutils/mooutils-fs.h | 107 + moo/mooutils/mooutils-gobject-private.h | 89 + moo/mooutils/mooutils-gobject.c | 2264 ++++++ moo/mooutils/mooutils-gobject.h | 165 + moo/mooutils/mooutils-macros.h | 136 + moo/mooutils/mooutils-mem.h | 94 + moo/mooutils/mooutils-messages.h | 255 + moo/mooutils/mooutils-misc.c | 2224 ++++++ moo/mooutils/mooutils-misc.h | 239 + moo/mooutils/mooutils-script.c | 193 + moo/mooutils/mooutils-script.h | 31 + moo/mooutils/mooutils-tests.h | 36 + moo/mooutils/mooutils-thread.c | 560 ++ moo/mooutils/mooutils-thread.h | 58 + moo/mooutils/mooutils-treeview.c | 1226 +++ moo/mooutils/mooutils-treeview.h | 107 + moo/mooutils/mooutils-win32.c | 570 ++ moo/mooutils/mooutils.h | 8 + moo/mooutils/moowin32/mingw/fnmatch.h | 13 + moo/mooutils/moowin32/mingw/netinet/in.h | 11 + moo/mooutils/moowin32/mingw/sys/mman.h | 22 + moo/mooutils/moowin32/ms/sys/time.h | 11 + moo/mooutils/moowin32/ms/unistd.h | 11 + moo/mooutils/moowindow.c | 2488 ++++++ moo/mooutils/moowindow.h | 142 + moo/mooutils/pixmaps/attach.png | Bin 0 -> 97 bytes moo/mooutils/pixmaps/close.png | Bin 0 -> 108 bytes moo/mooutils/pixmaps/detach.png | Bin 0 -> 102 bytes moo/mooutils/pixmaps/hide.png | Bin 0 -> 79 bytes moo/mooutils/pixmaps/keepontop.png | Bin 0 -> 102 bytes moo/mooutils/pixmaps/medit.ico | Bin 0 -> 9662 bytes moo/mooutils/pixmaps/medit.png | Bin 0 -> 3015 bytes moo/mooutils/pixmaps/sticky.png | Bin 0 -> 89 bytes moo/mooutils/pixmaps/symlink-small.png | Bin 0 -> 97 bytes moo/mooutils/pixmaps/symlink.png | Bin 0 -> 183 bytes moo/mooutils/stock-file-24.h | 127 + moo/mooutils/stock-file-selector-24.h | 98 + moo/mooutils/stock-terminal-24.h | 91 + moo/plugins/Makefile.incl | 24 + moo/plugins/ctags/Makefile.incl | 11 + moo/plugins/ctags/ctags-doc.c | 567 ++ moo/plugins/ctags/ctags-doc.h | 67 + moo/plugins/ctags/ctags-plugin.c | 179 + moo/plugins/ctags/ctags-view.c | 164 + moo/plugins/ctags/ctags-view.h | 51 + moo/plugins/ctags/readtags-mangle.h | 8 + moo/plugins/ctags/readtags.c | 959 +++ moo/plugins/ctags/readtags.h | 254 + moo/plugins/glade/moofileselector-prefs.glade | 296 + moo/plugins/glade/moofileselector.glade | 170 + moo/plugins/glade/moofind.glade | 172 + moo/plugins/glade/moogrep.glade | 234 + moo/plugins/moofilelist.c | 2428 ++++++ moo/plugins/moofileselector-prefs.c | 395 + moo/plugins/moofileselector.c | 1328 ++++ moo/plugins/moofileselector.h | 81 + moo/plugins/moofind.c | 1131 +++ moo/plugins/mooplugin-builtin.c | 46 + moo/plugins/mooplugin-builtin.h | 46 + moo/plugins/old/moopythonplugin.c | 76 + moo/plugins/support/Makefile.incl | 11 + moo/plugins/support/moocmdview.c | 597 ++ moo/plugins/support/moocmdview.h | 95 + moo/plugins/support/mooeditwindowoutput.c | 69 + moo/plugins/support/mooeditwindowoutput.h | 14 + moo/plugins/support/moolineview.c | 752 ++ moo/plugins/support/moolineview.h | 106 + moo/plugins/support/moooutputfilter.c | 422 + moo/plugins/support/moooutputfilter.h | 101 + moo/plugins/usertools/Makefile.incl | 76 + moo/plugins/usertools/context-tmpl.xml | 61 + moo/plugins/usertools/filters.xml | 60 + moo/plugins/usertools/genplatxml.py | 34 + .../usertools/glade/mooedittools-exe.glade | 126 + .../usertools/glade/mooedittools-script.glade | 33 + .../usertools/glade/moousertools.glade | 358 + moo/plugins/usertools/lua-tool-setup.lua | 3 + moo/plugins/usertools/menu-tmpl.xml | 194 + moo/plugins/usertools/moocommand-exe.c | 1209 +++ moo/plugins/usertools/moocommand-exe.h | 71 + moo/plugins/usertools/moocommand-private.h | 59 + moo/plugins/usertools/moocommand-script.cpp | 310 + moo/plugins/usertools/moocommand-script.h | 56 + moo/plugins/usertools/moocommand.c | 1362 ++++ moo/plugins/usertools/moocommand.h | 183 + moo/plugins/usertools/moocommanddisplay.c | 322 + moo/plugins/usertools/moocommanddisplay.h | 55 + moo/plugins/usertools/moooutputfilterregex.c | 1085 +++ moo/plugins/usertools/moooutputfilterregex.h | 52 + .../usertools/moousertools-enums-in.py | 9 + moo/plugins/usertools/moousertools-enums.c | 21 + moo/plugins/usertools/moousertools-enums.h | 21 + moo/plugins/usertools/moousertools-prefs.c | 573 ++ moo/plugins/usertools/moousertools-prefs.h | 29 + moo/plugins/usertools/moousertools.c | 1395 ++++ moo/plugins/usertools/moousertools.h | 76 + moo/plugins/usertools/python-tool-setup.py | 2 + moo/xdgmime/Makefile.incl | 16 + moo/xdgmime/README | 13 + moo/xdgmime/xdgmime.c | 896 +++ moo/xdgmime/xdgmime.h | 127 + moo/xdgmime/xdgmimealias.c | 184 + moo/xdgmime/xdgmimealias.h | 51 + moo/xdgmime/xdgmimecache.c | 1012 +++ moo/xdgmime/xdgmimecache.h | 82 + moo/xdgmime/xdgmimeglob.c | 605 ++ moo/xdgmime/xdgmimeglob.h | 68 + moo/xdgmime/xdgmimeicon.c | 183 + moo/xdgmime/xdgmimeicon.h | 50 + moo/xdgmime/xdgmimeint.c | 191 + moo/xdgmime/xdgmimeint.h | 217 + moo/xdgmime/xdgmimemagic.c | 819 ++ moo/xdgmime/xdgmimemagic.h | 57 + moo/xdgmime/xdgmimeparent.c | 219 + moo/xdgmime/xdgmimeparent.h | 51 + plat/win32/Makefile.incl | 4 + plat/win32/gtk-win/Makefile | 53 + plat/win32/gtk-win/config.sh | 36 + plat/win32/gtk-win/copybin.sh | 24 + plat/win32/gtk-win/copysrc.sh | 36 + plat/win32/gtk-win/copysrcdbg.sh | 86 + plat/win32/gtk-win/copyuser.sh | 211 + .../gtk-win/extra/medit-data/bin/grep.exe | Bin 0 -> 106496 bytes .../gtk-win/extra/medit-data/bin/intl.dll | Bin 0 -> 104861 bytes .../extra/medit-data/etc/gtk-2.0/gtkrc | 14 + .../win32/gtk-win/extra/readme-medit-deps.txt | 23 + plat/win32/gtk-win/jhbuildrc/glib-win32.cache | 2 + plat/win32/gtk-win/jhbuildrc/gtk.moduleset | 284 + plat/win32/gtk-win/jhbuildrc/jhbuildrc | 184 + .../jhbuildrc/patches/cairo-1.10.patch | 14 + .../jhbuildrc/patches/gettext-0.17.patch | 68 + .../gtk-win/jhbuildrc/patches/gettext.patch | 55 + .../jhbuildrc/patches/glade3-3.5.patch | 25 + .../gtk-win/jhbuildrc/patches/glade3.patch | 25 + .../gtk-win/jhbuildrc/patches/glib-2.22.patch | 48 + .../gtk-win/jhbuildrc/patches/glib-2.24.patch | 48 + .../gtk-win/jhbuildrc/patches/glib-2.26.patch | 60 + .../patches/glib-2.28-spawn-win32-moo.patch | 12 + .../patches/glib-2.28-spawn-win32.patch | 312 + .../gtk-win/jhbuildrc/patches/glib-2.28.patch | 60 + .../patches/glib-2.30-spawn-win32-moo.patch | 12 + .../patches/glib-2.30-spawn-win32.patch | 312 + .../gtk-win/jhbuildrc/patches/glib-2.30.patch | 60 + .../patches/glib-spawn-win32-moo.patch | 12 + .../jhbuildrc/patches/glib-spawn-win32.patch | 331 + .../gtk-win/jhbuildrc/patches/grep-2.7.patch | 40 + .../gtk-win/jhbuildrc/patches/grep.patch | 40 + .../gtk-win/jhbuildrc/patches/gtk-2.16.patch | 75 + .../gtk-win/jhbuildrc/patches/gtk-2.18.patch | 87 + .../gtk-win/jhbuildrc/patches/gtk-2.20.patch | 99 + .../gtk-win/jhbuildrc/patches/gtk-2.22.patch | 87 + .../gtk-win/jhbuildrc/patches/gtk-2.24.patch | 98 + .../jhbuildrc/patches/icon-naming-utils.patch | 12 + .../gtk-win/jhbuildrc/patches/intltool.patch | 45 + .../gtk-win/jhbuildrc/patches/libpng.patch | 33 + .../gtk-win/jhbuildrc/patches/libxml2.patch | 26 + .../jhbuildrc/patches/pango-1.28.patch | 12 + .../jhbuildrc/patches/pixman-0.17.patch | 13 + .../gtk-win/jhbuildrc/patches/pkgconfig.patch | 57 + .../jhbuildrc/patches/pygobject-2.26.0.patch | 5057 ++++++++++++ .../jhbuildrc/patches/pygtk-2.16.0.patch | 5057 ++++++++++++ .../jhbuildrc/patches/tango-icon-theme.patch | 29 + .../jhbuildrc/patches/zlib-1.2.5.patch | 98 + .../gtk-win/jhbuildrc/patches/zlib.patch | 90 + plat/win32/gtk-win/make.sh | 21 + plat/win32/gtk-win/mjhbuild.sh | 20 + plat/win32/gtk-win/readme-win32-build.txt | 50 + plat/win32/installer.iss.in | 61 + plat/win32/istrans.sh | 40 + plat/win32/makepythondist.py | 152 + plat/win32/mingw-configure | 100 + plat/win32/sitecustomize.py | 19 + po-gsv/ChangeLog | 3728 +++++++++ po-gsv/LINGUAS | 80 + po-gsv/Makefile.am | 61 + po-gsv/POTFILES.in | 99 + po-gsv/POTFILES.skip | 25 + po-gsv/ar.po | 358 + po-gsv/as.po | 274 + po-gsv/ast.po | 273 + po-gsv/az.po | 52 + po-gsv/be.po | 59 + po-gsv/bg.po | 284 + po-gsv/bn.po | 305 + po-gsv/bn_IN.po | 276 + po-gsv/bs.po | 59 + po-gsv/ca.po | 282 + po-gsv/ca@valencia.po | 282 + po-gsv/cs.po | 285 + po-gsv/cy.po | 66 + po-gsv/da.po | 286 + po-gsv/de.po | 290 + po-gsv/dz.po | 430 ++ po-gsv/el.po | 277 + po-gsv/en@shaw.po | 301 + po-gsv/en_CA.po | 83 + po-gsv/en_GB.po | 91 + po-gsv/es.po | 294 + po-gsv/et.po | 276 + po-gsv/eu.po | 294 + po-gsv/fa.po | 83 + po-gsv/fi.po | 282 + po-gsv/fr.po | 277 + po-gsv/ga.po | 80 + po-gsv/gl.po | 292 + po-gsv/gu.po | 338 + po-gsv/he.po | 119 + po-gsv/hi.po | 380 + po-gsv/hr.po | 55 + po-gsv/hu.po | 280 + po-gsv/id.po | 108 + po-gsv/it.po | 270 + po-gsv/ja.po | 269 + po-gsv/kn.po | 345 + po-gsv/ko.po | 303 + po-gsv/lt.po | 283 + po-gsv/lv.po | 278 + po-gsv/mai.po | 183 + po-gsv/maintain | 2 + po-gsv/mg.po | 65 + po-gsv/mk.po | 290 + po-gsv/ml.po | 304 + po-gsv/mn.po | 76 + po-gsv/mr.po | 313 + po-gsv/ms.po | 50 + po-gsv/msgs.py | 243 + po-gsv/nb.po | 240 + po-gsv/ne.po | 341 + po-gsv/nl.po | 260 + po-gsv/nn.po | 283 + po-gsv/oc.po | 61 + po-gsv/or.po | 282 + po-gsv/pa.po | 338 + po-gsv/pl.po | 284 + po-gsv/pt.po | 286 + po-gsv/pt_BR.po | 287 + po-gsv/ro.po | 274 + po-gsv/ru.po | 291 + po-gsv/rw.po | 47 + po-gsv/si.po | 83 + po-gsv/sk.po | 281 + po-gsv/sl.po | 292 + po-gsv/sq.po | 72 + po-gsv/sr.po | 389 + po-gsv/sr@Latn.po | 546 ++ po-gsv/sr@latin.po | 332 + po-gsv/strip.sh | 35 + po-gsv/sv.po | 271 + po-gsv/ta.po | 467 ++ po-gsv/te.po | 300 + po-gsv/th.po | 280 + po-gsv/tr.po | 279 + po-gsv/ug.po | 294 + po-gsv/uk.po | 272 + po-gsv/vi.po | 294 + po-gsv/xh.po | 136 + po-gsv/zh_CN.po | 280 + po-gsv/zh_HK.po | 283 + po-gsv/zh_TW.po | 283 + po/LINGUAS | 9 + po/Makefile.am | 61 + po/POTFILES.in | 78 + po/POTFILES.skip | 12 + po/cs.po | 2392 ++++++ po/de.po | 2354 ++++++ po/es.po | 2430 ++++++ po/fi.po | 2435 ++++++ po/fr.po | 2605 +++++++ po/ja.po | 2312 ++++++ po/maintain | 44 + po/medit-1.pot | 2279 ++++++ po/nl.po | 2420 ++++++ po/ru.po | 2574 +++++++ po/zh_CN.po | 2511 ++++++ tools/checkglade | 24 + tools/fixglade | 17 + tools/genenums.py | 151 + tools/glade2c.py | 369 + tools/medit.supp | 253 + tools/run-valgrind.sh | 25 + tools/test-release.sh | 124 + tools/xml2h.py | 40 + 998 files changed, 326124 insertions(+) create mode 100644 .hgignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 COPYING.GPL create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 THANKS create mode 100644 api/Makefile.am create mode 100644 api/gendefs.py create mode 100644 api/gendocbook.py create mode 100644 api/gendocs.py create mode 100644 api/genlua.py create mode 100644 api/mdp/__init__.py create mode 100644 api/mdp/docparser.py create mode 100644 api/mdp/module.py create mode 100644 api/mdp/xmlwriter.py create mode 100644 api/mpi/__init__.py create mode 100644 api/mpi/defswriter.py create mode 100644 api/mpi/docbookwriter.py create mode 100644 api/mpi/luawriter.py create mode 100644 api/mpi/module.py create mode 100644 api/mpi/util.py create mode 100644 api/parsedocs.py create mode 100644 api/sourcefiles.mak create mode 100755 autogen.sh create mode 100644 build/.empty create mode 100755 checkle.py create mode 100644 configure.ac create mode 100644 doc/Makefile.am create mode 100644 doc/genhelpsectionsh.py create mode 100644 doc/img/prefs-file-filters.png create mode 100644 doc/img/prefs-file-selector.png create mode 100644 doc/license.docbook create mode 100644 doc/man-medit.t2t.in create mode 100644 doc/medit-common.xsl create mode 100644 doc/medit-defines.ent.in create mode 100644 doc/medit-single.xsl create mode 100644 doc/medit.css create mode 100644 doc/medit.docbook create mode 100644 doc/medit.xsl create mode 100644 doc/prefs.docbook create mode 100644 doc/regex.docbook create mode 100644 doc/script-book.xsl create mode 100644 doc/script-lua-gtk.tmpl.docbook create mode 100644 doc/script-lua.tmpl.docbook create mode 100644 doc/script-python.tmpl.docbook create mode 100644 doc/script.docbook create mode 100644 doc/script.xsl create mode 100644 doc/user-tools.docbook create mode 100644 hgrc create mode 100644 m4/moo-flags.m4 create mode 100644 m4/moo-gtk.m4 create mode 100644 m4/moo-intltool.m4 create mode 100644 m4/moo-os.m4 create mode 100644 m4/moo-pygtk.m4 create mode 100644 m4/moo-python.m4 create mode 100644 moo/Makefile.am create mode 100644 moo/eggsmclient/Makefile.incl create mode 100644 moo/eggsmclient/eggdesktopfile.c create mode 100644 moo/eggsmclient/eggdesktopfile.h create mode 100644 moo/eggsmclient/eggsmclient-dbus.c create mode 100644 moo/eggsmclient/eggsmclient-dummy.c create mode 100644 moo/eggsmclient/eggsmclient-mangle.h create mode 100644 moo/eggsmclient/eggsmclient-osx.c create mode 100644 moo/eggsmclient/eggsmclient-private.h create mode 100644 moo/eggsmclient/eggsmclient-win32.c create mode 100644 moo/eggsmclient/eggsmclient-xsmp.c create mode 100644 moo/eggsmclient/eggsmclient.c create mode 100644 moo/eggsmclient/eggsmclient.h create mode 100644 moo/gtksourceview/Makefile.incl create mode 100644 moo/gtksourceview/gtksourcebuffer.h create mode 100644 moo/gtksourceview/gtksourcecontextengine.c create mode 100644 moo/gtksourceview/gtksourcecontextengine.h create mode 100644 moo/gtksourceview/gtksourceengine.c create mode 100644 moo/gtksourceview/gtksourceengine.h create mode 100644 moo/gtksourceview/gtksourceiter.c create mode 100644 moo/gtksourceview/gtksourceiter.h create mode 100644 moo/gtksourceview/gtksourcelanguage-parser-1.c create mode 100644 moo/gtksourceview/gtksourcelanguage-parser-2.c create mode 100644 moo/gtksourceview/gtksourcelanguage-private.h create mode 100644 moo/gtksourceview/gtksourcelanguage.c create mode 100644 moo/gtksourceview/gtksourcelanguage.h create mode 100644 moo/gtksourceview/gtksourcelanguagemanager.c create mode 100644 moo/gtksourceview/gtksourcelanguagemanager.h create mode 100644 moo/gtksourceview/gtksourcestyle-private.h create mode 100644 moo/gtksourceview/gtksourcestyle.c create mode 100644 moo/gtksourceview/gtksourcestyle.h create mode 100644 moo/gtksourceview/gtksourcestylescheme.c create mode 100644 moo/gtksourceview/gtksourcestylescheme.h create mode 100644 moo/gtksourceview/gtksourcestyleschememanager.c create mode 100644 moo/gtksourceview/gtksourcestyleschememanager.h create mode 100644 moo/gtksourceview/gtksourceview-api.h create mode 100644 moo/gtksourceview/gtksourceview-i18n.h create mode 100644 moo/gtksourceview/gtksourceview-marshal.h create mode 100644 moo/gtksourceview/gtksourceview-utils.c create mode 100644 moo/gtksourceview/gtksourceview-utils.h create mode 100644 moo/gtksourceview/gtksourceview.h create mode 100644 moo/gtksourceview/gtktextregion.c create mode 100644 moo/gtksourceview/gtktextregion.h create mode 100644 moo/marshals.list create mode 100644 moo/medit-app/Makefile.incl create mode 100644 moo/medit-app/data/encodings/ASCII.1 create mode 100644 moo/medit-app/data/encodings/ASCII.2 create mode 100644 moo/medit-app/data/encodings/GB2312 create mode 100644 moo/medit-app/data/encodings/KOI8-R.1 create mode 100644 moo/medit-app/data/encodings/UCS-4.1 create mode 100644 moo/medit-app/data/encodings/UCS-4.2 create mode 100644 moo/medit-app/data/encodings/UCS-4.3 create mode 100644 moo/medit-app/data/encodings/UCS-4.4 create mode 100644 moo/medit-app/data/encodings/UTF-16.1 create mode 100644 moo/medit-app/data/encodings/UTF-16.2 create mode 100644 moo/medit-app/data/encodings/UTF-16.3 create mode 100644 moo/medit-app/data/encodings/UTF-16.4 create mode 100644 moo/medit-app/data/encodings/UTF-8.1 create mode 100644 moo/medit-app/data/encodings/UTF-8.2 create mode 100644 moo/medit-app/data/encodings/UTF-8.3 create mode 100644 moo/medit-app/data/encodings/UTF-8.4 create mode 100644 moo/medit-app/data/encodings/UTF-8.5 create mode 100644 moo/medit-app/data/encodings/Windows-1251.1 create mode 100644 moo/medit-app/data/test-lua/lua/munit.lua create mode 100644 moo/medit-app/data/test-lua/testbasic.lua create mode 100644 moo/medit-app/data/test-lua/testedit.lua create mode 100644 moo/medit-app/data/test-lua/testeditor.lua create mode 100644 moo/medit-app/data/test-lua/testfileinfo.lua create mode 100644 moo/medit-app/data/test-lua/testgfile.lua create mode 100644 moo/medit-app/data/test-lua/testlineends.lua create mode 100644 moo/medit-app/data/test-lua/testmedit.lua create mode 100644 moo/medit-app/data/test-lua/testmoo.lua create mode 100644 moo/medit-app/data/test-lua/testmooeditor.lua create mode 100644 moo/medit-app/data/test-lua/testobject.lua create mode 100644 moo/medit-app/data/test-lua/testprefs.lua create mode 100644 moo/medit-app/data/test-lua/testtext.lua create mode 100644 moo/medit-app/data/test-lua/testunicode.lua create mode 100644 moo/medit-app/data/test-lua/testustring.lua create mode 100644 moo/medit-app/data/test-lua/testwindow.lua create mode 100644 moo/medit-app/data/test-python/munit.py create mode 100644 moo/medit-app/data/test-python/testmoo.py create mode 100644 moo/medit-app/main.c create mode 100644 moo/medit-app/medit-project.desktop.in create mode 100644 moo/medit-app/medit.desktop.in create mode 100644 moo/medit-app/medit.rc.in create mode 100644 moo/medit-app/mem-debug.h create mode 100644 moo/medit-app/parse.h create mode 100644 moo/medit-app/print-functions.py create mode 100755 moo/medit-app/run-tests-installed.sh create mode 100755 moo/medit-app/run-tests-uninstalled.sh create mode 100644 moo/medit-app/run-tests.h create mode 100755 moo/medit-app/run-tests.sh create mode 100644 moo/medit-module/Makefile.incl create mode 100644 moo/medit-module/medit-module.cpp create mode 100644 moo/medit-module/moo.rc.in create mode 100644 moo/moo-config.h create mode 100644 moo/mooapp/Makefile.incl create mode 100644 moo/mooapp/glade/mooappabout-credits.glade create mode 100644 moo/mooapp/glade/mooappabout-dialog.glade create mode 100644 moo/mooapp/glade/mooappabout-license.glade create mode 100644 moo/mooapp/mooapp-accels.h create mode 100644 moo/mooapp/mooapp-info.h create mode 100644 moo/mooapp/mooapp-private.h create mode 100644 moo/mooapp/mooapp.c create mode 100644 moo/mooapp/mooapp.h create mode 100644 moo/mooapp/mooappabout.c create mode 100644 moo/mooapp/mooappabout.h create mode 100644 moo/mooapp/moohtml.c create mode 100644 moo/mooapp/moohtml.h create mode 100644 moo/mooapp/moolinklabel.c create mode 100644 moo/mooapp/moolinklabel.h create mode 100644 moo/mooedit/Makefile.incl create mode 100644 moo/mooedit/glade/mooeditprefs-file.glade create mode 100644 moo/mooedit/glade/mooeditprefs-filters.glade create mode 100644 moo/mooedit/glade/mooeditprefs-general.glade create mode 100644 moo/mooedit/glade/mooeditprefs-langs.glade create mode 100644 moo/mooedit/glade/mooeditprefs-view.glade create mode 100644 moo/mooedit/glade/mooeditprogress.glade create mode 100644 moo/mooedit/glade/mooeditsavemult.glade create mode 100644 moo/mooedit/glade/moopluginprefs.glade create mode 100644 moo/mooedit/glade/mooprint.glade create mode 100644 moo/mooedit/glade/mooquicksearch.glade create mode 100644 moo/mooedit/glade/moostatusbar.glade create mode 100644 moo/mooedit/glade/mootextfind-prompt.glade create mode 100644 moo/mooedit/glade/mootextfind.glade create mode 100644 moo/mooedit/glade/mootextgotoline.glade create mode 100644 moo/mooedit/glade/mootryencoding.glade create mode 100644 moo/mooedit/langs/CMakeLists.txt create mode 100644 moo/mooedit/langs/Makefile.incl create mode 100644 moo/mooedit/langs/R.lang create mode 100644 moo/mooedit/langs/ada.lang create mode 100644 moo/mooedit/langs/asp.lang create mode 100644 moo/mooedit/langs/awk.lang create mode 100644 moo/mooedit/langs/bennugd.lang create mode 100644 moo/mooedit/langs/bibtex.lang create mode 100644 moo/mooedit/langs/boo.lang create mode 100644 moo/mooedit/langs/c.lang create mode 100644 moo/mooedit/langs/cg.lang create mode 100644 moo/mooedit/langs/changelog.lang create mode 100644 moo/mooedit/langs/chdr.lang create mode 100755 moo/mooedit/langs/check.sh create mode 100644 moo/mooedit/langs/classic.xml create mode 100644 moo/mooedit/langs/cmake.lang create mode 100644 moo/mooedit/langs/cobalt.xml create mode 100644 moo/mooedit/langs/cobol.lang create mode 100644 moo/mooedit/langs/convert.py create mode 100644 moo/mooedit/langs/convert.sh create mode 100644 moo/mooedit/langs/cpp.lang create mode 100644 moo/mooedit/langs/csharp.lang create mode 100644 moo/mooedit/langs/css.lang create mode 100644 moo/mooedit/langs/cuda.lang create mode 100644 moo/mooedit/langs/d.lang create mode 100644 moo/mooedit/langs/def.lang create mode 100644 moo/mooedit/langs/desktop.lang create mode 100644 moo/mooedit/langs/diff.lang create mode 100644 moo/mooedit/langs/docbook.lang create mode 100644 moo/mooedit/langs/dosbatch.lang create mode 100644 moo/mooedit/langs/dot.lang create mode 100644 moo/mooedit/langs/dpatch.lang create mode 100644 moo/mooedit/langs/dtd.lang create mode 100644 moo/mooedit/langs/eiffel.lang create mode 100644 moo/mooedit/langs/erlang.lang create mode 100644 moo/mooedit/langs/fcl.lang create mode 100644 moo/mooedit/langs/forth.lang create mode 100644 moo/mooedit/langs/fortran.lang create mode 100644 moo/mooedit/langs/fsharp.lang create mode 100644 moo/mooedit/langs/gap.lang create mode 100644 moo/mooedit/langs/gdb-log.lang create mode 100644 moo/mooedit/langs/glsl.lang create mode 100644 moo/mooedit/langs/go.lang create mode 100644 moo/mooedit/langs/gtk-doc.lang create mode 100644 moo/mooedit/langs/gtkrc.lang create mode 100644 moo/mooedit/langs/haddock.lang create mode 100644 moo/mooedit/langs/haskell-literate.lang create mode 100644 moo/mooedit/langs/haskell.lang create mode 100644 moo/mooedit/langs/html.lang create mode 100644 moo/mooedit/langs/idl.lang create mode 100644 moo/mooedit/langs/ini.lang create mode 100644 moo/mooedit/langs/java.lang create mode 100644 moo/mooedit/langs/javascript.lang create mode 100644 moo/mooedit/langs/kate.xml create mode 100644 moo/mooedit/langs/language2.rng create mode 100644 moo/mooedit/langs/latex.lang create mode 100644 moo/mooedit/langs/libtool.lang create mode 100644 moo/mooedit/langs/lua.lang create mode 100644 moo/mooedit/langs/m4.lang create mode 100644 moo/mooedit/langs/makefile.lang create mode 100644 moo/mooedit/langs/mallard.lang create mode 100644 moo/mooedit/langs/medit.xml create mode 100644 moo/mooedit/langs/msil.lang create mode 100644 moo/mooedit/langs/nemerle.lang create mode 100644 moo/mooedit/langs/nsis.lang create mode 100644 moo/mooedit/langs/objc.lang create mode 100644 moo/mooedit/langs/oblivion.xml create mode 100644 moo/mooedit/langs/ocaml.lang create mode 100644 moo/mooedit/langs/ocl.lang create mode 100644 moo/mooedit/langs/octave.lang create mode 100644 moo/mooedit/langs/ooc.lang create mode 100644 moo/mooedit/langs/opal.lang create mode 100644 moo/mooedit/langs/pascal.lang create mode 100644 moo/mooedit/langs/perl.lang create mode 100644 moo/mooedit/langs/php.lang create mode 100644 moo/mooedit/langs/pkgconfig.lang create mode 100644 moo/mooedit/langs/po.lang create mode 100644 moo/mooedit/langs/prolog.lang create mode 100644 moo/mooedit/langs/protobuf.lang create mode 100644 moo/mooedit/langs/python-console.lang create mode 100644 moo/mooedit/langs/python.lang create mode 100644 moo/mooedit/langs/rpmspec.lang create mode 100644 moo/mooedit/langs/ruby.lang create mode 100644 moo/mooedit/langs/scheme.lang create mode 100644 moo/mooedit/langs/scilab.lang create mode 100644 moo/mooedit/langs/sh.lang create mode 100644 moo/mooedit/langs/sparql.lang create mode 100644 moo/mooedit/langs/sql.lang create mode 100644 moo/mooedit/langs/styles.rng create mode 100644 moo/mooedit/langs/systemverilog.lang create mode 100644 moo/mooedit/langs/t2t.lang create mode 100644 moo/mooedit/langs/tango.xml create mode 100644 moo/mooedit/langs/tcl.lang create mode 100644 moo/mooedit/langs/testfiles.sh create mode 100644 moo/mooedit/langs/testv1.lang create mode 100644 moo/mooedit/langs/texinfo.lang create mode 100644 moo/mooedit/langs/vala.lang create mode 100644 moo/mooedit/langs/vbnet.lang create mode 100644 moo/mooedit/langs/verilog.lang create mode 100644 moo/mooedit/langs/vhdl.lang create mode 100644 moo/mooedit/langs/xml.lang create mode 100644 moo/mooedit/langs/xslt.lang create mode 100644 moo/mooedit/langs/yacc.lang create mode 100644 moo/mooedit/medit.xml create mode 100644 moo/mooedit/mooedit-accels.h create mode 100644 moo/mooedit/mooedit-enum-types.c create mode 100644 moo/mooedit/mooedit-enum-types.c.tmpl create mode 100644 moo/mooedit/mooedit-enum-types.h create mode 100644 moo/mooedit/mooedit-enum-types.h.tmpl create mode 100644 moo/mooedit/mooedit-enums.h create mode 100644 moo/mooedit/mooedit-fileops.c create mode 100644 moo/mooedit/mooedit-fileops.h create mode 100644 moo/mooedit/mooedit-impl.h create mode 100644 moo/mooedit/mooedit-misc.c create mode 100644 moo/mooedit/mooedit-private.h create mode 100644 moo/mooedit/mooedit-script.c create mode 100644 moo/mooedit/mooedit-script.h create mode 100644 moo/mooedit/mooedit.c create mode 100644 moo/mooedit/mooedit.h create mode 100644 moo/mooedit/mooedit.xml create mode 100644 moo/mooedit/mooeditaction-factory.c create mode 100644 moo/mooedit/mooeditaction-factory.h create mode 100644 moo/mooedit/mooeditaction.c create mode 100644 moo/mooedit/mooeditaction.h create mode 100644 moo/mooedit/mooeditbookmark.c create mode 100644 moo/mooedit/mooeditbookmark.h create mode 100644 moo/mooedit/mooeditconfig.c create mode 100644 moo/mooedit/mooeditconfig.h create mode 100644 moo/mooedit/mooeditdialogs.c create mode 100644 moo/mooedit/mooeditdialogs.h create mode 100644 moo/mooedit/mooeditfileinfo-impl.h create mode 100644 moo/mooedit/mooeditfileinfo.c create mode 100644 moo/mooedit/mooeditfileinfo.h create mode 100644 moo/mooedit/mooeditfiltersettings.c create mode 100644 moo/mooedit/mooeditfiltersettings.h create mode 100644 moo/mooedit/mooedithistoryitem.c create mode 100644 moo/mooedit/mooedithistoryitem.h create mode 100644 moo/mooedit/mooeditor-impl.h create mode 100644 moo/mooedit/mooeditor-private.h create mode 100644 moo/mooedit/mooeditor-tests.c create mode 100644 moo/mooedit/mooeditor-tests.h create mode 100644 moo/mooedit/mooeditor.c create mode 100644 moo/mooedit/mooeditor.h create mode 100644 moo/mooedit/mooeditprefs.c create mode 100644 moo/mooedit/mooeditprefs.h create mode 100644 moo/mooedit/mooeditprefspage.c create mode 100644 moo/mooedit/mooeditprogress.c create mode 100644 moo/mooedit/mooeditprogress.h create mode 100644 moo/mooedit/mooedittab-impl.h create mode 100644 moo/mooedit/mooedittab.c create mode 100644 moo/mooedit/mooedittab.h create mode 100644 moo/mooedit/mooedittypes.h create mode 100644 moo/mooedit/mooeditview-impl.h create mode 100644 moo/mooedit/mooeditview-priv.h create mode 100644 moo/mooedit/mooeditview-script.c create mode 100644 moo/mooedit/mooeditview-script.h create mode 100644 moo/mooedit/mooeditview.c create mode 100644 moo/mooedit/mooeditview.h create mode 100644 moo/mooedit/mooeditwindow-impl.h create mode 100644 moo/mooedit/mooeditwindow.c create mode 100644 moo/mooedit/mooeditwindow.h create mode 100644 moo/mooedit/moofold.c create mode 100644 moo/mooedit/moofold.h create mode 100644 moo/mooedit/mooindenter.c create mode 100644 moo/mooedit/mooindenter.h create mode 100644 moo/mooedit/moolang-private.h create mode 100644 moo/mooedit/moolang.c create mode 100644 moo/mooedit/moolang.h create mode 100644 moo/mooedit/moolangmgr-private.h create mode 100644 moo/mooedit/moolangmgr.c create mode 100644 moo/mooedit/moolangmgr.h create mode 100644 moo/mooedit/moolinebuffer.c create mode 100644 moo/mooedit/moolinebuffer.h create mode 100644 moo/mooedit/moolinemark.c create mode 100644 moo/mooedit/moolinemark.h create mode 100644 moo/mooedit/mooplugin-loader.c create mode 100644 moo/mooedit/mooplugin-loader.h create mode 100644 moo/mooedit/mooplugin-macro.h create mode 100644 moo/mooedit/mooplugin.c create mode 100644 moo/mooedit/mooplugin.h create mode 100644 moo/mooedit/mootext-private.h create mode 100644 moo/mooedit/mootextbtree.c create mode 100644 moo/mooedit/mootextbtree.h create mode 100644 moo/mooedit/mootextbuffer.c create mode 100644 moo/mooedit/mootextbuffer.h create mode 100644 moo/mooedit/mootextfind.c create mode 100644 moo/mooedit/mootextfind.h create mode 100644 moo/mooedit/mootextiter.h create mode 100644 moo/mooedit/mootextprint-private.h create mode 100644 moo/mooedit/mootextprint.c create mode 100644 moo/mooedit/mootextprint.h create mode 100644 moo/mooedit/mootextsearch-private.h create mode 100644 moo/mooedit/mootextsearch.c create mode 100644 moo/mooedit/mootextsearch.h create mode 100644 moo/mooedit/mootextstylescheme.c create mode 100644 moo/mooedit/mootextstylescheme.h create mode 100644 moo/mooedit/mootextview-input.c create mode 100644 moo/mooedit/mootextview-private.h create mode 100644 moo/mooedit/mootextview.c create mode 100644 moo/mooedit/mootextview.h create mode 100644 moo/moofileview/Makefile.incl create mode 100644 moo/moofileview/glade/moobookmark-editor.glade create mode 100644 moo/moofileview/glade/moocreatefolder.glade create mode 100644 moo/moofileview/glade/moofileprops.glade create mode 100644 moo/moofileview/glade/moofileview-drop.glade create mode 100644 moo/moofileview/moobookmarkmgr.c create mode 100644 moo/moofileview/moobookmarkmgr.h create mode 100644 moo/moofileview/moobookmarkview.c create mode 100644 moo/moofileview/moobookmarkview.h create mode 100644 moo/moofileview/moofile-private.h create mode 100644 moo/moofileview/moofile.c create mode 100644 moo/moofileview/moofile.h create mode 100644 moo/moofileview/moofileentry.c create mode 100644 moo/moofileview/moofileentry.h create mode 100644 moo/moofileview/moofilesystem.c create mode 100644 moo/moofileview/moofilesystem.h create mode 100644 moo/moofileview/moofileview-accels.h create mode 100644 moo/moofileview/moofileview-aux.h create mode 100644 moo/moofileview/moofileview-dialogs.c create mode 100644 moo/moofileview/moofileview-dialogs.h create mode 100644 moo/moofileview/moofileview-impl.h create mode 100644 moo/moofileview/moofileview-private.h create mode 100644 moo/moofileview/moofileview-tools.c create mode 100644 moo/moofileview/moofileview-tools.h create mode 100644 moo/moofileview/moofileview.c create mode 100644 moo/moofileview/moofileview.h create mode 100644 moo/moofileview/moofileview.xml create mode 100644 moo/moofileview/moofolder-private.h create mode 100644 moo/moofileview/moofolder.c create mode 100644 moo/moofileview/moofolder.h create mode 100644 moo/moofileview/moofoldermodel-private.h create mode 100644 moo/moofileview/moofoldermodel.c create mode 100644 moo/moofileview/moofoldermodel.h create mode 100644 moo/moofileview/mooiconview.c create mode 100644 moo/moofileview/mooiconview.h create mode 100644 moo/moofileview/mootreeview.c create mode 100644 moo/moofileview/mootreeview.h create mode 100644 moo/moolua/Makefile.incl create mode 100644 moo/moolua/gtk-api.c create mode 100644 moo/moolua/gtk-api.h create mode 100644 moo/moolua/gtk-lua-api.h create mode 100644 moo/moolua/lua-default-init.lua create mode 100644 moo/moolua/lua-module-init.lua create mode 100644 moo/moolua/lua-plugin-init.lua create mode 100644 moo/moolua/lua/COPYRIGHT create mode 100644 moo/moolua/lua/Makefile.incl create mode 100644 moo/moolua/lua/README create mode 100644 moo/moolua/lua/README.lfs create mode 100644 moo/moolua/lua/_moo/_string.lua create mode 100644 moo/moolua/lua/_moo/_table.lua create mode 100644 moo/moolua/lua/_moo/_util.lua create mode 100644 moo/moolua/lua/_moo/builtin.lua create mode 100644 moo/moolua/lua/_moo/os.lua create mode 100644 moo/moolua/lua/_moo/path.lua create mode 100644 moo/moolua/lua/lapi.c create mode 100644 moo/moolua/lua/lapi.h create mode 100644 moo/moolua/lua/lauxlib.c create mode 100644 moo/moolua/lua/lauxlib.h create mode 100644 moo/moolua/lua/lbaselib.c create mode 100644 moo/moolua/lua/lcode.c create mode 100644 moo/moolua/lua/lcode.h create mode 100644 moo/moolua/lua/ldblib.c create mode 100644 moo/moolua/lua/ldebug.c create mode 100644 moo/moolua/lua/ldebug.h create mode 100644 moo/moolua/lua/ldo.c create mode 100644 moo/moolua/lua/ldo.h create mode 100644 moo/moolua/lua/ldump.c create mode 100644 moo/moolua/lua/lfs.cpp create mode 100644 moo/moolua/lua/lfs.h create mode 100644 moo/moolua/lua/lfunc.c create mode 100644 moo/moolua/lua/lfunc.h create mode 100644 moo/moolua/lua/lgc.c create mode 100644 moo/moolua/lua/lgc.h create mode 100644 moo/moolua/lua/linit.c create mode 100644 moo/moolua/lua/liolib.c create mode 100644 moo/moolua/lua/llex.c create mode 100644 moo/moolua/lua/llex.h create mode 100644 moo/moolua/lua/llimits.h create mode 100644 moo/moolua/lua/lmathlib.c create mode 100644 moo/moolua/lua/lmem.c create mode 100644 moo/moolua/lua/lmem.h create mode 100644 moo/moolua/lua/loadlib.c create mode 100644 moo/moolua/lua/lobject.c create mode 100644 moo/moolua/lua/lobject.h create mode 100644 moo/moolua/lua/lopcodes.c create mode 100644 moo/moolua/lua/lopcodes.h create mode 100644 moo/moolua/lua/loslib.c create mode 100644 moo/moolua/lua/lparser.c create mode 100644 moo/moolua/lua/lparser.h create mode 100644 moo/moolua/lua/lstate.c create mode 100644 moo/moolua/lua/lstate.h create mode 100644 moo/moolua/lua/lstring.c create mode 100644 moo/moolua/lua/lstring.h create mode 100644 moo/moolua/lua/ltable.c create mode 100644 moo/moolua/lua/ltable.h create mode 100644 moo/moolua/lua/ltablib.c create mode 100644 moo/moolua/lua/ltm.c create mode 100644 moo/moolua/lua/ltm.h create mode 100644 moo/moolua/lua/lua.h create mode 100644 moo/moolua/lua/luaall.cpp create mode 100644 moo/moolua/lua/luaconf.h create mode 100644 moo/moolua/lua/lualib.h create mode 100644 moo/moolua/lua/lundump.c create mode 100644 moo/moolua/lua/lundump.h create mode 100644 moo/moolua/lua/lvm.c create mode 100644 moo/moolua/lua/lvm.h create mode 100644 moo/moolua/lua/lzio.c create mode 100644 moo/moolua/lua/lzio.h create mode 100644 moo/moolua/lua/moolua.cpp create mode 100644 moo/moolua/lua/moolua.h create mode 100644 moo/moolua/lua/slnudata.c create mode 100644 moo/moolua/lua/slnunico.c create mode 100644 moo/moolua/medit-lua.cpp create mode 100644 moo/moolua/medit-lua.h create mode 100644 moo/moolua/moo-lua-api-util.cpp create mode 100644 moo/moolua/moo-lua-api-util.h create mode 100644 moo/moolua/moo-lua-api.h create mode 100644 moo/moolua/moolua-tests.cpp create mode 100644 moo/moolua/moolua-tests.h create mode 100644 moo/moolua/mooluaplugin.cpp create mode 100644 moo/moopython/Makefile.incl create mode 100644 moo/moopython/codegen/README create mode 100644 moo/moopython/codegen/__init__.py create mode 100644 moo/moopython/codegen/argtypes.py create mode 100644 moo/moopython/codegen/argtypes_m.py create mode 100644 moo/moopython/codegen/codegen.py create mode 100644 moo/moopython/codegen/definitions.py create mode 100644 moo/moopython/codegen/defsparser.py create mode 100644 moo/moopython/codegen/docgen.py create mode 100644 moo/moopython/codegen/mergedefs.py create mode 100644 moo/moopython/codegen/mkskel.py create mode 100644 moo/moopython/codegen/override.py create mode 100644 moo/moopython/codegen/reversewrapper.py create mode 100644 moo/moopython/codegen/scmexpr.py create mode 100644 moo/moopython/medit-python-init.py create mode 100644 moo/moopython/medit-python.c create mode 100644 moo/moopython/medit-python.h create mode 100644 moo/moopython/moopython-api.h create mode 100644 moo/moopython/moopython-builtin.c create mode 100644 moo/moopython/moopython-builtin.h create mode 100644 moo/moopython/moopython-loader.c create mode 100644 moo/moopython/moopython-loader.h create mode 100644 moo/moopython/moopython-pygtkmod.h create mode 100644 moo/moopython/moopython-tests.c create mode 100644 moo/moopython/moopython-tests.h create mode 100644 moo/moopython/moopython-utils.c create mode 100644 moo/moopython/moopython-utils.h create mode 100644 moo/moopython/plugins/Makefile.incl create mode 100755 moo/moopython/plugins/autosave.ini create mode 100755 moo/moopython/plugins/autosave.py create mode 100644 moo/moopython/plugins/lib/insert_date_and_time.py create mode 100644 moo/moopython/plugins/lib/pyconsole.py create mode 100644 moo/moopython/plugins/medit/__init__.py create mode 100644 moo/moopython/plugins/medit/runpython.py create mode 100644 moo/moopython/plugins/pyproject/Makefile.ug create mode 100644 moo/moopython/plugins/pyproject/medit-mprj.xml.in create mode 100644 moo/moopython/plugins/pyproject/mprj/__init__.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/__init__.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/_config.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/_dict.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/_group.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/_item.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/_setting.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/_utils.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/_xml.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/test.py create mode 100644 moo/moopython/plugins/pyproject/mprj/config/view.py create mode 100644 moo/moopython/plugins/pyproject/mprj/factory.glade create mode 100644 moo/moopython/plugins/pyproject/mprj/factory.py create mode 100644 moo/moopython/plugins/pyproject/mprj/manager.py create mode 100644 moo/moopython/plugins/pyproject/mprj/optdialog.py create mode 100644 moo/moopython/plugins/pyproject/mprj/project.py create mode 100644 moo/moopython/plugins/pyproject/mprj/session.py create mode 100644 moo/moopython/plugins/pyproject/mprj/settings.py create mode 100644 moo/moopython/plugins/pyproject/mprj/simple.glade create mode 100644 moo/moopython/plugins/pyproject/mprj/simple.py create mode 100644 moo/moopython/plugins/pyproject/mprj/test.py create mode 100644 moo/moopython/plugins/pyproject/mprj/utils.glade create mode 100644 moo/moopython/plugins/pyproject/mprj/utils.py create mode 100644 moo/moopython/plugins/pyproject/project-plugin.ini.in.in create mode 100644 moo/moopython/plugins/pyproject/project-plugin.py create mode 100644 moo/moopython/plugins/pyproject/projects/c.py create mode 100644 moo/moopython/plugins/pyproject/projects/cproj/__init__.py create mode 100644 moo/moopython/plugins/pyproject/projects/cproj/config.py create mode 100644 moo/moopython/plugins/pyproject/projects/cproj/optdialog.py create mode 100644 moo/moopython/plugins/pyproject/projects/cproj/options.glade create mode 100644 moo/moopython/plugins/pyproject/projects/cproj/parser.py create mode 100644 moo/moopython/plugins/pyproject/projects/pyproj/__init__.py create mode 100644 moo/moopython/plugins/pyproject/projects/pyproj/config.py create mode 100644 moo/moopython/plugins/pyproject/projects/pyproj/optdialog.py create mode 100644 moo/moopython/plugins/pyproject/projects/pyproj/options.glade create mode 100644 moo/moopython/plugins/pyproject/projects/python.py create mode 100644 moo/moopython/plugins/pytest.py create mode 100644 moo/moopython/plugins/python.ini.in.in create mode 100644 moo/moopython/plugins/python.py create mode 100644 moo/moopython/plugins/simple.ini create mode 100644 moo/moopython/plugins/simple.py create mode 100644 moo/moopython/plugins/terminal.ini.in.in create mode 100644 moo/moopython/plugins/terminal.py create mode 100644 moo/moopython/pygtk/Makefile.incl create mode 100644 moo/moopython/pygtk/codeafter.c create mode 100644 moo/moopython/pygtk/codebefore.c create mode 100644 moo/moopython/pygtk/moo-pygtk.c create mode 100644 moo/moopython/pygtk/moo-pygtk.h create mode 100644 moo/moopython/pygtk/moo.override create mode 100644 moo/moopython/pygtk/moo.py create mode 100644 moo/moopython/pygtk/mooedit.override create mode 100644 moo/moopython/pygtk/moopaned.override create mode 100644 moo/moopython/pygtk/mooutils.override create mode 100644 moo/moopython/pymoo.ini.in create mode 100644 moo/mooutils/Makefile.incl create mode 100644 moo/mooutils/glade/mooaccelbutton.glade create mode 100644 moo/mooutils/glade/mooaccelprefs.glade create mode 100644 moo/mooutils/glade/moologwindow.glade create mode 100644 moo/mooutils/moo-environ.h create mode 100644 moo/mooutils/moo-intltool-merge create mode 100644 moo/mooutils/moo-intltool-merge.cmake create mode 100644 moo/mooutils/moo-mime.c create mode 100644 moo/mooutils/moo-mime.h create mode 100644 moo/mooutils/moo-test-macros.h create mode 100644 moo/mooutils/moo-test-utils.c create mode 100644 moo/mooutils/moo-test-utils.h create mode 100644 moo/mooutils/mooaccel.c create mode 100644 moo/mooutils/mooaccel.h create mode 100644 moo/mooutils/mooaccelbutton.c create mode 100644 moo/mooutils/mooaccelbutton.h create mode 100644 moo/mooutils/mooaccelprefs.c create mode 100644 moo/mooutils/mooaccelprefs.h create mode 100644 moo/mooutils/mooaction-private.h create mode 100644 moo/mooutils/mooaction.c create mode 100644 moo/mooutils/mooaction.h create mode 100644 moo/mooutils/mooactionbase-private.h create mode 100644 moo/mooutils/mooactionbase.c create mode 100644 moo/mooutils/mooactionbase.h create mode 100644 moo/mooutils/mooactioncollection.c create mode 100644 moo/mooutils/mooactioncollection.h create mode 100644 moo/mooutils/mooactionfactory.c create mode 100644 moo/mooutils/mooactionfactory.h create mode 100644 moo/mooutils/mooactiongroup.c create mode 100644 moo/mooutils/mooactiongroup.h create mode 100644 moo/mooutils/mooapp-ipc.c create mode 100644 moo/mooutils/mooapp-ipc.h create mode 100644 moo/mooutils/mooappinput.c create mode 100644 moo/mooutils/mooappinput.h create mode 100644 moo/mooutils/mooarray.h create mode 100644 moo/mooutils/mooatom.h create mode 100644 moo/mooutils/moobigpaned.c create mode 100644 moo/mooutils/moobigpaned.h create mode 100644 moo/mooutils/mooclosure.c create mode 100644 moo/mooutils/mooclosure.h create mode 100644 moo/mooutils/moocombo.c create mode 100644 moo/mooutils/moocombo.h create mode 100644 moo/mooutils/moocompat.h create mode 100644 moo/mooutils/moodialogs.c create mode 100644 moo/mooutils/moodialogs.h create mode 100644 moo/mooutils/mooeditops.c create mode 100644 moo/mooutils/mooeditops.h create mode 100644 moo/mooutils/mooencodings-data.h create mode 100644 moo/mooutils/mooencodings.c create mode 100644 moo/mooutils/mooencodings.h create mode 100644 moo/mooutils/mooentry.c create mode 100644 moo/mooutils/mooentry.h create mode 100644 moo/mooutils/moofiledialog.c create mode 100644 moo/mooutils/moofiledialog.h create mode 100644 moo/mooutils/moofileicon.c create mode 100644 moo/mooutils/moofileicon.h create mode 100644 moo/mooutils/moofilewatch.c create mode 100644 moo/mooutils/moofilewatch.h create mode 100644 moo/mooutils/moofilewriter-private.h create mode 100644 moo/mooutils/moofilewriter.c create mode 100644 moo/mooutils/moofilewriter.h create mode 100644 moo/mooutils/moofiltermgr.c create mode 100644 moo/mooutils/moofiltermgr.h create mode 100644 moo/mooutils/moofontsel.c create mode 100644 moo/mooutils/moofontsel.h create mode 100644 moo/mooutils/mooglade.c create mode 100644 moo/mooutils/mooglade.h create mode 100644 moo/mooutils/moohelp.c create mode 100644 moo/mooutils/moohelp.h create mode 100644 moo/mooutils/moohistorycombo.c create mode 100644 moo/mooutils/moohistorycombo.h create mode 100644 moo/mooutils/moohistorylist.c create mode 100644 moo/mooutils/moohistorylist.h create mode 100644 moo/mooutils/moohistorymgr.c create mode 100644 moo/mooutils/moohistorymgr.h create mode 100644 moo/mooutils/mooi18n.c create mode 100644 moo/mooutils/mooi18n.h create mode 100644 moo/mooutils/moolist.h create mode 100644 moo/mooutils/moomarkup.c create mode 100644 moo/mooutils/moomarkup.h create mode 100644 moo/mooutils/moomenu.c create mode 100644 moo/mooutils/moomenu.h create mode 100644 moo/mooutils/moomenuaction.c create mode 100644 moo/mooutils/moomenuaction.h create mode 100644 moo/mooutils/moomenumgr.c create mode 100644 moo/mooutils/moomenumgr.h create mode 100644 moo/mooutils/moomenutoolbutton.c create mode 100644 moo/mooutils/moomenutoolbutton.h create mode 100644 moo/mooutils/moonotebook.c create mode 100644 moo/mooutils/moonotebook.h create mode 100644 moo/mooutils/mooonce.h create mode 100644 moo/mooutils/moopane.c create mode 100644 moo/mooutils/moopane.h create mode 100644 moo/mooutils/moopaned.c create mode 100644 moo/mooutils/moopaned.h create mode 100644 moo/mooutils/mooprefs.c create mode 100644 moo/mooutils/mooprefs.h create mode 100644 moo/mooutils/mooprefsdialog.c create mode 100644 moo/mooutils/mooprefsdialog.h create mode 100644 moo/mooutils/mooprefspage.c create mode 100644 moo/mooutils/mooprefspage.h create mode 100644 moo/mooutils/moospawn.c create mode 100644 moo/mooutils/moospawn.h create mode 100644 moo/mooutils/moostat.h create mode 100644 moo/mooutils/moostock.c create mode 100644 moo/mooutils/moostock.h create mode 100644 moo/mooutils/mootype-macros.h create mode 100644 moo/mooutils/mootypedecl-macros.h create mode 100644 moo/mooutils/moouixml.c create mode 100644 moo/mooutils/moouixml.h create mode 100644 moo/mooutils/mooundo.c create mode 100644 moo/mooutils/mooundo.h create mode 100644 moo/mooutils/mooutils-debug.h create mode 100644 moo/mooutils/mooutils-enums.c create mode 100644 moo/mooutils/mooutils-enums.h create mode 100644 moo/mooutils/mooutils-enums.tmpl.c create mode 100644 moo/mooutils/mooutils-enums.tmpl.h create mode 100644 moo/mooutils/mooutils-file.c create mode 100644 moo/mooutils/mooutils-file.h create mode 100644 moo/mooutils/mooutils-fs.c create mode 100644 moo/mooutils/mooutils-fs.h create mode 100644 moo/mooutils/mooutils-gobject-private.h create mode 100644 moo/mooutils/mooutils-gobject.c create mode 100644 moo/mooutils/mooutils-gobject.h create mode 100644 moo/mooutils/mooutils-macros.h create mode 100644 moo/mooutils/mooutils-mem.h create mode 100644 moo/mooutils/mooutils-messages.h create mode 100644 moo/mooutils/mooutils-misc.c create mode 100644 moo/mooutils/mooutils-misc.h create mode 100644 moo/mooutils/mooutils-script.c create mode 100644 moo/mooutils/mooutils-script.h create mode 100644 moo/mooutils/mooutils-tests.h create mode 100644 moo/mooutils/mooutils-thread.c create mode 100644 moo/mooutils/mooutils-thread.h create mode 100644 moo/mooutils/mooutils-treeview.c create mode 100644 moo/mooutils/mooutils-treeview.h create mode 100644 moo/mooutils/mooutils-win32.c create mode 100644 moo/mooutils/mooutils.h create mode 100644 moo/mooutils/moowin32/mingw/fnmatch.h create mode 100644 moo/mooutils/moowin32/mingw/netinet/in.h create mode 100644 moo/mooutils/moowin32/mingw/sys/mman.h create mode 100644 moo/mooutils/moowin32/ms/sys/time.h create mode 100644 moo/mooutils/moowin32/ms/unistd.h create mode 100644 moo/mooutils/moowindow.c create mode 100644 moo/mooutils/moowindow.h create mode 100644 moo/mooutils/pixmaps/attach.png create mode 100644 moo/mooutils/pixmaps/close.png create mode 100644 moo/mooutils/pixmaps/detach.png create mode 100644 moo/mooutils/pixmaps/hide.png create mode 100644 moo/mooutils/pixmaps/keepontop.png create mode 100644 moo/mooutils/pixmaps/medit.ico create mode 100644 moo/mooutils/pixmaps/medit.png create mode 100644 moo/mooutils/pixmaps/sticky.png create mode 100644 moo/mooutils/pixmaps/symlink-small.png create mode 100644 moo/mooutils/pixmaps/symlink.png create mode 100644 moo/mooutils/stock-file-24.h create mode 100644 moo/mooutils/stock-file-selector-24.h create mode 100644 moo/mooutils/stock-terminal-24.h create mode 100644 moo/plugins/Makefile.incl create mode 100644 moo/plugins/ctags/Makefile.incl create mode 100644 moo/plugins/ctags/ctags-doc.c create mode 100644 moo/plugins/ctags/ctags-doc.h create mode 100644 moo/plugins/ctags/ctags-plugin.c create mode 100644 moo/plugins/ctags/ctags-view.c create mode 100644 moo/plugins/ctags/ctags-view.h create mode 100644 moo/plugins/ctags/readtags-mangle.h create mode 100644 moo/plugins/ctags/readtags.c create mode 100644 moo/plugins/ctags/readtags.h create mode 100644 moo/plugins/glade/moofileselector-prefs.glade create mode 100644 moo/plugins/glade/moofileselector.glade create mode 100644 moo/plugins/glade/moofind.glade create mode 100644 moo/plugins/glade/moogrep.glade create mode 100644 moo/plugins/moofilelist.c create mode 100644 moo/plugins/moofileselector-prefs.c create mode 100644 moo/plugins/moofileselector.c create mode 100644 moo/plugins/moofileselector.h create mode 100644 moo/plugins/moofind.c create mode 100644 moo/plugins/mooplugin-builtin.c create mode 100644 moo/plugins/mooplugin-builtin.h create mode 100644 moo/plugins/old/moopythonplugin.c create mode 100644 moo/plugins/support/Makefile.incl create mode 100644 moo/plugins/support/moocmdview.c create mode 100644 moo/plugins/support/moocmdview.h create mode 100644 moo/plugins/support/mooeditwindowoutput.c create mode 100644 moo/plugins/support/mooeditwindowoutput.h create mode 100644 moo/plugins/support/moolineview.c create mode 100644 moo/plugins/support/moolineview.h create mode 100644 moo/plugins/support/moooutputfilter.c create mode 100644 moo/plugins/support/moooutputfilter.h create mode 100644 moo/plugins/usertools/Makefile.incl create mode 100644 moo/plugins/usertools/context-tmpl.xml create mode 100644 moo/plugins/usertools/filters.xml create mode 100644 moo/plugins/usertools/genplatxml.py create mode 100644 moo/plugins/usertools/glade/mooedittools-exe.glade create mode 100644 moo/plugins/usertools/glade/mooedittools-script.glade create mode 100644 moo/plugins/usertools/glade/moousertools.glade create mode 100644 moo/plugins/usertools/lua-tool-setup.lua create mode 100644 moo/plugins/usertools/menu-tmpl.xml create mode 100644 moo/plugins/usertools/moocommand-exe.c create mode 100644 moo/plugins/usertools/moocommand-exe.h create mode 100644 moo/plugins/usertools/moocommand-private.h create mode 100644 moo/plugins/usertools/moocommand-script.cpp create mode 100644 moo/plugins/usertools/moocommand-script.h create mode 100644 moo/plugins/usertools/moocommand.c create mode 100644 moo/plugins/usertools/moocommand.h create mode 100644 moo/plugins/usertools/moocommanddisplay.c create mode 100644 moo/plugins/usertools/moocommanddisplay.h create mode 100644 moo/plugins/usertools/moooutputfilterregex.c create mode 100644 moo/plugins/usertools/moooutputfilterregex.h create mode 100644 moo/plugins/usertools/moousertools-enums-in.py create mode 100644 moo/plugins/usertools/moousertools-enums.c create mode 100644 moo/plugins/usertools/moousertools-enums.h create mode 100644 moo/plugins/usertools/moousertools-prefs.c create mode 100644 moo/plugins/usertools/moousertools-prefs.h create mode 100644 moo/plugins/usertools/moousertools.c create mode 100644 moo/plugins/usertools/moousertools.h create mode 100644 moo/plugins/usertools/python-tool-setup.py create mode 100644 moo/xdgmime/Makefile.incl create mode 100644 moo/xdgmime/README create mode 100644 moo/xdgmime/xdgmime.c create mode 100644 moo/xdgmime/xdgmime.h create mode 100644 moo/xdgmime/xdgmimealias.c create mode 100644 moo/xdgmime/xdgmimealias.h create mode 100644 moo/xdgmime/xdgmimecache.c create mode 100644 moo/xdgmime/xdgmimecache.h create mode 100644 moo/xdgmime/xdgmimeglob.c create mode 100644 moo/xdgmime/xdgmimeglob.h create mode 100644 moo/xdgmime/xdgmimeicon.c create mode 100644 moo/xdgmime/xdgmimeicon.h create mode 100644 moo/xdgmime/xdgmimeint.c create mode 100644 moo/xdgmime/xdgmimeint.h create mode 100644 moo/xdgmime/xdgmimemagic.c create mode 100644 moo/xdgmime/xdgmimemagic.h create mode 100644 moo/xdgmime/xdgmimeparent.c create mode 100644 moo/xdgmime/xdgmimeparent.h create mode 100644 plat/win32/Makefile.incl create mode 100644 plat/win32/gtk-win/Makefile create mode 100644 plat/win32/gtk-win/config.sh create mode 100755 plat/win32/gtk-win/copybin.sh create mode 100755 plat/win32/gtk-win/copysrc.sh create mode 100755 plat/win32/gtk-win/copysrcdbg.sh create mode 100755 plat/win32/gtk-win/copyuser.sh create mode 100755 plat/win32/gtk-win/extra/medit-data/bin/grep.exe create mode 100755 plat/win32/gtk-win/extra/medit-data/bin/intl.dll create mode 100644 plat/win32/gtk-win/extra/medit-data/etc/gtk-2.0/gtkrc create mode 100644 plat/win32/gtk-win/extra/readme-medit-deps.txt create mode 100644 plat/win32/gtk-win/jhbuildrc/glib-win32.cache create mode 100644 plat/win32/gtk-win/jhbuildrc/gtk.moduleset create mode 100644 plat/win32/gtk-win/jhbuildrc/jhbuildrc create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/cairo-1.10.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/gettext-0.17.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/gettext.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glade3-3.5.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glade3.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-2.22.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-2.24.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-2.26.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-2.28-spawn-win32-moo.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-2.28-spawn-win32.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-2.28.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-2.30-spawn-win32-moo.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-2.30-spawn-win32.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-2.30.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-spawn-win32-moo.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/glib-spawn-win32.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/grep-2.7.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/grep.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/gtk-2.16.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/gtk-2.18.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/gtk-2.20.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/gtk-2.22.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/gtk-2.24.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/icon-naming-utils.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/intltool.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/libpng.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/libxml2.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/pango-1.28.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/pixman-0.17.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/pkgconfig.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/pygobject-2.26.0.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/pygtk-2.16.0.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/tango-icon-theme.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/zlib-1.2.5.patch create mode 100644 plat/win32/gtk-win/jhbuildrc/patches/zlib.patch create mode 100755 plat/win32/gtk-win/make.sh create mode 100755 plat/win32/gtk-win/mjhbuild.sh create mode 100644 plat/win32/gtk-win/readme-win32-build.txt create mode 100644 plat/win32/installer.iss.in create mode 100644 plat/win32/istrans.sh create mode 100644 plat/win32/makepythondist.py create mode 100755 plat/win32/mingw-configure create mode 100644 plat/win32/sitecustomize.py create mode 100644 po-gsv/ChangeLog create mode 100644 po-gsv/LINGUAS create mode 100644 po-gsv/Makefile.am create mode 100644 po-gsv/POTFILES.in create mode 100644 po-gsv/POTFILES.skip create mode 100644 po-gsv/ar.po create mode 100644 po-gsv/as.po create mode 100644 po-gsv/ast.po create mode 100644 po-gsv/az.po create mode 100644 po-gsv/be.po create mode 100644 po-gsv/bg.po create mode 100644 po-gsv/bn.po create mode 100644 po-gsv/bn_IN.po create mode 100644 po-gsv/bs.po create mode 100644 po-gsv/ca.po create mode 100644 po-gsv/ca@valencia.po create mode 100644 po-gsv/cs.po create mode 100644 po-gsv/cy.po create mode 100644 po-gsv/da.po create mode 100644 po-gsv/de.po create mode 100644 po-gsv/dz.po create mode 100644 po-gsv/el.po create mode 100644 po-gsv/en@shaw.po create mode 100644 po-gsv/en_CA.po create mode 100644 po-gsv/en_GB.po create mode 100644 po-gsv/es.po create mode 100644 po-gsv/et.po create mode 100644 po-gsv/eu.po create mode 100644 po-gsv/fa.po create mode 100644 po-gsv/fi.po create mode 100644 po-gsv/fr.po create mode 100644 po-gsv/ga.po create mode 100644 po-gsv/gl.po create mode 100644 po-gsv/gu.po create mode 100644 po-gsv/he.po create mode 100644 po-gsv/hi.po create mode 100644 po-gsv/hr.po create mode 100644 po-gsv/hu.po create mode 100644 po-gsv/id.po create mode 100644 po-gsv/it.po create mode 100644 po-gsv/ja.po create mode 100644 po-gsv/kn.po create mode 100644 po-gsv/ko.po create mode 100644 po-gsv/lt.po create mode 100644 po-gsv/lv.po create mode 100644 po-gsv/mai.po create mode 100755 po-gsv/maintain create mode 100644 po-gsv/mg.po create mode 100644 po-gsv/mk.po create mode 100644 po-gsv/ml.po create mode 100644 po-gsv/mn.po create mode 100644 po-gsv/mr.po create mode 100644 po-gsv/ms.po create mode 100644 po-gsv/msgs.py create mode 100644 po-gsv/nb.po create mode 100644 po-gsv/ne.po create mode 100644 po-gsv/nl.po create mode 100644 po-gsv/nn.po create mode 100644 po-gsv/oc.po create mode 100644 po-gsv/or.po create mode 100644 po-gsv/pa.po create mode 100644 po-gsv/pl.po create mode 100644 po-gsv/pt.po create mode 100644 po-gsv/pt_BR.po create mode 100644 po-gsv/ro.po create mode 100644 po-gsv/ru.po create mode 100644 po-gsv/rw.po create mode 100644 po-gsv/si.po create mode 100644 po-gsv/sk.po create mode 100644 po-gsv/sl.po create mode 100644 po-gsv/sq.po create mode 100644 po-gsv/sr.po create mode 100644 po-gsv/sr@Latn.po create mode 100644 po-gsv/sr@latin.po create mode 100755 po-gsv/strip.sh create mode 100644 po-gsv/sv.po create mode 100644 po-gsv/ta.po create mode 100644 po-gsv/te.po create mode 100644 po-gsv/th.po create mode 100644 po-gsv/tr.po create mode 100644 po-gsv/ug.po create mode 100644 po-gsv/uk.po create mode 100644 po-gsv/vi.po create mode 100644 po-gsv/xh.po create mode 100644 po-gsv/zh_CN.po create mode 100644 po-gsv/zh_HK.po create mode 100644 po-gsv/zh_TW.po create mode 100644 po/LINGUAS create mode 100644 po/Makefile.am create mode 100644 po/POTFILES.in create mode 100644 po/POTFILES.skip create mode 100644 po/cs.po create mode 100644 po/de.po create mode 100644 po/es.po create mode 100644 po/fi.po create mode 100644 po/fr.po create mode 100644 po/ja.po create mode 100755 po/maintain create mode 100644 po/medit-1.pot create mode 100644 po/nl.po create mode 100644 po/ru.po create mode 100644 po/zh_CN.po create mode 100755 tools/checkglade create mode 100755 tools/fixglade create mode 100644 tools/genenums.py create mode 100644 tools/glade2c.py create mode 100644 tools/medit.supp create mode 100755 tools/run-valgrind.sh create mode 100755 tools/test-release.sh create mode 100644 tools/xml2h.py diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000..86f9b967 --- /dev/null +++ b/.hgignore @@ -0,0 +1,50 @@ +syntax: glob +Makefile.in +depcomp +compile +config.guess +config.sub +configure +doc/help.stamp +install-sh +ltmain.sh +m4/intltool.m4 +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 +missing +po-gsv/Makefile.in.in +po/Makefile.in.in +build/ +autom4te.cache/ +aclocal.m4 +*~ +*.orig +*.bak +*.tmp +*.rej +*.pyc +.goutputstream-* +po*/dist +po*/pot +po/notexist +po/missing +config.h.in +plat/win32/gtk-win/tarballs/ +plat/win32/gtk-win/debug/ +plat/win32/gtk-win/release/ +plat/win32/gtk-win/bdist-debug/ +plat/win32/gtk-win/bdist-release/ +api/moo.xml +api/gtk.xml +moo/plugins/usertools/old/ +moo/moopython/pygtk/moo-generated.defs +moo/moopython/plugins/old/ +moo/moolua/moo-lua-api.cpp +moo/moolua/gtk-lua-api.cpp +doc/help/ +doc/built/ +po-gsv/po-original/ +po-gsv/po-stripped/ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..a13e3ad1 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Yevgen Muntyan +Daniel Poelzleithner diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..5ab7695a --- /dev/null +++ b/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/COPYING.GPL b/COPYING.GPL new file mode 100644 index 00000000..d511905c --- /dev/null +++ b/COPYING.GPL @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/INSTALL b/INSTALL new file mode 100644 index 00000000..16b9c755 --- /dev/null +++ b/INSTALL @@ -0,0 +1,24 @@ +medit uses autotools for the build. Quick build with default options: + +tar xjf medit-x.x.x.tar.bz2 +cd medit-x.x.x +./configure +make +make install + +This will configure, build, and install medit to /usr/local. + +If you checked out a copy of mercurial repository, then do the following to build: + +./autogen.sh +./configure --enable-dev-mode +make +make install + +You must have docbook, xsltproc, txt2tags to build medit from mercurial. Other +tools may be required and configure may not check for them, please report those +to the author. + +To build a python module which could be used in other programs, use +--enable-moo-module --enable-shared --disable-static configure flags in addition +to above. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f206a9a1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,85 @@ +medit is distributed under GPL version 2 licence, its text +you can find in COPYING.GPL file. Nevertheless, everything but +several lang files (see the list below) is under LGPL version +2.1 or "version 2.1 or later", see COPYING file for its text. + +Portions of code are written not by me, those are clearly +marked as such (copyright headers in files copied from other +projects, or comments in source). Below is the list of +third-party files/code with their copyrights and licenses. + +If you want to use my code and LGPL doesn't work for you +because you have special clause in your license which is not +LGPL-compatible, or other garbage like that, I will gladly +release it under double license, or do something like that. +Just tell me what exactly is needed. + +If you aren't too picky about legal stuff, then you can simply +take code and use it in free software as you like, provided +you leave the copyright and project name in there (unless it's a +small portion of code of course, simple copy/paste must be free +as in WTFPL). This applies to all files (and only to those) which +do have copyright notice inside; WTFPL will work fine for the rest. + + +Third-party stuff, for debian/copyright. Everything below +is under LGPL unless noted otherwise. + +moo/moolua/lua/*: + Lua, LuaFileSystem. Distributed under MIT + license, see moo/moolua/COPYRIGHT, moo/moolua/ext/README.lfs. + Copyright (C) 1994-2007 Lua.org, PUC-Rio. + Copyright 2003-2007 PUC-Rio + +moo/mooedit/plugins/ctags/readtags.[hc] + Exuberant Ctags code, public domain + Copyright (c) 1996-2003, Darren Hiebert + +moo/mooutils/moofontsel.h: +moo/mooutils/moofontsel.c: + GTK font selector modified to show monospace fonts + (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + +moo/mooutils/mooencodings-data.h: + (C) 2002 Red Hat, Inc. + (C) 2000-2002 Free Software Foundation, Inc. + +moo/mooapp/smclient/*: - eggsmclient library + (C) 2007 Novell, Inc. + +moo/mooutils/pcre/*: pcre library by by Philip Hazel + BSD-licensed, (C) 1997-2006 University of Cambridge + +moo/mooedit/gtksourceview/upstream/*: +moo/mooedit/language-specs/*: + gtksourceview library + Written over many years by many people, see the files. + + Among those, GPL'ed files: + moo/mooedit/language-specs/gtkrc.lang + moo/mooedit/language-specs/ini.lang + moo/mooedit/language-specs/lua.lang + moo/mooedit/language-specs/msil.lang + moo/mooedit/language-specs/nemerle.lang + moo/mooedit/language-specs/pascal.lang + moo/mooedit/language-specs/php.lang + moo/mooedit/language-specs/R.lang + moo/mooedit/language-specs/ruby.lang + +moo/moopython/codegen/*: pygtk codegen + (C) 2004 Gustavo Carneiro + (C) ???? James Henstridge + (C) ???? Johan Dahlin + +moo/mooutils/xdgmime/*: xdgmime library + (C) 2003,2004 Red Hat, Inc. + (C) 2003,2004 Jonathan Blandford + +moo/xdg-utils: + Looks like some sort of BSD license + (C) 2006 Kevin Krammer + (C) 2006 Jeremy White + +moo/mooutils/pixmaps/medit.png: + xedit.png icon from Crystal Clear icon theme by Everaldo Coelho, + http://www.everaldo.com diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..55c14bb3 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,43 @@ +ACLOCAL_AMFLAGS = -I m4 $(ACLOCAL_FLAGS) + +SUBDIRS = po po-gsv api doc moo + +EXTRA_DIST = \ + tools/genenums.py \ + tools/glade2c.py \ + tools/xml2h.py + +DISTCHECK_CONFIGURE_FLAGS = +if MOO_DEV_MODE +DISTCHECK_CONFIGURE_FLAGS += MAKEFLAGS=-j3 +endif +if MOO_STRICT_MODE +DISTCHECK_CONFIGURE_FLAGS += --enable-strict +endif + +CLEANFILES = + +include plat/win32/Makefile.incl + +if MOO_OS_WIN32 +CLEANFILES += plat/win32/installer.iss +plat/win32/installer.iss: $(top_srcdir)/plat/win32/installer.iss.in $(top_builddir)/config.status + $(AM_V_at)$(MKDIR_P) plat/win32 + $(AM_V_GEN)cd $(top_builddir) && ./config.status --silent --file=plat/win32/installer.iss +installer: plat/win32/installer.iss + $(MAKE) $(AM_MAKEFLAGS) install + $(MEDIT_INNO_COMPILER) plat/win32/installer.iss +endif + +test: + $(MAKE) $(AM_MAKEFLAGS) all + $(MAKE) $(AM_MAKEFLAGS) check + $(MAKE) $(AM_MAKEFLAGS) uninstall + $(MAKE) $(AM_MAKEFLAGS) check + $(MAKE) $(AM_MAKEFLAGS) install + $(MAKE) $(AM_MAKEFLAGS) installcheck +fullcheck: + $(MAKE) $(AM_MAKEFLAGS) test + $(MAKE) $(AM_MAKEFLAGS) distcheck + +@MOO_PO_SUBDIRS_RULE@ diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..94f094ba --- /dev/null +++ b/NEWS @@ -0,0 +1,622 @@ +2012-03-04 Yevgen Muntyan + + * === Released 1.1.0 === + + Fixed few memory leaks. + Improved documentation. + Made lua scripts use UTF-8 strings by default. + Fixed font selector in terminal pane. + Fixed clicks and drags with Shift key pressed. + Store 'Show hidden files' setting of file selector in preferences. + Added Japanese translation by Toshiharu Kudoh. + Updated Spanish translation by Sebikul. + Added Finnish translation by Tommi Nieminen. + +2011-10-23 Yevgen Muntyan + + * === Released 1.0.5 === + + Fixed a bug where filename "12d1:1446" was treated as file "12d1" + and line number 1446. + Unbroke keyboard shortcuts when running under gtk-2.24.7. + +2011-09-11 Yevgen Muntyan + + * === Released 1.0.4 === + + Added protobuf lang file by Pavel Artyomkin. + +2011-04-14 Yevgen Muntyan + + * === Released 1.0.3 === + + Fixed "Move to Split Notebook" command. + +2011-04-04 Yevgen Muntyan + + * === Released 1.0.2 === + + Fixed importing pygobject and pygtk with python-2.7 (at least on + openSUSE newer pygtk in medit was broken). + +2011-02-26 Yevgen Muntyan + + * === Released 1.0.1 === + + Updated Russian translation. + Fixed a crash on windows when syntax highlighting gets disabled + because of very long lines. + Updated ini.lang. + +2011-01-29 Yevgen Muntyan + + * === Released 1.0.0 === + + New release which breaks backwards compatibility (which won't be + broken again for long time, not until medit-2.0). Bugs have been + fixed, features have been implemented, new bugs have been introduced. + + Highlights: + + - Consistent API for Python and Lua scripts, generated from + source code, together with documentation. This allows for + much more powerful Lua scripts. + - Added option to show all spaces, not just Tab characters. + - Allow searching in a list of directories instead of just a single one. + - Added --geometry command line option. + - Added split views - multiple views of the same document in one + notebook tab. + - Windows can display two notebooks to allow viewing two different + documents at the same as well. + - Build system uses autotools again. Cmake is nice but not nice enough. + +2010-10-05 Yevgen Muntyan + + * === Released 0.10.5 === + + Added an option to show spaces, not just tabs. + Added linker flags to fix build on some Linux distributions. + Fixed bug where Select All menu item wouldn't select text on + some builds (found by Andrey Frantsuzov). + +2010-04-29 Yevgen Muntyan + + * === Released 0.10.4 === + + Python plugins are back. + +2010-04-16 Yevgen Muntyan + + * === Released 0.10.3 === + + Fixed crash in Find File command. + + Windows: downgraded gtk to 2.16.6 because of problems with + "client-side windows" feature. Fixed a bug with synaptics + touchpad where scrolling would stop working after a dialog + was shown. + +2010-04-14 Yevgen Muntyan + + * === Released 0.10.2 === + + Added back man page, fixed compilation on systems where cmake + builds dynamic libraries by default. + + On windows, made medit use case-insensitive filename comparison + and fixed scrolling with a Synaptics touchpad. + +2010-03-19 Yevgen Muntyan + + * === Released 0.10.1 === + + Restored lost ctags plugin. Added simple installation + instructions. + +2010-02-28 Yevgen Muntyan + + * === Released 0.10.0 === + +2008-08-29 Yevgen Muntyan + + * === Released 0.9.4 === + + Corrected a brain dead logic error which led to data loss: + medit did not report a proper error when it could not convert + content to requested encoding (e.g. when trying to save text + with accented characters in ASCII). Thanks to LoneFox. + + Updated and corrected documentation. + Fixed recent files menu where underscore in filenames showed + up as mnemonics underline. + Added recent files dialog. + Fixed saving session on logout. + Made open-dialog-follows-doc option apply to Save As too. + Much improved Mac OS X port (which requires much improved Mac OS X + Gtk port from SVN). + Once again fixed document tab icon appearance. + Made tilde expansion work in Find in Files dialog. + Fixed bug in Preferences dialog caused by a buggy Ubuntu Gtk patch. + Worked around freezing when editing a document with very long lines. + Added Open With Default Application to file selector menu. + Made ctags plugin not try to run ctags on a non-existent file. + User data files on windows are now stored in /Application Data + instead of the home folder, medit will move old files on startup. + Fixed win32 bug with saving session file. + Lot of lang files were improved. + + Added Dutch translation by Kris Van Bruwaene. + Updated German translation by Christian Dywan. + +2008-02-10 Yevgen Muntyan + + * === Released 0.9.3 === + + Fixed double-click glitch in the file selector. + Made file list remember expanded and collapsed rows. + Implemented Find in Files on windows. + Fixed handling UNC paths on windows. + GtkRecent*, which caused freezing on load and save, + is no longer used. + Made ctags plugin enabled by default. + Fixed deleting folders on windows. + + Updated German translation by Christian Dywan. + +2008-01-10 Yevgen Muntyan + + * === Released 0.9.2 === + + Fixed broken windows build. + +2008-01-09 Yevgen Muntyan + + * === Released 0.9.1 === + + Added more editor functions for Lua scripts. + Made building with --disable-nls work again. + Fixed alternating button order in Find dialog; use it + in more dialogs (LoneFox). + Fixed a segfault in user tools preferences. + Small buttons in the notebook and window panes follow + gtk theme. + Notebook tabs no longer scroll on closing document. + Nicer arrow buttons in notebook. + configure doesn't fail to detect pygtk when pycairo-dev + is absent. + Added a setting to ensure trailing newline character + on save. + Emacs-like filename:line strings are now recognized by + default in shell tools output. + File list plugin. + Function list plugin (Christian Dywan). + File selector allows selecting by dragging the mouse + and DND for multiple items. + Window panes configurations is now saved and restored + properly; panes may be re-arranged by DND. + Added color schemes for the terminal. + Fixed the bug when the toolbar didn't pick up default gtk + style. + Fixed all memory leaks found by valgrind. + + Updated French translation by Collilieux. + Updated Spanish translation by Arnau Sanchez. + Updated German translation by Christian Dywan. + Updated Chinese translation by Chaosye. + + Special thanks to Christian Dywan. + +2007-11-29 Yevgen Muntyan + * === Released 0.9.0 === + + Replaced MooScript with Lua. + Improved user-defined tools, made it possible to store + tools in separate files instead of creating and editing + them via Preferences dialog. + Added online help. + Rearranged menu items, removed Settings menu. + Added a setting for window title format. + Made Preferences dialog remember its size to make + editing scripts more comfortable. + Fixed the bug with storing clipboard contents on exit. + Fixed files drag'n'drop on windows. + Improved syntax highlighting. + medit now reads meditrc files in data directories, so it's + possible to have pre-set options different from defaults. + Fixed Find in Files and Find File menu items broken on + some systems. + Fixed highlighting current line and behavior of Home/End + keys when line wrapping is enabled. + Fixed entries for language extensions and mime types in the + Preferences dialog. + Added a command-line option to open documents in a new window. + + Czech translation by Vlastimil Ott. + Updated German translation by Christian Dywan. + +2007-08-07 Yevgen Muntyan + * === Released 0.8.10 === + + Fixed numerous memory leaks. + Fixed an issue with indentation when pressing Enter key + on an empty line. + Fixed tooltips on toolbar, once again, now for gtk-2.10. + Added a setting for what directory should be shown in the + Open dialog. + +2007-07-31 Yevgen Muntyan + * === Released 0.8.9 === + + Russian translation by Andrey Fedoseev. + Spanish translation by Arnau Sanchez. + + Added a setting to disable sessions (Joris_M). + Added a setting to draw right margin in text, as in gedit. + Unbroke loading old lang files. + Fixed some bugs in notebook tabs drawing. + Re-added --with-broken-gtk-theme configure option for Suse. + Do not ignore displayed tab width when printing. + Worked around crash with Export as PDF and gtk-2.10. + + New and improved syntax highlighting: + c, css, pkgconfig. + +2007-07-10 Yevgen Muntyan + * === Released 0.8.8 === + + New command-line option --app-name: instances with different names + do not share sessions, and --app-name works as --pid argument when + an instance with given name is already running. + Added hidden setting for width of displayed Tab characters. + Fixed theming bug in the paned widget. + Improved Preferences dialog. + Updated German translation (Christian Dywan). + + New and improved syntax highlighting: + ada, awk, c, changelog, cpp, haskell, html, java, lua, m4, + objc, ocl, ruby, scheme, sh. + +2007-06-24 Yevgen Muntyan + * === Released 0.8.7 === + + Implemented session support. + Use xdg-open to open files from file selector. + Fixed build on darwin. + Added translation for language files. + + Changed license of almost everything to LGPL. + +2007-06-14 Yevgen Muntyan + * === Released 0.8.6 === + + Chinese translation by Chaosye. + German translation by Christian Dywan. + + User config and data files are moved into ~/.config, ~/.share, etc., + according to XDG specification. medit will move old files into new + locations on startup, so transition should be smooth and invisible (Oliwer). + + Improved file encodings handling, and fixed some bugs related to it. + Made it use more stock items and icons from icon themes. + Improved paned widget behavior, no more nasty drawing artifacts. + Fixed Find dialog which treated replacement text as a regular + expression. + Made editor recognize "nxml" emacs mode as xml. + freedesktop.org mime type database is used now on windows, so it knows + how to highlight files of common types. + 'medit filename' behaves better when another isntance is already running. + Underline character in a filename is no longer treated as mnemonic + underline in the Window menu. + Fixed tab icons background bug (Christian Dywan). + + Improved syntax highlighting: + c csharp css desktop fortran gap gtkrc idl java javascript latex m4 + ocaml octave pascal perl pkgconfig po ruby scheme sh sql tcl verilog + vhdl xml + New syntax highlighting: + docbook ocaml spec + +2007-04-09 Yevgen Muntyan + * === Released 0.8.5 === + + Fixed critical win32 bug: editor duplicated line endings on save, + screwing up files and everything (Thomas Gilgin). + + Implemented selecting whole lines on click/drag over line numbers + margin (stonecrest). + Made it save user-chosen encodings in Open and Save dialog. + It's now possible to print line numbers. + + Version 0.8.4 broke loading files on unix, and was deleted. + +2007-04-06 Yevgen Muntyan + * === Released 0.8.3 === + + French translation by Collilieux. + Finally made tools written in Python work. + Fixed Stop button in replace confirmation dialog. + Number following colon after filename on command line is treated as line number, + e.g. 'medit /home/user/foo:134'. + + File selector fixes: + Use exo-open when running on XFCE. + Correctly determine mime type of backup files and text files. + Executable files do not get passed to gnome-open and alike, so they are not + executed on double-click. + Improved performance and memory consumption. + + Improved syntax highlighting: C, C++, po, gtk-doc, Makefile, + +2007-02-03 Yevgen Muntyan + * === Released 0.8.2 === + + Made medit ask whether to save changes on logout. + Fixed build without libxml2. + Fixed PHP and Java syntax highlighting. + Double-click in file selector now opens text files in the editor, + and uses default applications for the rest of files. + +2007-01-20 Yevgen Muntyan + * === Released 0.8.1 === + + Added syntax highlighting for pkgconfig, libtool, dpatch, dtd. + Improved highlighting for shell, python, desktop, m4, xml. + Improved compiler and python output views. + Added simple python project type. + Made Alt- shortcuts switch tabs (stonecrest). + All filter settings matching given filename are applied now, + in order they are specified, so it's easy to set settings + for a folder or single glob, and then tweak them for individual + files. + Added character encoding selectors to Open and Save dialogs. + File permissions are now preserved when using Save As. + Input fifo's are created in a separate subdirectory of /tmp, + to pollute it less. + Windows build uses mime types database from freedesktop.org + now. + Implemented folder watching on windows: file selector updates + automatically when folder content changes. + Drag'n'drop in file selector works with Go to Current Doc Folder button. + Added bunch of tools for LaTeX documents. + +2006-11-30 Yevgen Muntyan + * === Released 0.8.0 === + + Added document bookmarks (Dmytro Savchuk). + "medit" without arguments opens new file if existing process instance + is used (Lontronics). + Open dialog remembers its size (Lontronics). + +2006-11-19 Yevgen Muntyan + * === Released 0.7.97 === + + Added Print Preview and Export as PDF menu items. + Improved print preview. + Fixed problem with multiple windows on twm (Lontronics). + +2006-11-13 Yevgen Muntyan + * === Released 0.7.96 === + + Fixed print preview for different page orientations. + Made editor use document font to display line numbers. + +2006-11-13 Yevgen Muntyan + * === Released 0.7.95 === + + Quick fix release for 0.7.9. + + Fixed crash on windows with pygtk older than 2.10. + Got drawing tabs and trailing spaces back. + +2006-11-12 Yevgen Muntyan + * === Released 0.7.9 === + + Beta version of new 0.8 release. + + Syntax highlighting has been replaced with brand new syntax + highlighting from GtkSourceView. It supports million different + languages, and has much better performance. + + New features and improvements: + Win32 installer includes GTK. + Implemented print preview; implemented printing with + text styles from the document. + Implemented customizable output filters, for highlighting + errors in commands output and automatic opening appropriate + files. + Copying file in the file selector copies its URI/path + when appropriate. + Added "async" mode for running shell commands: output is + redirected to medit's parent console and medit does not + wait for the command to terminate. + + Fixed bugs include: + Problem with new keyboard shortcuts not applied immediately + when set in Configure Shortcuts dialog. + Build errors with gtk-2.6. + Paned widgets can't be covered by document anymore. + Lot of other bugs too. + +2006-08-24 Yevgen Muntyan + * === Released 0.7.1 === + + Quick-fix release: fixed some build errors, + removed shell unix tools from win32 build. + +2006-08-23 Yevgen Muntyan + * === Released 0.7.0 === + + New features and impovements: + New and improved user-defined tools. + Much improved Find plugin. + I18n infrastructure is in place (no translations yet). + Reworked plugin system. + Experimental project plugin. + Printing with gtk-2.10. + + Made it possible to blacklist certain extensions, so no language + is picked up for them. + Added New File action in file selector. + Made displayed tab width configurable. + Close editor tabs by middle mouse button click (Thomas Gilgin). + Made font selectors show only monospace fonts. + Find Current Word action (Thomas Gilgin). + Improved terminal drawing. + Prettified python console (Geoffrey French). + + Disabled active strings and completion in this version, they will + be reenabled and made actually useful in next releases. + + Bug fixes include: + Fixed bug with simultaneous dragging icon and reordering tabs in notebook. + On opening files prefer locale encoding to builtin ones (Thomas Gilgin). + Do not try to open special files both on windows and unix. + Fix startup notification protocol handling, so the editor does not steal + focus when using single instance mode and does pop up on correct desktop + when needed. + Color scheme setting applied after opening a document (Thomas Gilgin). + Custom shortcuts did not get saved under certain conditions. + Fixed tab icon dnd which caused input lock in child windows. + Fixed bug in Search/Replace when replacement uses back references. + Store clipboard contents on quit (Thomas Gilgin). + Fixed crash on paste from another application. + Save search history and search options (Thomas Gilgin). + +2006-05-26 Yevgen Muntyan + * === Released 0.6.99 === + + Added Open With submenu in file selector popup menu. + Fixed bug with per-language settings lost on save. + Added document list menu in editor windows. + Fixed crash on paste from another application. + Improved entry completion behaviour. + Improved python plugin. + Updated printing code for changes in gtk api. + Fixed file selector background bug. + There is a single win32 installer now, and medit picks up python if it's present + on startup: medit is not linked to pythonxx.dll, python support has been moved + into pymo2x plugins. + Improved build infrastructure (Andreas Hanke). + Bug fixes. + +2006-05-04 Yevgen Muntyan + + * === Released 0.6.98 === + + Added printing support for gtk-2.9. + Printing support in gtk is not stable yet, and will likely change + in future, therefore printing in medit must be explicitely enabled by + --enable-printing argument to configure. + Fixed drawing bug when opening big files. + Made window panes save their position after changing it by drag-n-drop. + Build fixes on AMD Linux, other bug fixes. + +2006-05-04 Yevgen Muntyan + + * === Released 0.6.97 === + + medit now uses system pcre library if present; FAM support is disabled. + + Allow adding tools into different menus. + Added gnuplot syntax highlighting. + Fixed python output pane: it now jumps to correct line in error + messages, and it uses Stop button. + Added gui for setting per-language settings in Preferences dialog. + Dropping a file into the text area opens file now, instead of pasting uri. + "medit filename" from command line creates new file if the argument is not + an existing file. + +2006-04-30 Yevgen Muntyan + + * === Released 0.6.96 === + + Fixed bugs: + Build problems on AMD64 - David Hough. + Missing #include - Nick Treleaven, David Hough. + Problem with hidden toolbar showing up anyway - Andrey Fedoseev. + Missing window icon - Andrey Fedoseev. + +2006-04-30 Yevgen Muntyan + + * === Released 0.6.95 === + + Greatly improved syntax highlighting, zillion fixes. + +2006-04-20 Yevgen Muntyan + + * === Released 0.6.9 === + + User-defined completion, tools, and context menus. + +2006-04-09 Yevgen Muntyan + + * === Released 0.6.8 === + + Finally unbroke windows terminal, and finally fixed + bug with terminal eating 100% of cpu time. + + Active strings, user-defined commands: + Added support for python scripts and shell commands. + +2006-04-06 Yevgen Muntyan + + * === Released 0.6.7 === + + Reverted changes in windows terminal. + +2006-04-06 Yevgen Muntyan + + * === Released 0.6.6 === + + Lot of bugs fixed, lot of bugs introduced. + + MooEdit: + Made quick search work. + Line ends type is detected automatically, and + used when saving file. + +2006-03-29 Yevgen Muntyan + + * === Released 0.6.5 === + + 0.6.4 is broken + +2006-03-29 Yevgen Muntyan + + * === Released 0.6.4 === + + MooEdit: + Implemented printing for gtk-2.10. + + MooTerm: + Rewritten drawing. + + MooFileView: + Made it work (to some extent) on windows. + +2006-03-15 Yevgen Muntyan + + * === Released 0.6.3 === + + Added customizable menus, with user-defined actions using MooScript. + + MooApp: + Fixed input pipe eating all cpu time. + Use recursive g_remove on windows for removing directories, to avoid + shell api asking user if he wants t remove temporary directory. + Custom About dialog. + + MooEdit: + Reenabled force-tag-redraw hack in highlighter, due to GTK bug. + Fixed unreal slowness of highlighting matching brackets. + + MooTerm: + Made moo_term_get_selection_bounds() public. + + MooFileView: + Unbroke get_parent_folder + + Python module: + Made moo module behave like a package. + Made atributes of TermIter and TermTextAttr writable. + Added file-like objects which use g_print* for sys.stdout and sys.stderr. diff --git a/README b/README new file mode 100644 index 00000000..2bdf867a --- /dev/null +++ b/README @@ -0,0 +1,48 @@ +General Information +=================== + +medit is a GTK text editor. Started as an editor component of GGAP +(http://ggap.sourceforge.net/), it grew up to a real full-featured +text editor. + +medit is free software, released under GNU GPL license. See the LICENSE +file in this distribution for details. + +The web site is: + http://medit.bitbucket.org/ +releases are located at: + http://sourceforge.net/project/showfiles.php?group_id=167563 + + +Installation +============ + +See INSTALL file in this distribution. + + +Bug reports and contact +======================= + +Report bugs and file feature requests in medit bug base at +http://sourceforge.net/tracker/?group_id=167563&atid=843451 . +You can also send email to emuntyan@users.sourceforge.net if you don't feel like +using bug base, or have any questions/comments/beer. +medit IRC channel is #ggap at irc.freenode.net. + +Provide information available from menu Help > System Info +along with bug report. See also INSTALL file for instructions on how to +build medit for better debugging information. + + +CVS +=== + +medit sources are tracked using mercurial, therefore there is no +CVS or SVN access. You may browse medit repository online at +http://bitbucket.org/medit/medit/. To check out a copy of medit +repository, install mercurial, and do + hg clone http://bitbucket.org/medit/medit/ +This will create a new directory named 'medit' with medit sources inside. +To update them later on, do + hg pull; hg update +in this directory. diff --git a/THANKS b/THANKS new file mode 100644 index 00000000..c09c0bfe --- /dev/null +++ b/THANKS @@ -0,0 +1,40 @@ +To Sveta and Max Muntyan, + +and to all who contributed, including + +Andreas Hanke +Andrey Fedoseev +archxyne +Arnau Sanchez +Chaosye +Christian Dywan +Collilieux +Daniel Butzu +David Hough +Denis Koryavov +Dmytro Savchuk +Everaldo Coelho +Federico Mena Quintero +Geoffrey French +Jeroen Zwartepoorte +Kenneth Prugh +Kris Van Bruwaene +LoneFox +Lontronics +Malete Partner +Marco Barisione +Matthias Clasen +Nick Treleaven +Owen Taylor +Paolo Borelli +Paolo Maggi +Pavel Artyomkin +Philip Hazel +Scott Wimer +Sebikul +Thomas Gilgin +Tim Janik +Tim-Philipp Müller +Tommi Nieminen +Toshiharu Kudoh +Vlastimil Ott diff --git a/api/Makefile.am b/api/Makefile.am new file mode 100644 index 00000000..e145f446 --- /dev/null +++ b/api/Makefile.am @@ -0,0 +1,72 @@ +docparser_files = \ + parsedocs.py \ + mdp/__init__.py \ + mdp/module.py \ + mdp/docparser.py \ + mdp/xmlwriter.py + +gendefs_files = \ + gendefs.py \ + mpi/__init__.py \ + mpi/module.py \ + mpi/defswriter.py + +genlua_files = \ + genlua.py \ + mpi/__init__.py \ + mpi/module.py \ + mpi/luawriter.py + +gendocs_files = \ + gendocs.py \ + gendocbook.py \ + mpi/__init__.py \ + mpi/module.py \ + mpi/docbookwriter.py + +EXTRA_DIST = \ + $(docparser_files) \ + $(gendefs_files) \ + $(genlua_files) \ + $(gendocs_files) \ + sourcefiles.mak \ + moo.xml \ + gtk.xml + +BUILT_SOURCES = + +if MOO_DEV_MODE + +include sourcefiles.mak + +BUILT_SOURCES += moo.xml.stamp +moo.xml.stamp: $(docparser_files) $(source_files) + $(AM_V_GEN)$(PYTHON) $(srcdir)/parsedocs.py \ + --source-dir $(top_srcdir)/moo/mooapp \ + --source-dir $(top_srcdir)/moo/mooedit \ + --source-dir $(top_srcdir)/moo/moofileview \ + --source-dir $(top_srcdir)/moo/mooutils \ + --source-dir $(top_srcdir)/moo/moopython \ + --source-dir $(top_srcdir)/moo/plugins/usertools \ + --source-dir $(top_srcdir)/moo/plugins/support \ + --source-file $(top_srcdir)/moo/moolua/medit-lua.cpp \ + --source-file $(top_srcdir)/moo/moolua/medit-lua.h \ + --skip 'moofontsel.*' \ + --output moo.xml.tmp + $(AM_V_at)cmp -s moo.xml.tmp $(srcdir)/moo.xml || mv moo.xml.tmp $(srcdir)/moo.xml + $(AM_V_at)rm -f moo.xml.tmp + $(AM_V_at)echo stamp > moo.xml.stamp + +BUILT_SOURCES += gtk.xml.stamp +gtk.xml.stamp: $(docparser_files) $(top_srcdir)/moo/moolua/gtk-api.c $(top_srcdir)/moo/moolua/gtk-api.h + $(AM_V_at)$(MKDIR_P) moolua + $(AM_V_GEN)$(PYTHON) $(srcdir)/parsedocs.py \ + --source-file $(top_srcdir)/moo/moolua/gtk-api.c \ + --source-file $(top_srcdir)/moo/moolua/gtk-api.h \ + --module Gtk \ + --output gtk.xml.tmp + $(AM_V_at)cmp -s gtk.xml.tmp $(srcdir)/gtk.xml || mv gtk.xml.tmp $(srcdir)/gtk.xml + $(AM_V_at)rm -f gtk.xml.tmp + $(AM_V_at)echo stamp > gtk.xml.stamp + +endif diff --git a/api/gendefs.py b/api/gendefs.py new file mode 100644 index 00000000..4785947b --- /dev/null +++ b/api/gendefs.py @@ -0,0 +1,10 @@ +#! /usr/bin/env python + +import sys + +from mpi.module import Module +from mpi.defswriter import Writer + +for arg in sys.argv[1:]: + mod = Module.from_xml(arg) + Writer(sys.stdout).write(mod) diff --git a/api/gendocbook.py b/api/gendocbook.py new file mode 100644 index 00000000..cad1b0f6 --- /dev/null +++ b/api/gendocbook.py @@ -0,0 +1,31 @@ +#! /usr/bin/env python + +import sys +import optparse + +from mpi.module import Module +from mpi.docbookwriter import Writer + +op = optparse.OptionParser() +op.add_option("--python", action="store_true") +op.add_option("--lua", action="store_true") +op.add_option("--template", action="store") +op.add_option("-i", "--import", action="append", dest="import_modules") +(opts, args) = op.parse_args() + +assert len(args) == 1 +assert bool(opts.python) + bool(opts.lua) == 1 +if opts.python: + mode = 'python' +elif opts.lua: + mode = 'lua' + +import_modules = [] +if opts.import_modules: + for filename in opts.import_modules: + import_modules.append(Module.from_xml(filename)) + +mod = Module.from_xml(args[0]) +for im in import_modules: + mod.import_module(im) +Writer(mode, opts.template, sys.stdout).write(mod) diff --git a/api/gendocs.py b/api/gendocs.py new file mode 100644 index 00000000..8b7d49a8 --- /dev/null +++ b/api/gendocs.py @@ -0,0 +1,22 @@ +#! /usr/bin/env python + +import sys +import optparse + +from mpi.module import Module +from mpi.texiwriter import Writer + +op = optparse.OptionParser() +op.add_option("--python", action="store_true") +op.add_option("--lua", action="store_true") +(opts, args) = op.parse_args() + +assert len(args) == 1 +assert bool(opts.python) + bool(opts.lua) == 1 +if opts.python: + mode = 'python' +elif opts.lua: + mode = 'lua' + +mod = Module.from_xml(args[0]) +Writer(mode, sys.stdout).write(mod) diff --git a/api/genlua.py b/api/genlua.py new file mode 100644 index 00000000..1591c26a --- /dev/null +++ b/api/genlua.py @@ -0,0 +1,23 @@ +#! /usr/bin/env python + +import sys +import optparse + +from mpi.module import Module +from mpi.luawriter import Writer + +op = optparse.OptionParser() +op.add_option("-i", "--import", action="append", dest="import_modules") +op.add_option("--include-header", action="append", dest="include_headers") +(opts, args) = op.parse_args() + +import_modules = [] +if opts.import_modules: + for filename in opts.import_modules: + import_modules.append(Module.from_xml(filename)) + +assert len(args) == 1 +mod = Module.from_xml(args[0]) +for im in import_modules: + mod.import_module(im) +Writer(sys.stdout).write(mod, opts.include_headers) diff --git a/api/mdp/__init__.py b/api/mdp/__init__.py new file mode 100644 index 00000000..1bb8bf6d --- /dev/null +++ b/api/mdp/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/api/mdp/docparser.py b/api/mdp/docparser.py new file mode 100644 index 00000000..ce5ad128 --- /dev/null +++ b/api/mdp/docparser.py @@ -0,0 +1,691 @@ +import re +import string +import sys +import os + +DEBUG = False + +class ParseError(RuntimeError): + def __init__(self, message, block=None): + if block: + RuntimeError.__init__(self, '%s in file %s, around line %d' % \ + (message, block.filename, block.first_line)) + else: + RuntimeError.__init__(self, message) + +class DoxBlock(object): + def __init__(self, block): + object.__init__(self) + + self.symbol = None + self.annotations = [] + self.params = [] + self.attributes = [] + self.docs = [] + self.summary = None + + self.__parse(block) + + def __parse(self, block): + first_line = True + chunks = [] + cur = [None, None, []] + for line in block.lines: + if first_line: + m = re.match(r'([\w\d_.-]+(:+[\w\d_.-]+)*)(:(\s.*)?)?$', line) + if m is None: + raise ParseError('bad id line', block) + cur[0] = m.group(1) + annotations, docs = self.__parse_annotations(m.group(4) or '') + cur[1] = annotations + cur[2] = [docs] if docs else [] + elif not line: + if cur[0] is not None: + chunks.append(cur) + cur = [None, None, []] + elif cur[2]: + cur[2].append(line) + else: + m = re.match(r'(@[\w\d_.-]+|Returns|Return value|Since):(.*)$', line) + if m: + if cur[0] or cur[2]: + chunks.append(cur) + cur = [None, None, []] + cur[0] = m.group(1) + annotations, docs = self.__parse_annotations(m.group(2) or '') + cur[1] = annotations + cur[2] = [docs] if docs else [] + else: + cur[2].append(line) + first_line = False + if cur[0] or cur[2]: + chunks.append(cur) + + self.symbol = chunks[0][0] + self.annotations = chunks[0][1] + self.summary = chunks[0][2] + + for chunk in chunks[1:]: + if chunk[0]: + if chunk[0].startswith('@'): + self.params.append(chunk) + else: + self.attributes.append(chunk) + else: + self.docs = chunk[2] + + if not self.symbol: + raise ParseError('bad id line', block) + + def __parse_annotations(self, text): + annotations = [] + ann_start = -1 + for i in xrange(len(text)): + c = text[i] + if c in ' \t': + pass + elif c == ':': + if ann_start < 0: + if annotations: + return annotations, text[i+1:].strip() + else: + return None, text + else: + pass + elif c != '(' and ann_start < 0: + if annotations: + raise ParseError('bad annotations') + return None, text + elif c == '(': + if ann_start >= 0: + raise ParseError('( inside ()') + ann_start = i + elif c == ')': + assert ann_start >= 0 + if ann_start + 1 < i: + annotations.append(text[ann_start+1:i]) + ann_start = -1 + if ann_start >= 0: + raise ParseError('unterminated annotation') + return annotations, None + +class Block(object): + def __init__(self, lines, filename, first_line, last_line): + object.__init__(self) + self.lines = list(lines) + self.filename = filename + self.first_line = first_line + self.last_line = last_line + +class Symbol(object): + def __init__(self, name, annotations, docs, block): + object.__init__(self) + self.name = name + self.annotations = annotations or [] + self.docs = docs + self.block = block + +class Class(Symbol): + def __init__(self, name, annotations, docs, block): + Symbol.__init__(self, name, annotations, docs, block) + +class Boxed(Symbol): + def __init__(self, name, annotations, docs, block): + Symbol.__init__(self, name, annotations, docs, block) + +class Pointer(Symbol): + def __init__(self, name, annotations, docs, block): + Symbol.__init__(self, name, annotations, docs, block) + +class EnumBase(Symbol): + def __init__(self, name, annotations, docs, values, block): + super(EnumBase, self).__init__(name, annotations, docs, block) + self.values = values + +class Enum(EnumBase): + def __init__(self, name, annotations, docs, values, block): + super(Enum, self).__init__(name, annotations, docs, values, block) + +class Flags(EnumBase): + def __init__(self, name, annotations, docs, values, block): + super(Flags, self).__init__(name, annotations, docs, values, block) + +class EnumValue(object): + def __init__(self, name, annotations=None, docs=None): + object.__init__(self) + self.docs = docs + self.annotations = annotations or [] + self.name = name + +class FunctionBase(Symbol): + def __init__(self, name, annotations, params, retval, docs, block): + Symbol.__init__(self, name, annotations, docs, block) + self.params = params + self.retval = retval + +class Signal(FunctionBase): + def __init__(self, name, annotations, params, retval, docs, block): + FunctionBase.__init__(self, name, annotations, params, retval, docs, block) + +class Function(FunctionBase): + def __init__(self, name, annotations, params, retval, docs, block): + FunctionBase.__init__(self, name, annotations, params, retval, docs, block) + +class VMethod(Function): + def __init__(self, name, annotations, params, retval, docs, block): + Function.__init__(self, name, annotations, params, retval, docs, block) + +class ParamBase(object): + def __init__(self, annotations=[], docs=None): + object.__init__(self) + self.docs = docs + self.type = None + self.annotations = annotations or [] + +class Param(ParamBase): + def __init__(self, name=None, annotations=None, docs=None): + ParamBase.__init__(self, annotations, docs) + self.name = name + +class Retval(ParamBase): + def __init__(self, annotations=None, docs=None): + ParamBase.__init__(self, annotations, docs) + +class Parser(object): + def __init__(self): + object.__init__(self) + self.classes = [] + self.enums = [] + self.functions = [] + self.signals = [] + self.vmethods = [] + self.__symbol_dict = {} + + def __split_block(self, block): + chunks = [] + current_prefix = None + current_annotations = None + current_text = None + first_line = True + + re_id = re.compile(r'([\w\d._-]+:(((\s*\([^()]\)\s*)+):)?') + re_special = re.compile(r'(@[\w\d._-]+|(SECTION|PROPERTY|SIGNAL)-[\w\d._-]+||Since|Returns|Return value):(((\s*\([^()]\)\s*)+):)?') + + for line in block.lines: + if first_line: + line = re.sub(r'^SECTION:([\w\d_-]+):?', r'SECTION-\1:', line) + line = re.sub(r'^([\w\d_-]+):([\w\d_-]+):?', r'PROPERTY-\1-\2:', line) + line = re.sub(r'^([\w\d_-]+)::([\w\d_-]+):?', r'SIGNAL-\1-\2:', line) + first_line = False + if not line: + if current_prefix is not None: + chunks.append([current_prefix, current_annotations, current_text]) + current_prefix = None + current_annotations = None + current_text = None + elif current_text is not None: + current_text.append(line) + else: + m = re_special.match(line) + if m: + if current_prefix is not None or current_text is not None: + chunks.append([current_prefix, current_annotations, current_text]) + current_prefix = None + current_annotations = None + current_text = None + current_prefix = m.group(1) + suffix = m.group(4) or '' + annotations, text = self.__parse_annotations(suffix) + current_annotations = annotations + current_text = [text] if text else [] + else: + if current_text is not None: + current_text.append(line) + else: + current_text = [line] + if current_text is not None: + chunks.append([current_prefix, current_annotations, current_text]) + + return chunks + + def __parse_annotations(self, text): + annotations = [] + ann_start = -1 + for i in xrange(len(text)): + c = text[i] + if c in ' \t': + pass + elif c == ':': + if ann_start < 0: + if annotations: + return annotations, text[i+1:].strip() + else: + return None, text + else: + pass + elif c != '(' and ann_start < 0: + if annotations: + raise ParseError('bad annotations') + return None, text + elif c == '(': + if ann_start >= 0: + raise ParseError('( inside ()') + ann_start = i + elif c == ')': + assert ann_start >= 0 + if ann_start + 1 < i: + annotations.append(text[ann_start+1:i]) + ann_start = -1 + if ann_start >= 0: + raise ParseError('unterminated annotation') + if annotations: + return annotations, None + else: + return None, None + + def __parse_function(self, block): + db = DoxBlock(block) + + params = [] + retval = None + + for p in db.params: + params.append(Param(p[0][1:], p[1], p[2])) + for attr in db.attributes: + if attr[0] in ('Returns', 'Return value'): + retval = Retval(attr[1], attr[2]) + elif attr[0] in ('Since'): + pass + else: + raise ParseError('unknown attribute %s' % (attr[0],), block) + + if '::' in db.symbol: + What = Signal + db.symbol = 'signal:' + db.symbol.replace('_', '-').replace('::', ':') + symbol_list = self.signals + elif ':' in db.symbol: + What = Property + db.symbol = 'property:' + db.symbol.replace('_', '-') + symbol_list = self.properties + else: + What = Function + symbol_list = self.functions + + func = What(db.symbol, db.annotations, params, retval, db.docs, block) + func.summary = db.summary + if DEBUG: + print 'func.name:', func.name + if func.name in self.__symbol_dict: + raise ParseError('duplicated symbol %s' % (func.name,), block) + self.__symbol_dict[func.name] = func + symbol_list.append(func) + + def __parse_class(self, block): + db = DoxBlock(block) + + name = db.symbol + if name.startswith('class:'): + name = name[len('class:'):] + + if db.params: + raise ParseError('class params', block) + if db.attributes: + raise ParseError('class attributes', block) + + cls = Class(name, db.annotations, db.docs, block) + cls.summary = db.summary + self.classes.append(cls) + + def __parse_boxed(self, block): + What = None + db = DoxBlock(block) + + name = db.symbol + if name.startswith('boxed:'): + What = Boxed + name = name[len('boxed:'):] + elif name.startswith('pointer:'): + What = Pointer + name = name[len('pointer:'):] + else: + raise ParseError('bad id', block) + + if db.params: + raise ParseError('boxed params', block) + if db.attributes: + raise ParseError('boxed attributes', block) + + cls = What(name, db.annotations, db.docs, block) + cls.summary = db.summary + self.classes.append(cls) + + def __parse_enum_or_flags(self, block, What): + db = DoxBlock(block) + + name = db.symbol + prefix = 'enum:' if What is Enum else 'flags:' + if name.startswith(prefix): + name = name[len(prefix):] + + if db.attributes: + raise ParseError('enum attributes', block) + + values = [] + if db.params: + for p in db.params: + values.append(EnumValue(p[0][1:], p[1], p[2])) + + enum = What(name, db.annotations, db.docs, values, block) + enum.summary = db.summary + self.enums.append(enum) + + def __parse_enum(self, block): + return self.__parse_enum_or_flags(block, Enum) + + def __parse_flags(self, block): + return self.__parse_enum_or_flags(block, Flags) + + def __parse_block(self, block): + line = block.lines[0] + if line.startswith('class:'): + self.__parse_class(block) + elif line.startswith('boxed:'): + self.__parse_boxed(block) + elif line.startswith('pointer:'): + self.__parse_boxed(block) + elif line.startswith('enum:'): + self.__parse_enum(block) + elif line.startswith('flags:'): + self.__parse_flags(block) + elif line.startswith('SECTION:'): + pass + else: + self.__parse_function(block) + + def __add_block(self, block, filename, first_line, last_line): + lines = [] + for line in block: + if line.startswith('*'): + line = line[1:].strip() + if line or lines: + lines.append(line) + else: + first_line += 1 + i = len(lines) - 1 + while i >= 0: + if not lines[i]: + del lines[i] + i -= 1 + last_line -= 1 + else: + break + if lines: + assert last_line >= first_line + self.__parse_block(Block(lines, filename, first_line, last_line)) + + def __read_comments(self, filename): + block = None + first_line = 0 + line_no = 0 + for line in open(filename): + line = line.strip() + if not block: + if line.startswith('/**'): + line = line[3:] + if not line.startswith('*') and not '*/' in line: + block = [line] + first_line = line_no + else: + end = line.find('*/') + if end >= 0: + line = line[:end] + block.append(line) + self.__add_block(block, filename, first_line, line_no) + block = None + else: + block.append(line) + line_no += 1 + if block: + raise ParseError('unterminated block in file %s' % (filename,)) + + def read_files(self, filenames): + sys.stderr.write('parsing gtk-doc comments... ') + sys.stderr.flush() + for f in filenames: + self.__read_comments(f) + sys.stderr.write('done\n') + sys.stderr.write('parsing declarations... ') + sys.stderr.flush() + for f in filenames: + if f.endswith('.h'): + self.__read_declarations(f) + sys.stderr.write('done\n') + + # Code copied from h2def.py by Toby D. Reeves + + def __strip_comments(self, buf): + parts = [] + lastpos = 0 + while 1: + pos = string.find(buf, '/*', lastpos) + if pos >= 0: + if buf[pos:pos+len('/**vtable:')] == '/**vtable:': + parts.append(buf[lastpos:pos+len('/**vtable:')]) + lastpos = pos + len('/**vtable:') + elif buf[pos:pos+len('/**signal:')] == '/**signal:': + parts.append(buf[lastpos:pos+len('/**signal:')]) + lastpos = pos + len('/**signal:') + else: + parts.append(buf[lastpos:pos]) + pos = string.find(buf, '*/', pos) + if pos >= 0: + lastpos = pos + 2 + else: + break + else: + parts.append(buf[lastpos:]) + break + return string.join(parts, '') + + # Strips the dll API from buffer, for example WEBKIT_API + def __strip_dll_api(self, buf): + pat = re.compile("[A-Z]*_API ") + buf = pat.sub("", buf) + return buf + + def __clean_func(self, buf): + """ + Ideally would make buf have a single prototype on each line. + Actually just cuts out a good deal of junk, but leaves lines + where a regex can figure prototypes out. + """ + # bulk comments + buf = self.__strip_comments(buf) + + # dll api + buf = self.__strip_dll_api(buf) + + # compact continued lines + pat = re.compile(r"""\\\n""", re.MULTILINE) + buf = pat.sub('', buf) + + # Preprocess directives + pat = re.compile(r"""^[#].*?$""", re.MULTILINE) + buf = pat.sub('', buf) + + #typedefs, stucts, and enums + pat = re.compile(r"""^(typedef|struct|enum)(\s|.|\n)*?;\s*""", + re.MULTILINE) + buf = pat.sub('', buf) + + #strip DECLS macros + pat = re.compile(r"""G_(BEGIN|END)_DECLS|(BEGIN|END)_LIBGTOP_DECLS""", re.MULTILINE) + buf = pat.sub('', buf) + + #extern "C" + pat = re.compile(r"""^\s*(extern)\s+\"C\"\s+{""", re.MULTILINE) + buf = pat.sub('', buf) + + #multiple whitespace + pat = re.compile(r"""\s+""", re.MULTILINE) + buf = pat.sub(' ', buf) + + #clean up line ends + pat = re.compile(r""";\s*""", re.MULTILINE) + buf = pat.sub('\n', buf) + buf = buf.lstrip() + + #associate *, &, and [] with type instead of variable + #pat = re.compile(r'\s+([*|&]+)\s*(\w+)') + pat = re.compile(r' \s* ([*|&]+) \s* (\w+)', re.VERBOSE) + buf = pat.sub(r'\1 \2', buf) + pat = re.compile(r'\s+ (\w+) \[ \s* \]', re.VERBOSE) + buf = pat.sub(r'[] \1', buf) + + buf = string.replace(buf, '/** vtable:', '/**vtable:') + pat = re.compile(r'(\w+) \s* \* \s* \(', re.VERBOSE) + buf = pat.sub(r'\1* (', buf) + + # make return types that are const work. + buf = re.sub(r'\s*\*\s*G_CONST_RETURN\s*\*\s*', '** ', buf) + buf = string.replace(buf, 'G_CONST_RETURN ', 'const-') + buf = string.replace(buf, 'const ', 'const-') + + #strip GSEAL macros from the middle of function declarations: + pat = re.compile(r"""GSEAL""", re.VERBOSE) + buf = pat.sub('', buf) + + return buf + + def __read_declarations_in_buf(self, buf, filename): + vproto_pat=re.compile(r""" + /\*\*\s*(?P(vtable|signal)):(?P[\w\d_]+)\s*\*\*/\s* + (?P(-|\w|\&|\*)+\s*) # return type + \s+ # skip whitespace + \(\s*\*\s*(?P\w+)\s*\) + \s*[(] # match the function name until the opening ( + \s*(?P.*?)\s*[)] # group the function arguments + """, re.IGNORECASE|re.VERBOSE) + proto_pat=re.compile(r""" + (?P(-|\w|\&|\*)+\s*) # return type + \s+ # skip whitespace + (?P\w+)\s*[(] # match the function name until the opening ( + \s*(?P.*?)\s*[)] # group the function arguments + """, re.IGNORECASE|re.VERBOSE) + arg_split_pat = re.compile("\s*,\s*") + + buf = self.__clean_func(buf) + buf = string.split(buf,'\n') + + for p in buf: + if not p: + continue + + if DEBUG: + print 'matching line', repr(p) + + fname = None + vfname = None + is_signal = False + m = proto_pat.match(p) + if m is None: + if DEBUG: + print 'proto_pat not matched' + m = vproto_pat.match(p) + if m is None: + if DEBUG: + print 'vproto_pat not matched' + if p.find('vtable:') >= 0: + print "oops", repr(p) + if p.find('moo_file_enc_new') >= 0: + print '***', repr(p) + continue + else: + vfname = m.group('vfunc') + if m.group('what') == 'signal': + is_signal = True + if DEBUG: + print 'proto_pat matched', repr(m.group(0)) + print '+++ ', ('vfunc', 'signal')[is_signal], vfname + else: + if DEBUG: + print 'proto_pat matched', repr(m.group(0)) + fname = m.group('func') + ret = m.group('ret') + if ret in ('return', 'else', 'if', 'switch'): + continue + if fname: + func = self.__symbol_dict.get(fname) + if func is None: + continue + if DEBUG: + print 'match:|%s|' % fname + elif not is_signal: + symbol_name = 'vfunc:%s:%s' % (m.group('vtable'), m.group('vfunc')) + func = self.__symbol_dict.get(symbol_name) + if func is None: + func = VMethod(symbol_name, None, None, None, None, None) + self.__symbol_dict[symbol_name] = func + self.vmethods.append(func) + if DEBUG: + print 'match:|%s|' % func.name + else: + symbol_name = ('signal:%s:%s' % (m.group('vtable'), m.group('vfunc'))).replace('_', '-') + func = self.__symbol_dict.get(symbol_name) + if func is None: + func = Signal(symbol_name, None, None, None, None, None) + self.__symbol_dict[symbol_name] = func + self.signals.append(func) + if DEBUG: + print 'match:|%s|' % func.name + + args = m.group('args') + args = arg_split_pat.split(args) + for i in range(len(args)): + spaces = string.count(args[i], ' ') + if spaces > 1: + args[i] = string.replace(args[i], ' ', '-', spaces - 1).replace('gchar', 'char') + + if ret != 'void': + ret = ret.replace('gchar', 'char') + if func.retval is None: + func.retval = Retval() + if func.retval.type is None: + func.retval.type = ret + if ret in ('char*', 'strv', 'char**'): + func.retval.annotations.insert(0, 'transfer full') + + is_varargs = 0 + has_args = len(args) > 0 + for arg in args: + if arg == '...': + is_varargs = 1 + elif arg in ('void', 'void '): + has_args = 0 + if DEBUG: + print 'func ', ', '.join([p.name for p in func.params] if func.params else '') + if has_args and not is_varargs: + if func.params is None: + func.params = [] + elif func.params: + assert len(func.params) == len(args) + for i in range(len(args)): + if DEBUG: + print 'arg:', args[i] + argtype, argname = string.split(args[i]) + if DEBUG: + print argtype, argname + if len(func.params) <= i: + func.params.append(Param()) + if func.params[i].name is None: + func.params[i].name = argname + if func.params[i].type is None: + func.params[i].type = argtype + if DEBUG: + print 'func ', ', '.join([p.name for p in func.params]) + + def __read_declarations(self, filename): + if DEBUG: + print filename + buf = open(filename).read() + self.__read_declarations_in_buf(buf, filename) diff --git a/api/mdp/module.py b/api/mdp/module.py new file mode 100644 index 00000000..b00410bc --- /dev/null +++ b/api/mdp/module.py @@ -0,0 +1,631 @@ +import sys +import re + +import mdp.docparser as dparser + +DEBUG = False + +def split_camel_case_name(name): + comps = [] + cur = '' + for c in name: + if c.islower() or not cur: + cur += c + else: + comps.append(cur) + cur = c + if cur: + comps.append(cur) + return comps + +def get_class_method_c_name_prefix(cls): + comps = split_camel_case_name(cls) + return '_'.join([c.lower() for c in comps]) + '_' + +def strip_class_prefix(name, cls): + prefix = get_class_method_c_name_prefix(cls) + if name.startswith(prefix): + return name[len(prefix):] + else: + return name + +def strip_module_prefix(name, mod): + prefix = get_class_method_c_name_prefix(mod) + if name.startswith(prefix): + return name[len(prefix):] + else: + return name + +def strip_module_prefix_from_class(name, mod): + mod = mod.lower() + mod = mod[0].upper() + mod[1:] + if name.startswith(mod): + return name[len(mod):] + else: + return name + +def make_gtype_id(cls): + comps = split_camel_case_name(cls) + comps = [comps[0]] + ['TYPE'] + comps[1:] + return '_'.join([c.upper() for c in comps]) + +class Type(object): + def __init__(self, name): + object.__init__(self) + self.name = name + +class BasicType(Type): + def __init__(self, name): + Type.__init__(self, name) + +class _GTypedType(Type): + def __init__(self, name, short_name, gtype_id, docs): + Type.__init__(self, name) + self.docs = docs + self.methods = [] + self.static_methods = [] + self.gtype_id = gtype_id + self.short_name = short_name + self.annotations = {} + +class EnumBase(_GTypedType): + def __init__(self, name, short_name, gtype_id, docs): + super(EnumBase, self).__init__(name, short_name, gtype_id, docs) + self.values = [] + +class EnumValue(object): + def __init__(self, name, attributes, docs): + super(EnumValue, self).__init__() + self.name = name + self.attributes = attributes + self.docs = docs + +class Enum(EnumBase): + def __init__(self, name, short_name, gtype_id, docs): + super(Enum, self).__init__(name, short_name, gtype_id, docs) + +class Flags(EnumBase): + def __init__(self, name, short_name, gtype_id, docs): + super(Flags, self).__init__(name, short_name, gtype_id, docs) + +class _InstanceType(_GTypedType): + def __init__(self, name, short_name, gtype_id, docs): + _GTypedType.__init__(self, name, short_name, gtype_id, docs) + self.constructor = None + +class Class(_InstanceType): + def __init__(self, name, short_name, parent, gtype_id, docs): + _InstanceType.__init__(self, name, short_name, gtype_id, docs) + self.parent = parent + self.vmethods = [] + self.signals = [] + +class Boxed(_InstanceType): + def __init__(self, name, short_name, gtype_id, docs): + _InstanceType.__init__(self, name, short_name, gtype_id, docs) + +class Pointer(_InstanceType): + def __init__(self, name, short_name, gtype_id, docs): + _InstanceType.__init__(self, name, short_name, gtype_id, docs) + +class Symbol(object): + def __init__(self, name, c_name, docs): + object.__init__(self) + self.name = name + self.c_name = c_name + self.docs = docs + self.summary = None + self.annotations = {} + +class FunctionBase(Symbol): + def __init__(self, name, c_name, params, retval, docs): + Symbol.__init__(self, name, c_name, docs) + self.params = params + self.retval = retval + +class Function(FunctionBase): + def __init__(self, name, c_name, params, retval, docs): + FunctionBase.__init__(self, name, c_name, params, retval, docs) + +class Method(FunctionBase): + def __init__(self, name, c_name, cls, params, retval, docs): + FunctionBase.__init__(self, name, c_name, params, retval, docs) + self.cls = cls + +class StaticMethod(FunctionBase): + def __init__(self, name, c_name, cls, params, retval, docs): + FunctionBase.__init__(self, name, c_name, params, retval, docs) + self.cls = cls + +class Constructor(FunctionBase): + def __init__(self, name, c_name, cls, params, retval, docs): + FunctionBase.__init__(self, name, c_name, params, retval, docs) + self.cls = cls + +class VMethod(FunctionBase): + def __init__(self, name, cls, params, retval, docs): + FunctionBase.__init__(self, name, name, params, retval, docs) + self.cls = cls + +class Signal(FunctionBase): + def __init__(self, name, cls, params, retval, docs): + FunctionBase.__init__(self, name, name, params, retval, docs) + self.cls = cls + +class ParamBase(object): + def __init__(self, typ, docs): + object.__init__(self) + self.type = typ + self.docs = docs + self.attributes = {} + self.transfer_mode = None + self.element_type = None + self.array = False + self.array_fixed_len = None + self.array_len_param = None + self.array_zero_terminated = None + +class Param(ParamBase): + def __init__(self, name, typ, docs): + ParamBase.__init__(self, typ, docs) + self.name = name + self.out = False + self.caller_allocates = False + self.callee_allocates = False + self.in_ = False + self.inout = False + self.allow_none = False + self.default_value = None + self.scope = 'call' + +class Retval(ParamBase): + def __init__(self, typ, docs): + ParamBase.__init__(self, typ, docs) + +class Module(object): + def __init__(self, name): + object.__init__(self) + self.name = name + self.classes = [] + self.boxed = [] + self.__class_dict = {} + self.functions = [] + self.__methods = {} + self.__constructors = {} + self.__vmethods = {} + self.__signals = {} + self.types = {} + self.enums = [] + + def __add_class(self, pcls): + name = pcls.name + short_name = getattr(pcls, 'short_name', strip_module_prefix_from_class(pcls.name, self.name)) + gtype_id = getattr(pcls, 'gtype_id', make_gtype_id(pcls.name)) + docs = pcls.docs + parent = None + constructable = False + annotations = {} + for a in pcls.annotations: + pieces = a.split() + prefix = pieces[0] + if prefix == 'parent': + assert len(pieces) == 2 + parent = pieces[1] + elif prefix == 'constructable': + assert len(pieces) == 1 + constructable = True + elif prefix.find('.') >= 0: + annotations[prefix] = ' '.join(pieces[1:]) + else: + raise RuntimeError("unknown annotation '%s' in class %s" % (a, name)) + cls = Class(name, short_name, parent, gtype_id, docs) + cls.summary = pcls.summary + cls.annotations = annotations + cls.constructable = constructable + self.classes.append(cls) + self.__class_dict[name] = cls + + def __add_boxed_or_pointer(self, pcls, What): + name = pcls.name + short_name = getattr(pcls, 'short_name', strip_module_prefix_from_class(pcls.name, self.name)) + gtype_id = getattr(pcls, 'gtype_id', make_gtype_id(pcls.name)) + docs = pcls.docs + annotations = {} + for a in pcls.annotations: + pieces = a.split() + prefix = pieces[0] + if prefix.find('.') >= 0: + annotations[prefix] = ' '.join(pieces[1:]) + else: + raise RuntimeError("unknown annotation '%s' in class %s" % (a, name)) + cls = What(name, short_name, gtype_id, docs) + cls.summary = pcls.summary + cls.annotations = annotations + self.boxed.append(cls) + self.__class_dict[name] = cls + + def __add_boxed(self, pcls): + self.__add_boxed_or_pointer(pcls, Boxed) + + def __add_pointer(self, pcls): + self.__add_boxed_or_pointer(pcls, Pointer) + + def __add_enum(self, ptyp): + if DEBUG: + print 'enum', ptyp.name + name = ptyp.name + short_name = getattr(ptyp, 'short_name', strip_module_prefix_from_class(ptyp.name, self.name)) + gtype_id = getattr(ptyp, 'gtype_id', make_gtype_id(ptyp.name)) + docs = ptyp.docs + annotations = {} + for a in ptyp.annotations: + pieces = a.split() + prefix = pieces[0] + if prefix.find('.') >= 0: + annotations[prefix] = ' '.join(pieces[1:]) + else: + raise RuntimeError("unknown annotation '%s' in class %s" % (a, name)) + if isinstance(ptyp, dparser.Enum): + enum = Enum(name, short_name, gtype_id, docs) + else: + enum = Flags(name, short_name, gtype_id, docs) + for value in ptyp.values: + attributes = self.__parse_enum_value_annotations(value.annotations) + enum.values.append(EnumValue(value.name, attributes, value.docs)) + enum.summary = ptyp.summary + enum.annotations = annotations + self.enums.append(enum) + + def __parse_enum_value_annotations(self, annotations): + attributes = {} + for a in annotations: + pieces = a.split() + prefix = pieces[0] + if '.' in prefix[1:-1] and len(pieces) == 2: + attributes[prefix] = pieces[1] + if attributes: + return attributes + else: + return None + + def __parse_param_or_retval_annotation(self, annotation, param): + pieces = annotation.split() + prefix = pieces[0] + if prefix == 'transfer': + if len(pieces) > 2: + raise RuntimeError("invalid annotation '%s'" % (a,)) + if not pieces[1] in ('none', 'container', 'full'): + raise RuntimeError("invalid annotation '%s'" % (a,)) + param.transfer_mode = pieces[1] + return True + if prefix == 'element-type': + if len(pieces) > 3: + raise RuntimeError("invalid annotation '%s'" % (a,)) + if len(pieces) == 2: + param.element_type = pieces[1] + else: + param.element_type = pieces[1:] + return True + if prefix == 'array': + if len(pieces) == 1: + param.array = True + return True + if len(pieces) > 2: + raise RuntimeError("invalid annotation '%s'" % (a,)) + m = re.match(r'fixed-size\s*=\s*(\d+)$', pieces[1]) + if m: + param.array_fixed_size = int(m.group(1)) + return True + m = re.match(r'length\s*=\s*(\S+)$', pieces[1]) + if m: + param.array_len_param = m.group(1) + return True + m = re.match(r'zero-terminated\s*=\s*(\d+)$', pieces[1]) + if m: + param.array_zero_terminated = bool(int(m.group(1))) + return True + raise RuntimeError("invalid annotation '%s'" % (a,)) + if prefix == 'type': + if len(pieces) > 2: + raise RuntimeError("invalid annotation '%s'" % (a,)) + param.type = pieces[1] + return True + if '.' in prefix[1:-1] and len(pieces) == 2: + param.attributes[prefix] = pieces[1] + return False + + def __parse_param_annotation(self, annotation, param): + pieces = annotation.split() + prefix = pieces[0] + if prefix == 'out': + if len(pieces) > 2: + raise RuntimeError("invalid annotation '%s'" % (a,)) + if len(pieces) == 1: + param.out = True + return True + if pieces[1] == 'caller-allocates': + param.out = True + param.caller_allocates = True + return True + if pieces[1] == 'callee-allocates': + param.out = True + param.callee_allocates = True + return True + raise RuntimeError("invalid annotation '%s'" % (a,)) + if prefix == 'in': + if len(pieces) > 1: + raise RuntimeError("invalid annotation '%s'" % (a,)) + param.in_ = True + return True + if prefix == 'inout': + if len(pieces) > 1: + raise RuntimeError("invalid annotation '%s'" % (a,)) + param.inout = True + return True + if prefix == 'allow-none': + if len(pieces) > 1: + raise RuntimeError("invalid annotation '%s'" % (a,)) + param.allow_none = True + return True + if prefix == 'default': + if len(pieces) != 2: + raise RuntimeError("invalid annotation '%s'" % (a,)) + param.default_value = pieces[1] + return True + if prefix == 'scope': + if len(pieces) != 2: + raise RuntimeError("invalid annotation '%s'" % (a,)) + if not pieces[1] in ('call', 'async', 'notified'): + raise RuntimeError("invalid annotation '%s'" % (a,)) + param.scope = pieces[1] + return True + if '.' in prefix[1:-1] and len(pieces) == 2: + param.attributes[prefix] = pieces[1] + return False + + def __parse_retval(self, pretval): + retval = Retval(pretval.type, pretval.docs) + if pretval.annotations: + for a in pretval.annotations: + if not self.__parse_param_or_retval_annotation(a, retval): + raise RuntimeError("invalid annotation '%s'" % (a,)) + if not retval.type: + raise RuntimeError('return type missing') + return retval + + def __parse_param(self, pp, pfunc): + if DEBUG: + print pp.name, pp.type, pp.docs + param = Param(pp.name, pp.type, pp.docs) + if pp.annotations: + for a in pp.annotations: + if self.__parse_param_or_retval_annotation(a, param): + pass + elif self.__parse_param_annotation(a, param): + pass + else: + raise RuntimeError("in %s: invalid annotation '%s'" % (pfunc.name, a,)) + if param.type is None: + raise RuntimeError('in %s: param type missing' % pfunc.name) + return param + + def __add_vmethod(self, pfunc): + _, cls, name = pfunc.name.split(':') + assert _ == 'vfunc' + params = [] + retval = None + docs = pfunc.docs + if pfunc.annotations: + for a in pfunc.annotations: + raise RuntimeError("unknown annotation '%s' in function %s" % (a, name)) + + if pfunc.params: + for p in pfunc.params: + params.append(self.__parse_param(p, pfunc)) + + if pfunc.retval: + retval = self.__parse_retval(pfunc.retval) + + meth = VMethod(name, cls, params[1:], retval, docs) + this_class_methods = self.__vmethods.get(cls) + if not this_class_methods: + this_class_methods = [] + self.__vmethods[cls] = this_class_methods + this_class_methods.append(meth) + + def __add_signal(self, pfunc): + _, cls, name = pfunc.name.split(':') + assert _ == 'signal' + params = [] + retval = None + docs = pfunc.docs + if pfunc.annotations: + for a in pfunc.annotations: + raise RuntimeError("unknown annotation '%s' in function %s" % (a, name)) + + if pfunc.params: + for p in pfunc.params: + params.append(self.__parse_param(p, pfunc)) + + if pfunc.retval: + retval = self.__parse_retval(pfunc.retval) + + meth = Signal(name, cls, params[1:], retval, docs) + this_class_signals = self.__signals.get(cls) + if not this_class_signals: + this_class_signals = [] + self.__signals[cls] = this_class_signals + this_class_signals.append(meth) + + def __add_function(self, pfunc): + name = pfunc.name + c_name = pfunc.name + params = [] + retval = None + docs = pfunc.docs + cls = None + constructor_of = None + static_method_of = None + kwargs = False + annotations = {} + + if pfunc.annotations: + for a in pfunc.annotations: + pieces = a.split() + prefix = pieces[0] + if prefix == 'constructor-of': + assert len(pieces) == 2 + constructor_of = pieces[1] + elif prefix == 'static-method-of': + assert len(pieces) == 2 + static_method_of = pieces[1] + elif prefix == 'moo-kwargs': + assert len(pieces) == 1 + kwargs = True + elif prefix.find('.') >= 0: + annotations[prefix] = ' '.join(pieces[1:]) + else: + raise RuntimeError("unknown annotation '%s' in function %s" % (a, name)) + + if pfunc.params: + for p in pfunc.params: + params.append(self.__parse_param(p, pfunc)) + + if pfunc.retval: + retval = self.__parse_retval(pfunc.retval) + + if hasattr(pfunc, 'method_of'): + cls = pfunc.method_of + elif static_method_of: + cls = static_method_of + elif constructor_of: + cls = constructor_of + elif params: + m = re.match(r'(const-)?([\w\d_]+)\*', params[0].type) + if m: + cls = m.group(2) + if not self.__class_dict.has_key(cls): + cls = None + + if cls: + name = strip_class_prefix(name, cls) + else: + name = strip_module_prefix(name, self.name) + + if constructor_of: + func = Constructor(name, c_name, cls, params, retval, docs) + if constructor_of in self.__constructors: + raise RuntimeError('duplicated constructor of class %s' % constructor_of) + self.__constructors[constructor_of] = func + elif cls: + if static_method_of: + func = StaticMethod(name, c_name, cls, params, retval, docs) + else: + func = Method(name, c_name, cls, params[1:], retval, docs) + this_class_methods = self.__methods.get(cls) + if not this_class_methods: + this_class_methods = [] + self.__methods[cls] = this_class_methods + this_class_methods.append(func) + else: + func = Function(name, c_name, params, retval, docs) + self.functions.append(func) + + func.summary = pfunc.summary + func.annotations = annotations + func.kwargs = kwargs + + def init_from_dox(self, blocks): + for b in blocks: + if isinstance(b, dparser.Class): + self.__add_class(b) + elif isinstance(b, dparser.Boxed): + self.__add_boxed(b) + elif isinstance(b, dparser.Pointer): + self.__add_pointer(b) + elif isinstance(b, dparser.Enum) or isinstance(b, dparser.Flags): + self.__add_enum(b) + elif isinstance(b, dparser.Flags): + self.__add_flags(b) + elif isinstance(b, dparser.VMethod): + self.__add_vmethod(b) + elif isinstance(b, dparser.Signal): + self.__add_signal(b) + elif isinstance(b, dparser.Function): + self.__add_function(b) + else: + raise RuntimeError('oops') + + instance_types = {} + for cls in self.classes + self.boxed: + if cls.name in instance_types: + raise RuntimeError('duplicated class %s' % (cls.name,)) + instance_types[cls.name] = cls + + for cls in self.__constructors: + func = self.__constructors[cls] + if not cls in instance_types: + raise RuntimeError('Constructor of unknown class %s' % cls) + else: + cls = instance_types[cls] + if cls.constructor is not None: + raise RuntimeError('duplicated constructor in class %s' % cls) + cls.constructor = func + + for cls in self.__methods: + methods = self.__methods[cls] + if not cls in instance_types: + raise RuntimeError('Methods of unknown class %s' % cls) + else: + cls = instance_types[cls] + for m in methods: + m.cls = cls + if isinstance(m, Method): + cls.methods.append(m) + elif isinstance(m, StaticMethod): + cls.static_methods.append(m) + else: + oops() + + for cls in self.__vmethods: + methods = self.__vmethods[cls] + if not cls in instance_types: + raise RuntimeError('Virtual methods of unknown class %s' % cls) + else: + cls = instance_types[cls] + for m in methods: + m.cls = cls + cls.vmethods += methods + + for cls in self.__signals: + signals = self.__signals[cls] + if not cls in instance_types: + raise RuntimeError('Signals of unknown class %s' % cls) + else: + cls = instance_types[cls] + for s in signals: + s.cls = cls + cls.signals += signals + + def format_func(func): + if func.retval and func.retval.type: + s = func.retval.type + ' ' + else: + s = 'void ' + s += func.name + s += ' (' + for i in range(len(func.params)): + if i != 0: + s += ', ' + p = func.params[i] + s += '%s %s' % (p.type, p.name) + s += ')' + return s + + if DEBUG: + for cls in self.classes: + print 'class %s' % (cls.name,) + for meth in cls.methods: + print ' %s' % (format_func(meth),) + for func in self.functions: + print format_func(func) diff --git a/api/mdp/xmlwriter.py b/api/mdp/xmlwriter.py new file mode 100644 index 00000000..f77aba16 --- /dev/null +++ b/api/mdp/xmlwriter.py @@ -0,0 +1,201 @@ +import sys +import xml.etree.ElementTree as etree + +import mdp.module as module + +class Writer(object): + def __init__(self, out): + object.__init__(self) + self.out = out + self.xml = etree.TreeBuilder() + self.__tag_opened = False + self.__depth = 0 + + def __start_tag(self, tag, attrs={}): + if self.__tag_opened: + self.xml.data('\n') + if self.__depth > 0: + self.xml.data(' ' * self.__depth) + elm = self.xml.start(tag, attrs) + self.__tag_opened = True + self.__depth += 1 + return elm + + def __end_tag(self, tag): + if not self.__tag_opened and self.__depth > 1: + self.xml.data(' ' * (self.__depth - 1)) + elm = self.xml.end(tag) + self.xml.data('\n') + self.__tag_opened = False + self.__depth -= 1 + return elm + + def __write_docs(self, docs): + if not docs: + return + self.__start_tag('doc') + docs = ' '.join(docs) + self.xml.data(docs) + self.__end_tag('doc') + + def __write_summary(self, summary): + if not summary: + return + self.__start_tag('summary') + if isinstance(summary, list): + assert len(summary) == 1 + summary = summary[0] + self.xml.data(summary) + self.__end_tag('summary') + + def __write_param_or_retval_annotations(self, param, elm): + for k in param.attributes: + elm.set(k, self.attributes[v]) + if param.transfer_mode is not None: + elm.set('transfer_mode', param.transfer_mode) + if param.element_type is not None: + elm.set('element_type', param.element_type) + if param.array: + elm.set('array', '1') + if param.array_fixed_len is not None: + elm.set('array_fixed_len', str(param.array_fixed_len)) + if param.array_len_param is not None: + elm.set('array_len_param', param.array_len_param) + if param.array_zero_terminated: + elm.set('array_zero_terminated', '1') + + def __write_param_annotations(self, param, elm): + self.__write_param_or_retval_annotations(param, elm) + if param.inout: + elm.set('inout', '1') + elif param.out: + elm.set('out', '1') + if param.caller_allocates: + elm.set('caller_allocates', '1') + elif param.callee_allocates: + elm.set('callee_allocates', '1') + if param.allow_none: + elm.set('allow_none', '1') + if param.default_value is not None: + elm.set('default_value', param.default_value) + if param.scope != 'call': + elm.set('scope', param.scope) + + def __write_retval_annotations(self, retval, elm): + self.__write_param_or_retval_annotations(retval, elm) + + def __check_type(self, param, func): + if param.type in ('char*', 'const-char*'): + print >>sys.stderr, '*** WARNING: raw type %s used in function %s' % (param.type, func) + + def __write_param(self, param, func): + self.__check_type(param, func) + dic = dict(name=param.name, type=param.type) + elm = self.__start_tag('param', dic) + self.__write_param_annotations(param, elm) + self.__write_docs(param.docs) + self.__end_tag('param') + + def __write_retval(self, retval, func): + self.__check_type(retval, func) + dic = dict(type=retval.type) + elm = self.__start_tag('retval', dic) + self.__write_retval_annotations(retval, elm) + self.__write_docs(retval.docs) + self.__end_tag('retval') + + def __write_class(self, cls): + if not cls.parent and cls.name != 'GObject': + raise RuntimeError('parent missing in class %s' % (cls.name,)) + dic = dict(name=cls.name, short_name=cls.short_name, parent=cls.parent or 'none', gtype_id=cls.gtype_id) + for k in cls.annotations: + dic[k] = cls.annotations[k] + if cls.constructable: + dic['constructable'] = '1' + self.__start_tag('class', dic) + self.__write_summary(cls.summary) + self.__write_docs(cls.docs) + if cls.constructor is not None: + self.__write_function(cls.constructor, 'constructor') + for meth in sorted(cls.static_methods, lambda x, y: cmp(x.name, y.name)): + self.__write_function(meth, 'static-method') + for meth in sorted(cls.signals, lambda x, y: cmp(x.name, y.name)): + self.__write_function(meth, 'signal') + for meth in sorted(cls.vmethods, lambda x, y: cmp(x.name, y.name)): + self.__write_function(meth, 'virtual') + for meth in sorted(cls.methods, lambda x, y: cmp(x.name, y.name)): + self.__write_function(meth, 'method') + self.__end_tag('class') + + def __write_boxed(self, cls): + dic = dict(name=cls.name, short_name=cls.short_name, gtype_id=cls.gtype_id) + for k in cls.annotations: + dic[k] = cls.annotations[k] + tag = 'boxed' if isinstance(cls, module.Boxed) else 'pointer' + self.__start_tag(tag, dic) + self.__write_summary(cls.summary) + self.__write_docs(cls.docs) + if cls.constructor is not None: + self.__write_function(cls.constructor, 'constructor') + for meth in cls.methods: + self.__write_function(meth, 'method') + self.__end_tag(tag) + + def __write_enum(self, enum): + if isinstance(enum, module.Enum): + tag = 'enum' + else: + tag = 'flags' + dic = dict(name=enum.name, short_name=enum.short_name, gtype_id=enum.gtype_id) + for k in enum.annotations: + dic[k] = enum.annotations[k] + self.__start_tag(tag, dic) + + for v in enum.values: + dic = dict(name=v.name) + if v.attributes: + dic.update(v.attributes) + elm = self.__start_tag('value', dic) + self.__write_docs(v.docs) + self.__end_tag('value') + + self.__write_summary(enum.summary) + self.__write_docs(enum.docs) + self.__end_tag(tag) + + def __write_function(self, func, tag): + name=func.name + if tag == 'signal': + name = name.replace('_', '-') + dic = dict(name=name) + if tag != 'virtual' and tag != 'signal': + dic['c_name'] = func.c_name + if getattr(func, 'kwargs', False): + dic['kwargs'] = '1' + for k in func.annotations: + dic[k] = func.annotations[k] + self.__start_tag(tag, dic) + for p in func.params: + self.__write_param(p, func.c_name) + if func.retval: + self.__write_retval(func.retval, func.c_name) + self.__write_summary(func.summary) + self.__write_docs(func.docs) + self.__end_tag(tag) + + def write(self, module): + self.__start_tag('module', dict(name=module.name)) + for cls in sorted(module.classes, lambda x, y: cmp(x.name, y.name)): + self.__write_class(cls) + for cls in sorted(module.boxed, lambda x, y: cmp(x.name, y.name)): + self.__write_boxed(cls) + for enum in sorted(module.enums, lambda x, y: cmp(x.name, y.name)): + self.__write_enum(enum) + for func in sorted(module.functions, lambda x, y: cmp(x.name, y.name)): + self.__write_function(func, 'function') + self.__end_tag('module') + elm = self.xml.close() + etree.ElementTree(elm).write(self.out) + +def write_xml(module, out): + Writer(out).write(module) diff --git a/api/mpi/__init__.py b/api/mpi/__init__.py new file mode 100644 index 00000000..1bb8bf6d --- /dev/null +++ b/api/mpi/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/api/mpi/defswriter.py b/api/mpi/defswriter.py new file mode 100644 index 00000000..39f3ccc5 --- /dev/null +++ b/api/mpi/defswriter.py @@ -0,0 +1,193 @@ +from mpi.module import * + +def split_camel_case_name(name): + comps = [] + cur = '' + for c in name: + if c.islower() or not cur: + cur += c + else: + comps.append(cur) + cur = c + if cur: + comps.append(cur) + return comps + +class_template = """\ +(define-object %(short_name)s + (in-module "%(module)s") + (parent "%(parent)s") + (c-name "%(name)s") + (gtype-id "%(gtype_id)s") +) +""" + +method_start_template = """\ +(define-method %(name)s + (of-object "%(class)s") + (c-name "%(c_name)s") + (return-type "%(return_type)s") +""" + +vmethod_start_template = """\ +(define-virtual %(name)s + (of-object "%(class)s") + (return-type "%(return_type)s") +""" + +function_start_template = """\ +(define-function %(name)s + (c-name "%(c_name)s") + (return-type "%(return_type)s") +""" + +type_template = """\ +(define-%(what)s %(short_name)s + (in-module "%(module)s") + (c-name "%(name)s") + (gtype-id "%(gtype_id)s") +) +""" + +class Writer(object): + def __init__(self, out): + object.__init__(self) + self.out = out + self.module = None + + def __write_class_decl(self, cls): + dic = dict(name=cls.name, + short_name=cls.short_name, + module=self.module.name, + parent=cls.parent, + gtype_id=cls.gtype_id) + self.out.write(class_template % dic) + self.out.write('\n') + + def __write_boxed_decl(self, cls): + dic = dict(name=cls.name, + short_name=cls.short_name, + module=self.module.name, + gtype_id=cls.gtype_id, + what='boxed') + self.out.write(type_template % dic) + self.out.write('\n') + + def __write_pointer_decl(self, cls): + dic = dict(name=cls.name, + short_name=cls.short_name, + module=self.module.name, + gtype_id=cls.gtype_id, + what='pointer') + self.out.write(type_template % dic) + self.out.write('\n') + + def __write_enum_decl(self, cls): + dic = dict(name=cls.name, + short_name=cls.short_name, + module=self.module.name, + gtype_id=cls.gtype_id, + what='enum' if isinstance(cls, Enum) else 'flags') + self.out.write(type_template % dic) + self.out.write('\n') + + def __get_pygtk_type_name(self, typ): + if isinstance(typ, InstanceType): + return typ.name + '*' + elif typ.name in ('utf8', 'filename', 'cstring'): + return 'char*' + elif typ.name in ('const-utf8', 'const-filename', 'const-cstring'): + return 'const-char*' + else: + return typ.name + + def __write_function_or_method(self, meth, cls): + if meth.retval is None: + return_type = 'none' + else: + return_type = self.__get_pygtk_type_name(meth.retval.type) + dic = dict(name=meth.name, c_name=meth.c_name, return_type=return_type) + if not cls: + self.out.write(function_start_template % dic) + elif isinstance(meth, Constructor): + dic['class'] = cls.name + self.out.write(function_start_template % dic) + self.out.write(' (is-constructor-of %s)\n' % cls.name) + elif isinstance(meth, StaticMethod): + dic['class'] = cls.name + self.out.write(method_start_template % dic) + self.out.write(' (is-static-method #t)\n') + elif isinstance(meth, VMethod): + dic['class'] = cls.name + self.out.write(vmethod_start_template % dic) + else: + dic['class'] = cls.name + self.out.write(method_start_template % dic) + if meth.retval: + if meth.retval.transfer_mode == 'full': + self.out.write(' (caller-owns-return #t)\n') + elif meth.retval.transfer_mode is not None: + raise RuntimeError('do not know how to handle transfer mode %s' % (meth.retval.transfer_mode,)) + if meth.params: + self.out.write(' (parameters\n') + for p in meth.params: + self.out.write(' \'("%s" "%s"' % (self.__get_pygtk_type_name(p.type), p.name)) + if p.allow_none: + self.out.write(' (null-ok)') + if p.default_value is not None: + self.out.write(' (default "%s")' % (p.default_value,)) + self.out.write(')\n') + self.out.write(' )\n') + self.out.write(')\n\n') + + def __write_class_method(self, meth, cls): + self.__write_function_or_method(meth, cls) + + def __write_static_class_method(self, meth, cls): + self.__write_function_or_method(meth, cls) + + def __write_class_methods(self, cls): + self.out.write('; methods of %s\n\n' % cls.name) + if cls.constructor is not None: + self.__write_function_or_method(cls.constructor, cls) + if hasattr(cls, 'constructable') and cls.constructable: + cons = Constructor() + cons.name = '%s__new__' % cls.name + cons.c_name = cons.name + self.__write_function_or_method(cons, cls) + if isinstance(cls, Class): + for meth in cls.vmethods: + self.__write_class_method(meth, cls) + for meth in cls.methods: + self.__write_class_method(meth, cls) + for meth in cls.static_methods: + self.__write_static_class_method(meth, cls) + + def __write_function(self, func): + self.__write_function_or_method(func, None) + + def write(self, module): + self.module = module + self.out.write('; -*- scheme -*-\n\n') + self.out.write('; classes\n\n') + for cls in module.get_classes(): + self.__write_class_decl(cls) + self.out.write('; boxed types\n\n') + for cls in module.get_boxed(): + self.__write_boxed_decl(cls) + self.out.write('; pointer types\n\n') + for cls in module.get_pointers(): + self.__write_pointer_decl(cls) + self.out.write('; enums and flags\n\n') + for enum in module.get_enums(): + self.__write_enum_decl(enum) + for cls in module.get_classes(): + self.__write_class_methods(cls) + for cls in module.get_boxed(): + self.__write_class_methods(cls) + for cls in module.get_pointers(): + self.__write_class_methods(cls) + self.out.write('; functions\n\n') + for func in module.get_functions(): + self.__write_function(func) + self.module = None diff --git a/api/mpi/docbookwriter.py b/api/mpi/docbookwriter.py new file mode 100644 index 00000000..60994271 --- /dev/null +++ b/api/mpi/docbookwriter.py @@ -0,0 +1,496 @@ +import StringIO + +from mpi.util import * +from mpi.module import * + +lua_constants = { + 'NULL': 'nil', + 'TRUE': 'true', + 'FALSE': 'false', +} + +python_constants = { + 'NULL': 'None', + 'TRUE': 'True', + 'FALSE': 'False', + 'GTK_RESPONSE_OK': 'gtk.RESPONSE_OK', +} + +common_types = { + 'gboolean': 'bool', + 'strv': 'list of strings', +} + +lua_types = dict(common_types) +lua_types.update({ + 'index': 'index', + 'value': 'value', + 'gunichar': 'string', + 'double': 'number', + 'int': 'integer', + 'gint': 'integer', + 'guint': 'integer', + 'gulong': 'integer', + 'const-utf8': 'string', + 'utf8': 'string', + 'const-filename': 'string', + 'filename': 'string', + 'const-cstring': 'string', + 'cstring': 'string', +}) + +python_types = dict(common_types) +python_types.update({ + 'index': 'int', + 'gunichar': 'str', + 'double': 'float', + 'int': 'int', + 'gint': 'int', + 'guint': 'int', + 'gulong': 'int', + 'const-utf8': 'str', + 'utf8': 'str', + 'const-filename': 'str', + 'filename': 'str', + 'const-cstring': 'str', + 'cstring': 'str', +}) + +def format_python_symbol_ref(symbol): + constants = { + } + classes = { + 'GFile': 'gio.File', + 'GObject': 'gobject.Object', + 'GType': 'type', + } + if symbol in constants: + return '%s' % constants[symbol] + if symbol in classes: + return '%s' % classes[symbol] + if symbol.startswith('Gtk'): + return ('gtk.%s') % (symbol[3:].lower(), symbol[3:]) + if symbol.startswith('Gdk'): + return 'gtk.gdk.%s' % (symbol[3:].lower(), symbol[3:]) + raise NotImplementedError(symbol) + +def split_camel_case_name(name): + comps = [] + cur = '' + for c in name: + if c.islower() or not cur: + cur += c + else: + comps.append(cur) + cur = c + if cur: + comps.append(cur) + return comps + +def name_all_caps(cls): + return '_'.join([s.upper() for s in split_camel_case_name(cls.name)]) + +class Writer(object): + def __init__(self, mode, template, out): + super(Writer, self).__init__() + self.file = out + self.out = StringIO.StringIO() + self.mode = mode + self.template = template + if mode == 'python': + self.constants = python_constants + self.builtin_types = python_types + elif mode == 'lua': + self.constants = lua_constants + self.builtin_types = lua_types + else: + oops('unknown mode %s' % mode) + + self.section_suffix = ' (%s)' % self.mode.capitalize() + + def __format_symbol_ref(self, name): + sym = self.symbols.get(name) + if sym: + if isinstance(sym, EnumValue): + return '' % \ + dict(symbol=name, mode=self.mode, parent=sym.enum.symbol_id()) + elif isinstance(sym, Type): + return '%(name)s' % \ + dict(symbol=name, mode=self.mode, name=self.__make_class_name(sym)) + else: + oops(name) + if self.mode == 'python': + return format_python_symbol_ref(name) + else: + raise NotImplementedError(name) + + def __string_to_bool(self, s): + if s == '0': + return False + elif s == '1': + return True + else: + oops() + + def __check_bind_ann(self, obj): + bind = self.__string_to_bool(obj.annotations.get('moo.' + self.mode, '1')) + if bind: + bind = not self.__string_to_bool(obj.annotations.get('moo.private', '0')) + return bind + + def __format_constant(self, value): + if value in self.constants: + return self.constants[value] + try: + i = int(value) + return value + except ValueError: + pass + formatted = self.__format_symbol_ref(value) + if formatted: + return formatted + error("unknown constant '%s'" % value) + return value + + def __format_default_value(self, p): + if p.type.name == 'index' and self.mode == 'lua': + return str(int(p.default_value) + 1) + else: + return self.__format_constant(p.default_value) + + def __format_doc(self, doc): + text = doc.text + text = re.sub(r'@([\w\d_]+)(?!\{)', r'\1', text) + text = re.sub(r'%NULL\b', '%s' % self.constants['NULL'], text) + text = re.sub(r'%TRUE\b', '%s' % self.constants['TRUE'], text) + text = re.sub(r'%FALSE\b', '%s' % self.constants['FALSE'], text) + text = text.replace(r'', '') + text = text.replace(r'', '\n') + + def repl_func(m): + func_id = m.group(1) + symbol = self.symbols.get(func_id) + if not isinstance(symbol, MethodBase) or symbol.cls == self.current_class: + return '' % \ + dict(func_id=func_id, mode=self.mode) + else: + return '%(Class)s.%(method)s()' % \ + dict(func_id=func_id, mode=self.mode, Class=self.__make_class_name(symbol.cls), method=symbol.name) + text = re.sub(r'([\w\d_.]+)\(\)', repl_func, text) + + def repl_signal(m): + cls = m.group(1) + signal = m.group(2).replace('_', '-') + symbol = 'signal:%s:%s' % (cls, signal) + if self.symbols[cls] != self.current_class: + return '%(Class)s.%(signal)s' % \ + dict(Class=cls, symbol=symbol, mode=self.mode, signal=signal) + else: + return '%(signal)s' % \ + dict(symbol=symbol, mode=self.mode, signal=signal) + text = re.sub(r'([\w\d_-]+)::([\w\d_-]+)', repl_signal, text) + + def repl_symbol(m): + symbol = m.group(1) + formatted = self.__format_symbol_ref(symbol) + if not formatted: + raise RuntimeError('unknown symbol %s' % symbol) + return formatted + text = re.sub(r'#([\w\d_]+)', repl_symbol, text) + + assert not re.search(r'NULL|TRUE|FALSE', text) + return text + + def __make_class_name(self, cls): + return '%s.%s' % (cls.module.name.lower(), cls.short_name) + + def __get_obj_name(self, cls): + name = cls.annotations.get('moo.doc-object-name') + if not name: + name = '_'.join([c.lower() for c in split_camel_case_name(cls.name)[1:]]) + return name + + def __write_function(self, func, cls): + if not self.__check_bind_ann(func): + return + + func_params = list(func.params) + if func.has_gerror_return: + func_params = func_params[:-1] + params = [] + for p in func_params: + if not self.__check_bind_ann(p.type): + return + if p.default_value is not None: + params.append('%s=%s' % (p.name, self.__format_default_value(p))) + else: + params.append(p.name) + + if isinstance(func, Constructor): + if self.mode == 'python': + func_title = cls.short_name + '()' + func_name = cls.short_name + elif self.mode == 'lua': + func_title = 'new' + '()' + func_name = '%s.new' % cls.short_name + else: + oops() + elif isinstance(func, Signal): + if self.mode == 'python': + func_title = 'signal ' + func.name + func_name = func.name + elif self.mode == 'lua': + func_title = 'signal ' + func.name + func_name = func.name + else: + oops() + elif cls is not None: + if self.mode in ('python', 'lua'): + func_title = func.name + '()' + func_name = '%s.%s' % (self.__get_obj_name(cls), func.name) \ + if not isinstance(func, StaticMethod) \ + else '%s.%s' % (cls.short_name, func.name) + else: + oops() + else: + func_title = func.name + '()' + func_name = '%s.%s' % (self.module.name.lower(), func.name) + + params_string = ', '.join(params) + + if func.summary: + oops(func_id) + + func_id = func.symbol_id() + mode = self.mode + + if mode == 'lua' and func.kwargs: + left_paren, right_paren = '{}' + else: + left_paren, right_paren = '()' + + self.out.write("""\ + + +%(func_title)s +%(func_name)s%(left_paren)s%(params_string)s%(right_paren)s +""" % locals()) + + if func.doc: + self.out.write('%s\n' % self.__format_doc(func.doc)) + + if func_params: + self.out.write("""\ + + + +""") + + for p in func_params: + if p.doc: + docs = doc=self.__format_doc(p.doc) + else: + ptype = p.type.symbol_id() + if ptype.endswith('*'): + ptype = ptype[:-1] + if ptype.startswith('const-'): + ptype = ptype[len('const-'):] + if ptype in self.builtin_types: + docs = self.builtin_types[ptype] + if p.allow_none: + docs = docs + ' or ' + self.__format_constant('NULL') + elif ptype.endswith('Array'): + elmtype = ptype[:-len('Array')] + if elmtype in self.symbols and isinstance(self.symbols[elmtype], InstanceType): + docs = 'list of %s objects' % self.__format_symbol_ref(self.symbols[elmtype].symbol_id()) + else: + docs = self.__format_symbol_ref(ptype) + else: + docs = self.__format_symbol_ref(ptype) + if p.allow_none: + docs = docs + ' or ' + self.__format_constant('NULL') + + param_dic = dict(param=p.name, doc=docs) + self.out.write("""\ + +%(param)s +%(doc)s + +""" % param_dic) + + self.out.write('\n') + + if func.retval: + retdoc = None + if func.retval.doc: + retdoc = self.__format_doc(func.retval.doc) + else: + rettype = func.retval.type.symbol_id() + if rettype.endswith('*'): + rettype = rettype[:-1] + if rettype in self.builtin_types: + retdoc = self.builtin_types[rettype] + elif rettype.endswith('Array'): + elmtype = rettype[:-len('Array')] + if elmtype in self.symbols and isinstance(self.symbols[elmtype], InstanceType): + retdoc = 'list of %s objects' % self.__format_symbol_ref(self.symbols[elmtype].symbol_id()) + else: + retdoc = self.__format_symbol_ref(rettype) + else: + retdoc = self.__format_symbol_ref(rettype) + if retdoc: + self.out.write('Returns: %s\n' % retdoc) + + self.out.write('\n') + + def __write_gobject_constructor(self, cls): + func = Constructor() + self.__write_function(func, cls) + + def __write_class(self, cls): + if not self.__check_bind_ann(cls): + return + + self.current_class = cls + + title = self.__make_class_name(cls) + if cls.summary: + title += ' - ' + cls.summary.text + dic = dict(class_id=cls.symbol_id(), title=title, mode=self.mode) + self.out.write("""\ + + +%(title)s +""" % dic) + + if cls.doc: + self.out.write('%s\n' % self.__format_doc(cls.doc)) + + if getattr(cls, 'parent', 'none') != 'none': + self.out.write("""\ + +%(ParentClass)s + | + +-- %(Class)s + +""" % dict(ParentClass=self.__format_symbol_ref(cls.parent), Class=self.__make_class_name(cls))) + + if hasattr(cls, 'signals') and cls.signals: + for signal in cls.signals: + self.__write_function(signal, cls) + if cls.constructor is not None: + self.__write_function(cls.constructor, cls) + if hasattr(cls, 'constructable') and cls.constructable: + self.__write_gobject_constructor(cls) + for meth in cls.static_methods: + self.__write_function(meth, cls) + if isinstance(cls, Class): + if cls.vmethods: + implement_me('virtual methods of %s' % cls.name) + for meth in cls.methods: + self.__write_function(meth, cls) + + self.out.write("""\ + +""" % dic) + + self.current_class = None + + def __write_enum(self, enum): + if not self.__check_bind_ann(enum): + return + + do_write = False + for v in enum.values: + if self.__check_bind_ann(v): + do_write = True + break + + if not do_write: + return + + title = self.__make_class_name(enum) + if enum.summary: + title += ' - ' + enum.summary.text + dic = dict(title=title, + mode=self.mode, + enum_id=enum.symbol_id()) + + self.out.write("""\ + + +%(title)s +""" % dic) + + if enum.doc: + self.out.write('%s\n' % self.__format_doc(enum.doc)) + + self.out.write("""\ + + +""") + + for v in enum.values: + if not self.__check_bind_ann(v): + continue + value_dic = dict(mode=self.mode, enum_id=v.name, value=v.short_name, + doc=self.__format_doc(v.doc) if v.doc else '') + self.out.write("""\ + + %(value)s + %(doc)s + +""" % value_dic) + + self.out.write('\n') + + self.out.write('\n') + + def write(self, module): + self.module = module + self.symbols = module.get_symbols() + self.current_class = None + + classes = module.get_classes() + module.get_boxed() + module.get_pointers() + for cls in sorted(classes, lambda cls1, cls2: cmp(cls1.short_name, cls2.short_name)): + self.__write_class(cls) + + funcs = [] + for f in module.get_functions(): + if self.__check_bind_ann(f): + funcs.append(f) + + dic = dict(mode=self.mode) + + if funcs: + self.out.write("""\ + + +Functions +""" % dic) + + for func in funcs: + self.__write_function(func, None) + + self.out.write('\n') + + self.out.write("""\ + + +Enumerations +""" % dic) + + for func in module.get_enums(): + self.__write_enum(func) + + self.out.write('\n') + + content = self.out.getvalue() + template = open(self.template).read() + self.file.write(template.replace('###GENERATED###', content)) + + del self.out + del self.module diff --git a/api/mpi/luawriter.py b/api/mpi/luawriter.py new file mode 100644 index 00000000..6c8cd6f8 --- /dev/null +++ b/api/mpi/luawriter.py @@ -0,0 +1,547 @@ +import sys + +from mpi.module import * + +tmpl_file_start = """\ +#ifdef HAVE_CONFIG_H +#include +#endif +%(headers)s +#include "moo-lua-api-util.h" + +extern "C" void moo_test_coverage_record (const char *lang, const char *function); + +""" + +tmpl_cfunc_method_start = """\ +static int +%(cfunc)s (gpointer pself, G_GNUC_UNUSED lua_State *L, G_GNUC_UNUSED int first_arg) +{ +#ifdef MOO_ENABLE_COVERAGE + moo_test_coverage_record ("lua", "%(c_name)s"); +#endif + MooLuaCurrentFunc cur_func ("%(current_function)s"); +%(check_kwargs)s %(Class)s *self = (%(Class)s*) pself; +""" + +tmpl_cfunc_func_start = """\ +static int +%(cfunc)s (G_GNUC_UNUSED lua_State *L) +{ +#ifdef MOO_ENABLE_COVERAGE + moo_test_coverage_record ("lua", "%(c_name)s"); +#endif + MooLuaCurrentFunc cur_func ("%(current_function)s"); +%(check_kwargs)s""" + +tmpl_register_module_start = """\ +static void +%(module)s_lua_api_register (void) +{ + static gboolean been_here = FALSE; + + if (been_here) + return; + + been_here = TRUE; + +""" + +tmpl_register_one_type_start = """\ + MooLuaMethodEntry methods_%(Class)s[] = { +""" +tmpl_register_one_type_end = """\ + { NULL, NULL } + }; + moo_lua_register_methods (%(gtype_id)s, methods_%(Class)s); + +""" + +class ArgHelper(object): + def format_arg(self, allow_none, default_value, arg_name, arg_idx, param_name): + return '' + +def c_allow_none(attr): + if attr is True: + return 'TRUE' + elif attr in (False, None): + return 'FALSE' + else: + oops() + +class SimpleArgHelper(ArgHelper): + def __init__(self, name, suffix, can_be_null=False): + super(SimpleArgHelper, self).__init__() + self.name = name + self.suffix = suffix + self.can_be_null = can_be_null + + def format_arg(self, allow_none, default_value, arg_name, arg_idx, param_name): + dic = dict(type=self.name, arg_name=arg_name, default_value=default_value, + arg_idx=arg_idx, param_name=param_name, suffix=self.suffix, + allow_none=c_allow_none(allow_none)) + if self.can_be_null: + if default_value is not None: + return '%(type)s %(arg_name)s = moo_lua_get_arg_%(suffix)s_opt (L, %(arg_idx)s, "%(param_name)s", %(default_value)s, %(allow_none)s);' % dic + else: + return '%(type)s %(arg_name)s = moo_lua_get_arg_%(suffix)s (L, %(arg_idx)s, "%(param_name)s", %(allow_none)s);' % dic + else: + if default_value is not None: + return '%(type)s %(arg_name)s = moo_lua_get_arg_%(suffix)s_opt (L, %(arg_idx)s, "%(param_name)s", %(default_value)s);' % dic + else: + return '%(type)s %(arg_name)s = moo_lua_get_arg_%(suffix)s (L, %(arg_idx)s, "%(param_name)s");' % dic + +_arg_helpers = {} +_arg_helpers['int'] = SimpleArgHelper('int', 'int') +_arg_helpers['gint'] = SimpleArgHelper('int', 'int') +_arg_helpers['guint'] = SimpleArgHelper('guint', 'uint') +_arg_helpers['glong'] = SimpleArgHelper('long', 'long') +_arg_helpers['gulong'] = SimpleArgHelper('gulong', 'ulong') +_arg_helpers['gboolean'] = SimpleArgHelper('gboolean', 'bool') +_arg_helpers['index'] = SimpleArgHelper('int', 'index') +_arg_helpers['double'] = SimpleArgHelper('double', 'double') +_arg_helpers['const-char*'] = SimpleArgHelper('const char*', 'string', True) +_arg_helpers['char*'] = SimpleArgHelper('char*', 'string', True) +_arg_helpers['const-utf8'] = SimpleArgHelper('const char*', 'utf8', True) +_arg_helpers['utf8'] = SimpleArgHelper('char*', 'utf8', True) +_arg_helpers['const-filename'] = SimpleArgHelper('const char*', 'filename', True) +_arg_helpers['filename'] = SimpleArgHelper('char*', 'filename', True) +_arg_helpers['const-cstring'] = SimpleArgHelper('const char*', 'string', True) +_arg_helpers['cstring'] = SimpleArgHelper('char*', 'string', True) +def find_arg_helper(param): + return _arg_helpers[param.type.name] + +_pod_ret_helpers = {} +_pod_ret_helpers['int'] = ('int', 'int64') +_pod_ret_helpers['uint'] = ('guint', 'uint64') +_pod_ret_helpers['gint'] = ('int', 'int64') +_pod_ret_helpers['guint'] = ('guint', 'uint64') +_pod_ret_helpers['glong'] = ('long', 'int64') +_pod_ret_helpers['gulong'] = ('gulong', 'uint64') +_pod_ret_helpers['gboolean'] = ('gboolean', 'bool') +_pod_ret_helpers['index'] = ('int', 'index') +def find_pod_ret_helper(name): + return _pod_ret_helpers[name] + +class Writer(object): + def __init__(self, out): + super(Writer, self).__init__() + self.out = out + + def __write_function_param(self, func_body, param, i, meth, cls): + dic = dict(narg=i, gtype_id=param.type.gtype_id, param_name=param.name, + allow_none=c_allow_none(param.allow_none), + default_value=param.default_value, + first_arg='first_arg' if cls else '1', + arg_idx=('first_arg + %d' % (i,)) if cls else ('1 + %d' % (i,)), + TypeName=param.type.name, + ) + + if meth.kwargs: + func_body.start.append('int arg_idx%(narg)d = %(arg_idx)s;' % dic) + func_body.start.append('if (kwargs)') + func_body.start.append(' arg_idx%(narg)d = moo_lua_get_kwarg (L, %(first_arg)s, %(narg)d + 1, "%(param_name)s");' % dic) + + if param.default_value is None: + func_body.start.append('if (arg_idx%(narg)d == MOO_NONEXISTING_INDEX)' % dic) + func_body.start.append(' moo_lua_arg_error (L, 0, "%(param_name)s", "parameter \'%(param_name)s\' missing");' % dic) + + dic['arg_idx'] = 'arg_idx%(narg)d' % dic + + if param.type.name == 'GtkTextIter': + assert param.default_value is None or param.default_value == 'NULL' + if param.default_value is not None: + dic['get_arg'] = 'moo_lua_get_arg_iter_opt' + else: + dic['get_arg'] = 'moo_lua_get_arg_iter' + if cls.name == 'MooEdit': + dic['buffer'] = 'moo_edit_get_buffer (self)' + else: + dic['buffer'] = 'NULL' + if param.default_value is not None: + func_body.start.append('GtkTextIter arg%(narg)d_iter;' % dic) + func_body.start.append(('GtkTextIter *arg%(narg)d = %(get_arg)s (L, %(arg_idx)s, ' + \ + '"%(param_name)s", %(buffer)s, &arg%(narg)d_iter) ? &arg%(narg)d_iter : NULL;') % dic) + else: + func_body.start.append('GtkTextIter arg%(narg)d_iter;' % dic) + func_body.start.append('GtkTextIter *arg%(narg)d = &arg%(narg)d_iter;' % dic) + func_body.start.append('%(get_arg)s (L, %(arg_idx)s, "%(param_name)s", %(buffer)s, &arg%(narg)d_iter);' % dic) + elif param.type.name == 'GdkRectangle': + assert param.default_value is None or param.default_value == 'NULL' + if param.default_value is not None: + dic['get_arg'] = 'moo_lua_get_arg_rect_opt' + else: + dic['get_arg'] = 'moo_lua_get_arg_rect' + if param.default_value is not None: + func_body.start.append('GdkRectangle arg%(narg)d_rect;' % dic) + func_body.start.append(('GdkRectangle *arg%(narg)d = %(get_arg)s (L, %(arg_idx)s, ' + \ + '"%(param_name)s", &arg%(narg)d_rect) ? &arg%(narg)d_rect : NULL;') % dic) + else: + func_body.start.append('GdkRectangle arg%(narg)d_rect;' % dic) + func_body.start.append('GdkRectangle *arg%(narg)d = &arg%(narg)d_rect;' % dic) + func_body.start.append('%(get_arg)s (L, %(arg_idx)s, "%(param_name)s", &arg%(narg)d_rect);' % dic) + elif param.type.name == 'SignalClosure*': + assert param.default_value is None + func_body.start.append(('MooLuaSignalClosure *arg%(narg)d = moo_lua_get_arg_signal_closure ' + \ + '(L, %(arg_idx)s, "%(param_name)s");') % dic) + func_body.end.append('g_closure_unref ((GClosure*) arg%(narg)d);' % dic) + elif isinstance(param.type, Class) or isinstance(param.type, Boxed) or isinstance(param.type, Pointer): + if param.default_value is not None: + func_body.start.append(('%(TypeName)s *arg%(narg)d = (%(TypeName)s*) ' + \ + 'moo_lua_get_arg_instance_opt (L, %(arg_idx)s, "%(param_name)s", ' + \ + '%(gtype_id)s, %(allow_none)s);') % dic) + else: + func_body.start.append(('%(TypeName)s *arg%(narg)d = (%(TypeName)s*) ' + \ + 'moo_lua_get_arg_instance (L, %(arg_idx)s, "%(param_name)s", ' + \ + '%(gtype_id)s, %(allow_none)s);') % dic) + elif isinstance(param.type, Enum) or isinstance(param.type, Flags): + if param.default_value is not None: + func_body.start.append(('%(TypeName)s arg%(narg)d = (%(TypeName)s) ' + \ + 'moo_lua_get_arg_enum_opt (L, %(arg_idx)s, "%(param_name)s", ' + \ + '%(gtype_id)s, %(default_value)s);') % dic) + else: + func_body.start.append(('%(TypeName)s arg%(narg)d = (%(TypeName)s) ' + \ + 'moo_lua_get_arg_enum (L, %(arg_idx)s, "%(param_name)s", ' + \ + '%(gtype_id)s);') % dic) + elif isinstance(param.type, ArrayType): + assert isinstance(param.type.elm_type, Class) + dic['gtype_id'] = param.type.elm_type.gtype_id + if param.default_value is not None: + func_body.start.append(('%(TypeName)s arg%(narg)d = (%(TypeName)s) ' + \ + 'moo_lua_get_arg_object_array_opt (L, %(arg_idx)s, "%(param_name)s", ' + \ + '%(gtype_id)s);') % dic) + else: + func_body.start.append(('%(TypeName)s arg%(narg)d = (%(TypeName)s) ' + \ + 'moo_lua_get_arg_object_array (L, %(arg_idx)s, "%(param_name)s", ' + \ + '%(gtype_id)s);') % dic) + func_body.end.append('moo_object_array_free ((MooObjectArray*) arg%(narg)d);' % dic) + elif param.type.name == 'strv': + assert param.default_value is None or param.default_value == 'NULL' + if param.default_value is not None: + func_body.start.append(('char **arg%(narg)d = moo_lua_get_arg_strv_opt (L, %(arg_idx)s, "%(param_name)s", %(allow_none)s);') % dic) + else: + func_body.start.append(('char **arg%(narg)d = moo_lua_get_arg_strv (L, %(arg_idx)s, "%(param_name)s", %(allow_none)s);') % dic) + func_body.end.append('g_strfreev (arg%(narg)d);' % dic) + else: + assert param.transfer_mode is None + arg_helper = find_arg_helper(param) + func_body.start.append(arg_helper.format_arg(param.allow_none, param.default_value, + 'arg%(narg)d' % dic, '%(arg_idx)s' % dic, param.name)) + + def __write_function(self, meth, cls, method_cfuncs): + assert not isinstance(meth, VMethod) + + bind = meth.annotations.get('moo.lua', '1') + if bind == '0': + return + elif bind != '1': + raise RuntimeError('invalid value %s for moo.lua annotation' % (bind,)) + + cfunc = meth.annotations.get('moo.lua.cfunc') + if cfunc: + method_cfuncs.append([meth.name, cfunc]) + return + + is_constructor = isinstance(meth, Constructor) + static_method = isinstance(meth, StaticMethod) + own_return = is_constructor or (meth.retval and meth.retval.transfer_mode == 'full') + + params = [] + for i in range(len(meth.params)): + p = meth.params[i] + + if not p.type.name in _arg_helpers and not isinstance(p.type, ArrayType) and \ + not isinstance(p.type, GTypedType) and not isinstance(p.type, GErrorReturnType) and \ + not p.type.name in ('SignalClosure*', 'strv'): + raise RuntimeError("cannot write function %s because of '%s' parameter" % (meth.c_name, p.type.name)) + + if isinstance(p.type, GErrorReturnType): + assert i == len(meth.params) - 1 + assert meth.has_gerror_return + else: + params.append(p) + + check_kwargs = '' + has_self = cls and not is_constructor and not static_method + if has_self: + first_arg = 'first_arg' + else: + first_arg = '1' + + if meth.kwargs: + check_kwargs = ' bool kwargs = moo_lua_check_kwargs (L, %s);\n' % first_arg + + dic = dict(name=meth.name, c_name=meth.c_name, check_kwargs=check_kwargs, first_arg=first_arg) + if has_self: + dic['cfunc'] = 'cfunc_%s_%s' % (cls.name, meth.name) + dic['Class'] = cls.name + dic['current_function'] = '%s.%s' % (cls.name, meth.name) + self.out.write(tmpl_cfunc_method_start % dic) + elif static_method: + dic['cfunc'] = 'cfunc_%s_%s' % (cls.name, meth.name) + dic['Class'] = cls.name + dic['current_function'] = '%s.%s' % (cls.name, meth.name) + self.out.write(tmpl_cfunc_func_start % dic) + elif is_constructor: + dic['cfunc'] = 'cfunc_%s_new' % cls.name + dic['Class'] = cls.name + dic['current_function'] = '%s.new' % cls.name + self.out.write(tmpl_cfunc_func_start % dic) + else: + dic['cfunc'] = 'cfunc_%s' % meth.name + dic['current_function'] = meth.name + self.out.write(tmpl_cfunc_func_start % dic) + + method_cfuncs.append([meth.name, dic['cfunc']]) + + class FuncBody: + def __init__(self): + self.start = [] + self.end = [] + + func_body = FuncBody() + func_call = '' + + i = 0 + for p in params: + self.__write_function_param(func_body, p, i, meth, None if (is_constructor or static_method) else cls) + i += 1 + + if meth.has_gerror_return: + func_body.start.append('GError *error = NULL;') + + if meth.retval: + dic = {'gtype_id': meth.retval.type.gtype_id, + 'make_copy': 'FALSE' if own_return else 'TRUE', + } + if isinstance(meth.retval.type, Class): + func_call = 'gpointer ret = ' + push_ret = 'moo_lua_push_object (L, (GObject*) ret, %(make_copy)s);' % dic + elif isinstance(meth.retval.type, Boxed): + func_call = 'gpointer ret = ' + push_ret = 'moo_lua_push_boxed (L, ret, %(gtype_id)s, %(make_copy)s);' % dic + elif isinstance(meth.retval.type, Pointer): + func_call = 'gpointer ret = ' + push_ret = 'moo_lua_push_pointer (L, ret, %(gtype_id)s, %(make_copy)s);' % dic + elif isinstance(meth.retval.type, Enum) or isinstance(meth.retval.type, Flags): + func_call = '%s ret = ' % meth.retval.type.name + push_ret = 'moo_lua_push_int (L, ret);' % dic + elif isinstance(meth.retval.type, ArrayType): + assert isinstance(meth.retval.type.elm_type, Class) + dic['gtype_id'] = meth.retval.type.elm_type.gtype_id + func_call = 'MooObjectArray *ret = (MooObjectArray*) ' + push_ret = 'moo_lua_push_object_array (L, ret, %(make_copy)s);' % dic + elif meth.retval.type.name == 'strv': + assert meth.retval.transfer_mode == 'full' + func_call = 'char **ret = ' + push_ret = 'moo_lua_push_strv (L, ret);' + elif meth.retval.type.name in ('char*', 'cstring'): + assert meth.retval.transfer_mode == 'full' + func_call = 'char *ret = ' + push_ret = 'moo_lua_push_string (L, ret);' + elif meth.retval.type.name == 'utf8': + assert meth.retval.transfer_mode == 'full' + func_call = 'char *ret = ' + push_ret = 'moo_lua_push_utf8 (L, ret);' + elif meth.retval.type.name == 'filename': + assert meth.retval.transfer_mode == 'full' + func_call = 'char *ret = ' + push_ret = 'moo_lua_push_filename (L, ret);' + elif meth.retval.type.name in ('const-char*', 'const-cstring'): + assert meth.retval.transfer_mode != 'full' + func_call = 'const char *ret = ' + push_ret = 'moo_lua_push_string_copy (L, ret);' + elif meth.retval.type.name == 'const-utf8': + assert meth.retval.transfer_mode != 'full' + func_call = 'const char *ret = ' + push_ret = 'moo_lua_push_utf8_copy (L, ret);' + elif meth.retval.type.name == 'const-filename': + assert meth.retval.transfer_mode != 'full' + func_call = 'const char *ret = ' + push_ret = 'moo_lua_push_filename_copy (L, ret);' + elif meth.retval.type.name == 'gunichar': + func_call = 'gunichar ret = ' + push_ret = 'moo_lua_push_gunichar (L, ret);' + else: + typ, suffix = find_pod_ret_helper(meth.retval.type.name) + dic['suffix'] = suffix + func_call = '%s ret = ' % typ + push_ret = 'moo_lua_push_%(suffix)s (L, ret);' % dic + else: + push_ret = '0;' + + if not meth.has_gerror_return: + func_body.end.append('return %s' % push_ret) + else: + func_body.end.append('int ret_lua = %s' % push_ret) + func_body.end.append('ret_lua += moo_lua_push_error (L, error);') + func_body.end.append('return ret_lua;') + + func_call += '%s (' % meth.c_name + first_arg = True + if cls and not is_constructor and not static_method: + first_arg = False + func_call += 'self' + for i in range(len(params)): + if not first_arg: + func_call += ', ' + first_arg = False + func_call += 'arg%d' % i + if meth.has_gerror_return: + func_call += ', &error' + func_call += ');' + + for line in func_body.start: + print >>self.out, ' ' + line + print >>self.out, ' ' + func_call + for line in func_body.end: + print >>self.out, ' ' + line + + self.out.write('}\n\n') + +# if not cls: +# self.out.write(function_start_template % dic) +# elif isinstance(meth, Constructor): +# dic['class'] = cls.name +# self.out.write(function_start_template % dic) +# self.out.write(' (is-constructor-of %s)\n' % cls.name) +# elif isinstance(meth, VMethod): +# dic['class'] = cls.name +# self.out.write(vmethod_start_template % dic) +# else: +# dic['class'] = cls.name +# self.out.write(method_start_template % dic) +# if meth.retval: +# if meth.retval.transfer_mode == 'full': +# self.out.write(' (caller-owns-return #t)\n') +# elif meth.retval.transfer_mode is not None: +# raise RuntimeError('do not know how to handle transfer mode %s' % (meth.retval.transfer_mode,)) +# if meth.params: +# self.out.write(' (parameters\n') +# for p in meth.params: +# self.out.write(' \'("%s" "%s"' % (p.type, p.name)) +# if p.allow_none: +# self.out.write(' (null-ok)') +# if p.default_value is not None: +# self.out.write(' (default "%s")' % (p.default_value,)) +# self.out.write(')\n') +# self.out.write(' )\n') +# self.out.write(')\n\n') + + def __write_gobject_constructor(self, name, cls): + dic = dict(func=name, gtype_id=cls.gtype_id) + self.out.write("""\ +static void *%(func)s (void) +{ + return g_object_new (%(gtype_id)s, (char*) NULL); +} + +""" % dic) + + def __write_class(self, cls): + bind = cls.annotations.get('moo.lua', '1') + if bind == '0': + return ([], []) + self.out.write('// methods of %s\n\n' % cls.name) + method_cfuncs = [] + static_method_cfuncs = [] + for meth in cls.methods: + if not isinstance(meth, VMethod): + self.__write_function(meth, cls, method_cfuncs) + for meth in cls.static_methods: + self.__write_function(meth, cls, static_method_cfuncs) + if cls.constructor: + self.__write_function(cls.constructor, cls, static_method_cfuncs) +# if hasattr(cls, 'constructable') and cls.constructable: +# cons = Constructor() +# cons.retval = Retval() +# cons.retval.type = cls +# cons.retval.transfer_mode = 'full' +# cons.name = 'new' +# cons.c_name = '%s__new__' % cls.name +# self.__write_gobject_constructor(cons.c_name, cls) +# self.__write_function(cons, cls, static_method_cfuncs) + return (method_cfuncs, static_method_cfuncs) + + def __write_register_module(self, module, all_method_cfuncs): + self.out.write(tmpl_register_module_start % dict(module=module.name.lower())) + for cls in module.get_classes() + module.get_boxed() + module.get_pointers(): + method_cfuncs = all_method_cfuncs[cls.name] + if method_cfuncs: + dic = dict(Class=cls.name, gtype_id=cls.gtype_id) + self.out.write(tmpl_register_one_type_start % dic) + for name, cfunc in method_cfuncs: + self.out.write(' { "%s", %s },\n' % (name, cfunc)) + self.out.write(tmpl_register_one_type_end % dic) + self.out.write('}\n\n') + + def write(self, module, include_headers): + self.module = module + + start_dic = dict(headers='') + + if include_headers: + start_dic['headers'] = '\n' + '\n'.join(['#include "%s"' % h for h in include_headers]) + '\n' + + self.out.write(tmpl_file_start % start_dic) + + all_method_cfuncs = {} + all_static_method_cfuncs = {} + + for cls in module.get_classes() + module.get_boxed() + module.get_pointers(): + method_cfuncs, static_method_cfuncs = self.__write_class(cls) + all_method_cfuncs[cls.name] = method_cfuncs + all_static_method_cfuncs[cls.short_name] = static_method_cfuncs + + package_name=module.name.lower() + dic = dict(module=module.name.lower(), package_name=package_name) + + all_func_cfuncs = [] + +# for cls in module.get_classes() + module.get_boxed() + module.get_pointers(): +# if cls.constructor: +# self.__write_function(cls.constructor, cls, all_func_cfuncs) + + for func in module.get_functions(): + self.__write_function(func, None, all_func_cfuncs) + + self.out.write('static const luaL_Reg %(module)s_lua_functions[] = {\n' % dic) + for name, cfunc in all_func_cfuncs: + self.out.write(' { "%s", %s },\n' % (name, cfunc)) + self.out.write(' { NULL, NULL }\n') + self.out.write('};\n\n') + + for cls_name in all_static_method_cfuncs: + cfuncs = all_static_method_cfuncs[cls_name] + if not cfuncs: + continue + self.out.write('static const luaL_Reg %s_lua_functions[] = {\n' % cls_name) + for name, cfunc in cfuncs: + self.out.write(' { "%s", %s },\n' % (name, cfunc)) + self.out.write(' { NULL, NULL }\n') + self.out.write('};\n\n') + + self.__write_register_module(module, all_method_cfuncs) + + self.out.write("""\ +void %(module)s_lua_api_add_to_lua (lua_State *L) +{ + %(module)s_lua_api_register (); + + luaL_register (L, "%(package_name)s", %(module)s_lua_functions); + +""" % dic) + + for cls_name in all_static_method_cfuncs: + cfuncs = all_static_method_cfuncs[cls_name] + if not cfuncs: + continue + self.out.write(' moo_lua_register_static_methods (L, "%s", "%s", %s_lua_functions);\n' % (package_name, cls_name, cls_name)) + + self.out.write('\n') + for enum in module.get_enums(): + self.out.write(' moo_lua_register_enum (L, "%s", %s, "%s");\n' % (package_name, enum.gtype_id, module.name.upper() + '_')) + + self.out.write("}\n") + + del self.module diff --git a/api/mpi/module.py b/api/mpi/module.py new file mode 100644 index 00000000..bcd7e4ed --- /dev/null +++ b/api/mpi/module.py @@ -0,0 +1,553 @@ +import re +import sys +import xml.etree.ElementTree as _etree + +class Doc(object): + def __init__(self, text): + object.__init__(self) + self.text = text + + @staticmethod + def from_xml(elm): + return Doc(elm.text) + +def _set_unique_attribute(obj, attr, value): + if getattr(obj, attr) is not None: + raise RuntimeError("duplicated attribute '%s'" % (attr,)) + setattr(obj, attr, value) + +def _set_unique_attribute_bool(obj, attr, value): + if value.lower() in ('0', 'false', 'no'): + value = False + elif value.lower() in ('1', 'true', 'yes'): + value = True + else: + raise RuntimeError("bad value '%s' for boolean attribute '%s'" % (value, attr)) + _set_unique_attribute(obj, attr, value) + +class _XmlObject(object): + def __init__(self): + object.__init__(self) + self.doc = None + self.summary = None + self.annotations = {} + self.module = None + + @classmethod + def from_xml(cls, module, elm, *args): + obj = cls() + obj.module = module + obj._parse_xml(module, elm, *args) + return obj + + def _parse_xml_element(self, module, elm): + if elm.tag in ('doc', 'summary'): + _set_unique_attribute(self, elm.tag, Doc.from_xml(elm)) + else: + raise RuntimeError('unknown element %s' % (elm.tag,)) + + def _parse_attribute(self, attr, value): + if attr.find('.') >= 0: + self.annotations[attr] = value + return True + else: + return False + + def _parse_xml(self, module, elm, *args): + for attr, value in elm.items(): + if not self._parse_attribute(attr, value): + raise RuntimeError('unknown attribute %s' % (attr,)) + for child in elm.getchildren(): + self._parse_xml_element(module, child) + +class _ParamBase(_XmlObject): + def __init__(self): + _XmlObject.__init__(self) + self.type = None + self.transfer_mode = None + + def _parse_attribute(self, attr, value): + if attr in ('type', 'transfer_mode'): + _set_unique_attribute(self, attr, value) + else: + return _XmlObject._parse_attribute(self, attr, value) + return True + +class Param(_ParamBase): + def __init__(self): + _ParamBase.__init__(self) + self.name = None + self.default_value = None + self.allow_none = None + + def _parse_attribute(self, attr, value): + if attr in ('default_value', 'name'): + _set_unique_attribute(self, attr, value) + elif attr in ('allow_none',): + _set_unique_attribute_bool(self, attr, value) + else: + return _ParamBase._parse_attribute(self, attr, value) + return True + +class Retval(_ParamBase): + def __init__(self): + _ParamBase.__init__(self) + +class FunctionBase(_XmlObject): + def __init__(self): + _XmlObject.__init__(self) + self.name = None + self.c_name = None + self.retval = None + self.params = [] + self.has_gerror_return = False + self.kwargs = None + + def _parse_attribute(self, attr, value): + if attr in ('c_name', 'name'): + _set_unique_attribute(self, attr, value) + elif attr in ('kwargs',): + _set_unique_attribute_bool(self, attr, value) + else: + return _XmlObject._parse_attribute(self, attr, value) + return True + + def _parse_xml_element(self, module, elm): + if elm.tag == 'retval': + _set_unique_attribute(self, 'retval', Retval.from_xml(module, elm)) + elif elm.tag == 'param': + self.params.append(Param.from_xml(module, elm)) + else: + _XmlObject._parse_xml_element(self, module, elm) + + def _parse_xml(self, module, elm, *args): + _XmlObject._parse_xml(self, module, elm, *args) + if not self.name: + raise RuntimeError('function name missing') + if not self.c_name: + raise RuntimeError('function c_name missing') + +class Function(FunctionBase): + def __init__(self): + FunctionBase.__init__(self) + + def symbol_id(self): + return self.c_name + +class MethodBase(FunctionBase): + def __init__(self): + super(MethodBase, self).__init__() + + def _parse_xml(self, module, elm, cls): + super(MethodBase, self)._parse_xml(module, elm, cls) + self.cls = cls + +class Constructor(MethodBase): + def __init__(self): + super(Constructor, self).__init__() + + def symbol_id(self): + return self.c_name + +class StaticMethod(MethodBase): + def __init__(self): + super(StaticMethod, self).__init__() + + def symbol_id(self): + return self.c_name + +class Method(MethodBase): + def __init__(self): + super(Method, self).__init__() + + def symbol_id(self): + return self.c_name + +class VMethod(MethodBase): + def __init__(self): + super(VMethod, self).__init__() + self.c_name = "fake" + + def symbol_id(self): + return '%s::%s' % (self.cls.name, self.name) + +class Signal(MethodBase): + def __init__(self): + super(Signal, self).__init__() + self.c_name = "fake" + + def symbol_id(self): + return 'signal:%s:%s' % (self.cls.name, self.name) + +class Type(_XmlObject): + def __init__(self): + _XmlObject.__init__(self) + self.name = None + self.c_name = None + self.gtype_id = None + + def symbol_id(self): + return self.name + +class BasicType(Type): + def __init__(self, name): + Type.__init__(self) + self.name = name + +class ArrayType(Type): + def __init__(self, elm_type): + Type.__init__(self) + self.elm_type = elm_type + self.name = '%sArray*' % elm_type.name + self.c_name = '%sArray*' % elm_type.name + +class GErrorReturnType(Type): + def __init__(self): + Type.__init__(self) + self.name = 'GError**' + +class GTypedType(Type): + def __init__(self): + Type.__init__(self) + self.short_name = None + + def _parse_attribute(self, attr, value): + if attr in ('short_name', 'name', 'gtype_id'): + _set_unique_attribute(self, attr, value) + else: + return Type._parse_attribute(self, attr, value) + return True + + def _parse_xml(self, module, elm, *args): + Type._parse_xml(self, module, elm, *args) + if self.name is None: + raise RuntimeError('class name missing') + if self.short_name is None: + raise RuntimeError('class short name missing') + if self.gtype_id is None: + raise RuntimeError('class gtype missing') + +class EnumValue(_XmlObject): + def __init__(self): + super(EnumValue, self).__init__() + self.name = None + self.short_name = None + self.enum = None + + def symbol_id(self): + return self.name + + def _parse_attribute(self, attr, value): + if attr in ('name'): + _set_unique_attribute(self, attr, value) + else: + return super(EnumValue, self)._parse_attribute(attr, value) + return True + + def _parse_xml(self, module, elm, *args): + super(EnumValue, self)._parse_xml(module, elm, *args) + if self.name is None: + raise RuntimeError('enum value name missing') + if not self.short_name: + self.short_name = self.name + prefix = module.name.upper() + '_' + if self.short_name.startswith(prefix): + self.short_name = self.short_name[len(prefix):] + +class EnumBase(GTypedType): + def __init__(self): + super(EnumBase, self).__init__() + self.values = [] + self.__value_hash = {} + + def _parse_xml_element(self, module, elm): + if elm.tag == 'value': + value = EnumValue.from_xml(module, elm) + assert not value.name in self.__value_hash + self.__value_hash[value.name] = value + value.enum = self + self.values.append(value) + else: + super(EnumBase, self)._parse_xml_element(module, elm) + +class Enum(EnumBase): + def __init__(self): + super(Enum, self).__init__() + +class Flags(EnumBase): + def __init__(self): + super(Flags, self).__init__() + +class InstanceType(GTypedType): + def __init__(self): + GTypedType.__init__(self) + self.constructor = None + self.methods = [] + self.static_methods = [] + self.__method_hash = {} + + def _parse_xml_element(self, module, elm): + if elm.tag == 'method': + meth = Method.from_xml(module, elm, self) + assert not meth.name in self.__method_hash + self.__method_hash[meth.name] = meth + self.methods.append(meth) + elif elm.tag == 'static-method': + meth = StaticMethod.from_xml(module, elm, self) + assert not meth.name in self.__method_hash + self.__method_hash[meth.name] = meth + self.static_methods.append(meth) + elif elm.tag == 'constructor': + assert not self.constructor + self.constructor = Constructor.from_xml(module, elm, self) + else: + GTypedType._parse_xml_element(self, module, elm) + +class Pointer(InstanceType): + def __init__(self): + InstanceType.__init__(self) + +class Boxed(InstanceType): + def __init__(self): + InstanceType.__init__(self) + +class Class(InstanceType): + def __init__(self): + InstanceType.__init__(self) + self.parent = None + self.vmethods = [] + self.signals = [] + self.constructable = None + self.__vmethod_hash = {} + self.__signal_hash = {} + + def _parse_attribute(self, attr, value): + if attr in ('parent'): + _set_unique_attribute(self, attr, value) + elif attr in ('constructable'): + _set_unique_attribute_bool(self, attr, value) + else: + return InstanceType._parse_attribute(self, attr, value) + return True + + def _parse_xml_element(self, module, elm): + if elm.tag == 'virtual': + meth = VMethod.from_xml(module, elm, self) + assert not meth.name in self.__vmethod_hash + self.__vmethod_hash[meth.name] = meth + self.vmethods.append(meth) + elif elm.tag == 'signal': + meth = Signal.from_xml(module, elm, self) + assert not meth.name in self.__signal_hash + self.__signal_hash[meth.name] = meth + self.signals.append(meth) + else: + InstanceType._parse_xml_element(self, module, elm) + + def _parse_xml(self, module, elm, *args): + InstanceType._parse_xml(self, module, elm, *args) + if self.parent is None: + raise RuntimeError('class parent name missing') + if self.constructable and self.constructor: + raise RuntimeError('both constructor and constructable attributes present') + +class Module(object): + def __init__(self): + object.__init__(self) + self.name = None + self.__classes = [] + self.__boxed = [] + self.__pointers = [] + self.__enums = [] + self.__class_hash = {} + self.__functions = [] + self.__function_hash = {} + self.__import_modules = [] + self.__parsing_done = False + self.__types = {} + self.__symbols = {} + + def __add_type(self, typ): + if typ.name in self.__class_hash: + raise RuntimeError('duplicated type %s' % typ.name) + self.__class_hash[typ.name] = typ + + def __finish_type(self, typ): + if isinstance(typ, Type): + return typ + if typ in self.__types: + return self.__types[typ] + if typ == 'GError**': + return GErrorReturnType() + m = re.match(r'(const-)?([\w\d_]+)\*', typ) + if m: + name = m.group(2) + if name in self.__types: + obj_type = self.__types[name] + if isinstance(obj_type, InstanceType): + return obj_type + m = re.match(r'Array<([\w\d_]+)>\*', typ) + if m: + elm_type = self.__finish_type(m.group(1)) + return ArrayType(elm_type) + m = re.match(r'([\w\d_]+)Array\*', typ) + if m: + elm_type = self.__finish_type(m.group(1)) + return ArrayType(elm_type) + return BasicType(typ) + + def __finish_parsing_method(self, meth, typ): + sym_id = meth.symbol_id() + if self.__symbols.get(sym_id): + raise RuntimeError('duplicate symbol %s' % sym_id) + self.__symbols[sym_id] = meth + + for p in meth.params: + p.type = self.__finish_type(p.type) + if meth.retval: + meth.retval.type = self.__finish_type(meth.retval.type) + + meth.has_gerror_return = False + if meth.params and isinstance(meth.params[-1].type, GErrorReturnType): + meth.has_gerror_return = True + + if meth.kwargs: + params = list(meth.params) + pos_args = [] + kw_args = [] + if meth.has_gerror_return: + params = params[:-1] + seen_kwarg = False + for p in params: + if p.default_value is not None: + seen_kwarg = True + elif seen_kwarg: + raise RuntimeError('in %s: parameter without a default value follows a kwarg one' % sym_id) + if p.default_value is not None: + kw_args.append(p) + else: + pos_args.append(p) + + def __finish_parsing_type(self, typ): + if hasattr(typ, 'constructor') and typ.constructor is not None: + self.__finish_parsing_method(typ.constructor, typ) + for meth in typ.static_methods: + self.__finish_parsing_method(meth, typ) + for meth in typ.methods: + self.__finish_parsing_method(meth, typ) + if hasattr(typ, 'vmethods'): + for meth in typ.vmethods: + self.__finish_parsing_method(meth, typ) + if hasattr(typ, 'signals'): + for meth in typ.signals: + self.__finish_parsing_method(meth, typ) + + def __finish_parsing_enum(self, typ): + for v in typ.values: + sym_id = v.symbol_id() + if self.__symbols.get(sym_id): + raise RuntimeError('duplicate symbol %s' % sym_id) + self.__symbols[sym_id] = v + + def __add_type_symbol(self, typ): + sym_id = typ.symbol_id() + if self.__symbols.get(sym_id): + raise RuntimeError('duplicate symbol %s' % sym_id) + self.__symbols[sym_id] = typ + + def __finish_parsing(self): + if self.__parsing_done: + return + + for typ in self.__classes + self.__boxed + self.__pointers + self.__enums: + self.__add_type_symbol(typ) + self.__types[typ.name] = typ + + for module in self.__import_modules: + for typ in module.get_classes() + module.get_boxed() + \ + module.get_pointers() + module.get_enums(): + self.__types[typ.name] = typ + for sym in module.__symbols: + if self.__symbols.get(sym): + raise RuntimeError('duplicate symbol %s' % sym) + self.__symbols[sym] = module.__symbols[sym] + + for typ in self.__classes + self.__boxed + self.__pointers: + self.__finish_parsing_type(typ) + + for typ in self.__enums: + self.__finish_parsing_enum(typ) + + for func in self.__functions: + self.__finish_parsing_method(func, None) + + self.__parsing_done = True + + def get_classes(self): + self.__finish_parsing() + return list(self.__classes) + + def get_boxed(self): + self.__finish_parsing() + return list(self.__boxed) + + def get_pointers(self): + self.__finish_parsing() + return list(self.__pointers) + + def get_enums(self): + self.__finish_parsing() + return list(self.__enums) + + def get_functions(self): + self.__finish_parsing() + return list(self.__functions) + + def get_symbols(self): + self.__finish_parsing() + return dict(self.__symbols) + + def __parse_module_entry(self, elm): + if elm.tag == 'class': + cls = Class.from_xml(self, elm) + self.__add_type(cls) + self.__classes.append(cls) + elif elm.tag == 'boxed': + cls = Boxed.from_xml(self, elm) + self.__add_type(cls) + self.__boxed.append(cls) + elif elm.tag == 'pointer': + cls = Pointer.from_xml(self, elm) + self.__add_type(cls) + self.__pointers.append(cls) + elif elm.tag == 'enum': + enum = Enum.from_xml(self, elm) + self.__add_type(enum) + self.__enums.append(enum) + elif elm.tag == 'flags': + enum = Flags.from_xml(self, elm) + self.__add_type(enum) + self.__enums.append(enum) + elif elm.tag == 'function': + func = Function.from_xml(self, elm) + assert not func.name in self.__function_hash + self.__function_hash[func.name] = func + self.__functions.append(func) + else: + raise RuntimeError('unknown element %s' % (elm.tag,)) + + def import_module(self, mod): + self.__import_modules.append(mod) + + @staticmethod + def from_xml(filename): + mod = Module() + xml = _etree.ElementTree() + xml.parse(filename) + root = xml.getroot() + assert root.tag == 'module' + mod.name = root.get('name') + assert mod.name is not None + for elm in root.getchildren(): + mod.__parse_module_entry(elm) + return mod diff --git a/api/mpi/util.py b/api/mpi/util.py new file mode 100644 index 00000000..93e71e09 --- /dev/null +++ b/api/mpi/util.py @@ -0,0 +1,12 @@ +import sys + +__all__ = [ 'implement_me', 'oops', 'warning' ] + +def implement_me(what): + print >> sys.stderr, 'implement me: %s' % what + +def oops(message=None): + raise RuntimeError(message) + +def warning(message): + print >>sys.stderr, "WARNING:", message diff --git a/api/parsedocs.py b/api/parsedocs.py new file mode 100644 index 00000000..40127fc6 --- /dev/null +++ b/api/parsedocs.py @@ -0,0 +1,55 @@ +#! /usr/bin/env python + +import os +import re +import sys +import optparse +import fnmatch + +from mdp.docparser import Parser +from mdp.module import Module +import mdp.xmlwriter + +def read_files(files, opts): + p = Parser() + p.read_files(files) + mod = Module('Moo' if not opts.module else opts.module) + mod.init_from_dox(p.classes + p.enums + p.functions + p.vmethods + p.signals) + return mod + +def parse_args(): + op = optparse.OptionParser() + op.add_option("--source-dir", dest="source_dirs", action="append", + help="parse source files from DIR", metavar="DIR") + op.add_option("--source-file", dest="source_files", action="append", + help="parse source file FILE", metavar="FILE") + op.add_option("--skip", dest="skip_globs", action="append", + help="skip files which match pattern PAT", metavar="PAT") + op.add_option("--output", dest="output", action="store", + help="write result to FILE", metavar="FILE") + op.add_option("--module", dest="module", action="store", + help="generate module MOD", metavar="MOD") + (opts, args) = op.parse_args() + if args: + op.error("too many arguments") + source_files = [] + if opts.source_files: + source_files += [os.path.abspath(f) for f in opts.source_files] + skip_pat = None + if opts.skip_globs: + skip_pat = re.compile('|'.join([fnmatch.translate(g) for g in opts.skip_globs])) + if opts.source_dirs: + for source_dir in opts.source_dirs: + for root, dirs, files in os.walk(source_dir): + for f in files: + if f.endswith('.c') or f.endswith('.cpp') or f.endswith('.h'): + if skip_pat is None or not skip_pat.match(f): + source_files.append(os.path.join(root, f)) + if not source_files: + op.error("no input files") + return opts, source_files + +opts, files = parse_args() +mod = read_files(files, opts) +with open(opts.output, 'w') as out: + mdp.xmlwriter.write_xml(mod, out) diff --git a/api/sourcefiles.mak b/api/sourcefiles.mak new file mode 100644 index 00000000..e4d9ae53 --- /dev/null +++ b/api/sourcefiles.mak @@ -0,0 +1,290 @@ +source_files = \ + ../moo/moolua/medit-lua.h\ + ../moo/moolua/medit-lua.cpp\ + ../moo/mooapp/mooappabout.c\ + ../moo/mooapp/mooappabout.h\ + ../moo/mooapp/mooapp-accels.h\ + ../moo/mooapp/mooapp.c\ + ../moo/mooapp/mooapp.h\ + ../moo/mooapp/mooapp-info.h\ + ../moo/mooapp/mooapp-private.h\ + ../moo/mooapp/moohtml.c\ + ../moo/mooapp/moohtml.h\ + ../moo/mooapp/moolinklabel.c\ + ../moo/mooapp/moolinklabel.h\ + ../moo/mooedit/mooedit-accels.h\ + ../moo/mooedit/mooeditaction.c\ + ../moo/mooedit/mooeditaction-factory.c\ + ../moo/mooedit/mooeditaction-factory.h\ + ../moo/mooedit/mooeditaction.h\ + ../moo/mooedit/mooeditbookmark.c\ + ../moo/mooedit/mooeditbookmark.h\ + ../moo/mooedit/mooedit.c\ + ../moo/mooedit/mooeditconfig.c\ + ../moo/mooedit/mooeditconfig.h\ + ../moo/mooedit/mooeditdialogs.c\ + ../moo/mooedit/mooeditdialogs.h\ + ../moo/mooedit/mooedit-enum-types.c\ + ../moo/mooedit/mooedit-enum-types.h\ + ../moo/mooedit/mooedit-enums.h\ + ../moo/mooedit/mooeditfileinfo.c\ + ../moo/mooedit/mooeditfileinfo.c\ + ../moo/mooedit/mooeditfileinfo.h\ + ../moo/mooedit/mooedit-fileops.c\ + ../moo/mooedit/mooedit-fileops.h\ + ../moo/mooedit/mooeditfiltersettings.c\ + ../moo/mooedit/mooeditfiltersettings.h\ + ../moo/mooedit/mooedit.h\ + ../moo/mooedit/mooedithistoryitem.c\ + ../moo/mooedit/mooedithistoryitem.h\ + ../moo/mooedit/mooedit-impl.h\ + ../moo/mooedit/mooedit-misc.c\ + ../moo/mooedit/mooeditor.c\ + ../moo/mooedit/mooeditor.h\ + ../moo/mooedit/mooeditor-impl.h\ + ../moo/mooedit/mooeditor-private.h\ + ../moo/mooedit/mooeditor-tests.c\ + ../moo/mooedit/mooeditor-tests.h\ + ../moo/mooedit/mooeditprefs.c\ + ../moo/mooedit/mooeditprefs.h\ + ../moo/mooedit/mooeditprefspage.c\ + ../moo/mooedit/mooedit-private.h\ + ../moo/mooedit/mooedit-script.c\ + ../moo/mooedit/mooedit-script.h\ + ../moo/mooedit/mooedittab.c\ + ../moo/mooedit/mooedittab.h\ + ../moo/mooedit/mooedittypes.h\ + ../moo/mooedit/mooeditview.c\ + ../moo/mooedit/mooeditview.h\ + ../moo/mooedit/mooeditview-script.c\ + ../moo/mooedit/mooeditview-script.h\ + ../moo/mooedit/mooeditwindow.c\ + ../moo/mooedit/mooeditwindow.h\ + ../moo/mooedit/mooeditwindow-impl.h\ + ../moo/mooedit/moofold.c\ + ../moo/mooedit/moofold.h\ + ../moo/mooedit/mooindenter.c\ + ../moo/mooedit/mooindenter.h\ + ../moo/mooedit/moolang.c\ + ../moo/mooedit/moolang.h\ + ../moo/mooedit/moolangmgr.c\ + ../moo/mooedit/moolangmgr.h\ + ../moo/mooedit/moolangmgr-private.h\ + ../moo/mooedit/moolang-private.h\ + ../moo/mooedit/moolinebuffer.c\ + ../moo/mooedit/moolinebuffer.h\ + ../moo/mooedit/moolinemark.c\ + ../moo/mooedit/moolinemark.h\ + ../moo/mooedit/mooplugin.c\ + ../moo/mooedit/mooplugin.h\ + ../moo/mooedit/mooplugin-loader.c\ + ../moo/mooedit/mooplugin-loader.h\ + ../moo/mooedit/mooplugin-macro.h\ + ../moo/mooedit/mootextbtree.c\ + ../moo/mooedit/mootextbtree.h\ + ../moo/mooedit/mootextbuffer.c\ + ../moo/mooedit/mootextfind.c\ + ../moo/mooedit/mootextfind.h\ + ../moo/mooedit/mootextiter.h\ + ../moo/mooedit/mootextprint.c\ + ../moo/mooedit/mootextprint.h\ + ../moo/mooedit/mootextprint-private.h\ + ../moo/mooedit/mootext-private.h\ + ../moo/mooedit/mootextsearch.c\ + ../moo/mooedit/mootextsearch.h\ + ../moo/mooedit/mootextsearch-private.h\ + ../moo/mooedit/mootextstylescheme.c\ + ../moo/mooedit/mootextstylescheme.h\ + ../moo/mooedit/mootextview.c\ + ../moo/mooedit/mootextview.h\ + ../moo/mooedit/mootextview-input.c\ + ../moo/mooedit/mootextview-private.h\ + ../moo/moofileview/moobookmarkmgr.c\ + ../moo/moofileview/moobookmarkmgr.h\ + ../moo/moofileview/moobookmarkview.c\ + ../moo/moofileview/moobookmarkview.h\ + ../moo/moofileview/moofile.c\ + ../moo/moofileview/moofileentry.c\ + ../moo/moofileview/moofileentry.h\ + ../moo/moofileview/moofile.h\ + ../moo/moofileview/moofile-private.h\ + ../moo/moofileview/moofilesystem.c\ + ../moo/moofileview/moofilesystem.h\ + ../moo/moofileview/moofileview-accels.h\ + ../moo/moofileview/moofileview-aux.h\ + ../moo/moofileview/moofileview.c\ + ../moo/moofileview/moofileview-dialogs.c\ + ../moo/moofileview/moofileview-dialogs.h\ + ../moo/moofileview/moofileview.h\ + ../moo/moofileview/moofileview-impl.h\ + ../moo/moofileview/moofileview-private.h\ + ../moo/moofileview/moofileview-tools.c\ + ../moo/moofileview/moofileview-tools.h\ + ../moo/moofileview/moofolder.c\ + ../moo/moofileview/moofolder.h\ + ../moo/moofileview/moofoldermodel.c\ + ../moo/moofileview/moofoldermodel.h\ + ../moo/moofileview/moofoldermodel-private.h\ + ../moo/moofileview/moofolder-private.h\ + ../moo/moofileview/mooiconview.c\ + ../moo/moofileview/mooiconview.h\ + ../moo/moofileview/mootreeview.c\ + ../moo/moofileview/mootreeview.h\ + ../moo/mooutils/mooaccelbutton.c\ + ../moo/mooutils/mooaccelbutton.h\ + ../moo/mooutils/mooaccel.c\ + ../moo/mooutils/mooaccel.h\ + ../moo/mooutils/mooaccelprefs.c\ + ../moo/mooutils/mooaccelprefs.h\ + ../moo/mooutils/mooactionbase.c\ + ../moo/mooutils/mooactionbase.h\ + ../moo/mooutils/mooactionbase-private.h\ + ../moo/mooutils/mooaction.c\ + ../moo/mooutils/mooactioncollection.c\ + ../moo/mooutils/mooactioncollection.h\ + ../moo/mooutils/mooactionfactory.c\ + ../moo/mooutils/mooactionfactory.h\ + ../moo/mooutils/mooactiongroup.c\ + ../moo/mooutils/mooactiongroup.h\ + ../moo/mooutils/mooaction.h\ + ../moo/mooutils/mooaction-private.h\ + ../moo/mooutils/mooappinput.c\ + ../moo/mooutils/mooappinput.h\ + ../moo/mooutils/mooapp-ipc.c\ + ../moo/mooutils/mooapp-ipc.h\ + ../moo/mooutils/mooarray.h\ + ../moo/mooutils/mooatom.h\ + ../moo/mooutils/moobigpaned.c\ + ../moo/mooutils/moobigpaned.h\ + ../moo/mooutils/mooclosure.c\ + ../moo/mooutils/mooclosure.h\ + ../moo/mooutils/moocombo.c\ + ../moo/mooutils/moocombo.h\ + ../moo/mooutils/moocompat.h\ + ../moo/mooutils/moodialogs.c\ + ../moo/mooutils/moodialogs.h\ + ../moo/mooutils/mooeditops.c\ + ../moo/mooutils/mooeditops.h\ + ../moo/mooutils/mooencodings.c\ + ../moo/mooutils/mooencodings-data.h\ + ../moo/mooutils/mooencodings.h\ + ../moo/mooutils/mooentry.c\ + ../moo/mooutils/mooentry.h\ + ../moo/mooutils/moo-environ.h\ + ../moo/mooutils/moofiledialog.c\ + ../moo/mooutils/moofiledialog.h\ + ../moo/mooutils/moofileicon.c\ + ../moo/mooutils/moofileicon.h\ + ../moo/mooutils/moofilewatch.c\ + ../moo/mooutils/moofilewatch.h\ + ../moo/mooutils/moofilewriter.c\ + ../moo/mooutils/moofilewriter.h\ + ../moo/mooutils/moofilewriter-private.h\ + ../moo/mooutils/moofiltermgr.c\ + ../moo/mooutils/moofiltermgr.h\ + ../moo/mooutils/moofontsel.c\ + ../moo/mooutils/moofontsel.h\ + ../moo/mooutils/mooglade.c\ + ../moo/mooutils/mooglade.h\ + ../moo/mooutils/moohelp.c\ + ../moo/mooutils/moohelp.h\ + ../moo/mooutils/moohistorycombo.c\ + ../moo/mooutils/moohistorycombo.h\ + ../moo/mooutils/moohistorylist.c\ + ../moo/mooutils/moohistorylist.h\ + ../moo/mooutils/moohistorymgr.c\ + ../moo/mooutils/moohistorymgr.h\ + ../moo/mooutils/mooi18n.c\ + ../moo/mooutils/mooi18n.h\ + ../moo/mooutils/moolist.h\ + ../moo/mooutils/moomarkup.c\ + ../moo/mooutils/moomarkup.h\ + ../moo/mooutils/moomenuaction.c\ + ../moo/mooutils/moomenuaction.h\ + ../moo/mooutils/moomenu.c\ + ../moo/mooutils/moomenu.h\ + ../moo/mooutils/moomenumgr.c\ + ../moo/mooutils/moomenumgr.h\ + ../moo/mooutils/moomenutoolbutton.c\ + ../moo/mooutils/moomenutoolbutton.h\ + ../moo/mooutils/moo-mime.c\ + ../moo/mooutils/moo-mime.h\ + ../moo/mooutils/moonotebook.c\ + ../moo/mooutils/moonotebook.h\ + ../moo/mooutils/mooonce.h\ + ../moo/mooutils/moopane.c\ + ../moo/mooutils/moopaned.c\ + ../moo/mooutils/moopaned.h\ + ../moo/mooutils/moopane.h\ + ../moo/mooutils/mooprefs.c\ + ../moo/mooutils/mooprefsdialog.c\ + ../moo/mooutils/mooprefsdialog.h\ + ../moo/mooutils/mooprefs.h\ + ../moo/mooutils/mooprefspage.c\ + ../moo/mooutils/mooprefspage.h\ + ../moo/mooutils/moospawn.c\ + ../moo/mooutils/moospawn.h\ + ../moo/mooutils/moostat.h\ + ../moo/mooutils/moostock.c\ + ../moo/mooutils/moostock.h\ + ../moo/mooutils/moo-test-macros.h\ + ../moo/mooutils/moo-test-utils.c\ + ../moo/mooutils/moo-test-utils.h\ + ../moo/mooutils/mootypedecl-macros.h\ + ../moo/mooutils/mootype-macros.h\ + ../moo/mooutils/moouixml.c\ + ../moo/mooutils/moouixml.h\ + ../moo/mooutils/mooundo.c\ + ../moo/mooutils/mooundo.h\ + ../moo/mooutils/mooutils-debug.h\ + ../moo/mooutils/mooutils-enums.c\ + ../moo/mooutils/mooutils-enums.h\ + ../moo/mooutils/mooutils-file.c\ + ../moo/mooutils/mooutils-file.h\ + ../moo/mooutils/mooutils-fs.c\ + ../moo/mooutils/mooutils-fs.h\ + ../moo/mooutils/mooutils-gobject.c\ + ../moo/mooutils/mooutils-gobject.h\ + ../moo/mooutils/mooutils-gobject-private.h\ + ../moo/mooutils/mooutils.h\ + ../moo/mooutils/mooutils-macros.h\ + ../moo/mooutils/mooutils-mem.h\ + ../moo/mooutils/mooutils-messages.h\ + ../moo/mooutils/mooutils-misc.c\ + ../moo/mooutils/mooutils-misc.h\ + ../moo/mooutils/mooutils-script.c\ + ../moo/mooutils/mooutils-script.h\ + ../moo/mooutils/mooutils-tests.h\ + ../moo/mooutils/mooutils-thread.c\ + ../moo/mooutils/mooutils-thread.h\ + ../moo/mooutils/mooutils-treeview.c\ + ../moo/mooutils/mooutils-treeview.h\ + ../moo/mooutils/mooutils-win32.c\ + ../moo/mooutils/moowin32/mingw/fnmatch.h\ + ../moo/mooutils/moowin32/mingw/netinet/in.h\ + ../moo/mooutils/moowin32/mingw/sys/mman.h\ + ../moo/mooutils/moowin32/ms/sys/time.h\ + ../moo/mooutils/moowin32/ms/unistd.h\ + ../moo/mooutils/moowindow.c\ + ../moo/mooutils/moowindow.h\ + ../moo/plugins/support/moocmdview.c\ + ../moo/plugins/support/moocmdview.h\ + ../moo/plugins/support/mooeditwindowoutput.c\ + ../moo/plugins/support/mooeditwindowoutput.h\ + ../moo/plugins/support/moolineview.c\ + ../moo/plugins/support/moolineview.h\ + ../moo/plugins/support/moooutputfilter.c\ + ../moo/plugins/support/moooutputfilter.h\ + ../moo/plugins/usertools/moocommand.c\ + ../moo/plugins/usertools/moocommanddisplay.c\ + ../moo/plugins/usertools/moocommanddisplay.h\ + ../moo/plugins/usertools/moocommand.h\ + ../moo/plugins/usertools/moocommand-private.h\ + ../moo/plugins/usertools/moooutputfilterregex.c\ + ../moo/plugins/usertools/moooutputfilterregex.h\ + ../moo/plugins/usertools/moousertools.c\ + ../moo/plugins/usertools/moousertools-enums.c\ + ../moo/plugins/usertools/moousertools-enums.h\ + ../moo/plugins/usertools/moousertools.h\ + ../moo/plugins/usertools/moousertools-prefs.c\ + ../moo/plugins/usertools/moousertools-prefs.h diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..547e3919 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,37 @@ +[ -z "$ACLOCAL" ] && ACLOCAL=aclocal +[ -z "$AUTOCONF" ] && AUTOCONF=autoconf +[ -z "$AUTOHEADER" ] && AUTOHEADER=autoheader +[ -z "$AUTOMAKE" ] && AUTOMAKE=automake + +workingdir=`pwd` +rel_srcdir=`dirname "$0"` +srcdir=`cd "$rel_srcdir" && pwd` + +cd "$srcdir" + +run_cmd() { + echo "$@" + "$@" || exit $! +} + +run_cmd libtoolize --copy --force + +run_cmd $ACLOCAL --force -I m4 $ACLOCAL_FLAGS +run_cmd $AUTOCONF --force +run_cmd $AUTOHEADER --force +run_cmd $AUTOMAKE --add-missing --copy --force-missing + +cd $workingdir + +run_configure=true +configure_args="--enable-dev-mode" +if [ "$1" ]; then + : +else + echo "Done. Run '$rel_srcdir/configure --enable-dev-mode' to configure and then 'make' to build" + run_configure=false +fi + +if $run_configure; then + run_cmd $rel_srcdir/configure $configure_args "$@" +fi diff --git a/build/.empty b/build/.empty new file mode 100644 index 00000000..e69de29b diff --git a/checkle.py b/checkle.py new file mode 100755 index 00000000..f5a5b80a --- /dev/null +++ b/checkle.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +import os +import sys +import subprocess + +files = subprocess.Popen(['hg', 'log', '-r', 'tip', '--template', '{files}'], + stdout=subprocess.PIPE).communicate()[0].split() + +status = 0 + +for name in files: + if not os.path.exists(name) or name.startswith('medit/data') or \ + name.endswith('.icns') or name.endswith('.png'): + continue + f = open(name, 'rb') + if '\r' in f.read(): + print >> sys.stderr, "%s contains \\r character" % (name,) + status = 1 + f.close() + +sys.exit(status) diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..473b5f3d --- /dev/null +++ b/configure.ac @@ -0,0 +1,184 @@ +m4_define([_moo_major_version_],[1]) +m4_define([_moo_minor_version_],[1]) +m4_define([_moo_micro_version_],[0]) +m4_define([_moo_version_suffix_],[]) +m4_define([_moo_module_major_version_],[2]) +m4_define([_moo_module_minor_version_],[0]) +m4_define([_moo_version_],[_moo_major_version_._moo_minor_version_._moo_micro_version_]) + +m4_if(_moo_version_suffix_,[], + [m4_define([_moo_display_version_],[_moo_version_])], + [m4_define([_moo_display_version_],[_moo_version_-_moo_version_suffix_])]) + +m4_define([_moo_website_],[http://mooedit.sourceforge.net/]) +m4_define([_moo_web_contact_],[http://mooedit.sourceforge.net/contact.html]) +m4_define([_moo_email_],[emuntyan@users.sourceforge.net]) +m4_define([_moo_copyright_],[2004-2011 Yevgen Muntyan <_moo_email_>]) + +AC_PREREQ([2.65]) +AC_INIT([medit], [_moo_display_version_], [_moo_email_]) +AC_GNU_SOURCE +AC_CONFIG_MACRO_DIR([m4]) +AC_SUBST(ACLOCAL_FLAGS) + +AM_INIT_AUTOMAKE([1.11 foreign dist-bzip2 no-dist-gzip]) +AM_SILENT_RULES([yes]) +LT_INIT([disable-shared]) +# AC_PROG_RANLIB + +AC_SUBST(MOO_MAJOR_VERSION,_moo_major_version_) +AC_SUBST(MOO_MINOR_VERSION,_moo_minor_version_) +AC_SUBST(MOO_MICRO_VERSION,_moo_micro_version_) +AC_SUBST(MOO_MODULE_MAJOR_VERSION,_moo_module_major_version_) +AC_SUBST(MOO_MODULE_MINOR_VERSION,_moo_module_minor_version_) +AC_SUBST(MOO_VERSION,_moo_version_) +AC_SUBST(MOO_DISPLAY_VERSION,"_moo_display_version_") +AC_SUBST(MOO_EMAIL,"_moo_email_") +AC_SUBST(MOO_WEBSITE,"_moo_website_") +AC_SUBST(MOO_WEB_CONTACT,"_moo_web_contact_") +AC_SUBST(MOO_COPYRIGHT,"_moo_copyright_") + +# keep in sync with po/maintain +AC_SUBST(MOO_PACKAGE_NAME,"medit-1") + +MOO_PREFS_XML_FILE_NAME="prefs.xml" +MOO_STATE_XML_FILE_NAME="state.xml" +MOO_NAMED_SESSION_XML_FILE_NAME="session-%s.xml" +MOO_SESSION_XML_FILE_NAME="session.xml" + +AC_SUBST(GETTEXT_PACKAGE,$MOO_PACKAGE_NAME) +AC_SUBST(GETTEXT_PACKAGE_GSV,"$MOO_PACKAGE_NAME-gsv") +AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE",[GETTEXT_PACKAGE]) +MOO_INTL + +AC_PROG_CC +AC_PROG_CXX +AC_PROG_INSTALL +AC_PROG_MKDIR_P +AM_PROG_CC_C_O + +AC_ARG_VAR([WINDRES], [windres]) +AC_CHECK_TOOL(WINDRES, windres, :) + +AC_ARG_VAR([PYTHON], [Python executable, required to run build scripts]) +AC_CHECK_PROGS(PYTHON, python, [AC_MSG_ERROR([Python not found])]) + +MOO_AC_FLAGS + +AM_MAINTAINER_MODE([enable]) + +AC_CHECK_PROG([TXT2TAGS], txt2tags, txt2tags) +if test x$MOO_DEV_MODE = "xyes" -a "x$TXT2TAGS" = "x"; then + AC_MSG_ERROR([txt2tags not found]) +fi + +AC_CONFIG_HEADERS([config.h]) +AH_TOP([#ifndef __CONFIG_H__ +#define __CONFIG_H__]) +AH_BOTTOM([#include +#endif // __CONFIG_H__]) + +MOO_LT_LIB_M + +AC_DEFINE_UNQUOTED(MOO_VERSION,["$MOO_VERSION"],[MOO_VERSION]) +AC_DEFINE_UNQUOTED(MOO_DISPLAY_VERSION,["$MOO_DISPLAY_VERSION"],[MOO_DISPLAY_VERSION]) +AC_DEFINE_UNQUOTED(MOO_MAJOR_VERSION,[$MOO_MAJOR_VERSION],[MOO_MAJOR_VERSION]) +AC_DEFINE_UNQUOTED(MOO_MICRO_VERSION,[$MOO_MICRO_VERSION],[MOO_MICRO_VERSION]) +AC_DEFINE_UNQUOTED(MOO_MINOR_VERSION,[$MOO_MINOR_VERSION],[MOO_MINOR_VERSION]) +AC_DEFINE_UNQUOTED(MOO_MODULE_MAJOR_VERSION,[$MOO_MODULE_MAJOR_VERSION],[MOO_MODULE_MAJOR_VERSION]) +AC_DEFINE_UNQUOTED(MOO_MODULE_MINOR_VERSION,[$MOO_MODULE_MINOR_VERSION],[MOO_MODULE_MINOR_VERSION]) + +AC_DEFINE_UNQUOTED(MOO_EMAIL,["$MOO_EMAIL"],MOO_EMAIL) +AC_DEFINE_UNQUOTED(PACKAGE_BUGREPORT,["$PACKAGE_BUGREPORT"],PACKAGE_BUGREPORT) +AC_DEFINE_UNQUOTED(MOO_COPYRIGHT,["$MOO_COPYRIGHT"],MOO_COPYRIGHT) +AC_DEFINE_UNQUOTED(MOO_WEBSITE,["$MOO_WEBSITE"],MOO_WEBSITE) +AC_DEFINE_UNQUOTED(MOO_WEB_CONTACT,["$MOO_WEB_CONTACT"],MOO_WEB_CONTACT) + +MOO_AC_CHECK_OS +MOO_AC_PYTHON + +MOO_BUILD_APP=true +MOO_BUILD_MODULE=false +AC_ARG_ENABLE(moo-module, + AC_HELP_STRING([--enable-moo-module],[build standalone python module instead of medit, default NO (you must also use --enable-shared with this option)]),[ + if test "$enable_moo_module" = "yes"; then + MOO_BUILD_APP=false + MOO_BUILD_MODULE=true + fi +]) +if $MOO_BUILD_MODULE; then + if ! $MOO_ENABLE_PYTHON; then + AC_MSG_ERROR([Python bindings are not enabled, can't build python module]) + elif test "$enable_shared" != "yes"; then + AC_MSG_ERROR([Python module can't be built without --enable-shared option]) + fi +fi + +if $MOO_BUILD_APP; then + AC_DEFINE(MOO_BUILD_APP, 1, [build medit application]) +fi +if $MOO_BUILD_MODULE; then + AC_DEFINE(MOO_BUILD_MODULE, 1, [build standalone python module]) +fi + +AM_CONDITIONAL(MOO_BUILD_APP, [$MOO_BUILD_APP]) +AM_CONDITIONAL(MOO_BUILD_MODULE, [$MOO_BUILD_MODULE]) + +AC_SUBST(MOO_TOP_SRCDIR,`cd $srcdir && pwd`) +if test x"$MOO_TOP_SRCDIR" = x"`pwd`"; then + AC_SUBST(MOO_CP_TO_SRCDIR,true) + AC_SUBST(MOO_MV_TO_SRCDIR,true) +else + AC_SUBST(MOO_CP_TO_SRCDIR,cp) + AC_SUBST(MOO_MV_TO_SRCDIR,mv) +fi + +AM_CONDITIONAL(MOO_BUILD_CTAGS, [$MOO_OS_UNIX]) +if $MOO_OS_UNIX; then + AC_DEFINE(MOO_BUILD_CTAGS, 1, [build ctags plugin]) +fi + +AC_ARG_ENABLE(coverage, + AC_HELP_STRING([--enable-coverage],[check test coverage]),[ + MOO_ENABLE_COVERAGE="$enable_coverage" + ],[ + MOO_ENABLE_COVERAGE="no" +]) +AM_CONDITIONAL(MOO_ENABLE_COVERAGE, test "x$MOO_ENABLE_COVERAGE" = "xyes") +if test "x$MOO_ENABLE_COVERAGE" = "xyes"; then + AC_DEFINE(MOO_ENABLE_COVERAGE, 1, [enable code coverage testing]) +fi + +if $MOO_OS_WIN32; then + AC_SUBST(MEDIT_WIN32_APP_UID,"7F9F899F-EE8A-47F0-9981-8C525AF78E4D") + AC_SUBST(MEDIT_INNO_TOP_SRCDIR,"Z:`cd $srcdir && pwd`") + AC_SUBST(MEDIT_INNO_TOP_BUILDDIR,"Z:`pwd`") + AC_SUBST(MEDIT_INNO_INSTDIR,"Z:`cd $prefix && pwd`") + AC_SUBST(MEDIT_INNO_COMPILER,'wine "c:\\program files\\inno setup 5\\ISCC.exe"') + AC_SUBST(MEDIT_INNO_INSTALLER_SUFFIX,"") + if test "x$MOO_DEBUG_ENABLED" = "xyes"; then + MEDIT_INNO_INSTALLER_SUFFIX="-debug" + fi +fi + +AC_ARG_ENABLE(install-hooks, + AC_HELP_STRING([--enable-install-hooks],[run gtk-update-icon-cache during 'make install']),[ + MOO_ENABLE_INSTALL_HOOKS="$enable_install_hooks" + ],[ + if $MOO_BUILD_APP; then + MOO_ENABLE_INSTALL_HOOKS="yes" + else + MOO_ENABLE_INSTALL_HOOKS="no" + fi +]) +AM_CONDITIONAL(MOO_ENABLE_INSTALL_HOOKS, test "x$MOO_ENABLE_INSTALL_HOOKS" = "xyes") + +AC_CONFIG_FILES([ + Makefile + api/Makefile + doc/Makefile + moo/Makefile + po/Makefile + po-gsv/Makefile +]) +AC_OUTPUT diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 00000000..dd59884b --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,153 @@ +BUILT_SOURCES = + +if MOO_DEV_MODE + +$(srcdir)/built/medit-defines.ent: medit-defines.ent.in $(top_builddir)/config.status + $(AM_V_GEN)cd $(top_builddir) && ./config.status --silent --file=doc/medit-defines.ent + $(AM_V_at)$(MKDIR_P) $(srcdir)/built + $(AM_V_at)mv medit-defines.ent $(srcdir)/built/ + +gendocbook_files = \ + $(top_srcdir)/api/gendocbook.py \ + $(top_srcdir)/api/mpi/__init__.py \ + $(top_srcdir)/api/mpi/module.py \ + $(top_srcdir)/api/mpi/docbookwriter.py + +script_docbook_sources = \ + built/medit-defines.ent \ + built/script-python.docbook \ + built/script-lua.docbook \ + built/script-lua-gtk.docbook + +$(srcdir)/built/script-python.docbook: $(gendocbook_files) script-python.tmpl.docbook $(top_srcdir)/api/moo.xml + $(AM_V_at)$(MKDIR_P) $(srcdir)/built + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/api/gendocbook.py \ + --python --template $(srcdir)/script-python.tmpl.docbook \ + $(top_srcdir)/api/moo.xml > script-python.docbook.tmp && \ + mv script-python.docbook.tmp $(srcdir)/built/script-python.docbook + +$(srcdir)/built/script-lua.docbook: $(gendocbook_files) script-lua.tmpl.docbook $(top_srcdir)/api/moo.xml $(top_srcdir)/api/gtk.xml + $(AM_V_at)$(MKDIR_P) $(srcdir)/built + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/api/gendocbook.py \ + --lua --template $(srcdir)/script-lua.tmpl.docbook \ + --import $(top_srcdir)/api/gtk.xml \ + $(top_srcdir)/api/moo.xml > script-lua.docbook.tmp && \ + mv script-lua.docbook.tmp $(srcdir)/built/script-lua.docbook + +$(srcdir)/built/script-lua-gtk.docbook: $(gendocbook_files) script-lua-gtk.tmpl.docbook $(top_srcdir)/api/gtk.xml + $(AM_V_at)$(MKDIR_P) $(srcdir)/built + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/api/gendocbook.py \ + --lua --template $(srcdir)/script-lua-gtk.tmpl.docbook \ + $(top_srcdir)/api/gtk.xml > script-lua-gtk.docbook.tmp && \ + mv script-lua-gtk.docbook.tmp $(srcdir)/built/script-lua-gtk.docbook + +# $(srcdir)/help/script-python.html: built/script-python.docbook built/medit-defines.ent script.xsl +# $(AM_V_at)$(MKDIR_P) $(srcdir)/help/ +# $(AM_V_GEN)xsltproc --output script-python.html.tmp \ +# $(srcdir)/script.xsl $(srcdir)/built/script-python.docbook \ +# && mv script-python.html.tmp $(srcdir)/help/script-python.html +# +# $(srcdir)/help/script-lua.html: built/script-lua.docbook built/medit-defines.ent script.xsl +# $(AM_V_at)$(MKDIR_P) $(srcdir)/help/ +# $(AM_V_GEN)xsltproc --output script-lua.html.tmp \ +# $(srcdir)/script.xsl $(srcdir)/built/script-lua.docbook \ +# && mv script-lua.html.tmp $(srcdir)/help/script-lua.html +# +# $(srcdir)/help/script-lua-gtk.html: built/script-lua-gtk.docbook built/medit-defines.ent script.xsl +# $(AM_V_at)$(MKDIR_P) $(srcdir)/help/ +# $(AM_V_GEN)xsltproc --output script-lua-gtk.html.tmp \ +# $(srcdir)/script.xsl $(srcdir)/built/script-lua-gtk.docbook \ +# && mv script-lua-gtk.html.tmp $(srcdir)/help/script-lua-gtk.html + +$(srcdir)/help/script/index.html: $(script_docbook_sources) script-book.xsl + $(AM_V_GEN)cd $(srcdir) && xsltproc --xinclude script-book.xsl script.docbook + +$(srcdir)/help/medit.css: medit.css + $(AM_V_at)$(MKDIR_P) $(srcdir)/help/ + $(AM_V_GEN)cp $(srcdir)/medit.css $(srcdir)/help/ + +$(srcdir)/built/lgpl.no-fancy-chars: $(top_srcdir)/COPYING + $(AM_V_at)$(MKDIR_P) $(srcdir)/built + tr -d '\014' < $(top_srcdir)/COPYING > lgpl.no-fancy-chars.tmp && \ + mv lgpl.no-fancy-chars.tmp $(srcdir)/built/lgpl.no-fancy-chars + +docbook_files = \ + medit.docbook \ + prefs.docbook \ + regex.docbook \ + user-tools.docbook \ + license.docbook + +docbook_sources = \ + $(docbook_files) \ + built/lgpl.no-fancy-chars \ + built/medit-defines.ent + +$(srcdir)/help/index.html: $(docbook_sources) medit.xsl medit-common.xsl + $(AM_V_GEN)cd $(srcdir) && xsltproc --xinclude medit.xsl medit.docbook + +$(srcdir)/help/help.html: $(docbook_sources) medit-single.xsl medit-common.xsl + $(AM_V_at)$(MKDIR_P) $(srcdir)/help/ + $(AM_V_GEN)xsltproc --xinclude --output help.html.tmp $(srcdir)/medit-single.xsl $(srcdir)/medit.docbook && \ + mv help.html.tmp $(srcdir)/help/help.html + +toc.xml: $(docbook_sources) + $(AM_V_GEN)xsltproc --output toc.xml --xinclude \ + --stringparam chunk.first.sections 1 \ + http://docbook.sourceforge.net/release/xsl/current/html/maketoc.xsl $(srcdir)/medit.docbook + +moo-help-sections.h.stamp: $(docbook_files) toc.xml genhelpsectionsh.py + $(AM_V_at)$(MKDIR_P) $(srcdir)/built + $(AM_V_GEN)$(PYTHON) $(srcdir)/genhelpsectionsh.py --toc=toc.xml --srcdir=$(srcdir) $(docbook_files) > moo-help-sections.h.tmp + $(AM_V_at)cmp -s moo-help-sections.h.tmp $(srcdir)/built/moo-help-sections.h || \ + mv moo-help-sections.h.tmp $(srcdir)/built/moo-help-sections.h + $(AM_V_at)rm -f moo-help-sections.h.tmp + $(AM_V_at)echo stamp > moo-help-sections.h.stamp + +png_files = prefs-file-filters.png prefs-file-selector.png +dest_png_files = $(addprefix $(srcdir)/help/img/,$(png_files)) + +$(srcdir)/help/img/%.png: img/%.png + $(AM_V_at)$(MKDIR_P) $(srcdir)/help/img/ + $(AM_V_GEN)cp $(srcdir)/img/$*.png $(srcdir)/help/img/ + +all-am: doc +doc: \ + $(srcdir)/help/index.html \ + $(srcdir)/help/help.html \ + $(srcdir)/help/script/index.html \ + $(srcdir)/help/medit.css \ + $(srcdir)/built/medit.1 \ + $(dest_png_files) \ + moo-help-sections.h.stamp + +$(srcdir)/built/man-medit.t2t: man-medit.t2t.in $(top_builddir)/config.status + $(AM_V_at)$(MKDIR_P) $(srcdir)/built + $(AM_V_GEN)cd $(top_builddir) && ./config.status --silent --file=doc/man-medit.t2t + $(AM_V_at)mv man-medit.t2t $(srcdir)/built/man-medit.t2t + +$(srcdir)/built/medit.1: built/man-medit.t2t + $(AM_V_GEN)$(TXT2TAGS) --target=man --outfile=- $(srcdir)/built/man-medit.t2t | grep -v "cmdline: txt2tags" \ + > medit.1.tmp && mv medit.1.tmp $(srcdir)/built/medit.1 + +endif + +EXTRA_DIST = help built/moo-help-sections.h built/medit.1 + +install-data-local: + $(MKDIR_P) $(DESTDIR)$(MOO_HELP_DIR)/img $(DESTDIR)$(MOO_HELP_DIR)/script + cd $(srcdir) && $(INSTALL_DATA) help/*.html help/*.css $(DESTDIR)$(MOO_HELP_DIR) + cd $(srcdir) && $(INSTALL_DATA) help/script/*.html $(DESTDIR)$(MOO_HELP_DIR)/script + cd $(srcdir) && $(INSTALL_DATA) help/img/*.png $(DESTDIR)$(MOO_HELP_DIR)/img + +uninstall-local: + rm -f $(DESTDIR)$(MOO_HELP_DIR)/*.html \ + $(DESTDIR)$(MOO_HELP_DIR)/*.css \ + $(DESTDIR)$(MOO_HELP_DIR)/script/*.html \ + $(DESTDIR)$(MOO_HELP_DIR)/img/*.png + +if MOO_OS_UNIX +if MOO_BUILD_APP +dist_man_MANS = built/medit.1 +endif MOO_BUILD_APP +endif MOO_OS_UNIX diff --git a/doc/genhelpsectionsh.py b/doc/genhelpsectionsh.py new file mode 100644 index 00000000..90a9bafc --- /dev/null +++ b/doc/genhelpsectionsh.py @@ -0,0 +1,71 @@ +#! /usr/bin/env python + +import os +import re +import sys +import optparse + +op = optparse.OptionParser() +op.add_option("--toc", action="store") +op.add_option("--srcdir", action="store") +(opts, args) = op.parse_args() + +srcdir = opts.srcdir or '.' + +def resolve_filename(filename): + if os.path.exists(filename): + return filename + fullname = os.path.join(srcdir, filename) + if os.path.exists(fullname): + return fullname + raise RuntimeError('could not find file %s' % filename) + +def parse_toc(filename): + filename = resolve_filename(filename) + dic = {} + for line in open(filename): + m = re.search(r'<\?dbhtml filename="([\w\d_.-]+)"\?>', line) + if m: + dic[m.group(1)] = m.group(2) + return dic + +def parse_docbook(filename): + filename = resolve_filename(filename) + dic = {} + for line in open(filename): + m = re.search(r'id\s*=\s*"([\w\d_.-]+)"\s+moo.helpsection\s*=\s*"([\w\d_.-]+)"', line) + if m: + dic[m.group(2)] = m.group(1) + else: + m = re.search(r'moo.helpsection\s*=\s*"([\w\d_.-]+)"\s+id\s*=\s*"([\w\d_.-]+)"', line) + if m: + dic[m.group(1)] = m.group(2) + return dic + +map_id_to_html = parse_toc(opts.toc) +map_hsection_to_id = {} +for f in args: + map_hsection_to_id.update(parse_docbook(f)) + +map_hsection_to_html = { + 'PREFS_ACCELS': 'index.html', + 'DIALOG_REPLACE': 'index.html', + 'DIALOG_FIND': 'index.html', + 'FILE_SELECTOR': 'index.html', + 'DIALOG_FIND_FILE': 'index.html', + 'DIALOG_FIND_IN_FILES': 'index.html', +} + +for section in map_hsection_to_id: + map_hsection_to_html[section] = os.path.basename(map_id_to_html[map_hsection_to_id[section]]) + +map_hsection_to_html['PREFS_PLUGINS'] = map_hsection_to_html['PREFS_DIALOG'] +map_hsection_to_html['PREFS_VIEW'] = map_hsection_to_html['PREFS_DIALOG'] + +print '#ifndef MOO_HELP_SECTIONS_H' +print '#define MOO_HELP_SECTIONS_H' +print '' +for section in sorted(map_hsection_to_html.keys()): + print '#define HELP_SECTION_%s "%s"' % (section, map_hsection_to_html[section]) +print '' +print '#endif /* MOO_HELP_SECTIONS_H */' diff --git a/doc/img/prefs-file-filters.png b/doc/img/prefs-file-filters.png new file mode 100644 index 0000000000000000000000000000000000000000..cac8925de2a7fe0a074042206bfb25e2550b510e GIT binary patch literal 31939 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 literal 0 HcmV?d00001 diff --git a/doc/img/prefs-file-selector.png b/doc/img/prefs-file-selector.png new file mode 100644 index 0000000000000000000000000000000000000000..09f4419b16a6f7b1acc9f0fd67ab3b6e18f13c46 GIT binary patch literal 32720 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 z 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 + +%medit-defines; +]> + +License + + +&medit; as a whole is distributed under the terms of the GNU General +Public License, version 2, but most of its code is released under the +GNU Lesser General Public License. Full text of these licenses, as well +as licenses and acknowledgements for third-party software incorporated +in &medit;, can be found in this section. + + + + +GNU General Public License + + + + + + + +GNU Lesser General Public License + + + + + + + +Lua License + + + + + + + +LuaFileSystem License + +LuaFileSystem - File System Library for Lua +Copyright 2003-2007 PUC-Rio +http://www.keplerproject.org/luafilesystem + +LuaFileSystem is a Lua library developed to complement the set of functions +related to file systems offered by the standard Lua distribution. LuaFileSystem +offers a portable way to access the underlying directory structure and file +attributes. LuaFileSystem is free software and uses the same license as Lua 5.1 +Current version is 1.2.1. + + + + + +xdg-utils License + +Copyright 2006, Kevin Krammer <kevin.krammer@gmx.at> +Copyright 2006, Jeremy White <jwhite@codeweavers.com> + +LICENSE: + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + + + + + diff --git a/doc/man-medit.t2t.in b/doc/man-medit.t2t.in new file mode 100644 index 00000000..15836d98 --- /dev/null +++ b/doc/man-medit.t2t.in @@ -0,0 +1,99 @@ +MEDIT + +September 2010 + +%!encoding: UTF-8 + += NAME = + +medit - text editor + + += SYNOPSIS = + +**medit** [//OPTION//]... [//FILES//] + + += DESCRIPTION = + +**medit** is a text editor. + + += OPTIONS = + +: **-n**, **--new-app** +run new instance of **medit**. By default **medit** opens //FILES// +(or creates a new document if none are given) in an existing instance +of application + +: **-s**, **--use-session**[=//yes|no//] +load and save session. By default **medit** does it when **-n** is not used. +If this option is not given on command line then medit uses the corresponding +preferences setting. + +: **--pid** //PID// +use existing instance with process id //PID//. + +: **--app-name** //NAME// +use instance name //NAME//. If an instance with this name is already running, +then it will send files given on the command line to that instance and exit. + +: **-e**, **--encoding** //ENCODING// +use provided character encoding to open the file + +: **-l**, **--line** //LINE// +open file and position cursor on line //LINE//. Alternatively +line number may be specified with filename, e.g. + medit foo.txt:12 + +: **-r**, **--reload** +automatically reload opened file if it was modified on disk by another program. + +: **-w**, **--new-window** +open file in a new window. + +: **-t**, **--new-tab** +open file in a new tab. + +: **--log-file** //FILE// +write debug output into //FILE//. This option is only useful on Windows. + +: **--log-window** +show debug output in a log window. This option is only useful on Windows. + +: **--debug** //DOMAINS// +enable debug output for //DOMAINS// (if **medit** was compiled with +--enable-debug option). + +: **--geometry** //WIDTHxHEIGHT// +: **--geometry** //WIDTHxHEIGHT+X+Y// +default window size and position. + +: **-h**, **--help** +show summary of options. + +: **-v**, **--version** +show program version. + +: //FILES// +list of files to open. Filenames may include line numbers after colon, +e.g. /tmp/file.txt:200. Trailing colon is ignored. + + += ENVIRONMENT VARIABLES = + +: MEDIT_PID +if set, it is used as --pid argument. When medit spawns a process (e.g. a DVI viewer) it sets +MEDIT_PID to its own process id, so the child process may in turn simply use 'medit filename' +to open a file (e.g. for inverse DVI search). + + += CONTACT = + +@MOO_WEB_CONTACT@ + + += AUTHOR = + +Written and maintained by Yevgen Muntyan <@MOO_EMAIL@> + diff --git a/doc/medit-common.xsl b/doc/medit-common.xsl new file mode 100644 index 00000000..f4a5eba3 --- /dev/null +++ b/doc/medit-common.xsl @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/doc/medit-defines.ent.in b/doc/medit-defines.ent.in new file mode 100644 index 00000000..b1a9e617 --- /dev/null +++ b/doc/medit-defines.ent.in @@ -0,0 +1,6 @@ + +medit"> + +$HOME/.local/share/medit-1/"> +$HOME/.local/share/medit-1/prefs.xml"> +$HOME/.local/share/medit-1/tools/"> diff --git a/doc/medit-single.xsl b/doc/medit-single.xsl new file mode 100644 index 00000000..5f19d505 --- /dev/null +++ b/doc/medit-single.xsl @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/doc/medit.css b/doc/medit.css new file mode 100644 index 00000000..be16704e --- /dev/null +++ b/doc/medit.css @@ -0,0 +1,141 @@ +.variablelist +{ + padding: 4px; + margin-left: 3em; +} +.variablelist td:first-child +{ + vertical-align: top; +} + +.programlisting +{ + /* tango:sky blue 0/1 */ + background: #e6f3ff; +/* border: solid 1px #729fcf;*/ +/* padding: 0.5em;*/ + font-family: monospace; + white-space: pre; +/* background: #F4F4F4;*/ + border: solid 1px #C5C5C5; + padding: 0.25em; + margin: 0.5em; +} + +.code +{ + font-family: monospace; + white-space: pre; + background: #e6f3ff; +/* background: #F4F4F4;*/ +/* border: solid 1px #C5C5C5;*/ + padding: 0.1em; +/* margin: 0.5em;*/ +} + +div.table table +{ + border-collapse: collapse; + border-spacing: 0px; + /* tango:aluminium 3 */ + border: solid 1px #babdb6; +} + +div.table table td, div.table table th +{ + /* tango:aluminium 3 */ + border: solid 1px #babdb6; + padding: 3px; + vertical-align: top; +} + +div.table table th +{ + /* tango:aluminium 2 */ + background-color: #d3d7cf; +} + +hr +{ + /* tango:aluminium 3 */ + color: #babdb6; + background: #babdb6; + border: none 0px; + height: 1px; + clear: both; +} + +.warning +{ + /* tango:orange 0/1 */ + background: #ffeed9; + border-color: #ffb04f; +} +.note +{ + /* tango:chameleon 0/0.5 */ + background: #d8ffb2; + border-color: #abf562; +} +.note, .warning +{ + padding: 0.5em; + border-width: 1px; + border-style: solid; +} +.note h3, .warning h3 +{ + margin-top: 0.0em +} +/*.note p, .warning p +{ + margin-bottom: 0.0em +}*/ + +/* blob links */ +h2 .extralinks, h3 .extralinks +{ + float: right; + /* tango:aluminium 3 */ + color: #babdb6; + font-size: 80%; + font-weight: normal; +} + +.annotation +{ + /* tango:aluminium 5 */ + color: #555753; + font-size: 80%; + font-weight: normal; +} + +.listing_frame { + /* tango:sky blue 1 */ + border: solid 1px #729fcf; + padding: 0px; +} + +.listing_lines, .listing_code { + margin-top: 0px; + margin-bottom: 0px; + padding: 0.5em; +} +.listing_lines { + /* tango:sky blue 0.5 */ + background: #a6c5e3; + /* tango:aluminium 6 */ + color: #2e3436; +} +.listing_code { + /* tango:sky blue 0 */ + background: #e6f3ff; +} +.listing_code .programlisting { + /* override from previous */ + border: none 0px; + padding: 0px; +} +.listing_lines pre, .listing_code pre { + margin: 0px; +} diff --git a/doc/medit.docbook b/doc/medit.docbook new file mode 100644 index 00000000..b9e8f56e --- /dev/null +++ b/doc/medit.docbook @@ -0,0 +1,19 @@ + + +%medit-defines; +]> + + + +&medit; manual +12/26/2010 +&medit-version; + + + + + + + + diff --git a/doc/medit.xsl b/doc/medit.xsl new file mode 100644 index 00000000..452443af --- /dev/null +++ b/doc/medit.xsl @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/doc/prefs.docbook b/doc/prefs.docbook new file mode 100644 index 00000000..47346366 --- /dev/null +++ b/doc/prefs.docbook @@ -0,0 +1,441 @@ + + +%medit-defines; +]> + +Preferences + + +Preferences dialog + +Preferences provides access to almost all &medit; settings. +Some settings are not available here, see . + + +Preferences dialog has several tabs: + + + General + Contains settings which didn't find place in other sections, see + . + + + View + Contains settings which control how &medit; displays text. + + + File + Contains settings which control how &medit; saves and loads files, see + . + + + Languages + See . + + + File Filters + See . + + + Plugins + Displays information about available plugings and allows to disable/enable them. + + + File Selector + Contains File Selector settings, see . + + + Tools + User-defined tools, see . + + + + + + + +<guilabel>General</guilabel> tab + + + Smart Home and End + If checked, Home key moves cursor to first non-whitespace character + on the line on first Home key press, and moves cursor to the first character on the line + on second key press. Analogously End key moves cursor past last non-whitespace character + on the line, and then past last character on next key press. + + + Enable auto indentation + If checked, pressing Enter key inserts line end character and + whitespace to indent next line according to indentation settings. + + + Do not use tabs for indentation + If checked, spaces are used for indentation instead of tab character. + + + Tab key indents + If checked, Tab key inserts whitespace characters according + to indentation settings to indent text at cursor. Otherwise Tab key + only inserts single tab character. + + + Tab width + Displayed width of tab character in spaces. By default it is + 8. + + + Indent width + Number of spaces inserted by single Tab key press. If tabs are + used for indentation then Tab key inserts spaces until line indent is + multiple of tab width, then it replaces spaces with tabs (if indent width is a multiple + of tab width then only tab characters are used.) + + + + + + +<guilabel>File</guilabel> tab + + + Encodings to autodetect + This entry contains comma-separated list of encodings used when + loading files if encoding is not specified in the Open. + &medit; tries every encoding from the list one by one and stops when file + content is valid text in this encoding. LOCALE denotes encoding + from current locale. + + + + Encoding for new files + This is default encoding to save new files. For every document + its encoding on disk can be changed using Encoding submenu of Document menu. + + + + Remove trailing spaces + If checked, trailing whitespace characters are removed from each + line of the document on save. + + + + Ensure trailing newline + If checked, new line character will be added to document on save + if it doesn't end with one. + + + + Make backups + If checked, old file contents will be moved to backup file on + save. + + + Enable session support + If checked, &medit; will remember open documents on exit and restore + them next time it's launched. + + + Open and Save As dialogs show current document folder + If checked, Open and Save As dialogs will show folder of the current document. + Otherwise they will show last used folder. + + + + + + +<guilabel>Languages</guilabel> tab + +Languages and files tab allows customizing +how syntax highlighting language and editing options are chosen +depending on the document filename, as well as setting editing options for +all documents which use given language and choosing file patterns and mime types +for which the given language should be used. + + +Here you can set editing options on per-language basis, as well as define +for which file patterns and mime types given language should be used. + + +Language combo box + +Choose the language you want to customize. Settings for None will apply to +documents for which no syntax highlighting language was chosen. + + + +Mime types + +Selected language will be used for files with these mime types, unless the language +is chosen based on the filename or overridden in the File filters tab section. + + + +Extensions + +Selected language will be used for files whose filenames match these patterns, +unless overridden in the File filters tab section. + + + +Options + +Default editing options to use in documents which use the given language. These +options can be overridden using File filters tab section, and options set +in the file text have a higher priority as well. See +for format of this entry content. + + + + + + + + +File filters tab + +File filters tab section allows to customize editing options, +as well as syntax highlighting language, on per-document basis using regular +expressions which are matched against the document filename (globs can also be +used, see below). Full file paths are used, so one can have per-directory settings. + + +The filters are applied in the order they appear in the list, one by one. All filters +are applied to every file, so several filters may affect options in the same file. In +this way one can set some options for a set of files or a directory, then set or modify +some additional options for certain files in that set, etc. + + +To add a filter, use New button. Click the filter in the list to +select it, then click the Filter or Options +part of it to edit. Use Delete button to delete a filter, +and Up and Down buttons to change the order in +which they are applied. + + +Filter field contains a regular expression matched agains the +document filename. If it is found in the filename, +then the options from the Options field are applied to the +document. Example: +projects/moo/ + + +Use dollar if you need to match ends of filenames, e.g. "\.doc$" will work as +"*.doc" pattern. + + +Alternatively it can be +a comma-separated list of globs prefixed with "globs:" or a list +of language ids prefixed with "langs:", e.g. +globs:*.c,*.h +or +langs:c,c++ + + +Options field contains the options, in format described in +. + + + + + + + + +<guilabel>File Selector</guilabel> tab + +File Selector tab in the Preferences +dialog allows to define custom commands which are available in +Open With submenu of context menu in File Selector. By default +this submenu contains single item Default Application +which opens selected file with default application as configured in the system. +Here you can add additional commands and set whether they should be available +only for given file patterns or syntax highlighting languages. + + +Use New button to create new command, Delete +button to delete selected command, and Up and Down +to change relative order of the commands, they will appear in the menu in the same order +as in this list. + + +The following entries set the command properties: + + + + + Name + Menu item label for this command. + + + Command + Shell command to execute when the menu item is activated. %f will + be replaced with full path of the selected file; if more than one file is selected then + the command will be executed for each file one by one. If command + contains %F and several files are selected then %F will be + replaced with the space-separated list of paths of all selected files. If a single file + is selected then %f and %F behave in the same way. + Example: firefox %f, glade %F + + + Extensions + Semicolon-separated list of file patterns to define for which files this command + is available, e.g. *.c;*.h. Use * if the command should + be available for all files. + + + Mime types + Semicolon-separated list of mime types to define for which files this command + is available, e.g. application/docbook+xml;application/x-glade. Leave it empty + if Extensions entry defines whether the command should be enabled. + + + + + + + + + + +Options for editing text + +&medit; has some editing options which can be set in the document text, +or in the Preferences dialog for sets of files or for given syntax +highlighting language. + + +To set the options in the document text, place the following on the first, +second or the last line of the document: + +-%- options -%- + +where options is the option string + +key: value; key: value; ... + +(the latter is the format used also in the Preferences dialog). + + +For example, the following might be the first line in a C file: + +/* -%- indent-width: 2; use-tabs: yes; strip: yes -%- */ + + + +Values can be strings, integers, or booleans. + + +Booleans are yes, no, true, false, 1, 0. + + +If a string value contains : character, then the following syntax may be used: +key=/value/. Any character may be used instead of slash (and it +must not occur in the value). Example: word-chars=@-/:@ + + +The following options are available: + + + + +lang +syntax highlighting language to use in this document. Special value none will +turn off syntax highlighting in this document. + + +strip +a boolean value, whether trailing whitespace should be removed from the document on save. + + +add-newline +a boolean value, whether the editor should ensure that saved files have a trailing new line character. + + +indent-width +an integer specifying indentation offset used when the Tab key is pressed to indent text. + + +tab-width +displayed width of the tab character. Note that this is not the same as +indent-width. + + +use-tabs +whether tab character should be used for indentation. + + + + +&medit; tries to understand modelines of Vim, Emacs, and Kate text editors, so chances are it will correctly +pick up the conventional settings from source files. + + + + + +Preferences files + +&medit; preferences are stored in &medit-prefs-xml-unix; file. +It is an XML file which may be edited to set preferences which have not found +their place in the Preferences dialog. + + + +&medit; reads the preferences file on startup and writes it whenever OK +or Apply button is clicked in the Preferences dialog. Therefore, if you +modify the preferences file, your changes may be overwritten, and they not take +effect until you restart &medit;. + + + +The following "hidden" settings are available: + + + + Editor/window_title + Format of the window title. It is a string which may + contain format sequences, which are percent sign followed by a character: + + + + + %a + application name + + + %b + current document basename + + + %f + full path of the current document + + + %u + URI of the current document + + + %s + the status of the current document, e.g. " [modified]". It is prefixed + with a space, so that "%b%s" produces a nice string + + + %% + the percent character + + + Default value is "%a - %f%s" which produces something like "medit - /home/user/file [modified]". + + + + Editor/window_title_no_doc + same as Editor/window_title, used when no document is open. + Default value is "%a". + + + + + + diff --git a/doc/regex.docbook b/doc/regex.docbook new file mode 100644 index 00000000..f259d600 --- /dev/null +++ b/doc/regex.docbook @@ -0,0 +1,23 @@ + + +%medit-defines; +]> + +Regular expressions + + +&medit; uses regular expressions functionality provided by Glib, which in turn uses +PCRE library. See +Glib manual +for complete description of regular expression syntax. + + + +Regular expression searches in a document text are limited to single lines, unless the +search pattern includes newline character. For example, pattern ".*" will match every +line in the document, pattern ".*\n.*" will match pairs of consecutive lines. This means +that it is mostly impossible to perform searches for text which spawns multiple lines. + + + diff --git a/doc/script-book.xsl b/doc/script-book.xsl new file mode 100644 index 00000000..777bbc34 --- /dev/null +++ b/doc/script-book.xsl @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
diff --git a/doc/script-lua-gtk.tmpl.docbook b/doc/script-lua-gtk.tmpl.docbook new file mode 100644 index 00000000..763a45eb --- /dev/null +++ b/doc/script-lua-gtk.tmpl.docbook @@ -0,0 +1,27 @@ + + +%medit-defines; +]> + + +Gtk API for Lua scripts + +Introduction +Lua scripts running in &medit; have a limited access to +Gtk functionality +exposed through gtk package in addition to functions in +moo package. +It is not a goal to provide complete Lua bindings for Gtk, +and it is not a goal to enable creating UI in Lua scripts. +If there is a demand, &medit; might bind more Gtk functionality, +but so far Lua in &medit; is supposed to be lean and mean +scripting language which is always available. Use Python if +you need more functionality. + +Notations used in this manual are described +here. + + +###GENERATED### + diff --git a/doc/script-lua.tmpl.docbook b/doc/script-lua.tmpl.docbook new file mode 100644 index 00000000..72cb7b04 --- /dev/null +++ b/doc/script-lua.tmpl.docbook @@ -0,0 +1,79 @@ + + +%medit-defines; +]> + + +&medit; Lua API + + +Introduction +Lua scripts running in &medit; have access to its +functionality through the moo package. + +Functions which are not documented here may or may not work differently in +the future and they may disappear without notice. Contact the author if you +need functions which are not present here. + + + + +&medit; object model +&medit; uses a very simple object model where its objects are +represented as user data in Lua and methods are provided via metatable +shared by all objects of all "classes". Method dispatch is dynamic, +i.e. metatable does not contain functions which correspond to methods, +and obj:method returns a function object which knows which +method on which object it is going to call. +This manual lists and talks about "classes", but it is merely +to avoid complicated terminology. When we say that an object belongs +to or is an instance of a class Foo, it just means that +it has methods listed in manual section for class Foo +and methods of parent classes, if any. + +To call a method, you can use both obj:method(args) +and obj.method(args). + + + + +Notations +This manual uses the following conventions: + + + Optional parameters + + func(arg1=val1, arg2=val, arg3=val3) + arg=val means that + parameter arg is optional, and function receives + value val if it's missing. Not all parameters are necessarily + optional. For example, insert_text(text, where=nil) + means that text may not be missing or nil + (unless documentation says otherwise), and where is optional. + + + + Keyword parameters + + func{arg1, arg2, kwarg1=kwval1, kwarg2=kwval2} + This means that function can be called in an alternative way: actual parameters are + taken from the single table parameter, which must be a dictionary with keys + kwarg1, kwarg2, etc., and whose array part + must contain exactly as many values as there are non-optional arguments. Similarly + to regular parameters, kwarg=kwval means that + kwarg is optional. For example, above function can be called + as follows. + +func{1, 2, kwarg1='foo'} -- equivalent to func(1, 2, 'foo') +func{3, 4, kwarg2='bar'} -- equivalent to func(3, 4, kwval1, 'bar') +func{5, 6, kwarg2='baz', kwarg1='bud', } -- equivalent to func(5, 6, 'bud', 'baz') + This is similar to Python keyword arguments (with the difference that keyword arguments + may be used only if function is documented to support them). + + + + + +###GENERATED### + diff --git a/doc/script-python.tmpl.docbook b/doc/script-python.tmpl.docbook new file mode 100644 index 00000000..3586fb8d --- /dev/null +++ b/doc/script-python.tmpl.docbook @@ -0,0 +1,28 @@ + + +%medit-defines; +]> + + +&medit; Python API + +Introduction +When compiled with Python support, &medit; uses +PyGtk. Its functionality is +exposed through moo module. You can use built-in Python console +available from Tools menu to experiment with &medit; API. + +Classes and functions documented here are guaranteed to work as long as you +follow the rules from their documentation (most often there are no special +rules, but for some functions you may or may not use named arguments, etc.) + + +moo module has more classes and functions than documented here, +but undocumented classes and functions may or may not work differently in +the future and they may disappear without notice. Contact the author if you +need functions which are not present here. + + +###GENERATED### + diff --git a/doc/script.docbook b/doc/script.docbook new file mode 100644 index 00000000..484690a7 --- /dev/null +++ b/doc/script.docbook @@ -0,0 +1,18 @@ + + +%medit-defines; +]> + + + +&medit; scripting manual +1/22/2011 +&medit-version; + + + + + + + diff --git a/doc/script.xsl b/doc/script.xsl new file mode 100644 index 00000000..19e49d6c --- /dev/null +++ b/doc/script.xsl @@ -0,0 +1,26 @@ + + + + + + + + + + + + +
+
+ +
+ + + +
diff --git a/doc/user-tools.docbook b/doc/user-tools.docbook new file mode 100644 index 00000000..093d32d8 --- /dev/null +++ b/doc/user-tools.docbook @@ -0,0 +1,406 @@ + + +%medit-defines; +]> + +User-defined tools + + +&medit; allows extending its functionality with user-defined +tools. It can be a Lua script or a Python script (if &medit; has been +built with Python support) which are executed inside &medit;, +or a shell script which can use the text of the open document as +its input and/or output. + + +There are some predefined tools which you can use as +an example or to modify to suit your needs. + + + + +Managing tools in <guilabel>Preferences</guilabel> dialog + + +To create a new tool or to modify existing ones, open +Preferences dialog and select Tools in the list on the left. + + +Select the tool in the list or click the New +button to create a new one. To modify the order in which tools +appear in the Tools menu (or in the document +context menu), use Up and Down buttons. To rename a tool, +click its name in the list to select it and then click again to +edit the name. Use the Delete button to delete a tool. + + +The following controls are available to modify tools: + + + + Files entry specifies for which files the tool is going to be available. It can + contain the following: + + a comma-separated list of file patterns, e.g. *.c,*.h + a comma-separated list of languages prefixed with "langs:", e.g. + langs: c, c++, objc + a regular expression matching document filename prefixed with "regex:", e.g. the above + pattern list may be written as regex:\.[ch]$ + + + Empty entry means that the tool will be available for all documents. + + + Requires combo box specifies whether the tool should be + enabled depending on current document. + + + + + Nothing + the tool is enabled regardless whether there is an open document. + + + Document + the tool is enabled only if there is an open document. For example, if the tool manipulates + current document text, then it needs a document to be there. + + + File on disk + the tool is enabled only if current document is saved on disk (i.e. it is not "Untitled"). + For example, to compile a TeX file, it needs to be saved first. + + + + + Save combo box specifies what should be saved every time + before the command is executed. + + + + + Nothing + nothing will be saved. + + + Current document + current document will be automatically saved. For example, you probably want to save currrent + document before compiling it with latex. + + + All documents + all open documents will be automatically saved. For example, if the tool builds a C project, then + you probably want to save all open files before running make. + + + + + Type combo specifies the type of the tool: a Python script, a + Lua script, or a shell script. + + + Code text field contains script or shell command text. See + , , + for details on what can be here. + + + + + + + + +Storing tools in files + + +It is possible to create tools without using the Preferences dialog, +they can be stored in files in tools subfolder of the &medit; data +folders (or tools-context for tools which appear in the document context +menu). In particular, on Unix systems you can place files into &medit-user-tools-dir-unix; folder. + + +Names of the files in the tools folder are used as their menu item +labels, after stripping first three characters, so you can use trhee-character +prefix to affect the order of the menu items, e.g. you can have 00-Do Something, +01-Another tool files to have them in that order in the menu. The files +may be of three types: + +files with extension ".py", they will be used +as Python scripts; +files with extension ".lua", they will be used +as Lua scripts; +executable files, they will be executed in the same way +as shell commands. + + + +Note that files with .py and .lua extensions will be +executed inside &medit; process; if you want to use them as regular scripts, then just remove the +extension. + + +To set parameters for a tool, place them on the first or the second line of the file in +the following format: + +!! key=value; key=value; ... !! + + + +key may be one of the following: + + + + position + it can be start or end, and it defines whether the menu item + will be located at the start or at the end of the menu. + + + id + the tool identificator. + + + name + the tool name, i.e. the label used in the menu item. Overrides the file name. + + + accel + default keyboard accelerator used to invoke this tool. + + + menu + the menu to place this tool into. By default tools are located in the Tools menu, + but they can be as well put into any other menu. + + + langs + comma-separated list of languages for which this tool will be enabled. + + + file-filter + defines for which files this tool will be enabled. The value has the same format as + in the Preferences dialog. + + + options + this corresponds to Requires and Save controls in the Preferences dialog. It is a + comma-separated list of the following: + + + + need-doc + tool will be enabled only if there is an open document. + + + need-file + tool will be enabled only if current document is saved on disk (i.e. it is not "Untitled"). + + + need-save + current document will be automatically saved before the command is executed. + + + need-save-all + all open documents will be automatically saved before the command is executed. + + + + + + + +In addition to these, you can set input and output options for executable files (see +for the meaning of these options): + + + + input + none, lines, selection, or doc. + + + output + none, async, pane, insert, or new-doc. + + + filter + output filter name. + + + + + + + + +Shell scripts + + +Shell script user tools execute command entered in the Command +text field using default user shell on Unix systems or cmd.exe on Windows. + + +Its input and output are specified by the following controls: + + + Input entry specifies what text from the document should be passed to the command. + The text is passed via command's standard input, except for Document copy case. + + + + None + no input text. + + + Selected lines + the lines containing selection or the line containing the cursor in + case when no text is selected. + + + Selection + exact selected text. This will be different from Selected lines + if selection does not span whole lines of the document, for instance if it is a single word. + + + Whole document + whole document contents. + + + Document copy + document contents will be saved to a temporary file and the file path will be stored + in INPUT_FILE environment variable. No text will be passed to the command via standard + input. + + + + + + Output entry specifies how the standard output of the command should be redirected. + + + + None + the command output will be discarded. + + + None, asynchronous + the command output will be discarded, and the command will be executed in background. + Use this if you need to launch some external program like a web browser. + + + Output pane + the command output will be displayed in an output pane. This is useful for running programs + like compilers, where you want to see the output. + + + Insert into the document + output will be inserted into the current document at the cursor position. It will replace the + text used as an input, if any. + + + New document + new document will be created and the command output will be inserted into it. + + + + + + Filter combo. If the output pane is used, then it can be passed through a + filter: the filter can match filenames and line numbers, so when you click + the text in the output pane it will open the corresponding file. This is used for compilers and + similar commands, which output locations of errors in processed files. + + + + +Shell script user tools have a number of environment variables set. +APP_PID variable is set so that opening a file in the same instance +of &medit; is as simple as medit filename (on the other hand, you will +have to use command line options if you need to run a new &medit; instance). The +following environment variables are set when scripts are executed: + + + + + APP_PID + current process id. + + + DOC + document basename ("file.c" for file /home/user/file.c). + + + DOC_DIR + document directory ("/home/user" for file /home/user/file.c). Full file path is + $DOC_DIR/$DOC. + + + DOC_BASE + basename without extension ("file" for file /home/user/file.c). + + + DOC_EXT + document filename extension including the period (".c" for file + /home/user/file.c). Basename is always + $DOC_BASE$DOC_EXT. + + + DOC_PATH + full document path. + + + LINE + 1-based number of the line containing cursor. + For example, if cursor is at the first line then LINE will be + set to 1. + + + LINE0 + 0-based number of the line containing cursor. + For example, if cursor is at the first line then LINE0 will be + set to 0. + + + DATA_DIR + user data directory (&medit-user-data-dir-unix; on Unix systems). + + + INPUT_FILE + if input was set to "Document copy" then this is set to + full path of the temporary file containing document text. + + + + +Additionally, all shell commands which run inside &medit; will have +DATA_DIR/scripts +directories in $PATH, so you may place some &medit;-specific programs +or scripts into DATA_DIR/scripts/ to be used from shell script tools. + + + + + + +Lua scripts + + +Medit API for Lua scripts. + + +Gtk API for Lua scripts. + + + + + + +Python scripts + + +Medit API for Python scripts. + + + + + + diff --git a/hgrc b/hgrc new file mode 100644 index 00000000..929aea20 --- /dev/null +++ b/hgrc @@ -0,0 +1,7 @@ +[paths] +default = ssh://hg@bitbucket.org/medit/medit + +[hooks] +# Reject commits which would introduce windows-style text" files +pretxncommit.crlf = python:hgext.win32text.forbidcrlf +pretxncommit.glade = tools/checkglade diff --git a/m4/moo-flags.m4 b/m4/moo-flags.m4 new file mode 100644 index 00000000..08c8de73 --- /dev/null +++ b/m4/moo-flags.m4 @@ -0,0 +1,356 @@ +AC_DEFUN([_MOO_AC_CHECK_C_COMPILER_OPTIONS],[ + AC_LANG_PUSH([C]) + for opt in $1; do + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $opt" + if test "x$MOO_STRICT_MODE" = "xyes"; then + CFLAGS="-Werror $CFLAGS" + fi + AC_TRY_COMPILE([],[],[MOO_CFLAGS="$MOO_CFLAGS $opt"],[:]) + CFLAGS="$save_CFLAGS" + done + AC_LANG_POP([C]) +]) + +AC_DEFUN([_MOO_AC_CHECK_CXX_COMPILER_OPTIONS],[ + AC_LANG_PUSH([C++]) + for opt in $1; do + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $opt" + if test "x$MOO_STRICT_MODE" = "xyes"; then + CXXFLAGS="-Werror $CXXFLAGS" + fi + AC_TRY_COMPILE([],[],[MOO_CXXFLAGS="$MOO_CXXFLAGS $opt"],[:]) + CXXFLAGS="$save_CXXFLAGS" + done + AC_LANG_POP([C++]) +]) + +# _MOO_AC_CHECK_COMPILER_OPTIONS(options) +AC_DEFUN([_MOO_AC_CHECK_COMPILER_OPTIONS],[ + _MOO_AC_CHECK_C_COMPILER_OPTIONS([$1]) + _MOO_AC_CHECK_CXX_COMPILER_OPTIONS([$1]) +]) + +AC_DEFUN([MOO_COMPILER],[ +# icc pretends to be gcc or configure thinks it's gcc, but icc doesn't +# error on unknown options, so just don't try gcc options with icc +MOO_ICC=false +MOO_GCC=false +if test "$CC" = "icc"; then + MOO_ICC=true +elif test "x$GCC" = "xyes"; then + MOO_GCC=true +fi +]) + +############################################################################## +# MOO_AC_DEBUG() +# +AC_DEFUN_ONCE([MOO_AC_DEBUG],[ + +MOO_DEBUG_ENABLED="no" + +AC_ARG_ENABLE(debug, + AC_HELP_STRING([--enable-debug],[enable debug options (default = NO)]),[ + if test "$enableval" = "xno"; then + MOO_DEBUG_ENABLED="no" + else + MOO_DEBUG_ENABLED="yes" + fi + ],[ + MOO_DEBUG_ENABLED="no" +]) +AM_CONDITIONAL(MOO_DEBUG_ENABLED, test x$MOO_DEBUG_ENABLED = "xyes") + +AC_ARG_ENABLE(dev-mode, + AC_HELP_STRING([--enable-dev-mode],[dev-mode (default = NO, unless --enable-debug is used)]),[ + if test "$enableval" = "xno"; then + MOO_DEV_MODE="no" + else + MOO_DEV_MODE="yes" + fi + ],[ + MOO_DEV_MODE="$MOO_DEBUG_ENABLED" +]) +AM_CONDITIONAL(MOO_DEV_MODE, test x$MOO_DEV_MODE = "xyes") + +AC_ARG_ENABLE(strict, + AC_HELP_STRING([--enable-strict],[enable all warnings (default = NO, unless --enable-debug is used)]),[ + if test "$enableval" = "xno"; then + MOO_STRICT_MODE="no" + else + MOO_STRICT_MODE="yes" + fi + ],[ + if test "$MOO_DEBUG_ENABLED" = yes -o "$MOO_DEV_MODE" = yes; then + MOO_STRICT_MODE="yes" + else + MOO_STRICT_MODE="no" + fi +]) +AM_CONDITIONAL(MOO_STRICT_MODE, test x$MOO_STRICT_MODE = "xyes") + +MOO_COMPILER + +_MOO_AC_CHECK_COMPILER_OPTIONS([dnl +-Wall -Wextra -fexceptions -fno-strict-aliasing dnl +-Wno-missing-field-initializers -Wno-overlength-strings dnl +-Wno-format-y2k -Wno-overlength-strings dnl +]) +_MOO_AC_CHECK_C_COMPILER_OPTIONS([dnl +-Wno-missing-declarations dnl +]) +_MOO_AC_CHECK_CXX_COMPILER_OPTIONS([dnl +-std=c++98 -fno-rtti dnl +]) + +if test "x$MOO_DEBUG_ENABLED" = "xyes"; then + _MOO_AC_CHECK_COMPILER_OPTIONS([-ftrapv]) +else + _MOO_AC_CHECK_CXX_COMPILER_OPTIONS([-fno-enforce-eh-specs]) +fi + +if test "x$MOO_STRICT_MODE" = "xyes"; then + if $MOO_GCC; then + MOO_CFLAGS="$MOO_CFLAGS -Werror" + MOO_CXXFLAGS="$MOO_CXXFLAGS -Werror" + fi + _MOO_AC_CHECK_COMPILER_OPTIONS([dnl +-Wpointer-arith -Wsign-compare -Wreturn-type dnl +-Wwrite-strings -Wmissing-format-attribute dnl +-Wdisabled-optimization -Wendif-labels dnl +-Wvla -Winit-self dnl +]) + # -Wlogical-op triggers warning in strchr() when compiled with optimizations + if test "x$MOO_DEBUG_ENABLED" = "xyes"; then + _MOO_AC_CHECK_COMPILER_OPTIONS([-Wlogical-op]) + else + _MOO_AC_CHECK_COMPILER_OPTIONS([-Wuninitialized]) + fi + _MOO_AC_CHECK_C_COMPILER_OPTIONS([dnl +-Wmissing-prototypes -Wnested-externs -Wnolong-long dnl +]) + _MOO_AC_CHECK_CXX_COMPILER_OPTIONS([dnl +-fno-nonansi-builtins -fno-gnu-keywords dnl +-Wctor-dtor-privacy -Wabi -Wstrict-null-sentinel dnl +-Woverloaded-virtual -Wsign-promo -Wnon-virtual-dtor dnl +-Wno-long-long dnl +]) + MOO_CPPFLAGS="$MOO_CPPFLAGS -DG_DISABLE_DEPRECATED" +fi + +# m4_foreach([wname],[unused, sign-compare, write-strings],[dnl +# m4_define([_moo_WNAME],[MOO_W_NO_[]m4_bpatsubst(m4_toupper(wname),-,_)]) +# _moo_WNAME= +# _MOO_AC_CHECK_COMPILER_OPTIONS(_moo_WNAME,[-Wno-wname]) +# AC_SUBST(_moo_WNAME) +# m4_undefine([_moo_WNAME]) +# ]) + +if test "x$MOO_DEBUG_ENABLED" = "xyes"; then +MOO_CPPFLAGS="$MOO_CPPFLAGS -DENABLE_DEBUG -DENABLE_PROFILE -DG_ENABLE_DEBUG dnl +-DG_ENABLE_PROFILE -DMOO_DEBUG -DDEBUG" +else +MOO_CPPFLAGS="$MOO_CPPFLAGS -DNDEBUG=1 -DG_DISABLE_CAST_CHECKS -DG_DISABLE_ASSERT" +fi +]) + +############################################################################## +# MOO_AC_SET_DIRS +# +AC_DEFUN_ONCE([MOO_AC_SET_DIRS],[ + if test "x$MOO_PACKAGE_NAME" = x; then + AC_MSG_ERROR([MOO_PACKAGE_NAME not set]) + fi + + AC_SUBST(MOO_PACKAGE_NAME) + AC_DEFINE_UNQUOTED([MOO_PACKAGE_NAME], "$MOO_PACKAGE_NAME", [data goes into /usr/share/$MOO_PACKAGE_NAME, etc.]) + + AC_SUBST(MOO_DATA_DIR, "${datadir}/$MOO_PACKAGE_NAME") + AC_SUBST(MOO_LIB_DIR, "${libdir}/$MOO_PACKAGE_NAME") + + AC_SUBST(MOO_DOC_DIR, "${datadir}/doc/$MOO_PACKAGE_NAME") + AC_SUBST(MOO_HELP_DIR, "${MOO_DOC_DIR}/help") + + AC_SUBST(MOO_TEXT_LANG_FILES_DIR, "${MOO_DATA_DIR}/language-specs") + + AC_DEFINE_UNQUOTED([MOO_PREFS_XML_FILE_NAME], "$MOO_PREFS_XML_FILE_NAME", [prefs.xml]) + AC_DEFINE_UNQUOTED([MOO_STATE_XML_FILE_NAME], "$MOO_STATE_XML_FILE_NAME", [state.xml]) + AC_DEFINE_UNQUOTED([MOO_SESSION_XML_FILE_NAME], "$MOO_SESSION_XML_FILE_NAME", [session.xml]) + AC_DEFINE_UNQUOTED([MOO_NAMED_SESSION_XML_FILE_NAME], "$MOO_NAMED_SESSION_XML_FILE_NAME", [session-%s.xml]) + + AC_SUBST(MOO_PYTHON_PLUGIN_DIR, "${MOO_DATA_DIR}/plugins") + AC_SUBST(MOO_PYTHON_LIB_DIR, "${MOO_DATA_DIR}/python") +]) + +############################################################################## +# _MOO_AC_CHECK_FAM(action-if-found,action-if-not-found) +# +AC_DEFUN_ONCE([_MOO_AC_CHECK_FAM],[ + moo_ac_save_CFLAGS="$CFLAGS" + moo_ac_save_LIBS="$LIBS" + + if test x$FAM_LIBS = x; then + FAM_LIBS=-lfam + fi + + CFLAGS="$CFLAGS $FAM_CFLAGS" + LIBS="$LIBS $FAM_LIBS" + + AC_CHECK_HEADERS(fam.h,[ + AC_CHECK_FUNCS([FAMMonitorDirectory FAMOpen],[fam_found=yes],[fam_found=no]) + ],[fam_found=no]) + + if test x$fam_found != xno; then + AC_SUBST(FAM_CFLAGS) + AC_SUBST(FAM_LIBS) + + AC_MSG_CHECKING(for FAM_CFLAGS) + if test -z $FAM_CFLAGS; then + AC_MSG_RESULT(None) + else + AC_MSG_RESULT($FAM_CFLAGS) + fi + + AC_MSG_CHECKING(for FAM_LIBS) + if test -z $FAM_LIBS; then + AC_MSG_RESULT(None) + else + AC_MSG_RESULT($FAM_LIBS) + fi + + AC_CHECK_DECL([FAMNoExists],[ + AC_DEFINE(HAVE_FAMNOEXISTS, 1, [fam.h has FAMNoExists defined]) + AC_DEFINE(MOO_USE_GAMIN, 1, [whether libfam is provided by gamin]) + ],[],[#include ]) + + MOO_FAM_CFLAGS="$FAM_CFLAGS" + MOO_FAM_LIBS="$FAM_LIBS" + ifelse([$1], , :, [$1]) + else + unset FAM_CFLAGS + unset FAM_LIBS + MOO_FAM_LIBS= + MOO_FAM_CFLAGS= + ifelse([$2], , [AC_MSG_ERROR(libfam not found)], [$2]) + fi + + AC_SUBST(MOO_FAM_CFLAGS) + AC_SUBST(MOO_FAM_LIBS) + CFLAGS="$moo_ac_save_CFLAGS" + LIBS="$moo_ac_save_LIBS" +]) + +AC_DEFUN_ONCE([MOO_AC_FAM],[ + if $MOO_OS_UNIX; then + AC_ARG_WITH([fam], AC_HELP_STRING([--with-fam], [whether to use fam or gamin for monitoring files in the editor (default = NO)]), [ + if test x$with_fam = "xyes"; then + MOO_USE_FAM="yes" + else + MOO_USE_FAM="no" + fi + ],[ + MOO_USE_FAM="no" + ]) + + if test "$MOO_USE_FAM" = "yes"; then + _MOO_AC_CHECK_FAM([moo_has_fam=yes],[moo_has_fam=no]) + if test x$moo_has_fam = xyes; then + MOO_USE_FAM="yes" + AC_DEFINE(MOO_USE_FAM, 1, [use libfam for monitoring files]) + else + AC_MSG_ERROR([FAM or gamin not found.]) + fi + fi + fi + + AM_CONDITIONAL(MOO_USE_FAM, test x$MOO_USE_FAM = "xyes") +]) + +############################################################################## +# MOO_AC_FLAGS(moo_top_dir) +# +AC_DEFUN_ONCE([MOO_AC_FLAGS],[ + AC_REQUIRE([MOO_AC_CHECK_OS]) + AC_REQUIRE([MOO_AC_SET_DIRS]) + + MOO_PKG_CHECK_GTK_VERSIONS + MOO_AC_DEBUG + MOO_AC_FAM + + AC_CHECK_FUNCS_ONCE(getc_unlocked) + AC_CHECK_HEADERS(unistd.h sys/utsname.h signal.h) + + if $MOO_OS_WIN32; then + AC_DEFINE(HAVE_MMAP, 1, [Fake mmap on win32]) + else + AC_CHECK_FUNCS(mmap) + fi + + AC_DEFINE(MOO_COMPILATION, 1, [must be 1]) + + moo_top_src_dir=`cd $srcdir && pwd` + MOO_CFLAGS="$MOO_CFLAGS $GTK_CFLAGS" + MOO_CXXFLAGS="$MOO_CXXFLAGS $GTK_CFLAGS" + MOO_CPPFLAGS="$MOO_CPPFLAGS -I$moo_top_src_dir/moo -DXDG_PREFIX=_moo_edit_xdg -DG_LOG_DOMAIN=\\\"Moo\\\"" + MOO_LIBS="$MOO_LIBS $GTK_LIBS $GTHREAD_LIBS $LIBM" + + if $GDK_X11; then + _moo_x_pkgs= + m4_foreach([_pkg_],[x11, xext, xrender, ice, sm],[ + PKG_CHECK_EXISTS(_pkg_,[_moo_x_pkgs="$_moo_x_pkgs _pkg_"],[:]) + ]) + if test -n "$_moo_x_pkgs"; then + PKG_CHECK_MODULES(X,[$_moo_x_pkgs]) + MOO_CFLAGS="$MOO_CFLAGS $X_CFLAGS" + MOO_CXXFLAGS="$MOO_CXXFLAGS $X_CFLAGS" + MOO_LIBS="$MOO_LIBS $X_LIBS" + fi + fi + + if $MOO_OS_WIN32; then + MOO_CPPFLAGS="$MOO_CPPFLAGS -DUNICODE -D_UNICODE -DSTRICT -DWIN32_LEAN_AND_MEAN -I$moo_top_src_dir/moo/mooutils/moowin32/mingw" + + # work around bug in i586-mingw32msvc-gcc-4.2.1-sjlj + # it defines __STRICT_ANSI__ for some reason and that + # breaks compilation: + # /usr/lib/gcc/i586-mingw32msvc/4.2.1-sjlj/include/c++/cwchar:164: error: ‘::swprintf’ has not been declared + # /usr/lib/gcc/i586-mingw32msvc/4.2.1-sjlj/include/c++/cwchar:171: error: ‘::vswprintf’ has not been declared + MOO_CPPFLAGS="$MOO_CPPFLAGS -U__STRICT_ANSI__" + fi + + if test x$MOO_USE_FAM = xyes; then + MOO_CFLAGS="$MOO_CFLAGS $MOO_FAM_CFLAGS" + MOO_CXXFLAGS="$MOO_CXXFLAGS $MOO_FAM_CFLAGS" + MOO_LIBS="$MOO_LIBS $MOO_FAM_LIBS" + fi + + if $MOO_OS_UNIX; then + MOO_CPPFLAGS="$MOO_CPPFLAGS -DMOO_DATA_DIR=\\\"${MOO_DATA_DIR}\\\" -DMOO_LIB_DIR=\\\"${MOO_LIB_DIR}\\\"" + MOO_CPPFLAGS="$MOO_CPPFLAGS -DMOO_LOCALE_DIR=\\\"${localedir}\\\" -DMOO_HELP_DIR=\\\"${MOO_HELP_DIR}\\\"" + fi + + MOO_CFLAGS="$MOO_CFLAGS $XML_CFLAGS" + MOO_CXXFLAGS="$MOO_CXXFLAGS $XML_CFLAGS" + MOO_LIBS="$MOO_LIBS $XML_LIBS" + + AC_SUBST(MOO_CPPFLAGS) + AC_SUBST(MOO_CFLAGS) + AC_SUBST(MOO_CXXFLAGS) + AC_SUBST(MOO_LIBS) + +# MOO_INI_IN_IN_RULE='%.ini.desktop.in: %.ini.desktop.in.in $(top_builddir)/config.status ; cd $(top_builddir) && $(SHELL) ./config.status --file=$(subdir)/[$]@' +# MOO_INI_IN_RULE='%.ini: %.ini.in $(top_builddir)/config.status ; cd $(top_builddir) && $(SHELL) ./config.status --file=$(subdir)/[$]@' +# MOO_WIN32_RC_RULE='%.res: %.rc.in $(top_builddir)/config.status ; cd $(top_builddir) && $(SHELL) ./config.status --file=$(subdir)/[$]*.rc && cd $(subdir) && $(WINDRES) -i [$]*.rc --input-format=rc -o [$]@ -O coff && rm [$]*.rc' +# AC_SUBST(MOO_INI_IN_IN_RULE) +# AC_SUBST(MOO_INI_IN_RULE) +# AC_SUBST(MOO_WIN32_RC_RULE) + +# MOO_XML2H='$(top_srcdir)/moo/mooutils/xml2h.sh' +# MOO_GLADE_SUBDIR_RULE='%-glade.h: glade/%.glade $(MOO_XML2H) ; $(SHELL) $(top_srcdir)/moo/mooutils/xml2h.sh `basename "[$]*" | sed -e "s/-/_/"`_glade_xml [$]< > [$]@.tmp && mv [$]@.tmp [$]@' +# MOO_GLADE_RULE='%-glade.h: %.glade $(MOO_XML2H) ; $(SHELL) $(top_srcdir)/moo/mooutils/xml2h.sh `basename "[$]*" | sed -e "s/-/_/"`_glade_xml [$]< > [$]@.tmp && mv [$]@.tmp [$]@' +# AC_SUBST(MOO_XML2H) +# AC_SUBST(MOO_GLADE_SUBDIR_RULE) +# AC_SUBST(MOO_GLADE_RULE) +]) diff --git a/m4/moo-gtk.m4 b/m4/moo-gtk.m4 new file mode 100644 index 00000000..5ee4cb3c --- /dev/null +++ b/m4/moo-gtk.m4 @@ -0,0 +1,96 @@ +############################################################################## +# _MOO_SPLIT_VERSION(NAME,version) +# +AC_DEFUN([_MOO_SPLIT_VERSION],[AC_REQUIRE([LT_AC_PROG_SED]) +$1[]_VERSION="$2" +$1[]_MAJOR_VERSION=`echo "$2" | $SED 's/\([[^.]][[^.]]*\).*/\1/'` +$1[]_MINOR_VERSION=`echo "$2" | $SED 's/[[^.]][[^.]]*.\([[^.]][[^.]]*\).*/\1/'` +$1[]_MICRO_VERSION=`echo "$2" | $SED 's/[[^.]][[^.]]*.[[^.]][[^.]]*.\(.*\)/\1/'` +]) + +############################################################################## +# _MOO_SPLIT_VERSION_PKG(PKG_NAME,pkg-name) +# +AC_DEFUN([_MOO_SPLIT_VERSION_PKG],[ +AC_MSG_CHECKING($1 version) +_moo_ac_version=`$PKG_CONFIG --modversion $2` +_MOO_SPLIT_VERSION([$1],[$_moo_ac_version]) +AC_MSG_RESULT($[]$1[]_MAJOR_VERSION.$[]$1[]_MINOR_VERSION.$[]$1[]_MICRO_VERSION) +]) + + +############################################################################## +# MOO_CHECK_VERSION(PKG_NAME,pkg-name) +# +AC_DEFUN([MOO_CHECK_VERSION],[ +PKG_CHECK_MODULES($1,$2) +# _MOO_SPLIT_VERSION_PKG($1,$2) +# m4_foreach([num],[2,4,6,8,10,12,14], +# [AM_CONDITIONAL($1[]_2_[]num, test $[]$1[]_MINOR_VERSION -ge num) +# if test $[]$1[]_MINOR_VERSION -ge num; then +# $1[]_2_[]num=yes +# fi +# ]) +]) + + +############################################################################## +# _MOO_CHECK_BROKEN_GTK_THEME +# +AC_DEFUN([_MOO_CHECK_BROKEN_GTK_THEME],[ +AC_ARG_WITH([broken-gtk-theme], AC_HELP_STRING([--with-broken-gtk-theme], [Work around bug in gtk theme (Suse 9 has one)]), [ + if test x$with_broken_gtk_theme = "xyes"; then + MOO_BROKEN_GTK_THEME="yes" + fi +]) + +if test x$MOO_BROKEN_GTK_THEME = xyes; then + AC_MSG_NOTICE([Broken gtk theme]) + AC_DEFINE(MOO_BROKEN_GTK_THEME, 1, [broken gtk theme]) +fi +]) + + +############################################################################## +# MOO_PKG_CHECK_GTK_VERSIONS +# +AC_DEFUN_ONCE([MOO_PKG_CHECK_GTK_VERSIONS],[ +AC_REQUIRE([MOO_AC_CHECK_OS]) +MOO_CHECK_VERSION(GTK, gtk+-2.0) +MOO_CHECK_VERSION(GLIB, glib-2.0) +MOO_CHECK_VERSION(GTHREAD, gthread-2.0) +# MOO_CHECK_VERSION(GDK, gdk-2.0) + +MOO_CHECK_VERSION(XML, libxml-2.0) + +_MOO_CHECK_BROKEN_GTK_THEME + +gdk_target=`$PKG_CONFIG --variable=target gdk-2.0` + +GDK_X11=false +GDK_WIN32=false +GDK_QUARTZ=false + +case $gdk_target in +x11) + GDK_X11=true + ;; +quartz) + GDK_QUARTZ=true + ;; +win32) + GDK_WIN32=true + ;; +esac + +AM_CONDITIONAL(GDK_X11, $GDK_X11) +AM_CONDITIONAL(GDK_WIN32, $GDK_WIN32) +AM_CONDITIONAL(GDK_QUARTZ, $GDK_QUARTZ) + +AC_SUBST(GLIB_GENMARSHAL, `$PKG_CONFIG --variable=glib_genmarshal glib-2.0`) +AC_SUBST(GLIB_MKENUMS, `$PKG_CONFIG --variable=glib_mkenums glib-2.0`) + +AC_ARG_VAR([GDK_PIXBUF_CSOURCE], [gdk-pixbuf-csource]) +AC_CHECK_TOOL(GDK_PIXBUF_CSOURCE, gdk-pixbuf-csource, [AC_MSG_ERROR([gdk-pixbuf-csource not found])]) + +]) diff --git a/m4/moo-intltool.m4 b/m4/moo-intltool.m4 new file mode 100644 index 00000000..0b761d86 --- /dev/null +++ b/m4/moo-intltool.m4 @@ -0,0 +1,31 @@ +AC_DEFUN([_MOO_INTLTOOL],[ + AC_PATH_PROG(INTLTOOL_UPDATE, [intltool-update]) + AC_PATH_PROG(INTLTOOL_MERGE, [intltool-merge]) + AC_PATH_PROG(INTLTOOL_EXTRACT, [intltool-extract]) + if test -z "$INTLTOOL_UPDATE" -o -z "$INTLTOOL_MERGE" -o -z "$INTLTOOL_EXTRACT"; then + AC_MSG_ERROR([The intltool scripts were not found. Please install intltool or use --disable-nls to ignore.]) + fi + AC_SUBST(MOO_INTLTOOL_INI_DEPS,'$(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po)') + AC_SUBST(MOO_INTLTOOL_INI_CMD,'$(AM''_V_GEN)LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@') +]) + +AC_DEFUN([_MOO_INTLTOOL_NO_NLS],[ + AC_SUBST(MOO_INTLTOOL_INI_DEPS,'') + AC_SUBST(MOO_INTLTOOL_INI_CMD,'$(AM''_V_GEN)sed -e "s/^_//g" $< > [$]@.tmp && mv [$]@.tmp [$]@') +]) + +AC_DEFUN([MOO_INTL],[ + AM_GLIB_GNU_GETTEXT + AC_ARG_ENABLE([nls],AC_HELP_STRING([--disable-nls],[do not use Native Language Support]),[ + ENABLE_NLS=$enableval + ],[ + ENABLE_NLS=yes + ]) + AC_SUBST([ENABLE_NLS]) + if test "$ENABLE_NLS" = "yes"; then + _MOO_INTLTOOL + else + _MOO_INTLTOOL_NO_NLS + fi + AC_SUBST(MOO_PO_SUBDIRS_RULE,'$(top_srcdir)/po-gsv/Makefile.am: $(top_srcdir)/po/Makefile.am ; sed -e "s/GETTEXT_PACKAGE/GETTEXT_PACKAGE_GSV/g" $(top_srcdir)/po/Makefile.am > $(top_srcdir)/po-gsv/Makefile.am.tmp && mv $(top_srcdir)/po-gsv/Makefile.am.tmp $(top_srcdir)/po-gsv/Makefile.am') +]) diff --git a/m4/moo-os.m4 b/m4/moo-os.m4 new file mode 100644 index 00000000..e8d77b99 --- /dev/null +++ b/m4/moo-os.m4 @@ -0,0 +1,64 @@ +AC_DEFUN([MOO_AC_CHECK_OS],[ +AC_REQUIRE([AC_CANONICAL_HOST]) + + m4_define([_moo_oses_],[CYGWIN WIN32 MINGW DARWIN UNIX FREEBSD BSD LINUX FDO]) + + m4_foreach_w([_moo_os_],_moo_oses_,[dnl +MOO_OS_[]_moo_os_=false +]) + + case $host in + *-*-mingw32*) + MOO_OS_WIN32=true + MOO_OS_NAME="Win32" + ;; + *-*-cygwin*) + MOO_OS_CYGWIN=true + MOO_OS_NAME="CygWin" + ;; + *-*-darwin*) + MOO_OS_DARWIN=true + MOO_OS_NAME="Darwin" + ;; + *-*-freebsd*) + MOO_OS_FREEBSD=true + MOO_OS_NAME="FreeBSD" + ;; + *-*-linux*) + MOO_OS_LINUX=true + MOO_OS_NAME="Linux" + ;; + *) + MOO_OS_UNIX=true + MOO_OS_NAME="Unix" + ;; + esac + + if $MOO_OS_WIN32; then : ; else MOO_OS_UNIX=true; fi + if $MOO_OS_DARWIN; then MOO_OS_BSD=true; fi + if $MOO_OS_FREEBSD; then MOO_OS_BSD=true; fi + + m4_foreach_w([_moo_os_],_moo_oses_,[dnl +AM_CONDITIONAL(MOO_OS_[]_moo_os_,[$MOO_OS_[]_moo_os_]) +]) + +]) + +# LT_LIB_M macro from libtool.m4 +AC_DEFUN([MOO_LT_LIB_M], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cygwin* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM="-lmw") + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM="-lm") + ;; +esac +AC_SUBST([LIBM]) +])# LT_LIB_M diff --git a/m4/moo-pygtk.m4 b/m4/moo-pygtk.m4 new file mode 100644 index 00000000..9c93d2f8 --- /dev/null +++ b/m4/moo-pygtk.m4 @@ -0,0 +1,57 @@ +############################################################################## +# MOO_AC_PYTHON() +# +AC_DEFUN_ONCE([MOO_AC_PYTHON],[ + AC_REQUIRE([MOO_AC_CHECK_OS]) + + MOO_ENABLE_PYTHON=true + _moo_want_python="auto" + _moo_python_version=2.2 + + AC_ARG_WITH([python],AC_HELP_STRING([--with-python], [whether to compile python support (default = YES)]),[ + if test "x$with_python" = "xno"; then + MOO_ENABLE_PYTHON=false + elif test "x$with_python" = "xyes"; then + _moo_want_python="yes" + _moo_python_version="2.2" + else + _moo_want_python="yes" + _moo_python_version="$with_python" + fi + ]) + + if $MOO_ENABLE_PYTHON; then + MOO_ENABLE_PYTHON=false + MOO_AC_CHECK_PYTHON($_moo_python_version,[ + PKG_CHECK_MODULES(PYGTK,pygtk-2.0 >= 2.6.0,[ + MOO_ENABLE_PYTHON=true + ],[:]) + ]) + + if $MOO_ENABLE_PYTHON; then + AC_SUBST([PYGTK_DEFS_DIR],[`$PKG_CONFIG --variable=defsdir pygtk-2.0`]) + AC_SUBST([PYGOBJECT_DEFS_DIR],[`$PKG_CONFIG --variable=defsdir pygobject-2.0`]) + fi + + if $MOO_ENABLE_PYTHON; then + AC_MSG_NOTICE([compiling python support]) + elif test "x$_moo_want_python" = "xyes"; then + AC_MSG_ERROR([python support requested but python cannot be used]) + elif test "x$_moo_want_python" = "xauto"; then + AC_MSG_WARN([disabled python support]) + else + AC_MSG_NOTICE([disabled python support]) + fi + fi + + AM_CONDITIONAL(MOO_ENABLE_PYTHON, $MOO_ENABLE_PYTHON) + if $MOO_ENABLE_PYTHON; then + AC_DEFINE(MOO_ENABLE_PYTHON, 1, [build python bindings and plugin]) + fi + + if $MOO_ENABLE_PYTHON; then + MOO_CFLAGS="$MOO_CFLAGS $PYGTK_CFLAGS $PYTHON_INCLUDES" + MOO_CXXFLAGS="$MOO_CXXFLAGS $PYGTK_CFLAGS $PYTHON_INCLUDES" + MOO_LIBS="$MOO_LIBS $PYGTK_LIBS $PYTHON_LIBS" + fi +]) diff --git a/m4/moo-python.m4 b/m4/moo-python.m4 new file mode 100644 index 00000000..97a2239a --- /dev/null +++ b/m4/moo-python.m4 @@ -0,0 +1,146 @@ +############################################################################## +# _MOO_AC_PYTHON_DEVEL(action-if-found,action-if-not-found) +# checks python headers and libs. it's +# http://www.gnu.org/software/ac-archive/htmldoc/ac_python_devel.html, +# modified to allow actions if-found/if-not-found +# +AC_DEFUN([_MOO_AC_PYTHON_DEVEL],[ + python_found=no + + if test "$cross_compiling" = yes; then + test -z "$PYTHON_INCLUDES" || python_found=yes + else + # Check for distutils first + AC_MSG_CHECKING([for the distutils Python package]) + $PYTHON -c "import distutils" 2>/dev/null + if test $? -eq 0; then + python_found=yes + AC_MSG_RESULT([yes]) + else + python_found=no + AC_MSG_RESULT([no]) + AC_MSG_WARN([cannot import Python module "distutils". +Please check your Python installation.]) + fi + fi + + # Check for Python include path + # if PYTHON_INCLUDES is set, do not do anything + if test $python_found = yes; then + AC_MSG_CHECKING([for Python include path]) + + if test -z "$PYTHON_INCLUDES"; then + python_path=`$PYTHON -c "import distutils.sysconfig; \ + print distutils.sysconfig.get_python_inc();"` + if test -n "${python_path}"; then + python_path="-I$python_path" + fi + PYTHON_INCLUDES=$python_path + fi + + AC_MSG_RESULT([$PYTHON_INCLUDES]) + AC_SUBST([PYTHON_INCLUDES]) + fi + + # Check for Python linker flags + # if PYTHON_LIBS is set, do not do anything + if test $python_found = yes; then + AC_MSG_CHECKING([Python linker flags]) + + if test "x$PYTHON_LIBS" = "x"; then + # (makes two attempts to ensure we've got a version number + # from the interpreter) + py_version=`$PYTHON -c "from distutils.sysconfig import *; \ + from string import join; \ + print join(get_config_vars('VERSION'))"` + if test "x$py_version" = "x[None]"; then + if test "x$PYTHON_VERSION" != "x"; then + py_version=$PYTHON_VERSION + else + py_version=`$PYTHON -c "import sys; \ + print sys.version[[:3]]"` + fi + fi + + PYTHON_LIBS=`$PYTHON -c "from distutils.sysconfig import *; \ + from string import join; \ + print '-L' + PREFIX + '/lib', \ + '-lpython';"`$py_version + fi + + AC_MSG_RESULT([$PYTHON_LIBS]) + AC_SUBST([PYTHON_LIBS]) + fi + + if test $python_found = yes; then + m4_if([$1],[],[:],[$1]) + else + m4_if([$2],[],[:],[$2]) + fi +]) + + +############################################################################## +# MOO_AC_CHECK_PYTHON_NATIVE(min-version,action-if-found,action-if-not-found) +# checks python stuff when building for unix +# +AC_DEFUN([MOO_AC_CHECK_PYTHON_NATIVE],[ + AM_PATH_PYTHON([$1],[ + _MOO_AC_PYTHON_DEVEL([ + python_found=yes + ],[ + AC_MSG_WARN([Found python interpreter but no development headers or libraries]) + python_found=no + ]) + ],[ + python_found=no + ]) + + if test x$python_found = xyes; then + m4_if([$2],[],[:],[$2]) + else + PYTHON_INCLUDES="" + PYTHON_LIBS="" + m4_if([$3],[],[:],[$3]) + fi +]) + + +AC_DEFUN([MOO_AM_PYTHON_DEVEL_CROSS_MINGW],[ + if test x"$PYTHON_INCLUDES" = x -o x"$PYTHON_LIBS" = x -o x"$PYTHON_VERSION" = x; then + AC_MSG_ERROR([The following variables must be set: PYTHON_INCLUDES, PYTHON_LIBS, PYTHON_VERSION]) + fi + AC_ARG_VAR([PYTHON_INCLUDES], [python preprocessor flags]) + AC_ARG_VAR([PYTHON_LIBS], [python linker flags]) + AC_ARG_VAR([PYTHON_VERSION], [python version]) + + AC_SUBST(PYTHON_INCLUDES) + AC_SUBST(PYTHON_LIBS) + AC_MSG_CHECKING([for Python include path]) + AC_MSG_RESULT([$PYTHON_INCLUDES]) + AC_MSG_CHECKING([for Python linker flags]) + AC_MSG_RESULT([$PYTHON_LIBS]) + + AC_SUBST([PYTHON_VERSION],[$PYTHON_VERSION]) + AC_SUBST([PYTHON_PREFIX], ['${prefix}']) + AC_SUBST([PYTHON_EXEC_PREFIX], ['${exec_prefix}']) + AC_SUBST([PYTHON_PLATFORM], [nt]) + AC_SUBST([pythondir], [$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages]) + AC_SUBST([pyexecdir], [$PYTHON_EXEC_PREFIX/lib/python$PYTHON_VERSION/site-packages]) + + $1 +]) + + +############################################################################## +# MOO_AC_CHECK_PYTHON(min-version,action-if-found,action-if-not-found) +# checks for python, python includes and libs +# +AC_DEFUN([MOO_AC_CHECK_PYTHON],[ + AC_REQUIRE([MOO_AC_CHECK_OS]) + if test "$cross_compiling" = yes -a "$MOO_OS_WIN32" = true; then + MOO_AM_PYTHON_DEVEL_CROSS_MINGW([$2],[$3]) + else + MOO_AC_CHECK_PYTHON_NATIVE([$1],[$2],[$3]) + fi +]) diff --git a/moo/Makefile.am b/moo/Makefile.am new file mode 100644 index 00000000..dbe5f4ac --- /dev/null +++ b/moo/Makefile.am @@ -0,0 +1,85 @@ +EXTRA_DIST = +BUILT_SOURCES = +CLEANFILES = +bin_PROGRAMS = +noinst_LIBRARIES = + +AM_CPPFLAGS = $(MOO_CPPFLAGS) -I$(top_srcdir)/doc/built -Imooutils/glade +AM_CFLAGS = $(MOO_CFLAGS) +AM_CXXFLAGS = $(MOO_CXXFLAGS) + +moo_sources = +built_moo_sources = +moo_libadd = + +plugins_sources = +built_plugins_sources = + +moo_srcdir = $(srcdir) +moo_builddir = . + +moo_sources += moo-config.h + +EXTRA_DIST += marshals.list +built_moo_sources += marshals.h +marshals.h: marshals.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=_moo_marshal --header $(srcdir)/marshals.list > marshals.h.tmp \ + && mv marshals.h.tmp marshals.h + +include mooedit/Makefile.incl +include xdgmime/Makefile.incl +include mooutils/Makefile.incl +include moofileview/Makefile.incl +include gtksourceview/Makefile.incl +include eggsmclient/Makefile.incl +include mooapp/Makefile.incl +include moolua/Makefile.incl +include moopython/Makefile.incl +include plugins/Makefile.incl +include medit-app/Makefile.incl +include medit-module/Makefile.incl + +BUILT_SOURCES += $(built_moo_sources) $(built_plugins_sources) +CLEANFILES += $(built_moo_sources) $(built_plugins_sources) + +test: + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) test + +# glade/%-gxml.h: glade/%.glade $(top_srcdir)/tools/glade2c.py +# $(MKDIR_P) glade +# $(PYTHON) $(top_srcdir)/tools/glade2c.py $< > $@.tmp && mv $@.tmp $@ + +mooutils/%-gxml.h: mooutils/glade/%.glade $(top_srcdir)/tools/glade2c.py + $(AM_V_at) $(MKDIR_P) `dirname $@` + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glade2c.py $< > $@.tmp && mv $@.tmp $@ + +moofileview/%-gxml.h: moofileview/glade/%.glade $(top_srcdir)/tools/glade2c.py + $(AM_V_at) $(MKDIR_P) `dirname $@` + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glade2c.py $< > $@.tmp && mv $@.tmp $@ + +mooedit/%-gxml.h: mooedit/glade/%.glade $(top_srcdir)/tools/glade2c.py + $(AM_V_at) $(MKDIR_P) `dirname $@` + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glade2c.py $< > $@.tmp && mv $@.tmp $@ + +mooapp/%-gxml.h: mooapp/glade/%.glade $(top_srcdir)/tools/glade2c.py + $(AM_V_at) $(MKDIR_P) `dirname $@` + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glade2c.py $< > $@.tmp && mv $@.tmp $@ + +plugins/%-gxml.h: plugins/glade/%.glade $(top_srcdir)/tools/glade2c.py + $(AM_V_at) $(MKDIR_P) `dirname $@` + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glade2c.py $< > $@.tmp && mv $@.tmp $@ + +plugins/usertools/%-gxml.h: plugins/usertools/glade/%.glade $(top_srcdir)/tools/glade2c.py + $(AM_V_at) $(MKDIR_P) `dirname $@` + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glade2c.py $< > $@.tmp && mv $@.tmp $@ + +# %-ui.h: %.xml $(top_srcdir)/tools/xml2h.py +# $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/xml2h.py $< $@.tmp $*_ui_xml && mv $@.tmp $@ + +moofileview/%-ui.h: moofileview/%.xml $(top_srcdir)/tools/xml2h.py + $(AM_V_at) $(MKDIR_P) `dirname $@` + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/xml2h.py $< $@.tmp $*_ui_xml && mv $@.tmp $@ + +mooedit/%-ui.h: mooedit/%.xml $(top_srcdir)/tools/xml2h.py + $(AM_V_at) $(MKDIR_P) `dirname $@` + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/xml2h.py $< $@.tmp $*_ui_xml && mv $@.tmp $@ diff --git a/moo/eggsmclient/Makefile.incl b/moo/eggsmclient/Makefile.incl new file mode 100644 index 00000000..2e5b23d7 --- /dev/null +++ b/moo/eggsmclient/Makefile.incl @@ -0,0 +1,18 @@ +moo_sources += \ + eggsmclient/eggsmclient.c \ + eggsmclient/eggsmclient.h \ + eggsmclient/eggsmclient-mangle.h \ + eggsmclient/eggsmclient-private.h + +if MOO_OS_WIN32 +moo_sources += eggsmclient/eggsmclient-win32.c +else !MOO_OS_WIN32 +if MOO_OS_DARWIN +moo_sources += eggsmclient/eggsmclient-dummy.c +else !MOO_OS_DARWIN +AM_CFLAGS += -DEGG_SM_CLIENT_BACKEND_XSMP +moo_sources += eggsmclient/eggsmclient-xsmp.c eggsmclient/eggdesktopfile.h eggsmclient/eggdesktopfile.c +endif !MOO_OS_DARWIN +endif !MOO_OS_WIN32 + +# -%- strip:true -%- diff --git a/moo/eggsmclient/eggdesktopfile.c b/moo/eggsmclient/eggdesktopfile.c new file mode 100644 index 00000000..c524ed35 --- /dev/null +++ b/moo/eggsmclient/eggdesktopfile.c @@ -0,0 +1,1520 @@ +/* eggdesktopfile.c - Freedesktop.Org Desktop Files + * Copyright (C) 2007 Novell, Inc. + * + * Based on gnome-desktop-item.c + * Copyright (C) 1999, 2000 Red Hat Inc. + * Copyright (C) 2001 George Lebl + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - + * Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eggdesktopfile.h" + +#include +#include + +#include +#include +#include + +struct EggDesktopFile { + GKeyFile *key_file; + char *source; + + char *name, *icon; + EggDesktopFileType type; + char document_code; +}; + +/** + * egg_desktop_file_new: + * @desktop_file_path: path to a Freedesktop-style Desktop file + * @error: error pointer + * + * Creates a new #EggDesktopFile for @desktop_file. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new (const char *desktop_file_path, GError **error) +{ + GKeyFile *key_file; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, desktop_file_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + return egg_desktop_file_new_from_key_file (key_file, desktop_file_path, + error); +} + +/** + * egg_desktop_file_new_from_data_dirs: + * @desktop_file_path: relative path to a Freedesktop-style Desktop file + * @error: error pointer + * + * Looks for @desktop_file_path in the paths returned from + * g_get_user_data_dir() and g_get_system_data_dirs(), and creates + * a new #EggDesktopFile from it. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, + GError **error) +{ + EggDesktopFile *desktop_file; + GKeyFile *key_file; + char *full_path; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_data_dirs (key_file, desktop_file_path, + &full_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + desktop_file = egg_desktop_file_new_from_key_file (key_file, + full_path, + error); + g_free (full_path); + return desktop_file; +} + +/** + * egg_desktop_file_new_from_dirs: + * @desktop_file_path: relative path to a Freedesktop-style Desktop file + * @search_dirs: NULL-terminated array of directories to search + * @error: error pointer + * + * Looks for @desktop_file_path in the paths returned from + * g_get_user_data_dir() and g_get_system_data_dirs(), and creates + * a new #EggDesktopFile from it. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_dirs (const char *desktop_file_path, + const char **search_dirs, + GError **error) +{ + EggDesktopFile *desktop_file; + GKeyFile *key_file; + char *full_path; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_dirs (key_file, desktop_file_path, search_dirs, + &full_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + desktop_file = egg_desktop_file_new_from_key_file (key_file, + full_path, + error); + g_free (full_path); + return desktop_file; +} + +/** + * egg_desktop_file_new_from_key_file: + * @key_file: a #GKeyFile representing a desktop file + * @source: the path or URI that @key_file was loaded from, or %NULL + * @error: error pointer + * + * Creates a new #EggDesktopFile for @key_file. Assumes ownership of + * @key_file (on success or failure); you should consider @key_file to + * be freed after calling this function. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_key_file (GKeyFile *key_file, + const char *source, + GError **error) +{ + EggDesktopFile *desktop_file; + char *version, *type; + + if (!g_key_file_has_group (key_file, EGG_DESKTOP_FILE_GROUP)) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_INVALID, + _("File is not a valid .desktop file")); + g_key_file_free (key_file); + return NULL; + } + + version = g_key_file_get_value (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_VERSION, + NULL); + if (version) + { + double version_num; + char *end; + + version_num = g_ascii_strtod (version, &end); + if (*end) + { + g_warning ("Invalid Version string '%s' in %s", + version, source ? source : "(unknown)"); + } + else if (version_num > 1.0) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_INVALID, + _("Unrecognized desktop file Version '%s'"), version); + g_free (version); + g_key_file_free (key_file); + return NULL; + } + g_free (version); + } + + desktop_file = g_new0 (EggDesktopFile, 1); + desktop_file->key_file = key_file; + + if (g_path_is_absolute (source)) + desktop_file->source = g_filename_to_uri (source, NULL, NULL); + else + desktop_file->source = g_strdup (source); + + desktop_file->name = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_NAME, error); + if (!desktop_file->name) + { + egg_desktop_file_free (desktop_file); + return NULL; + } + + type = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TYPE, error); + if (!type) + { + egg_desktop_file_free (desktop_file); + return NULL; + } + + if (!strcmp (type, "Application")) + { + char *exec, *p; + + desktop_file->type = EGG_DESKTOP_FILE_TYPE_APPLICATION; + + exec = g_key_file_get_string (key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + error); + if (!exec) + { + egg_desktop_file_free (desktop_file); + g_free (type); + return NULL; + } + + /* See if it takes paths or URIs or neither */ + for (p = exec; *p; p++) + { + if (*p == '%') + { + if (p[1] == '\0' || strchr ("FfUu", p[1])) + { + desktop_file->document_code = p[1]; + break; + } + p++; + } + } + + g_free (exec); + } + else if (!strcmp (type, "Link")) + { + char *url; + + desktop_file->type = EGG_DESKTOP_FILE_TYPE_LINK; + + url = g_key_file_get_string (key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_URL, + error); + if (!url) + { + egg_desktop_file_free (desktop_file); + g_free (type); + return NULL; + } + g_free (url); + } + else if (!strcmp (type, "Directory")) + desktop_file->type = EGG_DESKTOP_FILE_TYPE_DIRECTORY; + else + desktop_file->type = EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED; + + g_free (type); + + /* Check the Icon key */ + desktop_file->icon = g_key_file_get_string (key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_ICON, + NULL); + if (desktop_file->icon && !g_path_is_absolute (desktop_file->icon)) + { + char *ext; + + /* Lots of .desktop files still get this wrong */ + ext = strrchr (desktop_file->icon, '.'); + if (ext && (!strcmp (ext, ".png") || + !strcmp (ext, ".xpm") || + !strcmp (ext, ".svg"))) + { + g_warning ("Desktop file '%s' has malformed Icon key '%s'" + "(should not include extension)", + source ? source : "(unknown)", + desktop_file->icon); + *ext = '\0'; + } + } + + return desktop_file; +} + +/** + * egg_desktop_file_free: + * @desktop_file: an #EggDesktopFile + * + * Frees @desktop_file. + **/ +void +egg_desktop_file_free (EggDesktopFile *desktop_file) +{ + g_key_file_free (desktop_file->key_file); + g_free (desktop_file->source); + g_free (desktop_file->name); + g_free (desktop_file->icon); + g_free (desktop_file); +} + +/** + * egg_desktop_file_get_source: + * @desktop_file: an #EggDesktopFile + * + * Gets the URI that @desktop_file was loaded from. + * + * Return value: @desktop_file's source URI + **/ +const char * +egg_desktop_file_get_source (EggDesktopFile *desktop_file) +{ + return desktop_file->source; +} + +/** + * egg_desktop_file_get_desktop_file_type: + * @desktop_file: an #EggDesktopFile + * + * Gets the desktop file type of @desktop_file. + * + * Return value: @desktop_file's type + **/ +EggDesktopFileType +egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file) +{ + return desktop_file->type; +} + +/** + * egg_desktop_file_get_name: + * @desktop_file: an #EggDesktopFile + * + * Gets the (localized) value of @desktop_file's "Name" key. + * + * Return value: the application/link name + **/ +const char * +egg_desktop_file_get_name (EggDesktopFile *desktop_file) +{ + return desktop_file->name; +} + +/** + * egg_desktop_file_get_icon: + * @desktop_file: an #EggDesktopFile + * + * Gets the value of @desktop_file's "Icon" key. + * + * If the icon string is a full path (that is, if g_path_is_absolute() + * returns %TRUE when called on it), it points to a file containing an + * unthemed icon. If the icon string is not a full path, it is the + * name of a themed icon, which can be looked up with %GtkIconTheme, + * or passed directly to a theme-aware widget like %GtkImage or + * %GtkCellRendererPixbuf. + * + * Return value: the icon path or name + **/ +const char * +egg_desktop_file_get_icon (EggDesktopFile *desktop_file) +{ + return desktop_file->icon; +} + +gboolean +egg_desktop_file_has_key (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char * +egg_desktop_file_get_string (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char * +egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + GError **error) +{ + return g_key_file_get_locale_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, locale, + error); +} + +gboolean +egg_desktop_file_get_boolean (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +double +egg_desktop_file_get_numeric (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_double (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +int +egg_desktop_file_get_integer (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_integer (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char ** +egg_desktop_file_get_string_list (EggDesktopFile *desktop_file, + const char *key, + gsize *length, + GError **error) +{ + return g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, length, + error); +} + +char ** +egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + gsize *length, + GError **error) +{ + return g_key_file_get_locale_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + locale, length, + error); +} + +/** + * egg_desktop_file_can_launch: + * @desktop_file: an #EggDesktopFile + * @desktop_environment: the name of the running desktop environment, + * or %NULL + * + * Tests if @desktop_file can/should be launched in the current + * environment. If @desktop_environment is non-%NULL, @desktop_file's + * "OnlyShowIn" and "NotShowIn" keys are checked to make sure that + * this desktop_file is appropriate for the named environment. + * + * Furthermore, if @desktop_file has type + * %EGG_DESKTOP_FILE_TYPE_APPLICATION, its "TryExec" key (if any) is + * also checked, to make sure the binary it points to exists. + * + * egg_desktop_file_can_launch() does NOT check the value of the + * "Hidden" key. + * + * Return value: %TRUE if @desktop_file can be launched + **/ +gboolean +egg_desktop_file_can_launch (EggDesktopFile *desktop_file, + const char *desktop_environment) +{ + char *try_exec, *found_program; + char **only_show_in, **not_show_in; + gboolean found; + int i; + + if (desktop_file->type != EGG_DESKTOP_FILE_TYPE_APPLICATION && + desktop_file->type != EGG_DESKTOP_FILE_TYPE_LINK) + return FALSE; + + if (desktop_environment) + { + only_show_in = g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN, + NULL, NULL); + if (only_show_in) + { + for (i = 0, found = FALSE; only_show_in[i] && !found; i++) + { + if (!strcmp (only_show_in[i], desktop_environment)) + found = TRUE; + } + + g_strfreev (only_show_in); + + if (!found) + return FALSE; + } + + not_show_in = g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN, + NULL, NULL); + if (not_show_in) + { + for (i = 0, found = FALSE; not_show_in[i] && !found; i++) + { + if (!strcmp (not_show_in[i], desktop_environment)) + found = TRUE; + } + + g_strfreev (not_show_in); + + if (found) + return FALSE; + } + } + + if (desktop_file->type == EGG_DESKTOP_FILE_TYPE_APPLICATION) + { + try_exec = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TRY_EXEC, + NULL); + if (try_exec) + { + found_program = g_find_program_in_path (try_exec); + g_free (try_exec); + + if (!found_program) + return FALSE; + g_free (found_program); + } + } + + return TRUE; +} + +/** + * egg_desktop_file_accepts_documents: + * @desktop_file: an #EggDesktopFile + * + * Tests if @desktop_file represents an application that can accept + * documents on the command line. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file) +{ + return desktop_file->document_code != 0; +} + +/** + * egg_desktop_file_accepts_multiple: + * @desktop_file: an #EggDesktopFile + * + * Tests if @desktop_file can accept multiple documents at once. + * + * If this returns %FALSE, you can still pass multiple documents to + * egg_desktop_file_launch(), but that will result in multiple copies + * of the application being launched. See egg_desktop_file_launch() + * for more details. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file) +{ + return (desktop_file->document_code == 'F' || + desktop_file->document_code == 'U'); +} + +/** + * egg_desktop_file_accepts_uris: + * @desktop_file: an #EggDesktopFile + * + * Tests if @desktop_file can accept (non-"file:") URIs as documents to + * open. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file) +{ + return (desktop_file->document_code == 'U' || + desktop_file->document_code == 'u'); +} + +static void +append_quoted_word (GString *str, + const char *s, + gboolean in_single_quotes, + gboolean in_double_quotes) +{ + const char *p; + + if (!in_single_quotes && !in_double_quotes) + g_string_append_c (str, '\''); + else if (!in_single_quotes && in_double_quotes) + g_string_append (str, "\"'"); + + if (!strchr (s, '\'')) + g_string_append (str, s); + else + { + for (p = s; *p != '\0'; p++) + { + if (*p == '\'') + g_string_append (str, "'\\''"); + else + g_string_append_c (str, *p); + } + } + + if (!in_single_quotes && !in_double_quotes) + g_string_append_c (str, '\''); + else if (!in_single_quotes && in_double_quotes) + g_string_append (str, "'\""); +} + +static void +do_percent_subst (EggDesktopFile *desktop_file, + char code, + GString *str, + GSList **documents, + gboolean in_single_quotes, + gboolean in_double_quotes) +{ + GSList *d; + char *doc; + + switch (code) + { + case '%': + g_string_append_c (str, '%'); + break; + + case 'F': + case 'U': + for (d = *documents; d; d = d->next) + { + doc = d->data; + g_string_append (str, " "); + append_quoted_word (str, doc, in_single_quotes, in_double_quotes); + } + *documents = NULL; + break; + + case 'f': + case 'u': + if (*documents) + { + doc = (*documents)->data; + g_string_append (str, " "); + append_quoted_word (str, doc, in_single_quotes, in_double_quotes); + *documents = (*documents)->next; + } + break; + + case 'i': + if (desktop_file->icon) + { + g_string_append (str, "--icon "); + append_quoted_word (str, desktop_file->icon, + in_single_quotes, in_double_quotes); + } + break; + + case 'c': + if (desktop_file->name) + { + append_quoted_word (str, desktop_file->name, + in_single_quotes, in_double_quotes); + } + break; + + case 'k': + if (desktop_file->source) + { + append_quoted_word (str, desktop_file->source, + in_single_quotes, in_double_quotes); + } + break; + + case 'D': + case 'N': + case 'd': + case 'n': + case 'v': + case 'm': + /* Deprecated; skip */ + break; + + default: + g_warning ("Unrecognized %%-code '%%%c' in Exec", code); + break; + } +} + +static char * +parse_exec (EggDesktopFile *desktop_file, + GSList **documents, + GError **error) +{ + char *exec, *p, *command; + gboolean escape, single_quot, double_quot; + GString *gs; + + exec = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + error); + if (!exec) + return NULL; + + /* Build the command */ + gs = g_string_new (NULL); + escape = single_quot = double_quot = FALSE; + + for (p = exec; *p != '\0'; p++) + { + if (escape) + { + escape = FALSE; + g_string_append_c (gs, *p); + } + else if (*p == '\\') + { + if (!single_quot) + escape = TRUE; + g_string_append_c (gs, *p); + } + else if (*p == '\'') + { + g_string_append_c (gs, *p); + if (!single_quot && !double_quot) + single_quot = TRUE; + else if (single_quot) + single_quot = FALSE; + } + else if (*p == '"') + { + g_string_append_c (gs, *p); + if (!single_quot && !double_quot) + double_quot = TRUE; + else if (double_quot) + double_quot = FALSE; + } + else if (*p == '%' && p[1]) + { + do_percent_subst (desktop_file, p[1], gs, documents, + single_quot, double_quot); + p++; + } + else + g_string_append_c (gs, *p); + } + + g_free (exec); + command = g_string_free (gs, FALSE); + + /* Prepend "xdg-terminal " if needed (FIXME: use gvfs) */ + if (g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TERMINAL, + NULL)) + { + GError *terminal_error = NULL; + gboolean use_terminal = + g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TERMINAL, + &terminal_error); + if (terminal_error) + { + g_free (command); + g_propagate_error (error, terminal_error); + return NULL; + } + + if (use_terminal) + { + gs = g_string_new ("xdg-terminal "); + append_quoted_word (gs, command, FALSE, FALSE); + g_free (command); + command = g_string_free (gs, FALSE); + } + } + + return command; +} + +static GSList * +translate_document_list (EggDesktopFile *desktop_file, GSList *documents) +{ + gboolean accepts_uris = egg_desktop_file_accepts_uris (desktop_file); + GSList *ret, *d; + + for (d = documents, ret = NULL; d; d = d->next) + { + const char *document = d->data; + gboolean is_uri = !g_path_is_absolute (document); + char *translated; + + if (accepts_uris) + { + if (is_uri) + translated = g_strdup (document); + else + translated = g_filename_to_uri (document, NULL, NULL); + } + else + { + if (is_uri) + translated = g_filename_from_uri (document, NULL, NULL); + else + translated = g_strdup (document); + } + + if (translated) + ret = g_slist_prepend (ret, translated); + } + + return g_slist_reverse (ret); +} + +static void +free_document_list (GSList *documents) +{ + GSList *d; + + for (d = documents; d; d = d->next) + g_free (d->data); + g_slist_free (documents); +} + +/** + * egg_desktop_file_parse_exec: + * @desktop_file: a #EggDesktopFile + * @documents: a list of document paths or URIs + * @error: error pointer + * + * Parses @desktop_file's Exec key, inserting @documents into it, and + * returns the result. + * + * If @documents contains non-file: URIs and @desktop_file does not + * accept URIs, those URIs will be ignored. Likewise, if @documents + * contains more elements than @desktop_file accepts, the extra + * documents will be ignored. + * + * Return value: the parsed Exec string + **/ +char * +egg_desktop_file_parse_exec (EggDesktopFile *desktop_file, + GSList *documents, + GError **error) +{ + GSList *translated, *docs; + char *command; + + docs = translated = translate_document_list (desktop_file, documents); + command = parse_exec (desktop_file, &docs, error); + free_document_list (translated); + + return command; +} + +static gboolean +parse_link (EggDesktopFile *desktop_file, + EggDesktopFile **app_desktop_file, + GSList **documents, + GError **error) +{ + char *url; + GKeyFile *key_file; + + url = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_URL, + error); + if (!url) + return FALSE; + *documents = g_slist_prepend (NULL, url); + + /* FIXME: use gvfs */ + key_file = g_key_file_new (); + g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_NAME, + "xdg-open"); + g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TYPE, + "Application"); + g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + "xdg-open %u"); + *app_desktop_file = egg_desktop_file_new_from_key_file (key_file, NULL, NULL); + return TRUE; +} + +#if GTK_CHECK_VERSION (2, 12, 0) +static char * +start_startup_notification (GdkDisplay *display, + EggDesktopFile *desktop_file, + const char *argv0, + int screen, + int workspace, + guint32 launch_time) +{ + static int sequence = 0; + char *startup_id; + char *description, *wmclass; + char *screen_str, *workspace_str; + + if (g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY, + NULL)) + { + if (!g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY, + NULL)) + return NULL; + wmclass = NULL; + } + else + { + wmclass = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS, + NULL); + if (!wmclass) + return NULL; + } + + if (launch_time == (guint32)-1) + launch_time = gdk_x11_display_get_user_time (display); + startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu", + g_get_prgname (), + (unsigned long)getpid (), + g_get_host_name (), + argv0, + sequence++, + (unsigned long)launch_time); + + description = g_strdup_printf (_("Starting %s"), desktop_file->name); + screen_str = g_strdup_printf ("%d", screen); + workspace_str = workspace == -1 ? NULL : g_strdup_printf ("%d", workspace); + + gdk_x11_display_broadcast_startup_message (display, "new", + "ID", startup_id, + "NAME", desktop_file->name, + "SCREEN", screen_str, + "BIN", argv0, + "ICON", desktop_file->icon, + "DESKTOP", workspace_str, + "DESCRIPTION", description, + "WMCLASS", wmclass, + NULL); + + g_free (description); + g_free (wmclass); + g_free (screen_str); + g_free (workspace_str); + + return startup_id; +} + +static void +end_startup_notification (GdkDisplay *display, + const char *startup_id) +{ + gdk_x11_display_broadcast_startup_message (display, "remove", + "ID", startup_id, + NULL); +} + +#define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */) + +typedef struct { + GdkDisplay *display; + char *startup_id; +} StartupNotificationData; + +static gboolean +startup_notification_timeout (gpointer data) +{ + StartupNotificationData *sn_data = data; + + end_startup_notification (sn_data->display, sn_data->startup_id); + g_object_unref (sn_data->display); + g_free (sn_data->startup_id); + g_free (sn_data); + + return FALSE; +} + +static void +set_startup_notification_timeout (GdkDisplay *display, + const char *startup_id) +{ + StartupNotificationData *sn_data; + + sn_data = g_new (StartupNotificationData, 1); + sn_data->display = g_object_ref (display); + sn_data->startup_id = g_strdup (startup_id); + + g_timeout_add_seconds (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH, + startup_notification_timeout, sn_data); +} +#endif /* GTK 2.12 */ + +static GPtrArray * +array_putenv (GPtrArray *env, char *variable) +{ + guint i, keylen; + + if (!env) + { + char **envp; + + env = g_ptr_array_new (); + + envp = g_listenv (); + for (i = 0; envp[i]; i++) + { + const char *value; + + value = g_getenv (envp[i]); + g_ptr_array_add (env, g_strdup_printf ("%s=%s", envp[i], + value ? value : "")); + } + g_strfreev (envp); + } + + keylen = strcspn (variable, "="); + + /* Remove old value of key */ + for (i = 0; i < env->len; i++) + { + char *envvar = env->pdata[i]; + + if (!strncmp (envvar, variable, keylen) && envvar[keylen] == '=') + { + g_free (envvar); + g_ptr_array_remove_index_fast (env, i); + break; + } + } + + /* Add new value */ + g_ptr_array_add (env, g_strdup (variable)); + + return env; +} + +static gboolean +egg_desktop_file_launchv (EggDesktopFile *desktop_file, + GSList *documents, va_list args, + GError **error) +{ + EggDesktopFileLaunchOption option; + GSList *translated_documents = NULL, *docs = NULL; + char *command, **argv; + int argc, i, screen_num; + gboolean success, current_success; + GdkDisplay *display; + char *startup_id; + + GPtrArray *env = NULL; + char **variables = NULL; + GdkScreen *screen = NULL; + int workspace = -1; + const char *directory = NULL; + guint32 launch_time = (guint32)-1; + GSpawnFlags flags = G_SPAWN_SEARCH_PATH; + GSpawnChildSetupFunc setup_func = NULL; + gpointer setup_data = NULL; + + GPid *ret_pid = NULL; + int *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL; + char **ret_startup_id = NULL; + + if (documents && desktop_file->document_code == 0) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + _("Application does not accept documents on command line")); + return FALSE; + } + + /* Read the options: technically it's incorrect for the caller to + * NULL-terminate the list of options (rather than 0-terminating + * it), but NULL-terminating lets us use G_GNUC_NULL_TERMINATED, + * it's more consistent with other glib/gtk methods, and it will + * work as long as sizeof (int) <= sizeof (NULL), and NULL is + * represented as 0. (Which is true everywhere we care about.) + */ + while ((option = va_arg (args, EggDesktopFileLaunchOption))) + { + switch (option) + { + case EGG_DESKTOP_FILE_LAUNCH_CLEARENV: + if (env) + g_ptr_array_free (env, TRUE); + env = g_ptr_array_new (); + break; + case EGG_DESKTOP_FILE_LAUNCH_PUTENV: + variables = va_arg (args, char **); + for (i = 0; variables[i]; i++) + env = array_putenv (env, variables[i]); + break; + + case EGG_DESKTOP_FILE_LAUNCH_SCREEN: + screen = va_arg (args, GdkScreen *); + break; + case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: + workspace = va_arg (args, int); + break; + + case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: + directory = va_arg (args, const char *); + break; + case EGG_DESKTOP_FILE_LAUNCH_TIME: + launch_time = va_arg (args, guint32); + break; + case EGG_DESKTOP_FILE_LAUNCH_FLAGS: + flags |= va_arg (args, GSpawnFlags); + /* Make sure they didn't set any flags that don't make sense. */ + flags &= ~G_SPAWN_FILE_AND_ARGV_ZERO; + break; + case EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC: + setup_func = va_arg (args, GSpawnChildSetupFunc); + setup_data = va_arg (args, gpointer); + break; + + case EGG_DESKTOP_FILE_LAUNCH_RETURN_PID: + ret_pid = va_arg (args, GPid *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE: + ret_stdin = va_arg (args, int *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE: + ret_stdout = va_arg (args, int *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE: + ret_stderr = va_arg (args, int *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID: + ret_startup_id = va_arg (args, char **); + break; + + default: + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION, + _("Unrecognized launch option: %d"), + GPOINTER_TO_INT (option)); + success = FALSE; + goto out; + } + } + + if (screen) + { + char *display_name = gdk_screen_make_display_name (screen); + char *display_env = g_strdup_printf ("DISPLAY=%s", display_name); + env = array_putenv (env, display_env); + g_free (display_name); + g_free (display_env); + + display = gdk_screen_get_display (screen); + } + else + { + display = gdk_display_get_default (); + screen = gdk_display_get_default_screen (display); + } + screen_num = gdk_screen_get_number (screen); + + translated_documents = translate_document_list (desktop_file, documents); + docs = translated_documents; + + success = FALSE; + + do + { + command = parse_exec (desktop_file, &docs, error); + if (!command) + goto out; + + if (!g_shell_parse_argv (command, &argc, &argv, error)) + { + g_free (command); + goto out; + } + g_free (command); + +#if GTK_CHECK_VERSION (2, 12, 0) + startup_id = start_startup_notification (display, desktop_file, + argv[0], screen_num, + workspace, launch_time); + if (startup_id) + { + char *startup_id_env = g_strdup_printf ("DESKTOP_STARTUP_ID=%s", + startup_id); + env = array_putenv (env, startup_id_env); + g_free (startup_id_env); + } +#else + startup_id = NULL; +#endif /* GTK 2.12 */ + + if (env != NULL) + g_ptr_array_add (env, NULL); + + current_success = + g_spawn_async_with_pipes (directory, + argv, + env ? (char **)(env->pdata) : NULL, + flags, + setup_func, setup_data, + ret_pid, + ret_stdin, ret_stdout, ret_stderr, + error); + g_strfreev (argv); + + if (startup_id) + { +#if GTK_CHECK_VERSION (2, 12, 0) + if (current_success) + { + set_startup_notification_timeout (display, startup_id); + + if (ret_startup_id) + *ret_startup_id = startup_id; + else + g_free (startup_id); + } + else +#endif /* GTK 2.12 */ + g_free (startup_id); + } + else if (ret_startup_id) + *ret_startup_id = NULL; + + if (current_success) + { + /* If we successfully launch any instances of the app, make + * sure we return TRUE and don't set @error. + */ + success = TRUE; + error = NULL; + + /* Also, only set the output params on the first one */ + ret_pid = NULL; + ret_stdin = ret_stdout = ret_stderr = NULL; + ret_startup_id = NULL; + } + } + while (docs && current_success); + + out: + if (env) + { + g_ptr_array_foreach (env, (GFunc)g_free, NULL); + g_ptr_array_free (env, TRUE); + } + free_document_list (translated_documents); + + return success; +} + +/** + * egg_desktop_file_launch: + * @desktop_file: an #EggDesktopFile + * @documents: a list of URIs or paths to documents to open + * @error: error pointer + * @...: additional options + * + * Launches @desktop_file with the given arguments. Additional options + * can be specified as follows: + * + * %EGG_DESKTOP_FILE_LAUNCH_CLEARENV: (no arguments) + * clears the environment in the child process + * %EGG_DESKTOP_FILE_LAUNCH_PUTENV: (char **variables) + * adds the NAME=VALUE strings in the given %NULL-terminated + * array to the child process's environment + * %EGG_DESKTOP_FILE_LAUNCH_SCREEN: (GdkScreen *screen) + * causes the application to be launched on the given screen + * %EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: (int workspace) + * causes the application to be launched on the given workspace + * %EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: (char *dir) + * causes the application to be launched in the given directory + * %EGG_DESKTOP_FILE_LAUNCH_TIME: (guint32 launch_time) + * sets the "launch time" for the application. If the user + * interacts with another window after @launch_time but before + * the launched application creates its first window, the window + * manager may choose to not give focus to the new application. + * Passing 0 for @launch_time will explicitly request that the + * application not receive focus. + * %EGG_DESKTOP_FILE_LAUNCH_FLAGS (GSpawnFlags flags) + * Sets additional #GSpawnFlags to use. See g_spawn_async() for + * more details. + * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC (GSpawnChildSetupFunc, gpointer) + * Sets the child setup callback and the data to pass to it. + * (See g_spawn_async() for more details.) + * + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID (GPid **pid) + * On a successful launch, sets *@pid to the PID of the launched + * application. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID (char **startup_id) + * On a successful launch, sets *@startup_id to the Startup + * Notification "startup id" of the launched application. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE (int *fd) + * On a successful launch, sets *@fd to the file descriptor of + * a pipe connected to the application's stdin. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE (int *fd) + * On a successful launch, sets *@fd to the file descriptor of + * a pipe connected to the application's stdout. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE (int *fd) + * On a successful launch, sets *@fd to the file descriptor of + * a pipe connected to the application's stderr. + * + * The options should be terminated with a single %NULL. + * + * If @documents contains multiple documents, but + * egg_desktop_file_accepts_multiple() returns %FALSE for + * @desktop_file, then egg_desktop_file_launch() will actually launch + * multiple instances of the application. In that case, the return + * value (as well as any values passed via + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, etc) will only reflect the + * first instance of the application that was launched (but the + * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC will be called for each + * instance). + * + * Return value: %TRUE if the application was successfully launched. + **/ +gboolean +egg_desktop_file_launch (EggDesktopFile *desktop_file, + GSList *documents, GError **error, + ...) +{ + va_list args; + gboolean success; + EggDesktopFile *app_desktop_file; + + switch (desktop_file->type) + { + case EGG_DESKTOP_FILE_TYPE_APPLICATION: + va_start (args, error); + success = egg_desktop_file_launchv (desktop_file, documents, + args, error); + va_end (args); + break; + + case EGG_DESKTOP_FILE_TYPE_LINK: + if (documents) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + _("Can't pass document URIs to a 'Type=Link' desktop entry")); + return FALSE; + } + + if (!parse_link (desktop_file, &app_desktop_file, &documents, error)) + return FALSE; + + va_start (args, error); + success = egg_desktop_file_launchv (app_desktop_file, documents, + args, error); + va_end (args); + + egg_desktop_file_free (app_desktop_file); + free_document_list (documents); + break; + + case EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED: + case EGG_DESKTOP_FILE_TYPE_DIRECTORY: + default: + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + _("Not a launchable item")); + success = FALSE; + break; + } + + return success; +} + + +GQuark +egg_desktop_file_error_quark (void) +{ + return g_quark_from_static_string ("egg-desktop_file-error-quark"); +} + + +G_LOCK_DEFINE_STATIC (egg_desktop_file); +static EggDesktopFile *egg_desktop_file; + +static void +egg_set_desktop_file_internal (const char *desktop_file_path, + gboolean set_defaults) +{ + GError *error = NULL; + + G_LOCK (egg_desktop_file); + if (egg_desktop_file) + egg_desktop_file_free (egg_desktop_file); + + egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error); + if (error) + { + g_warning ("Could not load desktop file '%s': %s", + desktop_file_path, error->message); + g_error_free (error); + } + + if (set_defaults && egg_desktop_file != NULL) { + /* Set localized application name and default window icon */ + if (egg_desktop_file->name) + g_set_application_name (egg_desktop_file->name); + if (egg_desktop_file->icon) + { + if (g_path_is_absolute (egg_desktop_file->icon)) + gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL); + else + gtk_window_set_default_icon_name (egg_desktop_file->icon); + } + } + + G_UNLOCK (egg_desktop_file); +} + +/** + * egg_set_desktop_file: + * @desktop_file_path: path to the application's desktop file + * + * Creates an #EggDesktopFile for the application from the data at + * @desktop_file_path. This will also call g_set_application_name() + * with the localized application name from the desktop file, and + * gtk_window_set_default_icon_name() or + * gtk_window_set_default_icon_from_file() with the application's + * icon. Other code may use additional information from the desktop + * file. + * See egg_set_desktop_file_without_defaults() for a variant of this + * function that does not set the application name and default window + * icon. + * + * Note that for thread safety reasons, this function can only + * be called once, and is mutually exclusive with calling + * egg_set_desktop_file_without_defaults(). + **/ +void +egg_set_desktop_file (const char *desktop_file_path) +{ + egg_set_desktop_file_internal (desktop_file_path, TRUE); +} + +/** + * egg_set_desktop_file_without_defaults: + * @desktop_file_path: path to the application's desktop file + * + * Creates an #EggDesktopFile for the application from the data at + * @desktop_file_path. + * See egg_set_desktop_file() for a variant of this function that + * sets the application name and default window icon from the information + * in the desktop file. + * + * Note that for thread safety reasons, this function can only + * be called once, and is mutually exclusive with calling + * egg_set_desktop_file(). + **/ +void +egg_set_desktop_file_without_defaults (const char *desktop_file_path) +{ + egg_set_desktop_file_internal (desktop_file_path, FALSE); +} + +/** + * egg_get_desktop_file: + * + * Gets the application's #EggDesktopFile, as set by + * egg_set_desktop_file(). + * + * Return value: the #EggDesktopFile, or %NULL if it hasn't been set. + **/ +EggDesktopFile * +egg_get_desktop_file (void) +{ + EggDesktopFile *retval; + + G_LOCK (egg_desktop_file); + retval = egg_desktop_file; + G_UNLOCK (egg_desktop_file); + + return retval; +} diff --git a/moo/eggsmclient/eggdesktopfile.h b/moo/eggsmclient/eggdesktopfile.h new file mode 100644 index 00000000..16c5426c --- /dev/null +++ b/moo/eggsmclient/eggdesktopfile.h @@ -0,0 +1,163 @@ +/* eggdesktopfile.h - Freedesktop.Org Desktop Files + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - + * Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_DESKTOP_FILE_H__ +#define __EGG_DESKTOP_FILE_H__ + +#include + +G_BEGIN_DECLS + +typedef struct EggDesktopFile EggDesktopFile; + +typedef enum { + EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED, + + EGG_DESKTOP_FILE_TYPE_APPLICATION, + EGG_DESKTOP_FILE_TYPE_LINK, + EGG_DESKTOP_FILE_TYPE_DIRECTORY +} EggDesktopFileType; + +EggDesktopFile *egg_desktop_file_new (const char *desktop_file_path, + GError **error); + +EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, + GError **error); +EggDesktopFile *egg_desktop_file_new_from_dirs (const char *desktop_file_path, + const char **search_dirs, + GError **error); +EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *key_file, + const char *source, + GError **error); + +void egg_desktop_file_free (EggDesktopFile *desktop_file); + +const char *egg_desktop_file_get_source (EggDesktopFile *desktop_file); + +EggDesktopFileType egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file); + +const char *egg_desktop_file_get_name (EggDesktopFile *desktop_file); +const char *egg_desktop_file_get_icon (EggDesktopFile *desktop_file); + +gboolean egg_desktop_file_can_launch (EggDesktopFile *desktop_file, + const char *desktop_environment); + +gboolean egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file); +gboolean egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file); +gboolean egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file); + +char *egg_desktop_file_parse_exec (EggDesktopFile *desktop_file, + GSList *documents, + GError **error); + +gboolean egg_desktop_file_launch (EggDesktopFile *desktop_file, + GSList *documents, + GError **error, + ...) G_GNUC_NULL_TERMINATED; + +typedef enum { + EGG_DESKTOP_FILE_LAUNCH_CLEARENV = 1, + EGG_DESKTOP_FILE_LAUNCH_PUTENV, + EGG_DESKTOP_FILE_LAUNCH_SCREEN, + EGG_DESKTOP_FILE_LAUNCH_WORKSPACE, + EGG_DESKTOP_FILE_LAUNCH_DIRECTORY, + EGG_DESKTOP_FILE_LAUNCH_TIME, + EGG_DESKTOP_FILE_LAUNCH_FLAGS, + EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC, + EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID +} EggDesktopFileLaunchOption; + +/* Standard Keys */ +#define EGG_DESKTOP_FILE_GROUP "Desktop Entry" + +#define EGG_DESKTOP_FILE_KEY_TYPE "Type" +#define EGG_DESKTOP_FILE_KEY_VERSION "Version" +#define EGG_DESKTOP_FILE_KEY_NAME "Name" +#define EGG_DESKTOP_FILE_KEY_GENERIC_NAME "GenericName" +#define EGG_DESKTOP_FILE_KEY_NO_DISPLAY "NoDisplay" +#define EGG_DESKTOP_FILE_KEY_COMMENT "Comment" +#define EGG_DESKTOP_FILE_KEY_ICON "Icon" +#define EGG_DESKTOP_FILE_KEY_HIDDEN "Hidden" +#define EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN "OnlyShowIn" +#define EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN "NotShowIn" +#define EGG_DESKTOP_FILE_KEY_TRY_EXEC "TryExec" +#define EGG_DESKTOP_FILE_KEY_EXEC "Exec" +#define EGG_DESKTOP_FILE_KEY_PATH "Path" +#define EGG_DESKTOP_FILE_KEY_TERMINAL "Terminal" +#define EGG_DESKTOP_FILE_KEY_MIME_TYPE "MimeType" +#define EGG_DESKTOP_FILE_KEY_CATEGORIES "Categories" +#define EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY "StartupNotify" +#define EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS "StartupWMClass" +#define EGG_DESKTOP_FILE_KEY_URL "URL" + +/* Accessors */ +gboolean egg_desktop_file_has_key (EggDesktopFile *desktop_file, + const char *key, + GError **error); +char *egg_desktop_file_get_string (EggDesktopFile *desktop_file, + const char *key, + GError **error) G_GNUC_MALLOC; +char *egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + GError **error) G_GNUC_MALLOC; +gboolean egg_desktop_file_get_boolean (EggDesktopFile *desktop_file, + const char *key, + GError **error); +double egg_desktop_file_get_numeric (EggDesktopFile *desktop_file, + const char *key, + GError **error); +int egg_desktop_file_get_integer (EggDesktopFile *desktop_file, + const char *key, + GError **error); +char **egg_desktop_file_get_string_list (EggDesktopFile *desktop_file, + const char *key, + gsize *length, + GError **error) G_GNUC_MALLOC; +char **egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + gsize *length, + GError **error) G_GNUC_MALLOC; + + +/* Errors */ +#define EGG_DESKTOP_FILE_ERROR egg_desktop_file_error_quark() + +GQuark egg_desktop_file_error_quark (void); + +typedef enum { + EGG_DESKTOP_FILE_ERROR_INVALID, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION +} EggDesktopFileError; + +/* Global application desktop file */ +void egg_set_desktop_file (const char *desktop_file_path); +void egg_set_desktop_file_without_defaults (const char *desktop_file_path); +EggDesktopFile *egg_get_desktop_file (void); + + +G_END_DECLS + +#endif /* __EGG_DESKTOP_FILE_H__ */ diff --git a/moo/eggsmclient/eggsmclient-dbus.c b/moo/eggsmclient/eggsmclient-dbus.c new file mode 100644 index 00000000..21d9199b --- /dev/null +++ b/moo/eggsmclient/eggsmclient-dbus.c @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "eggsmclient.h" +#include "eggsmclient-private.h" + +#include "eggdesktopfile.h" + +#include +#include +#include +#include +#include + +#include +#include + +#define GSM_DBUS_NAME "org.gnome.SessionManager" +#define GSM_DBUS_PATH "/org/gnome/SessionManager" +#define GSM_DBUS_INTERFACE "org.gnome.SessionManager" + +#define GSM_CLIENT_PRIVATE_DBUS_INTERFACE "org.gnome.SessionManager.ClientPrivate" +#define GSM_CLIENT_DBUS_INTERFACE "org.gnome.SessionManager.Client" + +#define EGG_TYPE_SM_CLIENT_DBUS (egg_sm_client_dbus_get_type ()) +#define EGG_SM_CLIENT_DBUS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_DBUS, EggSMClientDBus)) +#define EGG_SM_CLIENT_DBUS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_DBUS, EggSMClientDBusClass)) +#define EGG_IS_SM_CLIENT_DBUS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_DBUS)) +#define EGG_IS_SM_CLIENT_DBUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_DBUS)) +#define EGG_SM_CLIENT_DBUS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_DBUS, EggSMClientDBusClass)) + +typedef struct _EggSMClientDBus EggSMClientDBus; +typedef struct _EggSMClientDBusClass EggSMClientDBusClass; + +struct _EggSMClientDBus +{ + EggSMClient parent; + + DBusGConnection *conn; + DBusGProxy *sm_proxy, *client_proxy; + char *client_path; +}; + +struct _EggSMClientDBusClass +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_dbus_startup (EggSMClient *client, + const char *client_id); +static void sm_client_dbus_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_dbus_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static void dbus_client_query_end_session (DBusGProxy *proxy, + guint flags, + gpointer smclient); +static void dbus_client_end_session (DBusGProxy *proxy, + guint flags, + gpointer smclient); +static void dbus_client_cancel_end_session (DBusGProxy *proxy, + gpointer smclient); +static void dbus_client_stop (DBusGProxy *proxy, + gpointer smclient); + +G_DEFINE_TYPE (EggSMClientDBus, egg_sm_client_dbus, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_dbus_init (EggSMClientDBus *dbus) +{ + ; +} + +static void +egg_sm_client_dbus_class_init (EggSMClientDBusClass *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_dbus_startup; + sm_client_class->will_quit = sm_client_dbus_will_quit; + sm_client_class->end_session = sm_client_dbus_end_session; +} + +EggSMClient * +egg_sm_client_dbus_new (void) +{ + DBusGConnection *conn; + DBusGProxy *proxy; + EggSMClientDBus *dbus; + + conn = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); + if (!conn) + return NULL; + + proxy = dbus_g_proxy_new_for_name (conn, GSM_DBUS_NAME, GSM_DBUS_PATH, + GSM_DBUS_INTERFACE); + if (!proxy) + { + g_object_unref (conn); + return NULL; + } + + dbus = g_object_new (EGG_TYPE_SM_CLIENT_DBUS, NULL); + dbus->conn = conn; + dbus->sm_proxy = proxy; + return (EggSMClient *)dbus; +} + +static void +sm_client_dbus_startup (EggSMClient *client, + const char *client_id) +{ + EggSMClientDBus *dbus = (EggSMClientDBus *)client; + GError *error = NULL; + char *client_path, *ret_client_id; + DBusGProxy *client_public; + + if (!dbus_g_proxy_call (dbus->sm_proxy, "RegisterClient", &error, + G_TYPE_STRING, g_get_prgname (), + G_TYPE_STRING, client_id, + G_TYPE_INVALID, + DBUS_TYPE_G_OBJECT_PATH, &client_path, + G_TYPE_INVALID)) + { + g_warning ("Failed to register client: %s", error->message); + g_error_free (error); + return; + } + + g_debug ("Client registered with session manager: %s", client_path); + dbus->client_proxy = dbus_g_proxy_new_for_name (dbus->conn, GSM_DBUS_NAME, + client_path, + GSM_CLIENT_PRIVATE_DBUS_INTERFACE); + dbus_g_proxy_add_signal (dbus->client_proxy, "QueryEndSession", + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (dbus->client_proxy, "QueryEndSession", + G_CALLBACK (dbus_client_query_end_session), + dbus, NULL); + dbus_g_proxy_add_signal (dbus->client_proxy, "EndSession", + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (dbus->client_proxy, "EndSession", + G_CALLBACK (dbus_client_end_session), + dbus, NULL); + dbus_g_proxy_add_signal (dbus->client_proxy, "CancelEndSession", + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (dbus->client_proxy, "CancelEndSession", + G_CALLBACK (dbus_client_cancel_end_session), + dbus, NULL); + dbus_g_proxy_add_signal (dbus->client_proxy, "Stop", + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (dbus->client_proxy, "Stop", + G_CALLBACK (dbus_client_stop), + dbus, NULL); + + client_public = dbus_g_proxy_new_for_name (dbus->conn, GSM_DBUS_NAME, + client_path, + GSM_CLIENT_DBUS_INTERFACE); + if (dbus_g_proxy_call (client_public, "GetStartupId", &error, + G_TYPE_INVALID, + G_TYPE_STRING, &ret_client_id, + G_TYPE_INVALID)) + { + gdk_threads_enter (); + gdk_set_sm_client_id (ret_client_id); + gdk_threads_leave (); + + g_debug ("Got client ID \"%s\"", ret_client_id); + g_free (ret_client_id); + } + else + { + g_warning ("Could not get client id: %s", error->message); + g_error_free (error); + } + g_object_unref (client_public); +} + +static void +sm_client_dbus_shutdown (EggSMClient *client) +{ + EggSMClientDBus *dbus = EGG_SM_CLIENT_DBUS (client); + GError *error = NULL; + + if (!dbus_g_proxy_call (dbus->sm_proxy, "UnregisterClient", &error, + DBUS_TYPE_G_OBJECT_PATH, dbus->client_path, + G_TYPE_INVALID, + G_TYPE_INVALID)) + { + g_warning ("Failed to unregister client: %s", error->message); + g_error_free (error); + return; + } + + g_free (dbus->client_path); + dbus->client_path = NULL; + + g_object_unref (dbus->client_proxy); + dbus->client_proxy = NULL; +} + +static void +dbus_client_query_end_session (DBusGProxy *proxy, + guint flags, + gpointer smclient) +{ + egg_sm_client_quit_requested (smclient); +} + +static void +sm_client_dbus_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientDBus *dbus = (EggSMClientDBus *)client; + + g_return_if_fail (dbus->client_proxy != NULL); + + dbus_g_proxy_call (dbus->client_proxy, "EndSessionResponse", NULL, + G_TYPE_BOOLEAN, will_quit, + G_TYPE_STRING, NULL, + G_TYPE_INVALID, + G_TYPE_INVALID); +} + +static void +dbus_client_end_session (DBusGProxy *proxy, + guint flags, + gpointer smclient) +{ + sm_client_dbus_will_quit (smclient, TRUE); + sm_client_dbus_shutdown (smclient); + egg_sm_client_quit (smclient); +} + +static void +dbus_client_cancel_end_session (DBusGProxy *proxy, + gpointer smclient) +{ + egg_sm_client_quit_cancelled (smclient); +} + +static void +dbus_client_stop (DBusGProxy *proxy, + gpointer smclient) +{ + sm_client_dbus_shutdown (smclient); + egg_sm_client_quit (smclient); +} + +static gboolean +sm_client_dbus_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + EggSMClientDBus *dbus = (EggSMClientDBus *)client; + + if (style == EGG_SM_CLIENT_END_SESSION_DEFAULT || + style == EGG_SM_CLIENT_LOGOUT) + { + return dbus_g_proxy_call (dbus->sm_proxy, "Logout", NULL, + G_TYPE_UINT, request_confirmation ? 0 : 1, + G_TYPE_INVALID, + G_TYPE_INVALID); + } + else + { + return dbus_g_proxy_call (dbus->sm_proxy, "Shutdown", NULL, + G_TYPE_INVALID, + G_TYPE_INVALID); + } +} diff --git a/moo/eggsmclient/eggsmclient-dummy.c b/moo/eggsmclient/eggsmclient-dummy.c new file mode 100644 index 00000000..63402ec7 --- /dev/null +++ b/moo/eggsmclient/eggsmclient-dummy.c @@ -0,0 +1,37 @@ +#include "config.h" +#include "eggsmclient-private.h" + +#define EGG_TYPE_SM_CLIENT_DUMMY (egg_sm_client_dummy_get_type ()) +#define EGG_SM_CLIENT_DUMMY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_DUMMY, EggSMClientDummy)) +#define EGG_SM_CLIENT_DUMMY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_DUMMY, EggSMClientDummyClass)) +#define EGG_IS_SM_CLIENT_DUMMY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_DUMMY)) +#define EGG_IS_SM_CLIENT_DUMMY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_DUMMY)) +#define EGG_SM_CLIENT_DUMMY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_DUMMY, EggSMClientDummyClass)) + +typedef EggSMClient EggSMClientDummy; +typedef EggSMClientClass EggSMClientDummyClass; + +G_DEFINE_TYPE (EggSMClientDummy, egg_sm_client_dummy, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_dummy_init (G_GNUC_UNUSED EggSMClientDummy *client) +{ +} + +static void +sm_client_dummy_startup (G_GNUC_UNUSED EggSMClient *client, + G_GNUC_UNUSED const char *client_id) +{ +} + +static void +egg_sm_client_dummy_class_init (EggSMClientDummyClass *klass) +{ + klass->startup = sm_client_dummy_startup; +} + +EggSMClient * +egg_sm_client_dummy_new (void) +{ + return g_object_new (EGG_TYPE_SM_CLIENT_DUMMY, NULL); +} diff --git a/moo/eggsmclient/eggsmclient-mangle.h b/moo/eggsmclient/eggsmclient-mangle.h new file mode 100644 index 00000000..a69e759b --- /dev/null +++ b/moo/eggsmclient/eggsmclient-mangle.h @@ -0,0 +1,31 @@ +#ifndef EGG_SM_CLIENT_MANGLE_H +#define EGG_SM_CLIENT_MANGLE_H + +#define egg_sm_client_xsmp_get_type _moo_egg_sm_client_xsmp_get_type +#define egg_sm_client_win32_get_type _moo_egg_sm_client_win32_get_type +#define egg_sm_client_osx_get_type _moo_egg_sm_client_osx_get_type + +#define egg_sm_client_get_type _moo_egg_sm_client_get_type +#define egg_sm_client_get_option_group _moo_egg_sm_client_get_option_group +#define egg_sm_client_register _moo_egg_sm_client_register +#define egg_sm_client_get _moo_egg_sm_client_get +#define egg_sm_client_is_resumed _moo_egg_sm_client_is_resumed +#define egg_sm_client_get_state_dir _moo_egg_sm_client_get_state_dir +#define egg_sm_client_get_config_prefix _moo_egg_sm_client_get_config_prefix +#define egg_sm_client_set_restart_command _moo_egg_sm_client_set_restart_command +#define egg_sm_client_will_quit _moo_egg_sm_client_will_quit +#define egg_sm_client_end_session _moo_egg_sm_client_end_session +#define egg_sm_client_save_state _moo_egg_sm_client_save_state +#define egg_sm_client_quit_requested _moo_egg_sm_client_quit_requested +#define egg_sm_client_quit_cancelled _moo_egg_sm_client_quit_cancelled +#define egg_sm_client_quit _moo_egg_sm_client_quit +#define egg_sm_client_xsmp_new _moo_egg_sm_client_xsmp_new +#define egg_sm_client_dbus_new _moo_egg_sm_client_dbus_new +#define egg_sm_client_win32_new _moo_egg_sm_client_win32_new +#define egg_sm_client_osx_new _moo_egg_sm_client_osx_new +#define egg_sm_client_dummy_new _moo_egg_sm_client_dummy_new +#define egg_sm_client_get_mode _moo_egg_sm_client_get_mode +#define egg_sm_client_get_state_file _moo_egg_sm_client_get_state_file +#define egg_sm_client_set_mode _moo_egg_sm_client_set_mode + +#endif /* EGG_SM_CLIENT_MANGLE_H */ diff --git a/moo/eggsmclient/eggsmclient-osx.c b/moo/eggsmclient/eggsmclient-osx.c new file mode 100644 index 00000000..7d3ff4b6 --- /dev/null +++ b/moo/eggsmclient/eggsmclient-osx.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* EggSMClientOSX + * + * For details on the OS X logout process, see: + * http://developer.apple.com/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/BootProcess.html#//apple_ref/doc/uid/20002130-114618 + * + * EggSMClientOSX registers for the kAEQuitApplication AppleEvent; the + * handler we register (quit_requested()) will be invoked from inside + * the quartz event-handling code (specifically, from inside + * [NSApplication nextEventMatchingMask]) when an AppleEvent arrives. + * We use AESuspendTheCurrentEvent() and AEResumeTheCurrentEvent() to + * allow asynchronous / non-main-loop-reentering processing of the + * quit request. (These are part of the Carbon framework; it doesn't + * seem to be possible to handle AppleEvents asynchronously from + * Cocoa.) + */ + +#include "config.h" + +#include "eggsmclient-private.h" +#include +#include +#include + +#define EGG_TYPE_SM_CLIENT_OSX (egg_sm_client_osx_get_type ()) +#define EGG_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSX)) +#define EGG_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass)) +#define EGG_IS_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_OSX)) +#define EGG_IS_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_OSX)) +#define EGG_SM_CLIENT_OSX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass)) + +typedef struct _EggSMClientOSX EggSMClientOSX; +typedef struct _EggSMClientOSXClass EggSMClientOSXClass; + +struct _EggSMClientOSX { + EggSMClient parent; + + AppleEvent quit_event, quit_reply; + gboolean quit_requested, quitting; +}; + +struct _EggSMClientOSXClass +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_osx_startup (EggSMClient *client, + const char *client_id); +static void sm_client_osx_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_osx_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static pascal OSErr quit_requested (const AppleEvent *, AppleEvent *, long); + +G_DEFINE_TYPE (EggSMClientOSX, egg_sm_client_osx, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_osx_init (EggSMClientOSX *osx) +{ + ; +} + +static void +egg_sm_client_osx_class_init (EggSMClientOSXClass *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_osx_startup; + sm_client_class->will_quit = sm_client_osx_will_quit; + sm_client_class->end_session = sm_client_osx_end_session; +} + +EggSMClient * +egg_sm_client_osx_new (void) +{ + return g_object_new (EGG_TYPE_SM_CLIENT_OSX, NULL); +} + +static void +sm_client_osx_startup (EggSMClient *client, + const char *client_id) +{ + AEInstallEventHandler (kCoreEventClass, kAEQuitApplication, + NewAEEventHandlerUPP (quit_requested), + (long)GPOINTER_TO_SIZE (client), false); +} + +static gboolean +idle_quit_requested (gpointer client) +{ + egg_sm_client_quit_requested (client); + return FALSE; +} + +static pascal OSErr +quit_requested (const AppleEvent *aevt, AppleEvent *reply, long refcon) +{ + EggSMClient *client = GSIZE_TO_POINTER ((gsize)refcon); + EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon); + + g_return_val_if_fail (!osx->quit_requested, userCanceledErr); + + /* FIXME AEInteractWithUser? */ + + osx->quit_requested = TRUE; + AEDuplicateDesc (aevt, &osx->quit_event); + AEDuplicateDesc (reply, &osx->quit_reply); + AESuspendTheCurrentEvent (aevt); + + /* Don't emit the "quit_requested" signal immediately, since we're + * called from a weird point in the guts of gdkeventloop-quartz.c + */ + g_idle_add (idle_quit_requested, client); + return noErr; +} + +static pascal OSErr +quit_requested_resumed (const AppleEvent *aevt, AppleEvent *reply, long refcon) +{ + EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon); + + osx->quit_requested = FALSE; + return osx->quitting ? noErr : userCanceledErr; +} + +static gboolean +idle_will_quit (gpointer client) +{ + EggSMClientOSX *osx = (EggSMClientOSX *)client; + + /* Resume the event with a new handler that will return a value to + * the system. + */ + AEResumeTheCurrentEvent (&osx->quit_event, &osx->quit_reply, + NewAEEventHandlerUPP (quit_requested_resumed), + (long)GPOINTER_TO_SIZE (client)); + AEDisposeDesc (&osx->quit_event); + AEDisposeDesc (&osx->quit_reply); + + if (osx->quitting) + egg_sm_client_quit (client); + return FALSE; +} + +static void +sm_client_osx_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientOSX *osx = (EggSMClientOSX *)client; + + g_return_if_fail (osx->quit_requested); + + osx->quitting = will_quit; + + /* Finish in an idle handler since the caller might have called + * egg_sm_client_will_quit() from inside the "quit_requested" signal + * handler, but may not expect the "quit" signal to arrive during + * the _will_quit() call. + */ + g_idle_add (idle_will_quit, client); +} + +static gboolean +sm_client_osx_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + static const ProcessSerialNumber loginwindow_psn = { 0, kSystemProcess }; + AppleEvent event = { typeNull, NULL }, reply = { typeNull, NULL }; + AEAddressDesc target; + AEEventID id; + OSErr err; + + switch (style) + { + case EGG_SM_CLIENT_END_SESSION_DEFAULT: + case EGG_SM_CLIENT_LOGOUT: + id = request_confirmation ? kAELogOut : kAEReallyLogOut; + break; + case EGG_SM_CLIENT_REBOOT: + id = request_confirmation ? kAEShowRestartDialog : kAERestart; + break; + case EGG_SM_CLIENT_SHUTDOWN: + id = request_confirmation ? kAEShowShutdownDialog : kAEShutDown; + break; + } + + err = AECreateDesc (typeProcessSerialNumber, &loginwindow_psn, + sizeof (loginwindow_psn), &target); + if (err != noErr) + { + g_warning ("Could not create descriptor for loginwindow: %d", err); + return FALSE; + } + + err = AECreateAppleEvent (kCoreEventClass, id, &target, + kAutoGenerateReturnID, kAnyTransactionID, + &event); + AEDisposeDesc (&target); + if (err != noErr) + { + g_warning ("Could not create logout AppleEvent: %d", err); + return FALSE; + } + + err = AESend (&event, &reply, kAENoReply, kAENormalPriority, + kAEDefaultTimeout, NULL, NULL); + AEDisposeDesc (&event); + if (err == noErr) + AEDisposeDesc (&reply); + + return err == noErr; +} diff --git a/moo/eggsmclient/eggsmclient-private.h b/moo/eggsmclient/eggsmclient-private.h new file mode 100644 index 00000000..57f23071 --- /dev/null +++ b/moo/eggsmclient/eggsmclient-private.h @@ -0,0 +1,62 @@ +/* eggsmclient-private.h + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_SM_CLIENT_PRIVATE_H__ +#define __EGG_SM_CLIENT_PRIVATE_H__ + +#include + +#if !GTK_CHECK_VERSION(2,91,7) && !GTK_CHECK_VERSION(3,0,0) +/* GTK+ 3 includes this automatically */ +#include +#endif + +#include "eggsmclient.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "EggSMClient" + +G_BEGIN_DECLS + +GKeyFile *egg_sm_client_save_state (EggSMClient *client); +void egg_sm_client_quit_requested (EggSMClient *client); +void egg_sm_client_quit_cancelled (EggSMClient *client); +void egg_sm_client_quit (EggSMClient *client); + +#if defined (GDK_WINDOWING_X11) +# ifdef EGG_SM_CLIENT_BACKEND_XSMP +GType egg_sm_client_xsmp_get_type (void); +EggSMClient *egg_sm_client_xsmp_new (void); +# endif +# ifdef EGG_SM_CLIENT_BACKEND_DBUS +GType egg_sm_client_dbus_get_type (void); +EggSMClient *egg_sm_client_dbus_new (void); +# endif +#elif defined (GDK_WINDOWING_WIN32) +GType egg_sm_client_win32_get_type (void); +EggSMClient *egg_sm_client_win32_new (void); +#elif defined (GDK_WINDOWING_QUARTZ) +GType egg_sm_client_osx_get_type (void); +EggSMClient *egg_sm_client_osx_new (void); +#endif + +G_END_DECLS + + +#endif /* __EGG_SM_CLIENT_PRIVATE_H__ */ diff --git a/moo/eggsmclient/eggsmclient-win32.c b/moo/eggsmclient/eggsmclient-win32.c new file mode 100644 index 00000000..d3d8d9e5 --- /dev/null +++ b/moo/eggsmclient/eggsmclient-win32.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* EggSMClientWin32 + * + * For details on the Windows XP logout process, see: + * http://msdn.microsoft.com/en-us/library/aa376876.aspx. + * + * Vista adds some new APIs which EggSMClient does not make use of; see + * http://msdn.microsoft.com/en-us/library/ms700677(VS.85).aspx + * + * When shutting down, Windows sends every top-level window a + * WM_QUERYENDSESSION event, which the application must respond to + * synchronously, saying whether or not it will quit. To avoid main + * loop re-entrancy problems (and to avoid having to muck about too + * much with the guts of the gdk-win32 main loop), we watch for this + * event in a separate thread, which then signals the main thread and + * waits for the main thread to handle the event. Since we don't want + * to require g_thread_init() to be called, we do this all using + * Windows-specific thread methods. + * + * After the application handles the WM_QUERYENDSESSION event, + * Windows then sends it a WM_ENDSESSION event with a TRUE or FALSE + * parameter indicating whether the session is or is not actually + * going to end now. We handle this from the other thread as well. + * + * As mentioned above, Vista introduces several additional new APIs + * that don't fit into the (current) EggSMClient API. Windows also has + * an entirely separate shutdown-notification scheme for non-GUI apps, + * which we also don't handle here. + */ + +#include "config.h" + +#include "eggsmclient-private.h" +#include + +#include +#include + +#define EGG_TYPE_SM_CLIENT_WIN32 (egg_sm_client_win32_get_type ()) +#define EGG_SM_CLIENT_WIN32(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32)) +#define EGG_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class)) +#define EGG_IS_SM_CLIENT_WIN32(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_WIN32)) +#define EGG_IS_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_WIN32)) +#define EGG_SM_CLIENT_WIN32_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class)) + +typedef struct _EggSMClientWin32 EggSMClientWin32; +typedef struct _EggSMClientWin32Class EggSMClientWin32Class; + +struct _EggSMClientWin32 { + EggSMClient parent; + + HANDLE message_event, response_event; + + volatile GSourceFunc event; + volatile gboolean will_quit; +}; + +struct _EggSMClientWin32Class +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_win32_startup (EggSMClient *client, + const char *client_id); +static void sm_client_win32_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_win32_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static GSource *g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, + gpointer user_data); +static gboolean got_message (gpointer user_data); +static void sm_client_thread (gpointer data); + +G_DEFINE_TYPE (EggSMClientWin32, egg_sm_client_win32, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_win32_init (G_GNUC_UNUSED EggSMClientWin32 *win32) +{ + ; +} + +static void +egg_sm_client_win32_class_init (EggSMClientWin32Class *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_win32_startup; + sm_client_class->will_quit = sm_client_win32_will_quit; + sm_client_class->end_session = sm_client_win32_end_session; +} + +EggSMClient * +egg_sm_client_win32_new (void) +{ + return g_object_new (EGG_TYPE_SM_CLIENT_WIN32, NULL); +} + +static void +sm_client_win32_startup (EggSMClient *client, + G_GNUC_UNUSED const char *client_id) +{ + EggSMClientWin32 *win32 = (EggSMClientWin32 *)client; + + win32->message_event = CreateEvent (NULL, FALSE, FALSE, NULL); + win32->response_event = CreateEvent (NULL, FALSE, FALSE, NULL); + g_win32_handle_source_add (win32->message_event, got_message, win32); + _beginthread (sm_client_thread, 0, client); +} + +static void +sm_client_win32_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientWin32 *win32 = (EggSMClientWin32 *)client; + + win32->will_quit = will_quit; + SetEvent (win32->response_event); +} + +static gboolean +sm_client_win32_end_session (G_GNUC_UNUSED EggSMClient *client, + EggSMClientEndStyle style, + G_GNUC_UNUSED gboolean request_confirmation) +{ + UINT uFlags = EWX_LOGOFF; + + switch (style) + { + case EGG_SM_CLIENT_END_SESSION_DEFAULT: + case EGG_SM_CLIENT_LOGOUT: + uFlags = EWX_LOGOFF; + break; + case EGG_SM_CLIENT_REBOOT: + uFlags = EWX_REBOOT; + break; + case EGG_SM_CLIENT_SHUTDOWN: + uFlags = EWX_POWEROFF; + break; + } + + /* There's no way to make ExitWindowsEx() show a logout dialog, so + * we ignore @request_confirmation. + */ + +#ifdef SHTDN_REASON_FLAG_PLANNED + ExitWindowsEx (uFlags, SHTDN_REASON_FLAG_PLANNED); +#else + ExitWindowsEx (uFlags, 0); +#endif + + return TRUE; +} + + +/* callbacks from logout-listener thread */ + +static gboolean +emit_quit_requested (gpointer smclient) +{ + gdk_threads_enter (); + egg_sm_client_quit_requested (smclient); + gdk_threads_leave (); + + return FALSE; +} + +static gboolean +emit_quit (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + gdk_threads_enter (); + egg_sm_client_quit (smclient); + gdk_threads_leave (); + + SetEvent (win32->response_event); + return FALSE; +} + +static gboolean +emit_quit_cancelled (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + gdk_threads_enter (); + egg_sm_client_quit_cancelled (smclient); + gdk_threads_leave (); + + SetEvent (win32->response_event); + return FALSE; +} + +static gboolean +got_message (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + win32->event (win32); + return TRUE; +} + +/* Windows HANDLE GSource */ + +typedef struct { + GSource source; + GPollFD pollfd; +} GWin32HandleSource; + +static gboolean +g_win32_handle_source_prepare (G_GNUC_UNUSED GSource *source, gint *timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean +g_win32_handle_source_check (GSource *source) +{ + GWin32HandleSource *hsource = (GWin32HandleSource *)source; + + return hsource->pollfd.revents; +} + +static gboolean +g_win32_handle_source_dispatch (G_GNUC_UNUSED GSource *source, GSourceFunc callback, gpointer user_data) +{ + return (*callback) (user_data); +} + +static void +g_win32_handle_source_finalize (G_GNUC_UNUSED GSource *source) +{ + ; +} + +GSourceFuncs g_win32_handle_source_funcs = { + g_win32_handle_source_prepare, + g_win32_handle_source_check, + g_win32_handle_source_dispatch, + g_win32_handle_source_finalize +}; + +static GSource * +g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, gpointer user_data) +{ + GWin32HandleSource *hsource; + GSource *source; + + source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource)); + hsource = (GWin32HandleSource *)source; + hsource->pollfd.fd = (int)handle; + hsource->pollfd.events = G_IO_IN; + hsource->pollfd.revents = 0; + g_source_add_poll (source, &hsource->pollfd); + + g_source_set_callback (source, callback, user_data, NULL); + g_source_attach (source, NULL); + return source; +} + +/* logout-listener thread */ + +static LRESULT CALLBACK +sm_client_win32_window_procedure (HWND hwnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + EggSMClientWin32 *win32 = + (EggSMClientWin32 *)GetWindowLongPtr (hwnd, GWLP_USERDATA); + + switch (message) + { + case WM_QUERYENDSESSION: + win32->event = emit_quit_requested; + SetEvent (win32->message_event); + + WaitForSingleObject (win32->response_event, INFINITE); + return win32->will_quit; + + case WM_ENDSESSION: + if (wParam) + { + /* The session is ending */ + win32->event = emit_quit; + } + else + { + /* Nope, the session *isn't* ending */ + win32->event = emit_quit_cancelled; + } + + SetEvent (win32->message_event); + WaitForSingleObject (win32->response_event, INFINITE); + + return 0; + + default: + return DefWindowProc (hwnd, message, wParam, lParam); + } +} + +static void +sm_client_thread (gpointer smclient) +{ + HINSTANCE instance; + WNDCLASSEXW wcl; + ATOM klass; + HWND window; + MSG msg; + + instance = GetModuleHandle (NULL); + + memset (&wcl, 0, sizeof (WNDCLASSEX)); + wcl.cbSize = sizeof (WNDCLASSEX); + wcl.lpfnWndProc = sm_client_win32_window_procedure; + wcl.hInstance = instance; + wcl.lpszClassName = L"EggSmClientWindow"; + klass = RegisterClassEx (&wcl); + + window = CreateWindowEx (0, MAKEINTRESOURCE (klass), + L"EggSmClientWindow", 0, + 10, 10, 50, 50, GetDesktopWindow (), + NULL, instance, NULL); + SetWindowLongPtr (window, GWLP_USERDATA, (LONG_PTR)smclient); + + /* main loop */ + while (GetMessage (&msg, NULL, 0, 0)) + DispatchMessage (&msg); +} diff --git a/moo/eggsmclient/eggsmclient-xsmp.c b/moo/eggsmclient/eggsmclient-xsmp.c new file mode 100644 index 00000000..7c2ae1b0 --- /dev/null +++ b/moo/eggsmclient/eggsmclient-xsmp.c @@ -0,0 +1,1389 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * + * Inspired by various other pieces of code including GsmClient (C) + * 2001 Havoc Pennington, GnomeClient (C) 1998 Carsten Schaar, and twm + * session code (C) 1998 The Open Group. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "eggsmclient.h" +#include "eggsmclient-private.h" + +#include "eggdesktopfile.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ()) +#define EGG_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP)) +#define EGG_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass)) +#define EGG_IS_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_XSMP)) +#define EGG_IS_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_XSMP)) +#define EGG_SM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass)) + +typedef struct _EggSMClientXSMP EggSMClientXSMP; +typedef struct _EggSMClientXSMPClass EggSMClientXSMPClass; + +/* These mostly correspond to the similarly-named states in section + * 9.1 of the XSMP spec. Some of the states there aren't represented + * here, because we don't need them. SHUTDOWN_CANCELLED is slightly + * different from the spec; we use it when the client is IDLE after a + * ShutdownCancelled message, but the application is still interacting + * and doesn't know the shutdown has been cancelled yet. + */ +typedef enum +{ + XSMP_STATE_IDLE, + XSMP_STATE_SAVE_YOURSELF, + XSMP_STATE_INTERACT_REQUEST, + XSMP_STATE_INTERACT, + XSMP_STATE_SAVE_YOURSELF_DONE, + XSMP_STATE_SHUTDOWN_CANCELLED, + XSMP_STATE_CONNECTION_CLOSED +} EggSMClientXSMPState; + +static const char *state_names[] = { + "idle", + "save-yourself", + "interact-request", + "interact", + "save-yourself-done", + "shutdown-cancelled", + "connection-closed" +}; + +#define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state]) + +struct _EggSMClientXSMP +{ + EggSMClient parent; + + SmcConn connection; + char *client_id; + + EggSMClientXSMPState state; + char **restart_command; + gboolean set_restart_command; + int restart_style; + + guint idle; + + /* Current SaveYourself state */ + guint expecting_initial_save_yourself : 1; + guint need_save_state : 1; + guint need_quit_requested : 1; + guint interact_errors : 1; + guint shutting_down : 1; + + /* Todo list */ + guint waiting_to_set_initial_properties : 1; + guint waiting_to_emit_quit : 1; + guint waiting_to_emit_quit_cancelled : 1; + guint waiting_to_save_myself : 1; + +}; + +struct _EggSMClientXSMPClass +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_xsmp_finalize (GObject *object); + +static void sm_client_xsmp_startup (EggSMClient *client, + const char *client_id); +static void sm_client_xsmp_set_restart_command (EggSMClient *client, + int argc, + const char **argv); +static void sm_client_xsmp_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_xsmp_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static void xsmp_save_yourself (SmcConn smc_conn, + SmPointer client_data, + int save_style, + Bool shutdown, + int interact_style, + Bool fast); +static void xsmp_die (SmcConn smc_conn, + SmPointer client_data); +static void xsmp_save_complete (SmcConn smc_conn, + SmPointer client_data); +static void xsmp_shutdown_cancelled (SmcConn smc_conn, + SmPointer client_data); +static void xsmp_interact (SmcConn smc_conn, + SmPointer client_data); + +static SmProp *array_prop (const char *name, + ...); +static SmProp *ptrarray_prop (const char *name, + GPtrArray *values); +static SmProp *string_prop (const char *name, + const char *value); +static SmProp *card8_prop (const char *name, + unsigned char value); + +static void set_properties (EggSMClientXSMP *xsmp, ...); +static void delete_properties (EggSMClientXSMP *xsmp, ...); + +static GPtrArray *generate_command (char **restart_command, + const char *client_id, + const char *state_file); + +static void save_state (EggSMClientXSMP *xsmp); +static void do_save_yourself (EggSMClientXSMP *xsmp); +static void update_pending_events (EggSMClientXSMP *xsmp); + +static void ice_init (void); +static gboolean process_ice_messages (IceConn ice_conn); +static void smc_error_handler (SmcConn smc_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + SmPointer values); + +G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp) +{ + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + xsmp->connection = NULL; + xsmp->restart_style = SmRestartIfRunning; +} + +static void +egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_xsmp_startup; + sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command; + sm_client_class->will_quit = sm_client_xsmp_will_quit; + sm_client_class->end_session = sm_client_xsmp_end_session; + + G_OBJECT_CLASS (klass)->finalize = sm_client_xsmp_finalize; +} + +static void +sm_client_xsmp_finalize (GObject *object) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)object; + + g_free (xsmp->client_id); + g_strfreev (xsmp->restart_command); + + G_OBJECT_CLASS (egg_sm_client_xsmp_parent_class)->finalize (object); +} + +EggSMClient * +egg_sm_client_xsmp_new (void) +{ + if (!g_getenv ("SESSION_MANAGER")) + return NULL; + + return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL); +} + +static gboolean +sm_client_xsmp_set_initial_properties (gpointer user_data) +{ + EggSMClientXSMP *xsmp = user_data; + EggDesktopFile *desktop_file; + GPtrArray *clone, *restart; + char pid_str[64]; + + if (xsmp->idle) + { + g_source_remove (xsmp->idle); + xsmp->idle = 0; + } + xsmp->waiting_to_set_initial_properties = FALSE; + + if (egg_sm_client_get_mode () == EGG_SM_CLIENT_MODE_NO_RESTART) + xsmp->restart_style = SmRestartNever; + + /* Parse info out of desktop file */ + desktop_file = egg_get_desktop_file (); + if (desktop_file) + { + GError *err = NULL; + char *cmdline, **argv; + int argc; + + if (xsmp->restart_style == SmRestartIfRunning) + { + if (egg_desktop_file_get_boolean (desktop_file, + "X-GNOME-AutoRestart", NULL)) + xsmp->restart_style = SmRestartImmediately; + } + + if (!xsmp->set_restart_command) + { + cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err); + if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err)) + { + egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp), + argc, (const char **)argv); + g_strfreev (argv); + } + else + { + g_warning ("Could not parse Exec line in desktop file: %s", + err->message); + g_error_free (err); + } + g_free (cmdline); + } + } + + if (!xsmp->set_restart_command) + xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1); + + clone = generate_command (xsmp->restart_command, NULL, NULL); + restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL); + + g_debug ("Setting initial properties"); + + /* Program, CloneCommand, RestartCommand, and UserID are required. + * ProcessID isn't required, but the SM may be able to do something + * useful with it. + */ + g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ()); + set_properties (xsmp, + string_prop (SmProgram, g_get_prgname ()), + ptrarray_prop (SmCloneCommand, clone), + ptrarray_prop (SmRestartCommand, restart), + string_prop (SmUserID, g_get_user_name ()), + string_prop (SmProcessID, pid_str), + card8_prop (SmRestartStyleHint, xsmp->restart_style), + NULL); + g_ptr_array_free (clone, TRUE); + g_ptr_array_free (restart, TRUE); + + if (desktop_file) + { + set_properties (xsmp, + string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)), + NULL); + } + + update_pending_events (xsmp); + return FALSE; +} + +/* This gets called from two different places: xsmp_die() (when the + * server asks us to disconnect) and process_ice_messages() (when the + * server disconnects unexpectedly). + */ +static void +sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp) +{ + SmcConn connection; + + if (!xsmp->connection) + return; + + g_debug ("Disconnecting"); + + connection = xsmp->connection; + xsmp->connection = NULL; + SmcCloseConnection (connection, 0, NULL); + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + + xsmp->waiting_to_save_myself = FALSE; + update_pending_events (xsmp); +} + +static void +sm_client_xsmp_startup (EggSMClient *client, + const char *client_id) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + SmcCallbacks callbacks; + char *ret_client_id; + char error_string_ret[256]; + + xsmp->client_id = g_strdup (client_id); + + ice_init (); + SmcSetErrorHandler (smc_error_handler); + + callbacks.save_yourself.callback = xsmp_save_yourself; + callbacks.die.callback = xsmp_die; + callbacks.save_complete.callback = xsmp_save_complete; + callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled; + + callbacks.save_yourself.client_data = xsmp; + callbacks.die.client_data = xsmp; + callbacks.save_complete.client_data = xsmp; + callbacks.shutdown_cancelled.client_data = xsmp; + + client_id = NULL; + error_string_ret[0] = '\0'; + xsmp->connection = + SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor, + SmcSaveYourselfProcMask | SmcDieProcMask | + SmcSaveCompleteProcMask | + SmcShutdownCancelledProcMask, + &callbacks, + xsmp->client_id, &ret_client_id, + sizeof (error_string_ret), error_string_ret); + + if (!xsmp->connection) + { + g_warning ("Failed to connect to the session manager: %s\n", + error_string_ret[0] ? + error_string_ret : "no error message given"); + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + return; + } + + /* We expect a pointless initial SaveYourself if either (a) we + * didn't have an initial client ID, or (b) we DID have an initial + * client ID, but the server rejected it and gave us a new one. + */ + if (!xsmp->client_id || + (ret_client_id && strcmp (xsmp->client_id, ret_client_id) != 0)) + xsmp->expecting_initial_save_yourself = TRUE; + + if (ret_client_id) + { + g_free (xsmp->client_id); + xsmp->client_id = g_strdup (ret_client_id); + free (ret_client_id); + +#if !GTK_CHECK_VERSION(2,91,7) && !GTK_CHECK_VERSION(3,0,0) + gdk_set_sm_client_id (xsmp->client_id); +#else + gdk_x11_set_sm_client_id (xsmp->client_id); +#endif + + g_debug ("Got client ID \"%s\"", xsmp->client_id); + } + + xsmp->state = XSMP_STATE_IDLE; + + /* Do not set the initial properties until we reach the main loop, + * so that the application has a chance to call + * egg_set_desktop_file(). (This may also help the session manager + * have a better idea of when the application is fully up and + * running.) + */ + xsmp->waiting_to_set_initial_properties = TRUE; + xsmp->idle = g_idle_add (sm_client_xsmp_set_initial_properties, client); +} + +static void +sm_client_xsmp_set_restart_command (EggSMClient *client, + int argc, + const char **argv) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + int i; + + g_strfreev (xsmp->restart_command); + + xsmp->restart_command = g_new (char *, argc + 1); + for (i = 0; i < argc; i++) + xsmp->restart_command[i] = g_strdup (argv[i]); + xsmp->restart_command[i] = NULL; + + xsmp->set_restart_command = TRUE; +} + +static void +sm_client_xsmp_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + + if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED) + { + /* The session manager has already exited! Schedule a quit + * signal. + */ + xsmp->waiting_to_emit_quit = TRUE; + update_pending_events (xsmp); + return; + } + else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) + { + /* We received a ShutdownCancelled message while the application + * was interacting; Schedule a quit_cancelled signal. + */ + xsmp->waiting_to_emit_quit_cancelled = TRUE; + update_pending_events (xsmp); + return; + } + + g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT); + + g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True"); + SmcInteractDone (xsmp->connection, !will_quit); + + if (will_quit && xsmp->need_save_state) + save_state (xsmp); + + g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False"); + SmcSaveYourselfDone (xsmp->connection, will_quit); + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; +} + +static gboolean +sm_client_xsmp_end_session (EggSMClient *client, + G_GNUC_UNUSED EggSMClientEndStyle style, + gboolean request_confirmation) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + int save_type; + + /* To end the session via XSMP, we have to send a + * SaveYourselfRequest. We aren't allowed to do that if anything + * else is going on, but we don't want to expose this fact to the + * application. So we do our best to patch things up here... + * + * In the worst case, this method might block for some length of + * time in process_ice_messages, but the only time that code path is + * honestly likely to get hit is if the application tries to end the + * session as the very first thing it does, in which case it + * probably won't actually block anyway. It's not worth gunking up + * the API to try to deal nicely with the other 0.01% of cases where + * this happens. + */ + + while (xsmp->state != XSMP_STATE_IDLE || + xsmp->expecting_initial_save_yourself) + { + /* If we're already shutting down, we don't need to do anything. */ + if (xsmp->shutting_down) + return TRUE; + + switch (xsmp->state) + { + case XSMP_STATE_CONNECTION_CLOSED: + return FALSE; + + case XSMP_STATE_SAVE_YOURSELF: + /* Trying to log out from the save_state callback? Whatever. + * Abort the save_state. + */ + SmcSaveYourselfDone (xsmp->connection, FALSE); + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; + break; + + case XSMP_STATE_INTERACT_REQUEST: + case XSMP_STATE_INTERACT: + case XSMP_STATE_SHUTDOWN_CANCELLED: + /* Already in a shutdown-related state, just ignore + * the new shutdown request... + */ + return TRUE; + + case XSMP_STATE_IDLE: + if (xsmp->waiting_to_set_initial_properties) + sm_client_xsmp_set_initial_properties (xsmp); + + if (!xsmp->expecting_initial_save_yourself) + break; + /* else fall through */ + + case XSMP_STATE_SAVE_YOURSELF_DONE: + /* We need to wait for some response from the server.*/ + process_ice_messages (SmcGetIceConnection (xsmp->connection)); + break; + + default: + /* Hm... shouldn't happen */ + return FALSE; + } + } + + /* xfce4-session will do the wrong thing if we pass SmSaveGlobal and + * the user chooses to save the session. But gnome-session will do + * the wrong thing if we pass SmSaveBoth and the user chooses NOT to + * save the session... Sigh. + */ + if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session")) + save_type = SmSaveBoth; + else + save_type = SmSaveGlobal; + + g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : ""); + SmcRequestSaveYourself (xsmp->connection, + save_type, + True, /* shutdown */ + SmInteractStyleAny, + !request_confirmation, /* fast */ + True /* global */); + return TRUE; +} + +static gboolean +idle_do_pending_events (gpointer data) +{ + EggSMClientXSMP *xsmp = data; + EggSMClient *client = data; + + gdk_threads_enter (); + + xsmp->idle = 0; + + if (xsmp->waiting_to_emit_quit) + { + xsmp->waiting_to_emit_quit = FALSE; + egg_sm_client_quit (client); + goto out; + } + + if (xsmp->waiting_to_emit_quit_cancelled) + { + xsmp->waiting_to_emit_quit_cancelled = FALSE; + egg_sm_client_quit_cancelled (client); + xsmp->state = XSMP_STATE_IDLE; + } + + if (xsmp->waiting_to_save_myself) + { + xsmp->waiting_to_save_myself = FALSE; + do_save_yourself (xsmp); + } + + out: + gdk_threads_leave (); + return FALSE; +} + +static void +update_pending_events (EggSMClientXSMP *xsmp) +{ + gboolean want_idle = + xsmp->waiting_to_emit_quit || + xsmp->waiting_to_emit_quit_cancelled || + xsmp->waiting_to_save_myself; + + if (want_idle) + { + if (xsmp->idle == 0) + xsmp->idle = g_idle_add (idle_do_pending_events, xsmp); + } + else + { + if (xsmp->idle != 0) + g_source_remove (xsmp->idle); + xsmp->idle = 0; + } +} + +static void +fix_broken_state (EggSMClientXSMP *xsmp, const char *message, + gboolean send_interact_done, + gboolean send_save_yourself_done) +{ + g_warning ("Received XSMP %s message in state %s: client or server error", + message, EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + /* Forget any pending SaveYourself plans we had */ + xsmp->waiting_to_save_myself = FALSE; + update_pending_events (xsmp); + + if (send_interact_done) + SmcInteractDone (xsmp->connection, False); + if (send_save_yourself_done) + SmcSaveYourselfDone (xsmp->connection, True); + + xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE; +} + +/* SM callbacks */ + +static void +xsmp_save_yourself (G_GNUC_UNUSED SmcConn smc_conn, + SmPointer client_data, + int save_type, + Bool shutdown, + int interact_style, + Bool fast) +{ + EggSMClientXSMP *xsmp = client_data; + gboolean wants_quit_requested; + + g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s", + save_type == SmSaveLocal ? "SmSaveLocal" : + save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth", + shutdown ? "Shutdown" : "!Shutdown", + interact_style == SmInteractStyleAny ? "SmInteractStyleAny" : + interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" : + "SmInteractStyleNone", fast ? "Fast" : "!Fast", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + if (xsmp->state != XSMP_STATE_IDLE && + xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED) + { + fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE); + return; + } + + if (xsmp->waiting_to_set_initial_properties) + sm_client_xsmp_set_initial_properties (xsmp); + + /* If this is the initial SaveYourself, ignore it; we've already set + * properties and there's no reason to actually save state too. + */ + if (xsmp->expecting_initial_save_yourself) + { + xsmp->expecting_initial_save_yourself = FALSE; + + if (save_type == SmSaveLocal && + interact_style == SmInteractStyleNone && + !shutdown && !fast) + { + g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself"); + SmcSaveYourselfDone (xsmp->connection, True); + /* As explained in the comment at the end of + * do_save_yourself(), SAVE_YOURSELF_DONE is the correct + * state here, not IDLE. + */ + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; + return; + } + else + g_warning ("First SaveYourself was not the expected one!"); + } + + /* Even ignoring the "fast" flag completely, there are still 18 + * different combinations of save_type, shutdown and interact_style. + * We interpret them as follows: + * + * Type Shutdown Interact Interpretation + * G F A/E/N do nothing (1) + * G T N do nothing (1)* + * G T A/E quit_requested (2) + * L/B F A/E/N save_state (3) + * L/B T N save_state (3)* + * L/B T A/E quit_requested, then save_state (4) + * + * 1. Do nothing, because the SM asked us to do something + * uninteresting (save open files, but then don't quit + * afterward) or rude (save open files without asking the user + * for confirmation). + * + * 2. Request interaction and then emit ::quit_requested. This + * perhaps isn't quite correct for the SmInteractStyleErrors + * case, but we don't care. + * + * 3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these + * rows essentially get demoted to SmSaveLocal, because their + * Global halves correspond to "do nothing". + * + * 4. Request interaction, emit ::quit_requested, and then emit + * ::save_state after interacting. This is the SmSaveBoth + * equivalent of #2, but we also promote SmSaveLocal shutdown + * SaveYourselfs to SmSaveBoth here, because we want to give + * the user a chance to save open files before quitting. + * + * (* It would be nice if we could do something useful when the + * session manager sends a SaveYourself with shutdown True and + * SmInteractStyleNone. But we can't, so we just pretend it didn't + * even tell us it was shutting down. The docs for ::quit mention + * that it might not always be preceded by ::quit_requested.) + */ + + /* As an optimization, we don't actually request interaction and + * emit ::quit_requested if the application isn't listening to the + * signal. + */ + wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE); + + xsmp->need_save_state = (save_type != SmSaveGlobal); + xsmp->need_quit_requested = (shutdown && wants_quit_requested && + interact_style != SmInteractStyleNone); + xsmp->interact_errors = (interact_style == SmInteractStyleErrors); + + xsmp->shutting_down = shutdown; + + do_save_yourself (xsmp); +} + +static void +do_save_yourself (EggSMClientXSMP *xsmp) +{ + if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) + { + /* The SM cancelled a previous SaveYourself, but we haven't yet + * had a chance to tell the application, so we can't start + * processing this SaveYourself yet. + */ + xsmp->waiting_to_save_myself = TRUE; + update_pending_events (xsmp); + return; + } + + if (xsmp->need_quit_requested) + { + xsmp->state = XSMP_STATE_INTERACT_REQUEST; + + g_debug ("Sending InteractRequest(%s)", + xsmp->interact_errors ? "Error" : "Normal"); + SmcInteractRequest (xsmp->connection, + xsmp->interact_errors ? SmDialogError : SmDialogNormal, + xsmp_interact, + xsmp); + return; + } + + if (xsmp->need_save_state) + { + save_state (xsmp); + + /* Though unlikely, the client could have been disconnected + * while the application was saving its state. + */ + if (!xsmp->connection) + return; + } + + g_debug ("Sending SaveYourselfDone(True)"); + SmcSaveYourselfDone (xsmp->connection, True); + + /* The client state diagram in the XSMP spec says that after a + * non-shutdown SaveYourself, we go directly back to "idle". But + * everything else in both the XSMP spec and the libSM docs + * disagrees. + */ + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; +} + +static void +save_state (EggSMClientXSMP *xsmp) +{ + GKeyFile *state_file; + char *state_file_path, *data; + EggDesktopFile *desktop_file; + GPtrArray *restart; + int offset, fd; + + /* We set xsmp->state before emitting save_state, but our caller is + * responsible for setting it back afterward. + */ + xsmp->state = XSMP_STATE_SAVE_YOURSELF; + + state_file = egg_sm_client_save_state ((EggSMClient *)xsmp); + if (!state_file) + { + restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL); + set_properties (xsmp, + ptrarray_prop (SmRestartCommand, restart), + NULL); + g_ptr_array_free (restart, TRUE); + delete_properties (xsmp, SmDiscardCommand, NULL); + return; + } + + desktop_file = egg_get_desktop_file (); + if (desktop_file) + { + GKeyFile *merged_file; + char *desktop_file_path; + + merged_file = g_key_file_new (); + desktop_file_path = + g_filename_from_uri (egg_desktop_file_get_source (desktop_file), + NULL, NULL); + if (desktop_file_path && + g_key_file_load_from_file (merged_file, desktop_file_path, + G_KEY_FILE_KEEP_COMMENTS | + G_KEY_FILE_KEEP_TRANSLATIONS, NULL)) + { + guint g, k, i; + char **groups, **keys, *value, *exec; + + groups = g_key_file_get_groups (state_file, NULL); + for (g = 0; groups[g]; g++) + { + keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL); + for (k = 0; keys[k]; k++) + { + value = g_key_file_get_value (state_file, groups[g], + keys[k], NULL); + if (value) + { + g_key_file_set_value (merged_file, groups[g], + keys[k], value); + g_free (value); + } + } + g_strfreev (keys); + } + g_strfreev (groups); + + g_key_file_free (state_file); + state_file = merged_file; + + /* Update Exec key using "--sm-client-state-file %k" */ + restart = generate_command (xsmp->restart_command, + NULL, "%k"); + for (i = 0; i < restart->len; i++) + restart->pdata[i] = g_shell_quote (restart->pdata[i]); + g_ptr_array_add (restart, NULL); + exec = g_strjoinv (" ", (char **)restart->pdata); + g_strfreev ((char **)restart->pdata); + g_ptr_array_free (restart, FALSE); + + g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + exec); + g_free (exec); + } + else + desktop_file = NULL; + + g_free (desktop_file_path); + } + + /* Now write state_file to disk. (We can't use mktemp(), because + * that requires the filename to end with "XXXXXX", and we want + * it to end with ".desktop".) + */ + + data = g_key_file_to_data (state_file, NULL, NULL); + g_key_file_free (state_file); + + offset = 0; + while (1) + { + state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s", + g_get_user_config_dir (), + G_DIR_SEPARATOR, G_DIR_SEPARATOR, + g_get_prgname (), + (long)time (NULL) + offset, + desktop_file ? "desktop" : "state"); + + fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd == -1) + { + if (errno == EEXIST) + { + offset++; + g_free (state_file_path); + continue; + } + else if (errno == ENOTDIR || errno == ENOENT) + { + char *sep = strrchr (state_file_path, G_DIR_SEPARATOR); + + *sep = '\0'; + if (g_mkdir_with_parents (state_file_path, 0755) != 0) + { + g_warning ("Could not create directory '%s'", + state_file_path); + g_free (state_file_path); + state_file_path = NULL; + break; + } + + continue; + } + + g_warning ("Could not create file '%s': %s", + state_file_path, g_strerror (errno)); + g_free (state_file_path); + state_file_path = NULL; + break; + } + + close (fd); + g_file_set_contents (state_file_path, data, -1, NULL); + break; + } + g_free (data); + + restart = generate_command (xsmp->restart_command, xsmp->client_id, + state_file_path); + set_properties (xsmp, + ptrarray_prop (SmRestartCommand, restart), + NULL); + g_ptr_array_free (restart, TRUE); + + if (state_file_path) + { + set_properties (xsmp, + array_prop (SmDiscardCommand, + "/bin/rm", "-rf", state_file_path, + NULL), + NULL); + g_free (state_file_path); + } +} + +static void +xsmp_interact (G_GNUC_UNUSED SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + EggSMClient *client = client_data; + + g_debug ("Received Interact message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + if (xsmp->state != XSMP_STATE_INTERACT_REQUEST) + { + fix_broken_state (xsmp, "Interact", TRUE, TRUE); + return; + } + + xsmp->state = XSMP_STATE_INTERACT; + egg_sm_client_quit_requested (client); +} + +static void +xsmp_die (G_GNUC_UNUSED SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + EggSMClient *client = client_data; + + g_debug ("Received Die message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + sm_client_xsmp_disconnect (xsmp); + egg_sm_client_quit (client); +} + +static void +xsmp_save_complete (G_GNUC_UNUSED SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + + g_debug ("Received SaveComplete message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE) + xsmp->state = XSMP_STATE_IDLE; + else + fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE); +} + +static void +xsmp_shutdown_cancelled (G_GNUC_UNUSED SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + EggSMClient *client = client_data; + + g_debug ("Received ShutdownCancelled message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + xsmp->shutting_down = FALSE; + + if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE) + { + /* We've finished interacting and now the SM has agreed to + * cancel the shutdown. + */ + xsmp->state = XSMP_STATE_IDLE; + egg_sm_client_quit_cancelled (client); + } + else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) + { + /* Hm... ok, so we got a shutdown SaveYourself, which got + * cancelled, but the application was still interacting, so we + * didn't tell it yet, and then *another* SaveYourself arrived, + * which we must still be waiting to tell the app about, except + * that now that SaveYourself has been cancelled too! Dizzy yet? + */ + xsmp->waiting_to_save_myself = FALSE; + update_pending_events (xsmp); + } + else + { + g_debug ("Sending SaveYourselfDone(False)"); + SmcSaveYourselfDone (xsmp->connection, False); + + if (xsmp->state == XSMP_STATE_INTERACT) + { + /* The application is currently interacting, so we can't + * tell it about the cancellation yet; we will wait until + * after it calls egg_sm_client_will_quit(). + */ + xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED; + } + else + { + /* The shutdown was cancelled before the application got a + * chance to interact. + */ + xsmp->state = XSMP_STATE_IDLE; + } + } +} + +/* Utilities */ + +/* Create a restart/clone/Exec command based on @restart_command. + * If @client_id is non-%NULL, add "--sm-client-id @client_id". + * If @state_file is non-%NULL, add "--sm-client-state-file @state_file". + * + * None of the input strings are g_strdup()ed; the caller must keep + * them around until it is done with the returned GPtrArray, and must + * then free the array, but not its contents. + */ +static GPtrArray * +generate_command (char **restart_command, const char *client_id, + const char *state_file) +{ + GPtrArray *cmd; + int i; + + cmd = g_ptr_array_new (); + g_ptr_array_add (cmd, restart_command[0]); + + if (client_id) + { + g_ptr_array_add (cmd, (char *)"--sm-client-id"); + g_ptr_array_add (cmd, (char *)client_id); + } + + if (state_file) + { + g_ptr_array_add (cmd, (char *)"--sm-client-state-file"); + g_ptr_array_add (cmd, (char *)state_file); + } + + for (i = 1; restart_command[i]; i++) + g_ptr_array_add (cmd, restart_command[i]); + + return cmd; +} + +/* Takes a NULL-terminated list of SmProp * values, created by + * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and + * frees them. + */ +static void +set_properties (EggSMClientXSMP *xsmp, ...) +{ + GPtrArray *props; + SmProp *prop; + va_list ap; + guint i; + + props = g_ptr_array_new (); + + va_start (ap, xsmp); + while ((prop = va_arg (ap, SmProp *))) + g_ptr_array_add (props, prop); + va_end (ap); + + if (xsmp->connection) + { + SmcSetProperties (xsmp->connection, props->len, + (SmProp **)props->pdata); + } + + for (i = 0; i < props->len; i++) + { + prop = props->pdata[i]; + g_free (prop->vals); + g_free (prop); + } + g_ptr_array_free (props, TRUE); +} + +/* Takes a NULL-terminated list of property names and deletes them. */ +static void +delete_properties (EggSMClientXSMP *xsmp, ...) +{ + GPtrArray *props; + char *prop; + va_list ap; + + if (!xsmp->connection) + return; + + props = g_ptr_array_new (); + + va_start (ap, xsmp); + while ((prop = va_arg (ap, char *))) + g_ptr_array_add (props, prop); + va_end (ap); + + SmcDeleteProperties (xsmp->connection, props->len, + (char **)props->pdata); + + g_ptr_array_free (props, TRUE); +} + +/* Takes an array of strings and creates a LISTofARRAY8 property. The + * strings are neither dupped nor freed; they need to remain valid + * until you're done with the SmProp. + */ +static SmProp * +array_prop (const char *name, ...) +{ + SmProp *prop; + SmPropValue pv; + GArray *vals; + char *value; + va_list ap; + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = (char *)SmLISTofARRAY8; + + vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue)); + + va_start (ap, name); + while ((value = va_arg (ap, char *))) + { + pv.length = strlen (value); + pv.value = value; + g_array_append_val (vals, pv); + } + + prop->num_vals = vals->len; + prop->vals = (SmPropValue *)vals->data; + + g_array_free (vals, FALSE); + + return prop; +} + +/* Takes a GPtrArray of strings and creates a LISTofARRAY8 property. + * The array contents are neither dupped nor freed; they need to + * remain valid until you're done with the SmProp. + */ +static SmProp * +ptrarray_prop (const char *name, GPtrArray *values) +{ + SmProp *prop; + SmPropValue pv; + GArray *vals; + guint i; + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = (char *)SmLISTofARRAY8; + + vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue)); + + for (i = 0; i < values->len; i++) + { + pv.length = strlen (values->pdata[i]); + pv.value = values->pdata[i]; + g_array_append_val (vals, pv); + } + + prop->num_vals = vals->len; + prop->vals = (SmPropValue *)vals->data; + + g_array_free (vals, FALSE); + + return prop; +} + +/* Takes a string and creates an ARRAY8 property. The string is + * neither dupped nor freed; it needs to remain valid until you're + * done with the SmProp. + */ +static SmProp * +string_prop (const char *name, const char *value) +{ + SmProp *prop; + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = (char *)SmARRAY8; + + prop->num_vals = 1; + prop->vals = g_new (SmPropValue, 1); + + prop->vals[0].length = strlen (value); + prop->vals[0].value = (char *)value; + + return prop; +} + +/* Takes a char and creates a CARD8 property. */ +static SmProp * +card8_prop (const char *name, unsigned char value) +{ + SmProp *prop; + char *card8val; + + /* To avoid having to allocate and free prop->vals[0], we cheat and + * make vals a 2-element-long array and then use the second element + * to store value. + */ + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = (char *)SmCARD8; + + prop->num_vals = 1; + prop->vals = g_new (SmPropValue, 2); + card8val = (char *)(&prop->vals[1]); + card8val[0] = value; + + prop->vals[0].length = 1; + prop->vals[0].value = card8val; + + return prop; +} + +/* ICE code. This makes no effort to play nice with anyone else trying + * to use libICE. Fortunately, no one uses libICE for anything other + * than SM. (DCOP uses ICE, but it has its own private copy of + * libICE.) + * + * When this moves to gtk, it will need to be cleverer, to avoid + * tripping over old apps that use GnomeClient or that use libSM + * directly. + */ + +#include +#include + +static void ice_error_handler (IceConn ice_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + IcePointer values); +static void ice_io_error_handler (IceConn ice_conn); +static void ice_connection_watch (IceConn ice_conn, + IcePointer client_data, + Bool opening, + IcePointer *watch_data); + +static void +ice_init (void) +{ + IceSetIOErrorHandler (ice_io_error_handler); + IceSetErrorHandler (ice_error_handler); + IceAddConnectionWatch (ice_connection_watch, NULL); +} + +static gboolean +process_ice_messages (IceConn ice_conn) +{ + IceProcessMessagesStatus status; + + gdk_threads_enter (); + status = IceProcessMessages (ice_conn, NULL, NULL); + gdk_threads_leave (); + + switch (status) + { + case IceProcessMessagesSuccess: + return TRUE; + + case IceProcessMessagesIOError: + sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn)); + return FALSE; + + case IceProcessMessagesConnectionClosed: + return FALSE; + + default: + g_assert_not_reached (); + return FALSE; + } +} + +static gboolean +ice_iochannel_watch (G_GNUC_UNUSED GIOChannel *channel, + G_GNUC_UNUSED GIOCondition condition, + gpointer client_data) +{ + return process_ice_messages (client_data); +} + +static void +ice_connection_watch (IceConn ice_conn, + G_GNUC_UNUSED IcePointer client_data, + Bool opening, + IcePointer *watch_data) +{ + guint watch_id; + + if (opening) + { + GIOChannel *channel; + int fd = IceConnectionNumber (ice_conn); + + fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC); + channel = g_io_channel_unix_new (fd); + watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR, + ice_iochannel_watch, ice_conn); + g_io_channel_unref (channel); + + *watch_data = GUINT_TO_POINTER (watch_id); + } + else + { + watch_id = GPOINTER_TO_UINT (*watch_data); + g_source_remove (watch_id); + } +} + +static void +ice_error_handler (G_GNUC_UNUSED IceConn ice_conn, + G_GNUC_UNUSED Bool swap, + G_GNUC_UNUSED int offending_minor_opcode, + G_GNUC_UNUSED unsigned long offending_sequence, + G_GNUC_UNUSED int error_class, + G_GNUC_UNUSED int severity, + G_GNUC_UNUSED IcePointer values) +{ + /* Do nothing */ +} + +static void +ice_io_error_handler (G_GNUC_UNUSED IceConn ice_conn) +{ + /* Do nothing */ +} + +static void +smc_error_handler (G_GNUC_UNUSED SmcConn smc_conn, + G_GNUC_UNUSED Bool swap, + G_GNUC_UNUSED int offending_minor_opcode, + G_GNUC_UNUSED unsigned long offending_sequence, + G_GNUC_UNUSED int error_class, + G_GNUC_UNUSED int severity, + G_GNUC_UNUSED SmPointer values) +{ + /* Do nothing */ +} diff --git a/moo/eggsmclient/eggsmclient.c b/moo/eggsmclient/eggsmclient.c new file mode 100644 index 00000000..831c1876 --- /dev/null +++ b/moo/eggsmclient/eggsmclient.c @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "eggsmclient.h" +#include "eggsmclient-private.h" + +static void egg_sm_client_debug_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer user_data); + +enum { + SAVE_STATE, + QUIT_REQUESTED, + QUIT_CANCELLED, + QUIT, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +struct _EggSMClientPrivate { + GKeyFile *state_file; +}; + +#define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate)) + +G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT) + +static EggSMClient *global_client; +static EggSMClientMode global_client_mode = EGG_SM_CLIENT_MODE_NORMAL; + +static void +egg_sm_client_init (G_GNUC_UNUSED EggSMClient *client) +{ + ; +} + +static void +egg_sm_client_class_init (EggSMClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (EggSMClientPrivate)); + + /** + * EggSMClient::save_state: + * @client: the client + * @state_file: a #GKeyFile to save state information into + * + * Emitted when the session manager has requested that the + * application save information about its current state. The + * application should save its state into @state_file, and then the + * session manager may then restart the application in a future + * session and tell it to initialize itself from that state. + * + * You should not save any data into @state_file's "start group" + * (ie, the %NULL group). Instead, applications should save their + * data into groups with names that start with the application name, + * and libraries that connect to this signal should save their data + * into groups with names that start with the library name. + * + * Alternatively, rather than (or in addition to) using @state_file, + * the application can save its state by calling + * egg_sm_client_set_restart_command() during the processing of this + * signal (eg, to include a list of files to open). + **/ + signals[SAVE_STATE] = + g_signal_new ("save_state", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, save_state), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * EggSMClient::quit_requested: + * @client: the client + * + * Emitted when the session manager requests that the application + * exit (generally because the user is logging out). The application + * should decide whether or not it is willing to quit (perhaps after + * asking the user what to do with documents that have unsaved + * changes) and then call egg_sm_client_will_quit(), passing %TRUE + * or %FALSE to give its answer to the session manager. (It does not + * need to give an answer before returning from the signal handler; + * it can interact with the user asynchronously and then give its + * answer later on.) If the application does not connect to this + * signal, then #EggSMClient will automatically return %TRUE on its + * behalf. + * + * The application should not save its session state as part of + * handling this signal; if the user has requested that the session + * be saved when logging out, then ::save_state will be emitted + * separately. + * + * If the application agrees to quit, it should then wait for either + * the ::quit_cancelled or ::quit signals to be emitted. + **/ + signals[QUIT_REQUESTED] = + g_signal_new ("quit_requested", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, quit_requested), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * EggSMClient::quit_cancelled: + * @client: the client + * + * Emitted when the session manager decides to cancel a logout after + * the application has already agreed to quit. After receiving this + * signal, the application can go back to what it was doing before + * receiving the ::quit_requested signal. + **/ + signals[QUIT_CANCELLED] = + g_signal_new ("quit_cancelled", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * EggSMClient::quit: + * @client: the client + * + * Emitted when the session manager wants the application to quit + * (generally because the user is logging out). The application + * should exit as soon as possible after receiving this signal; if + * it does not, the session manager may choose to forcibly kill it. + * + * Normally a GUI application would only be sent a ::quit if it + * agreed to quit in response to a ::quit_requested signal. However, + * this is not guaranteed; in some situations the session manager + * may decide to end the session without giving applications a + * chance to object. + **/ + signals[QUIT] = + g_signal_new ("quit", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, quit), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +static gboolean sm_client_disable = FALSE; +static char *sm_client_state_file = NULL; +static char *sm_client_id = NULL; +static char *sm_config_prefix = NULL; + +static gboolean +sm_client_post_parse_func (G_GNUC_UNUSED GOptionContext *context, + G_GNUC_UNUSED GOptionGroup *group, + G_GNUC_UNUSED gpointer data, + G_GNUC_UNUSED GError **error) +{ + EggSMClient *client = egg_sm_client_get (); + + if (sm_client_id == NULL) + { + const gchar *desktop_autostart_id; + + desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + + if (desktop_autostart_id != NULL) + sm_client_id = g_strdup (desktop_autostart_id); + } + + /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to + * use the same client id. */ + g_unsetenv ("DESKTOP_AUTOSTART_ID"); + + if (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED && + EGG_SM_CLIENT_GET_CLASS (client)->startup) + EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id); + return TRUE; +} + +/** + * egg_sm_client_get_option_group: + * + * Creates a %GOptionGroup containing the session-management-related + * options. You should add this group to the application's + * %GOptionContext if you want to use #EggSMClient. + * + * Return value: the %GOptionGroup + **/ +GOptionGroup * +egg_sm_client_get_option_group (void) +{ + const GOptionEntry entries[] = { + { "sm-client-disable", 0, 0, + G_OPTION_ARG_NONE, &sm_client_disable, + N_("Disable connection to session manager"), NULL }, + { "sm-client-state-file", 0, 0, + G_OPTION_ARG_FILENAME, &sm_client_state_file, + N_("Specify file containing saved configuration"), N_("FILE") }, + { "sm-client-id", 0, 0, + G_OPTION_ARG_STRING, &sm_client_id, + N_("Specify session management ID"), N_("ID") }, + /* GnomeClient compatibility option */ + { "sm-disable", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_NONE, &sm_client_disable, + NULL, NULL }, + /* GnomeClient compatibility option. This is a dummy option that only + * exists so that sessions saved by apps with GnomeClient can be restored + * later when they've switched to EggSMClient. See bug #575308. + */ + { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, &sm_config_prefix, + NULL, NULL }, + { NULL } + }; + GOptionGroup *group; + + /* Use our own debug handler for the "EggSMClient" domain. */ + g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, + egg_sm_client_debug_handler, NULL); + + group = g_option_group_new ("sm-client", + _("Session management options:"), + _("Show session management options"), + NULL, NULL); + g_option_group_add_entries (group, entries); + g_option_group_set_parse_hooks (group, NULL, sm_client_post_parse_func); + + return group; +} + +/** + * egg_sm_client_set_mode: + * @mode: an #EggSMClient mode + * + * Sets the "mode" of #EggSMClient as follows: + * + * %EGG_SM_CLIENT_MODE_DISABLED: Session management is completely + * disabled, until the mode is changed again. The application will + * not even connect to the session manager. (egg_sm_client_get() + * will still return an #EggSMClient object.) + * + * %EGG_SM_CLIENT_MODE_NO_RESTART: The application will connect to + * the session manager (and thus will receive notification when the + * user is logging out, etc), but will request to not be + * automatically restarted with saved state in future sessions. + * + * %EGG_SM_CLIENT_MODE_NORMAL: The default. #EggSMCLient will + * function normally. + * + * This must be called before the application's main loop begins and + * before any call to egg_sm_client_get(), unless the mode was set + * earlier to %EGG_SM_CLIENT_MODE_DISABLED and this call enables + * session management. Note that option parsing will call + * egg_sm_client_get(). + **/ +void +egg_sm_client_set_mode (EggSMClientMode mode) +{ + EggSMClientMode old_mode = global_client_mode; + + g_return_if_fail (global_client == NULL || global_client_mode == EGG_SM_CLIENT_MODE_DISABLED); + g_return_if_fail (!(global_client != NULL && mode == EGG_SM_CLIENT_MODE_DISABLED)); + + global_client_mode = mode; + + if (global_client != NULL && old_mode == EGG_SM_CLIENT_MODE_DISABLED) + { + if (EGG_SM_CLIENT_GET_CLASS (global_client)->startup) + EGG_SM_CLIENT_GET_CLASS (global_client)->startup (global_client, sm_client_id); + } +} + +/** + * egg_sm_client_get_mode: + * + * Gets the global #EggSMClientMode. See egg_sm_client_set_mode() + * for details. + * + * Return value: the global #EggSMClientMode + **/ +EggSMClientMode +egg_sm_client_get_mode (void) +{ + return global_client_mode; +} + +/** + * egg_sm_client_get: + * + * Returns the master #EggSMClient for the application. + * + * On platforms that support saved sessions (ie, POSIX/X11), the + * application will only request to be restarted by the session + * manager if you call egg_set_desktop_file() to set an application + * desktop file. In particular, if the desktop file contains the key + * "X + * + * Return value: the master #EggSMClient. + **/ +EggSMClient * +egg_sm_client_get (void) +{ + if (!global_client) + { + if (!sm_client_disable) + { +#if defined (GDK_WINDOWING_WIN32) + global_client = egg_sm_client_win32_new (); +#elif defined (GDK_WINDOWING_QUARTZ) + global_client = egg_sm_client_osx_new (); +#else + /* If both D-Bus and XSMP are compiled in, try XSMP first + * (since it supports state saving) and fall back to D-Bus + * if XSMP isn't available. + */ +# ifdef EGG_SM_CLIENT_BACKEND_XSMP + global_client = egg_sm_client_xsmp_new (); +# endif +# ifdef EGG_SM_CLIENT_BACKEND_DBUS + if (!global_client) + global_client = egg_sm_client_dbus_new (); +# endif +#endif + } + + /* Fallback: create a dummy client, so that callers don't have + * to worry about a %NULL return value. + */ + if (!global_client) + global_client = g_object_new (EGG_TYPE_SM_CLIENT, NULL); + } + + return global_client; +} + +/** + * egg_sm_client_is_resumed: + * @client: the client + * + * Checks whether or not the current session has been resumed from + * a previous saved session. If so, the application should call + * egg_sm_client_get_state_file() and restore its state from the + * returned #GKeyFile. + * + * Return value: %TRUE if the session has been resumed + **/ +gboolean +egg_sm_client_is_resumed (EggSMClient *client) +{ + g_return_val_if_fail (client == global_client, FALSE); + + return sm_client_state_file != NULL; +} + +/** + * egg_sm_client_get_state_file: + * @client: the client + * + * If the application was resumed by the session manager, this will + * return the #GKeyFile containing its state from the previous + * session. + * + * Note that other libraries and #EggSMClient itself may also store + * state in the key file, so if you call egg_sm_client_get_groups(), + * on it, the return value will likely include groups that you did not + * put there yourself. (It is also not guaranteed that the first + * group created by the application will still be the "start group" + * when it is resumed.) + * + * Return value: the #GKeyFile containing the application's earlier + * state, or %NULL on error. You should not free this key file; it + * is owned by @client. + **/ +GKeyFile * +egg_sm_client_get_state_file (EggSMClient *client) +{ + EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client); + char *state_file_path; + GError *err = NULL; + + g_return_val_if_fail (client == global_client, NULL); + + if (!sm_client_state_file) + return NULL; + if (priv->state_file) + return priv->state_file; + + if (!strncmp (sm_client_state_file, "file://", 7)) + state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL); + else + state_file_path = g_strdup (sm_client_state_file); + + priv->state_file = g_key_file_new (); + if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err)) + { + g_warning ("Could not load SM state file '%s': %s", + sm_client_state_file, err->message); + g_clear_error (&err); + g_key_file_free (priv->state_file); + priv->state_file = NULL; + } + + g_free (state_file_path); + return priv->state_file; +} + +/** + * egg_sm_client_set_restart_command: + * @client: the client + * @argc: the length of @argv + * @argv: argument vector + * + * Sets the command used to restart @client if it does not have a + * .desktop file that can be used to find its restart command. + * + * This can also be used when handling the ::save_state signal, to + * save the current state via an updated command line. (Eg, providing + * a list of filenames to open when the application is resumed.) + **/ +void +egg_sm_client_set_restart_command (EggSMClient *client, + int argc, + const char **argv) +{ + g_return_if_fail (EGG_IS_SM_CLIENT (client)); + + if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command) + EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv); +} + +/** + * egg_sm_client_will_quit: + * @client: the client + * @will_quit: whether or not the application is willing to quit + * + * This MUST be called in response to the ::quit_requested signal, to + * indicate whether or not the application is willing to quit. The + * application may call it either directly from the signal handler, or + * at some later point (eg, after asynchronously interacting with the + * user). + * + * If the application does not connect to ::quit_requested, + * #EggSMClient will call this method on its behalf (passing %TRUE + * for @will_quit). + * + * After calling this method, the application should wait to receive + * either ::quit_cancelled or ::quit. + **/ +void +egg_sm_client_will_quit (EggSMClient *client, + gboolean will_quit) +{ + g_return_if_fail (EGG_IS_SM_CLIENT (client)); + + if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit) + EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit); +} + +/** + * egg_sm_client_end_session: + * @style: a hint at how to end the session + * @request_confirmation: whether or not the user should get a chance + * to confirm the action + * + * Requests that the session manager end the current session. @style + * indicates how the session should be ended, and + * @request_confirmation indicates whether or not the user should be + * given a chance to confirm the logout/reboot/shutdown. Both of these + * flags are merely hints though; the session manager may choose to + * ignore them. + * + * Return value: %TRUE if the request was sent; %FALSE if it could not + * be (eg, because it could not connect to the session manager). + **/ +gboolean +egg_sm_client_end_session (EggSMClientEndStyle style, + gboolean request_confirmation) +{ + EggSMClient *client = egg_sm_client_get (); + + g_return_val_if_fail (EGG_IS_SM_CLIENT (client), FALSE); + + if (EGG_SM_CLIENT_GET_CLASS (client)->end_session) + { + return EGG_SM_CLIENT_GET_CLASS (client)->end_session (client, style, + request_confirmation); + } + else + return FALSE; +} + +/* Signal-emitting callbacks from platform-specific code */ + +GKeyFile * +egg_sm_client_save_state (EggSMClient *client) +{ + GKeyFile *state_file; + char *group; + + g_return_val_if_fail (client == global_client, NULL); + + state_file = g_key_file_new (); + + g_debug ("Emitting save_state"); + g_signal_emit (client, signals[SAVE_STATE], 0, state_file); + g_debug ("Done emitting save_state"); + + group = g_key_file_get_start_group (state_file); + if (group) + { + g_free (group); + return state_file; + } + else + { + g_key_file_free (state_file); + return NULL; + } +} + +void +egg_sm_client_quit_requested (EggSMClient *client) +{ + g_return_if_fail (client == global_client); + + if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE)) + { + g_debug ("Not emitting quit_requested because no one is listening"); + egg_sm_client_will_quit (client, TRUE); + return; + } + + g_debug ("Emitting quit_requested"); + g_signal_emit (client, signals[QUIT_REQUESTED], 0); + g_debug ("Done emitting quit_requested"); +} + +void +egg_sm_client_quit_cancelled (EggSMClient *client) +{ + g_return_if_fail (client == global_client); + + g_debug ("Emitting quit_cancelled"); + g_signal_emit (client, signals[QUIT_CANCELLED], 0); + g_debug ("Done emitting quit_cancelled"); +} + +void +egg_sm_client_quit (EggSMClient *client) +{ + g_return_if_fail (client == global_client); + + g_debug ("Emitting quit"); + g_signal_emit (client, signals[QUIT], 0); + g_debug ("Done emitting quit"); + + /* FIXME: should we just call gtk_main_quit() here? */ +} + +static void +egg_sm_client_debug_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + G_GNUC_UNUSED gpointer user_data) +{ + static int debug = -1; + + if (debug < 0) + debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL); + + if (debug) + g_log_default_handler (log_domain, log_level, message, NULL); +} diff --git a/moo/eggsmclient/eggsmclient.h b/moo/eggsmclient/eggsmclient.h new file mode 100644 index 00000000..e226d44b --- /dev/null +++ b/moo/eggsmclient/eggsmclient.h @@ -0,0 +1,118 @@ +/* eggsmclient.h + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_SM_CLIENT_H__ +#define __EGG_SM_CLIENT_H__ + +#include +#include "eggsmclient-mangle.h" + +G_BEGIN_DECLS + +#define EGG_TYPE_SM_CLIENT (egg_sm_client_get_type ()) +#define EGG_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT, EggSMClient)) +#define EGG_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT, EggSMClientClass)) +#define EGG_IS_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT)) +#define EGG_IS_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT)) +#define EGG_SM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT, EggSMClientClass)) + +typedef struct _EggSMClient EggSMClient; +typedef struct _EggSMClientClass EggSMClientClass; +typedef struct _EggSMClientPrivate EggSMClientPrivate; + +typedef enum { + EGG_SM_CLIENT_END_SESSION_DEFAULT, + EGG_SM_CLIENT_LOGOUT, + EGG_SM_CLIENT_REBOOT, + EGG_SM_CLIENT_SHUTDOWN +} EggSMClientEndStyle; + +typedef enum { + EGG_SM_CLIENT_MODE_DISABLED, + EGG_SM_CLIENT_MODE_NO_RESTART, + EGG_SM_CLIENT_MODE_NORMAL +} EggSMClientMode; + +struct _EggSMClient +{ + GObject parent; + +}; + +struct _EggSMClientClass +{ + GObjectClass parent_class; + + /* signals */ + void (*save_state) (EggSMClient *client, + GKeyFile *state_file); + + void (*quit_requested) (EggSMClient *client); + void (*quit_cancelled) (EggSMClient *client); + void (*quit) (EggSMClient *client); + + /* virtual methods */ + void (*startup) (EggSMClient *client, + const char *client_id); + void (*set_restart_command) (EggSMClient *client, + int argc, + const char **argv); + void (*will_quit) (EggSMClient *client, + gboolean will_quit); + gboolean (*end_session) (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + + /* Padding for future expansion */ + void (*_egg_reserved1) (void); + void (*_egg_reserved2) (void); + void (*_egg_reserved3) (void); + void (*_egg_reserved4) (void); +}; + +GType egg_sm_client_get_type (void) G_GNUC_CONST; + +GOptionGroup *egg_sm_client_get_option_group (void); + +/* Initialization */ +void egg_sm_client_set_mode (EggSMClientMode mode); +EggSMClientMode egg_sm_client_get_mode (void); +EggSMClient *egg_sm_client_get (void); + +/* Resuming a saved session */ +gboolean egg_sm_client_is_resumed (EggSMClient *client); +GKeyFile *egg_sm_client_get_state_file (EggSMClient *client); + +/* Alternate means of saving state */ +void egg_sm_client_set_restart_command (EggSMClient *client, + int argc, + const char **argv); + +/* Handling "quit_requested" signal */ +void egg_sm_client_will_quit (EggSMClient *client, + gboolean will_quit); + +/* Initiate a logout/reboot/shutdown */ +gboolean egg_sm_client_end_session (EggSMClientEndStyle style, + gboolean request_confirmation); + +G_END_DECLS + + +#endif /* __EGG_SM_CLIENT_H__ */ diff --git a/moo/gtksourceview/Makefile.incl b/moo/gtksourceview/Makefile.incl new file mode 100644 index 00000000..47e17a63 --- /dev/null +++ b/moo/gtksourceview/Makefile.incl @@ -0,0 +1,32 @@ +moo_sources += \ + gtksourceview/gtksourcecontextengine.c \ + gtksourceview/gtksourcecontextengine.h \ + gtksourceview/gtksourceengine.c \ + gtksourceview/gtksourceengine.h \ + gtksourceview/gtksourceiter.c \ + gtksourceview/gtksourceiter.h \ + gtksourceview/gtksourcelanguage-parser-1.c \ + gtksourceview/gtksourcelanguage-parser-2.c \ + gtksourceview/gtksourcelanguage-private.h \ + gtksourceview/gtksourcelanguage.c \ + gtksourceview/gtksourcelanguage.h \ + gtksourceview/gtksourcelanguagemanager.c \ + gtksourceview/gtksourcelanguagemanager.h \ + gtksourceview/gtksourcestyle-private.h \ + gtksourceview/gtksourcestyle.c \ + gtksourceview/gtksourcestyle.h \ + gtksourceview/gtksourcestylescheme.c \ + gtksourceview/gtksourcestylescheme.h \ + gtksourceview/gtksourcestyleschememanager.c \ + gtksourceview/gtksourcestyleschememanager.h \ + gtksourceview/gtksourceview-utils.c \ + gtksourceview/gtksourceview-utils.h \ + gtksourceview/gtktextregion.c \ + gtksourceview/gtktextregion.h \ + gtksourceview/gtksourceview-i18n.h \ + gtksourceview/gtksourceview-marshal.h \ + gtksourceview/gtksourceview-api.h \ + gtksourceview/gtksourcebuffer.h \ + gtksourceview/gtksourceview.h + +# -%- strip:true -%- diff --git a/moo/gtksourceview/gtksourcebuffer.h b/moo/gtksourceview/gtksourcebuffer.h new file mode 100644 index 00000000..4ae3218c --- /dev/null +++ b/moo/gtksourceview/gtksourcebuffer.h @@ -0,0 +1 @@ +#include "mooutils/moocompat.h" diff --git a/moo/gtksourceview/gtksourcecontextengine.c b/moo/gtksourceview/gtksourcecontextengine.c new file mode 100644 index 00000000..1b7053cc --- /dev/null +++ b/moo/gtksourceview/gtksourcecontextengine.c @@ -0,0 +1,6797 @@ +/* -*- mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- + * gtksourcecontextengine.c + * + * Copyright (C) 2003 - Gustavo Giráldez + * Copyright (C) 2005, 2006 - Marco Barisione, Emanuele Aina + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gtksourceview-i18n.h" +#include "gtksourcecontextengine.h" +#include "gtktextregion.h" +#include "gtksourcelanguage-private.h" +#include "gtksourcebuffer.h" +#include "gtksourcestyle-private.h" +#include +#include +#include + +#undef DEBUG +#undef ENABLE_DEBUG +#undef ENABLE_PROFILE +#undef ENABLE_CHECK_TREE +#undef ENABLE_MEMORY_DEBUG /* define it to make it print memory usage information */ + /* it won't work with GRegex */ + +#ifdef ENABLE_DEBUG +#define DEBUG(x) (x) +#else +#define DEBUG(x) +#endif + +#ifdef ENABLE_PROFILE +#define PROFILE(x) (x) +#else +#define PROFILE(x) +#endif + +#if defined (ENABLE_DEBUG) || defined (ENABLE_PROFILE) || \ + defined (ENABLE_CHECK_TREE) +#define NEED_DEBUG_ID +#endif + +/* Regex used to match "\%{...@start}". */ +#define START_REF_REGEX "(?definitions, (id))) + +#define HAS_OPTION(def,opt) (((def)->flags & GTK_SOURCE_CONTEXT_##opt) != 0) + +/* Can the context be terminated by ancestor? */ +/* Root context can't be terminated; its child may not be terminated by it; + * grandchildren look at the flag */ +#define ANCESTOR_CAN_END_CONTEXT(ctx) \ + ((ctx)->parent != NULL && (ctx)->parent->parent != NULL && \ + (!HAS_OPTION ((ctx)->definition, EXTEND_PARENT) || !(ctx)->all_ancestors_extend)) + +/* Root context and its children have this TRUE; grandchildren use the flag */ +#define CONTEXT_EXTENDS_PARENT(ctx) \ + ((ctx)->parent == NULL || (ctx)->parent->parent == NULL || \ + HAS_OPTION ((ctx)->definition, EXTEND_PARENT)) + +/* Root and its children have this FALSE; grandchildren use the flag */ +#define CONTEXT_ENDS_PARENT(ctx) \ + ((ctx)->parent != NULL && (ctx)->parent->parent != NULL && \ + HAS_OPTION ((ctx)->definition, END_PARENT)) +#define SEGMENT_ENDS_PARENT(s) CONTEXT_ENDS_PARENT ((s)->context) + +/* Does the segment terminate at line end? */ +/* Root segment doesn't, children look at the flag */ +#define CONTEXT_END_AT_LINE_END(ctx) \ + ((ctx)->parent != NULL && HAS_OPTION ((ctx)->definition, END_AT_LINE_END)) +#define SEGMENT_END_AT_LINE_END(s) CONTEXT_END_AT_LINE_END((s)->context) + +#define CONTEXT_IS_SIMPLE(c) ((c)->definition->type == CONTEXT_TYPE_SIMPLE) +#define CONTEXT_IS_CONTAINER(c) ((c)->definition->type == CONTEXT_TYPE_CONTAINER) +#define SEGMENT_IS_INVALID(s) ((s)->context == NULL) +#define SEGMENT_IS_SIMPLE(s) CONTEXT_IS_SIMPLE ((s)->context) +#define SEGMENT_IS_CONTAINER(s) CONTEXT_IS_CONTAINER ((s)->context) + +#define ENGINE_ID(ce) ((ce)->priv->ctx_data->lang->priv->id) +#define ENGINE_STYLES_MAP(ce) ((ce)->priv->ctx_data->lang->priv->styles) + +typedef struct _RegexInfo RegexInfo; +typedef struct _RegexAndMatch RegexAndMatch; +typedef struct _Regex Regex; +typedef struct _SubPatternDefinition SubPatternDefinition; +typedef struct _SubPattern SubPattern; +typedef struct _Segment Segment; +typedef struct _Context Context; +typedef struct _ContextPtr ContextPtr; +typedef struct _ContextDefinition ContextDefinition; +typedef struct _DefinitionChild DefinitionChild; +typedef struct _DefinitionsIter DefinitionsIter; +typedef struct _LineInfo LineInfo; +typedef struct _InvalidRegion InvalidRegion; + +typedef enum { + GTK_SOURCE_CONTEXT_ENGINE_ERROR_DUPLICATED_ID = 0, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_ARGS, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_PARENT, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_REF, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_WHERE, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_START_REF, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_REGEX, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_STYLE, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_BAD_FILE +} GtkSourceContextEngineError; + +typedef enum { + CONTEXT_TYPE_SIMPLE = 0, + CONTEXT_TYPE_CONTAINER +} ContextType; + +typedef enum { + SUB_PATTERN_WHERE_DEFAULT = 0, + SUB_PATTERN_WHERE_START, + SUB_PATTERN_WHERE_END +} SubPatternWhere; + +struct _RegexInfo +{ + gchar *pattern; + GRegexCompileFlags flags; +}; + +/* glib has now so fscking nice API! */ +struct _RegexAndMatch +{ + GRegex *regex; + GMatchInfo *match; +}; + +/* We do not use directly GRegex to allow the use of "\%{...@start}". */ +struct _Regex +{ + union { + RegexAndMatch regex; + RegexInfo info; + } u; + guint ref_count; + guint resolved : 1; +}; + +struct _ContextDefinition +{ + gchar *id; + + ContextType type; + union + { + Regex *match; + struct { + Regex *start; + Regex *end; + } start_end; + } u; + + /* Name of the style used for contexts of this type. */ + gchar *default_style; + + /* This is a list of DefinitionChild pointers. */ + GSList *children; + + /* Sub patterns (list of SubPatternDefinition pointers.) */ + GSList *sub_patterns; + guint n_sub_patterns; + + /* Union of every regular expression we can find from this + * context. */ + Regex *reg_all; + + guint flags : 8; + guint ref_count : 24; +}; + +struct _SubPatternDefinition +{ +#ifdef NEED_DEBUG_ID + /* We need the id only for debugging. */ + gchar *id; +#endif + gchar *style; + SubPatternWhere where; + + /* index in the ContextDefinition's list */ + guint index; + + union + { + gint num; + gchar *name; + } u; + guint is_named : 1; +}; + +struct _DefinitionChild +{ + union + { + /* Equal to definition->id, used when it's not resolved yet */ + gchar *id; + ContextDefinition *definition; + } u; + + gchar *style; + + /* Whether this child is a reference to all child contexts of + * . */ + guint is_ref_all : 1; + /* Whether it is resolved, i.e. points to actual context definition. */ + guint resolved : 1; + /* Whether style is overridden, i.e. use child->style instead of what definition says. */ + guint override_style : 1; + /* Whether style should be ignored for this and all child contexts. */ + guint override_style_deep : 1; +}; + +struct _DefinitionsIter +{ + GSList *children_stack; +}; + +struct _Context +{ + /* Definition for the context. */ + ContextDefinition *definition; + + Context *parent; + ContextPtr *children; + + /* This is the regex returned by regex_resolve() called on + * definition->start_end.end. */ + Regex *end; + /* The regular expression containing every regular expression that + * could be matched in this context. */ + Regex *reg_all; + + /* Either definition->default_style or child_def->style, not copied. */ + const gchar *style; + GtkTextTag *tag; + GtkTextTag **subpattern_tags; + + guint ref_count; + /* see context_freeze() */ + guint frozen : 1; + /* Do all the ancestors extend their parent? */ + guint all_ancestors_extend : 1; + /* Do not apply styles to children contexts */ + guint ignore_children_style : 1; +}; + +struct _ContextPtr +{ + ContextDefinition *definition; + + ContextPtr *next; + + union { + Context *context; + GHashTable *hash; /* char* -> Context* */ + } u; + guint fixed : 1; +}; + +struct _GtkSourceContextReplace +{ + gchar *id; + gchar *replace_with; +}; + +struct _Segment +{ + Segment *parent; + Segment *next; + Segment *prev; + Segment *children; + Segment *last_child; + + /* This is NULL if and only if it's a dummy segment which denotes + * inserted or deleted text. */ + Context *context; + + /* Subpatterns found in this segment. */ + SubPattern *sub_patterns; + + /* The context is used in the interval [start_at; end_at). */ + gint start_at; + gint end_at; + + /* In case of container contexts, start_len/end_len is length in chars + * of start/end match. */ + gint start_len; + gint end_len; + + /* Whether this segment is a whole good segment, or it's an + * an end of bigger one left after erase_segments() call. */ + guint is_start : 1; +}; + +struct _SubPattern +{ + SubPatternDefinition *definition; + gint start_at; + gint end_at; + SubPattern *next; +}; + +/* Line terminator characters (\n, \r, \r\n, or unicode paragraph separator) + * are removed from the line text. The problem is that pcre does not understand + * arbitrary line terminators, so $ in pcre means (?=\n) (not quite, it's also + * end of matched string), while we really need "((?=\r\n)|(?=[\r\n])|(?=\xE2\x80\xA9)|$)". + * It could be worked around by replacing line terminator in matched text with + * \n, but it's a good source of errors, since offsets (not all, unfortunately) returned + * from pcre need to be compared to line length, and adjusted when necessary. + * Not using line terminator only means that \n can't be in patterns, it's not a + * big deal: line end can't be highlighted anyway; if a rule needs to match it, it can + * can use "$" as start and "^" as end (not in a single pattern of course, "$^" will + * never match). + * + * UPDATE: the above isn't true anymore, pcre can do arbitrary line terminators. + * BUT: how do we know whether we should get one/two/N lines to match? Single-line + * case to highlight end of line is covered by above ($). I do not feel brave enough + * to modify this now for no real benefit. (muntyan) + */ +#define NEXT_LINE_OFFSET(l_) ((l_)->start_at + (l_)->char_length + (l_)->eol_length) +struct _LineInfo +{ + /* Line text. */ + gchar *text; + /* Character offset of the line in text buffer. */ + gint start_at; + /* Character length of line terminator, or 0 if it's the + * last line in buffer. */ + gint eol_length; + /* Length of the line text not including line terminator */ + gint char_length; + gint byte_length; +}; + +struct _InvalidRegion +{ + gboolean empty; + GtkTextMark *start; + GtkTextMark *end; + /* offset_at(end) - delta == original offset, + * i.e. offset in the tree */ + gint delta; +}; + +struct _GtkSourceContextData +{ + guint ref_count; + + GtkSourceLanguage *lang; + + /* Contains every ContextDefinition indexed by its id. */ + GHashTable *definitions; +}; + +struct _GtkSourceContextEnginePrivate +{ + GtkSourceContextData *ctx_data; + + GtkTextBuffer *buffer; + GtkSourceStyleScheme *style_scheme; + + /* All tags indexed by style name: values are GSList's of tags, ref()'ed. */ + GHashTable *tags; + /* Number of all syntax tags created by the engine, needed to set correct + * tag priorities */ + guint n_tags; + + /* Whether or not to actually highlight the buffer. */ + gboolean highlight; + + /* Whether highlighting was disabled because of errors. */ + gboolean disabled; + + /* Region covering the unhighlighted text. */ + GtkTextRegion *refresh_region; + + /* Tree of contexts. */ + Context *root_context; + Segment *root_segment; + Segment *hint; + Segment *hint2; + /* list of Segment* */ + GSList *invalid; + InvalidRegion invalid_region; + + guint first_update; + guint incremental_update; + + /* Views highlight requests. */ + GtkTextRegion *highlight_requests; + +#ifdef ENABLE_MEMORY_DEBUG + guint mem_usage_timeout; +#endif +}; + + +#ifdef ENABLE_CHECK_TREE +static void check_tree (GtkSourceContextEngine *ce); +static void check_segment_list (Segment *segment); +static void check_segment_children (Segment *segment); +#define CHECK_TREE check_tree +#define CHECK_SEGMENT_LIST check_segment_list +#define CHECK_SEGMENT_CHILDREN check_segment_children +#else +#define CHECK_TREE(ce) +#define CHECK_SEGMENT_LIST(s) +#define CHECK_SEGMENT_CHILDREN(s) +#endif + + +static GQuark gtk_source_context_engine_error_quark (void) G_GNUC_CONST; + +static Segment *create_segment (GtkSourceContextEngine *ce, + Segment *parent, + Context *context, + gint start_at, + gint end_at, + gboolean is_start, + Segment *hint); +static Segment *segment_new (GtkSourceContextEngine *ce, + Segment *parent, + Context *context, + gint start_at, + gint end_at, + gboolean is_start); +static Context *context_new (Context *parent, + ContextDefinition *definition, + const gchar *line_text, + const gchar *style, + gboolean ignore_children_style); +static void context_unref (Context *context); +static void context_freeze (Context *context); +static void context_thaw (Context *context); +static void erase_segments (GtkSourceContextEngine *ce, + gint start, + gint end, + Segment *hint); +static void segment_remove (GtkSourceContextEngine *ce, + Segment *segment); + +static void find_insertion_place (Segment *segment, + gint offset, + Segment **parent, + Segment **prev, + Segment **next, + Segment *hint); +static void segment_destroy (GtkSourceContextEngine *ce, + Segment *segment); +static ContextDefinition *context_definition_ref(ContextDefinition *definition); +static void context_definition_unref(ContextDefinition *definition); + +static void segment_extend (Segment *state, + gint end_at); +static Context *ancestor_context_ends_here (Context *state, + LineInfo *line, + gint pos); +static void definition_iter_init (DefinitionsIter *iter, + ContextDefinition *definition); +static DefinitionChild *definition_iter_next (DefinitionsIter *iter); +static void definition_iter_destroy (DefinitionsIter *iter); + +static void update_syntax (GtkSourceContextEngine *ce, + const GtkTextIter *end, + gint time); +static void install_idle_worker (GtkSourceContextEngine *ce); +static void install_first_update (GtkSourceContextEngine *ce); + +#ifdef ENABLE_MEMORY_DEBUG +static gboolean mem_usage_timeout (GtkSourceContextEngine *ce); +#endif + + +/* TAGS AND STUFF -------------------------------------------------------------- */ + +struct BufAndIters { + GtkTextBuffer *buffer; + const GtkTextIter *start, *end; +}; + +static void +unhighlight_region_cb (G_GNUC_UNUSED gpointer style, + GSList *tags, + gpointer user_data) +{ + struct BufAndIters *data = user_data; + + while (tags != NULL) + { + gtk_text_buffer_remove_tag (data->buffer, + tags->data, + data->start, + data->end); + tags = tags->next; + } +} + +static void +unhighlight_region (GtkSourceContextEngine *ce, + const GtkTextIter *start, + const GtkTextIter *end) +{ + struct BufAndIters data; + + data.buffer = ce->priv->buffer; + data.start = start; + data.end = end; + + if (gtk_text_iter_equal (start, end)) + return; + + g_hash_table_foreach (ce->priv->tags, (GHFunc) unhighlight_region_cb, &data); +} + +#define MAX_STYLE_DEPENDENCY_DEPTH 50 + +static void +set_tag_style (GtkSourceContextEngine *ce, + GtkTextTag *tag, + const gchar *style_id) +{ + GtkSourceStyle *style; + + const char *map_to = style_id; + + int guard = 0; + + g_return_if_fail (GTK_IS_TEXT_TAG (tag)); + g_return_if_fail (style_id != NULL); + + _gtk_source_style_apply (NULL, tag); + + if (ce->priv->style_scheme == NULL) + return; + + style = gtk_source_style_scheme_get_style (ce->priv->style_scheme, style_id); + + while (style == NULL) + { + GtkSourceStyleInfo *info; + + if (guard > MAX_STYLE_DEPENDENCY_DEPTH) + { + g_warning ("Potential circular dependency between styles detected for style '%s'", style_id); + break; + } + + ++guard; + + /* FIXME Style references really must be fixed, both parser for + * sane use in lang files, and engine for safe use. */ + info = g_hash_table_lookup (ENGINE_STYLES_MAP(ce), map_to); + + map_to = (info != NULL) ? info->map_to : NULL; + + if (!map_to) + break; + + style = gtk_source_style_scheme_get_style (ce->priv->style_scheme, map_to); + } + + /* not having style is fine, since parser checks validity of every style reference, + * so we don't need to spit a warning here */ + if (style != NULL) + _gtk_source_style_apply (style, tag); +} + +static GtkTextTag * +create_tag (GtkSourceContextEngine *ce, + const gchar *style_id) +{ + GSList *tags; + GtkTextTag *new_tag; + + g_assert (style_id != NULL); + + tags = g_hash_table_lookup (ce->priv->tags, style_id); + + new_tag = gtk_text_buffer_create_tag (ce->priv->buffer, NULL, NULL); + /* It must have priority lower than user tags but still + * higher than highlighting tags created before */ + gtk_text_tag_set_priority (new_tag, ce->priv->n_tags); + set_tag_style (ce, new_tag, style_id); + ce->priv->n_tags += 1; + + tags = g_slist_prepend (tags, g_object_ref (new_tag)); + g_hash_table_insert (ce->priv->tags, g_strdup (style_id), tags); + + return new_tag; +} + +/* Find tag which has to be overridden. */ +static GtkTextTag * +get_parent_tag (Context *context, + const char *style) +{ + while (context != NULL) + { + /* Lang files may repeat same style for nested contexts, + * ignore them. */ + if (context->style && + strcmp (context->style, style) != 0) + { + g_assert (context->tag != NULL); + return context->tag; + } + + context = context->parent; + } + + return NULL; +} + +static GtkTextTag * +get_tag_for_parent (GtkSourceContextEngine *ce, + const char *style, + Context *parent) +{ + GSList *tags; + GtkTextTag *parent_tag = NULL; + GtkTextTag *tag; + + g_return_val_if_fail (style != NULL, NULL); + + parent_tag = get_parent_tag (parent, style); + tags = g_hash_table_lookup (ce->priv->tags, style); + + if (tags && (!parent_tag || + gtk_text_tag_get_priority (tags->data) > gtk_text_tag_get_priority (parent_tag))) + { + GSList *link; + + tag = tags->data; + + /* Now get the tag with lowest priority, so that tag lists do not grow + * indefinitely. */ + for (link = tags->next; link != NULL; link = link->next) + { + if (parent_tag && + gtk_text_tag_get_priority (link->data) < gtk_text_tag_get_priority (parent_tag)) + break; + tag = link->data; + } + } + else + { + tag = create_tag (ce, style); + +#ifdef ENABLE_DEBUG + { + GString *style_path = g_string_new (style); + gint n; + + while (parent != NULL) + { + if (parent->style != NULL) + { + g_string_prepend (style_path, "/"); + g_string_prepend (style_path, + parent->style); + } + + parent = parent->parent; + } + + tags = g_hash_table_lookup (ce->priv->tags, style); + n = g_slist_length (tags); + g_print ("created %d tag for style %s: %s\n", n, style, style_path->str); + g_string_free (style_path, TRUE); + } +#endif + } + + return tag; +} + +static GtkTextTag * +get_subpattern_tag (GtkSourceContextEngine *ce, + Context *context, + SubPatternDefinition *sp_def) +{ + if (sp_def->style == NULL) + return NULL; + + g_assert (sp_def->index < context->definition->n_sub_patterns); + + if (context->subpattern_tags == NULL) + context->subpattern_tags = g_new0 (GtkTextTag*, context->definition->n_sub_patterns); + + if (context->subpattern_tags[sp_def->index] == NULL) + context->subpattern_tags[sp_def->index] = get_tag_for_parent (ce, sp_def->style, context); + + g_return_val_if_fail (context->subpattern_tags[sp_def->index] != NULL, NULL); + return context->subpattern_tags[sp_def->index]; +} + +static GtkTextTag * +get_context_tag (GtkSourceContextEngine *ce, + Context *context) +{ + if (context->style != NULL && context->tag == NULL) + context->tag = get_tag_for_parent (ce, + context->style, + context->parent); + return context->tag; +} + +static void +apply_tags (GtkSourceContextEngine *ce, + Segment *segment, + gint start_offset, + gint end_offset) +{ + GtkTextTag *tag; + GtkTextIter start_iter, end_iter; + GtkTextBuffer *buffer = ce->priv->buffer; + SubPattern *sp; + Segment *child; + + g_assert (segment != NULL); + + if (SEGMENT_IS_INVALID (segment)) + return; + + if (segment->start_at >= end_offset || segment->end_at <= start_offset) + return; + + start_offset = MAX (start_offset, segment->start_at); + end_offset = MIN (end_offset, segment->end_at); + + tag = get_context_tag (ce, segment->context); + + if (tag != NULL) + { + gint style_start_at, style_end_at; + + style_start_at = start_offset; + style_end_at = end_offset; + + if (HAS_OPTION (segment->context->definition, STYLE_INSIDE)) + { + style_start_at = MAX (segment->start_at + segment->start_len, start_offset); + style_end_at = MIN (segment->end_at - segment->end_len, end_offset); + } + + if (style_start_at > style_end_at) + { + g_critical ("oops"); + } + else + { + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, style_start_at); + end_iter = start_iter; + gtk_text_iter_forward_chars (&end_iter, style_end_at - style_start_at); + gtk_text_buffer_apply_tag (ce->priv->buffer, tag, &start_iter, &end_iter); + } + } + + for (sp = segment->sub_patterns; sp != NULL; sp = sp->next) + { + if (sp->start_at >= start_offset && sp->end_at <= end_offset) + { + tag = get_subpattern_tag (ce, segment->context, sp->definition); + + if (tag != NULL) + { + gint start = MAX (start_offset, sp->start_at); + gint end = MIN (end_offset, sp->end_at); + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + end_iter = start_iter; + gtk_text_iter_forward_chars (&end_iter, end - start); + gtk_text_buffer_apply_tag (ce->priv->buffer, tag, &start_iter, &end_iter); + } + } + } + + for (child = segment->children; + child != NULL && child->start_at < end_offset; + child = child->next) + { + if (child->end_at > start_offset) + apply_tags (ce, child, start_offset, end_offset); + } +} + +/** + * highlight_region: + * + * @ce: a #GtkSourceContextEngine. + * @start: the beginning of the region to highlight. + * @end: the end of the region to highlight. + * + * Highlights the specified region. + */ +static void +highlight_region (GtkSourceContextEngine *ce, + GtkTextIter *start, + GtkTextIter *end) +{ +#ifdef ENABLE_PROFILE + GTimer *timer; +#endif + + if (gtk_text_iter_starts_line (end)) + gtk_text_iter_backward_char (end); + if (gtk_text_iter_compare (start, end) >= 0) + return; + +#ifdef ENABLE_PROFILE + timer = g_timer_new (); +#endif + + /* First we need to delete tags in the regions. */ + unhighlight_region (ce, start, end); + + apply_tags (ce, ce->priv->root_segment, + gtk_text_iter_get_offset (start), + gtk_text_iter_get_offset (end)); + +#ifdef ENABLE_PROFILE + g_print ("highlight (from %d to %d), %g ms elapsed\n", + gtk_text_iter_get_offset (start), + gtk_text_iter_get_offset (end), + g_timer_elapsed (timer, NULL) * 1000); + g_timer_destroy (timer); +#endif +} + +/** + * ensure_highlighted: + * + * @ce: a #GtkSourceContextEngine. + * @start: the beginning of the region to highlight. + * @end: the end of the region to highlight. + * + * Updates text tags in reanalyzed parts of given area. + * It applies tags according to whatever is in the syntax + * tree currently, so highlighting may not be correct + * (gtk_source_context_engine_update_highlight is the method + * that actually ensures correct highlighting). + */ +static void +ensure_highlighted (GtkSourceContextEngine *ce, + const GtkTextIter *start, + const GtkTextIter *end) +{ + GtkTextRegion *region; + GtkTextRegionIterator reg_iter; + + /* Get the subregions not yet highlighted. */ + region = gtk_text_region_intersect (ce->priv->refresh_region, start, end); + + if (region == NULL) + return; + + gtk_text_region_get_iterator (region, ®_iter, 0); + + /* Highlight all subregions from the intersection. + * hopefully this will only be one subregion. */ + while (!gtk_text_region_iterator_is_end (®_iter)) + { + GtkTextIter s, e; + gtk_text_region_iterator_get_subregion (®_iter, &s, &e); + highlight_region (ce, &s, &e); + gtk_text_region_iterator_next (®_iter); + } + + gtk_text_region_destroy (region, TRUE); + + /* Remove the just highlighted region. */ + gtk_text_region_subtract (ce->priv->refresh_region, start, end); +} + +/** + * refresh_range: + * + * @ce: a #GtkSourceContextEngine. + * @start: the beginning of updated area. + * @end: the end of updated area. + * @modify_refresh_region: whether updated area should be added to + * refresh_region. + * + * Marks the area as updated - notifies view about it, and adds it to + * refresh_region if @modify_refresh_region is %TRUE (update_syntax may + * process huge area though actually updated is couple of lines, so in + * that case update_syntax() takes care of refresh_region, and this + * function only notifies the view). + */ +static void +refresh_range (GtkSourceContextEngine *ce, + const GtkTextIter *start, + const GtkTextIter *end, + gboolean modify_refresh_region) +{ + GtkTextIter real_end; + + if (gtk_text_iter_equal (start, end)) + return; + + if (modify_refresh_region) + gtk_text_region_add (ce->priv->refresh_region, start, end); + + /* Here we need to make sure we do not make it redraw next line */ + real_end = *end; + if (gtk_text_iter_starts_line (&real_end)) + /* I don't quite like this here, but at least it won't jump into + * the middle of \r\n */ + gtk_text_iter_backward_cursor_position (&real_end); + + g_signal_emit_by_name (ce->priv->buffer, + "highlight_updated", + start, + &real_end); +} + + +/* SEGMENT TREE ----------------------------------------------------------- */ + +/** + * segment_cmp: + * + * @s1: first segment. + * @s2: second segment. + * + * Compares segments by their offset, used to sort list of invalid segments. + * + * Returns: an integer like strcmp() does. + */ +static gint +segment_cmp (Segment *s1, + Segment *s2) +{ + if (s1->start_at < s2->start_at) + return -1; + else if (s1->start_at > s2->start_at) + return 1; + /* one of them must be zero-length */ + g_assert (s1->start_at == s1->end_at || s2->start_at == s2->end_at); +#ifdef ENABLE_DEBUG + /* A new zero-length segment should never be created if there is + * already an invalid segment. */ + g_assert_not_reached (); +#endif + g_return_val_if_reached (s1->end_at < s2->end_at ? -1 : + (s1->end_at > s2->end_at ? 1 : 0)); +} + +/** + * add_invalid: + * + * @ce: the engine. + * @segment: segment. + * + * Inserts segment into the list of invalid segments. + * Called whenever new invalid segment is created or when + * a segment is marked invalid. + */ +static void +add_invalid (GtkSourceContextEngine *ce, + Segment *segment) +{ +#ifdef ENABLE_CHECK_TREE + g_assert (!g_slist_find (ce->priv->invalid, segment)); +#endif + g_return_if_fail (SEGMENT_IS_INVALID (segment)); + + ce->priv->invalid = g_slist_insert_sorted (ce->priv->invalid, + segment, + (GCompareFunc) segment_cmp); + + DEBUG (g_print ("%d invalid\n", g_slist_length (ce->priv->invalid))); +} + +/** + * remove_invalid: + * + * @ce: the engine. + * @segment: segment. + * + * Removes segment from the list of invalid segments; + * Called when an invalid segment is destroyed (invalid + * segments never become valid). + */ +static void +remove_invalid (GtkSourceContextEngine *ce, + Segment *segment) +{ + g_assert (g_slist_find (ce->priv->invalid, segment) != NULL); + ce->priv->invalid = g_slist_remove (ce->priv->invalid, segment); +} + +/** + * fix_offsets_insert_: + * + * @segment: segment. + * @start: start offset. + * @delta: length of inserted text. + * + * Recursively updates offsets after inserting text. To be called + * only from insert_range(). + */ +static void +fix_offsets_insert_ (Segment *segment, + gint start, + gint delta) +{ + Segment *child; + SubPattern *sp; + + g_assert (segment->start_at >= start); + + if (delta == 0) + return; + + segment->start_at += delta; + segment->end_at += delta; + + for (child = segment->children; child != NULL; child = child->next) + fix_offsets_insert_ (child, start, delta); + + for (sp = segment->sub_patterns; sp != NULL; sp = sp->next) + { + sp->start_at += delta; + sp->end_at += delta; + } +} + +/** + * find_insertion_place_forward_: + * + * @segment: (grand)parent segment the new one should be inserted into. + * @offset: offset at which text is inserted. + * @start: segment from which to start search (to avoid + * walking whole tree). + * @parent: initialized with the parent of new segment. + * @prev: initialized with the previous sibling of new segment. + * @next: initialized with the next sibling of new segment. + * + * Auxiliary function used in find_insertion_place(). + */ +static void +find_insertion_place_forward_ (Segment *segment, + gint offset, + Segment *start, + Segment **parent, + Segment **prev, + Segment **next) +{ + Segment *child; + + g_assert (start->end_at < offset); + + for (child = start; child != NULL; child = child->next) + { + if (child->start_at <= offset && child->end_at >= offset) + { + find_insertion_place (child, offset, parent, prev, next, NULL); + return; + } + + if (child->end_at == offset) + { + if (SEGMENT_IS_INVALID (child)) + { + *parent = child; + *prev = NULL; + *next = NULL; + } + else + { + *prev = child; + *next = child->next; + *parent = segment; + } + + return; + } + + if (child->end_at < offset) + { + *prev = child; + continue; + } + + if (child->start_at > offset) + { + *next = child; + break; + } + + g_assert_not_reached (); + } + + *parent = segment; +} + +/** + * find_insertion_place_backward_: + * + * @segment: (grand)parent segment the new one should be inserted into. + * @offset: offset at which text is inserted. + * @start: segment from which to start search (to avoid + * walking whole tree). + * @parent: initialized with the parent of new segment. + * @prev: initialized with the previous sibling of new segment. + * @next: initialized with the next sibling of new segment. + * + * Auxiliary function used in find_insertion_place(). + */ +static void +find_insertion_place_backward_ (Segment *segment, + gint offset, + Segment *start, + Segment **parent, + Segment **prev, + Segment **next) +{ + Segment *child; + + g_assert (start->end_at >= offset); + + for (child = start; child != NULL; child = child->prev) + { + if (child->start_at <= offset && child->end_at >= offset) + { + find_insertion_place (child, offset, parent, prev, next, NULL); + return; + } + + if (child->end_at == offset) + { + if (SEGMENT_IS_INVALID (child)) + { + *parent = child; + *prev = NULL; + *next = NULL; + } + else + { + *prev = child; + *next = child->next; + *parent = segment; + } + + return; + } + + if (child->end_at < offset) + { + *prev = child; + *next = child->next; + break; + } + + if (child->start_at > offset) + { + *next = child; + continue; + } + + g_assert_not_reached (); + } + + *parent = segment; +} + +/** + * find_insertion_place: + * + * @segment: (grand)parent segment the new one should be inserted into. + * @offset: offset at which text is inserted. + * @start: segment from which to start search (to avoid + * walking whole tree). + * @parent: initialized with the parent of new segment. + * @prev: initialized with the previous sibling of new segment. + * @hint: a segment somewhere near insertion place to optimize search. + * + * After text is inserted, a new invalid segment is created and inserted + * into the tree. This function finds an appropriate position for the new + * segment. To make it faster, it uses hint and calls + * find_insertion_place_forward_ or find_insertion_place_backward_ depending + * on position of offset relative to hint. + * There is no return value, it always succeeds (or crashes). + */ +static void +find_insertion_place (Segment *segment, + gint offset, + Segment **parent, + Segment **prev, + Segment **next, + Segment *hint) +{ + g_assert (segment->start_at <= offset && segment->end_at >= offset); + + *prev = NULL; + *next = NULL; + + if (SEGMENT_IS_INVALID (segment) || segment->children == NULL) + { + *parent = segment; + return; + } + + if (segment->start_at == offset) + { +#ifdef ENABLE_CHECK_TREE + g_assert (!segment->children || + !SEGMENT_IS_INVALID (segment->children) || + segment->children->start_at > offset); +#endif + + *parent = segment; + *next = segment->children; + + return; + } + + if (hint != NULL) + while (hint != NULL && hint->parent != segment) + hint = hint->parent; + + if (hint == NULL) + hint = segment->children; + + if (hint->end_at < offset) + find_insertion_place_forward_ (segment, offset, hint, parent, prev, next); + else + find_insertion_place_backward_ (segment, offset, hint, parent, prev, next); +} + +/** + * get_invalid_at: + * + * @ce: the engine. + * @offset: the offset. + * + * Finds invalid segment adjacent to offset (i.e. such that start <= offset <= end), + * if any. + * + * Returns: invalid segment or %NULL. + */ +static Segment * +get_invalid_at (GtkSourceContextEngine *ce, + gint offset) +{ + GSList *link = ce->priv->invalid; + + while (link != NULL) + { + Segment *segment = link->data; + + link = link->next; + + if (segment->start_at > offset) + break; + + if (segment->end_at < offset) + continue; + + return segment; + } + + return NULL; +} + +/** + * segment_add_subpattern: + * + * @state: the segment. + * @sp: subpattern. + * + * Prepends subpattern to subpatterns list in the segment. + */ +static void +segment_add_subpattern (Segment *state, + SubPattern *sp) +{ + sp->next = state->sub_patterns; + state->sub_patterns = sp; +} + +/** + * sub_pattern_new: + * + * @segment: the segment. + * @start_at: start offset of the subpattern. + * @end_at: end offset of the subpattern. + * @sp_def: the subppatern definition. + * + * Creates new subpattern and adds it to the segment's + * subpatterns list. + * + * Returns: new subpattern. + */ +static SubPattern * +sub_pattern_new (Segment *segment, + gint start_at, + gint end_at, + SubPatternDefinition *sp_def) +{ + SubPattern *sp; + + sp = g_slice_new0 (SubPattern); + sp->start_at = start_at; + sp->end_at = end_at; + sp->definition = sp_def; + + segment_add_subpattern (segment, sp); + + return sp; +} + +/** + * sub_pattern_free: + * + * @sp: subppatern. + * + * Calls g_free on subpattern, was useful for debugging. + */ +static inline void +sub_pattern_free (SubPattern *sp) +{ +#ifdef ENABLE_DEBUG + memset (sp, 1, sizeof (SubPattern)); +#else + g_slice_free (SubPattern, sp); +#endif +} + +/** + * segment_make_invalid_: + * + * @ce: the engine. + * @segment: segment to invalidate. + * + * Invalidates segment. Called only from insert_range(). + */ +static void +segment_make_invalid_ (GtkSourceContextEngine *ce, + Segment *segment) +{ + Context *ctx; + SubPattern *sp; + + g_assert (!SEGMENT_IS_INVALID (segment)); + + sp = segment->sub_patterns; + segment->sub_patterns = NULL; + + while (sp != NULL) + { + SubPattern *next = sp->next; + sub_pattern_free (sp); + sp = next; + } + + ctx = segment->context; + segment->context = NULL; + segment->is_start = FALSE; + segment->start_len = 0; + segment->end_len = 0; + add_invalid (ce, segment); + context_unref (ctx); +} + +/** + * simple_segment_split_: + * + * @ce: the engine. + * @segment: segment to split. + * @offset: offset at which text insertion occurred. + * + * Creates a new invalid segment and inserts it in the middle + * of the given one. Called from insert_range() to mark inserted + * text. + * + * Returns: new invalid segment. + */ +static Segment * +simple_segment_split_ (GtkSourceContextEngine *ce, + Segment *segment, + gint offset) +{ + SubPattern *sp; + Segment *new_segment, *invalid; + gint end_at = segment->end_at; + + g_assert (SEGMENT_IS_SIMPLE (segment)); + g_assert (segment->start_at < offset && offset < segment->end_at); + + sp = segment->sub_patterns; + segment->sub_patterns = NULL; + segment->end_at = offset; + + invalid = create_segment (ce, segment->parent, NULL, offset, offset, FALSE, segment); + new_segment = create_segment (ce, segment->parent, segment->context, offset, end_at, FALSE, invalid); + + while (sp != NULL) + { + Segment *append_to = NULL; + SubPattern *next = sp->next; + + if (sp->end_at <= offset) + { + append_to = segment; + } + else if (sp->start_at >= offset) + { + append_to = new_segment; + } + else + { + sub_pattern_new (new_segment, + offset, + sp->end_at, + sp->definition); + sp->end_at = offset; + append_to = segment; + } + + segment_add_subpattern (append_to, sp); + + sp = next; + } + + return invalid; +} + +/** + * invalidate_region: + * + * @ce: a #GtkSourceContextEngine. + * @offset: the start of invalidated area. + * @length: the length of the area. + * + * Adds the area to the invalid region and queues highlighting. + * @length may be negative which means deletion; positive + * means insertion; 0 means "something happened here", it's + * treated as zero-length insertion. + */ +static void +invalidate_region (GtkSourceContextEngine *ce, + gint offset, + gint length) +{ + InvalidRegion *region = &ce->priv->invalid_region; + GtkTextBuffer *buffer = ce->priv->buffer; + GtkTextIter iter; + gint end_offset; + + end_offset = length >= 0 ? offset + length : offset; + + if (region->empty) + { + region->empty = FALSE; + region->delta = length; + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset); + gtk_text_buffer_move_mark (buffer, region->start, &iter); + + gtk_text_iter_set_offset (&iter, end_offset); + gtk_text_buffer_move_mark (buffer, region->end, &iter); + } + else + { + gtk_text_buffer_get_iter_at_mark (buffer, &iter, region->start); + + if (gtk_text_iter_get_offset (&iter) > offset) + { + gtk_text_iter_set_offset (&iter, offset); + gtk_text_buffer_move_mark (buffer, region->start, &iter); + } + + gtk_text_buffer_get_iter_at_mark (buffer, &iter, region->end); + + if (gtk_text_iter_get_offset (&iter) < end_offset) + { + gtk_text_iter_set_offset (&iter, end_offset); + gtk_text_buffer_move_mark (buffer, region->end, &iter); + } + + region->delta += length; + } + + DEBUG (({ + gint start, end; + gtk_text_buffer_get_iter_at_mark (buffer, &iter, region->start); + start = gtk_text_iter_get_offset (&iter); + gtk_text_buffer_get_iter_at_mark (buffer, &iter, region->end); + end = gtk_text_iter_get_offset (&iter); + g_assert (start <= end - region->delta); + })); + + CHECK_TREE (ce); + + install_first_update (ce); +} + +/** + * insert_range: + * + * @ce: a #GtkSourceContextEngine. + * @offset: the start of new segment. + * @length: the length of the segment. + * + * Updates segment tree after insertion: it updates tree + * offsets as appropriate, and inserts a new invalid segment + * or extends existing invalid segment as @offset, so + * after the call segment [@offset, @offset + @length) is marked + * invalid in the tree. + * It may be safely called with length == 0 at any moment + * to invalidate some offset (and it's used here and there). + */ +static void +insert_range (GtkSourceContextEngine *ce, + gint offset, + gint length) +{ + Segment *parent, *prev = NULL, *next = NULL, *new_segment; + Segment *segment; + + /* If there is an invalid segment adjacent to offset, use it. + * Otherwise, find the deepest segment to split and insert + * dummy segment in there. */ + + parent = get_invalid_at (ce, offset); + + if (parent == NULL) + find_insertion_place (ce->priv->root_segment, offset, + &parent, &prev, &next, + ce->priv->hint); + + g_assert (parent->start_at <= offset); + g_assert (parent->end_at >= offset); + g_assert (!prev || prev->parent == parent); + g_assert (!next || next->parent == parent); + g_assert (!prev || prev->next == next); + g_assert (!next || next->prev == prev); + + if (SEGMENT_IS_INVALID (parent)) + { + /* If length is zero, and we already have an invalid segment there, + * do nothing. */ + if (length == 0) + return; + + segment = parent; + } + else if (SEGMENT_IS_SIMPLE (parent)) + { + /* If it's a simple context, then: + * if one of its ends is offset, then we just invalidate it; + * otherwise, we split it into two, and insert zero-lentgh + * invalid segment in the middle. */ + if (parent->start_at < offset && parent->end_at > offset) + { + segment = simple_segment_split_ (ce, parent, offset); + } + else + { + segment_make_invalid_ (ce, parent); + segment = parent; + } + } + else + { + /* Just insert new zero-length invalid segment. */ + + new_segment = segment_new (ce, parent, NULL, offset, offset, FALSE); + + new_segment->next = next; + new_segment->prev = prev; + + if (next != NULL) + next->prev = new_segment; + else + parent->last_child = new_segment; + + if (prev != NULL) + prev->next = new_segment; + else + parent->children = new_segment; + + segment = new_segment; + } + + g_assert (!segment->children); + + if (length != 0) + { + /* now fix offsets in all the segments "to the right" + * of segment. */ + while (segment != NULL) + { + Segment *tmp; + SubPattern *sp; + + for (tmp = segment->next; tmp != NULL; tmp = tmp->next) + fix_offsets_insert_ (tmp, offset, length); + + segment->end_at += length; + + for (sp = segment->sub_patterns; sp != NULL; sp = sp->next) + { + if (sp->start_at > offset) + sp->start_at += length; + if (sp->end_at > offset) + sp->end_at += length; + } + + segment = segment->parent; + } + } + + CHECK_TREE (ce); +} + +/** + * gtk_source_context_engine_text_inserted: + * + * @ce: a #GtkSourceContextEngine. + * @start_offset: the start of inserted text. + * @end_offset: the end of inserted text. + * + * Called from GtkTextBuffer::insert_text. + */ +static void +gtk_source_context_engine_text_inserted (GtkSourceEngine *engine, + gint start_offset, + gint end_offset) +{ + GtkTextIter iter; + GtkSourceContextEngine *ce = GTK_SOURCE_CONTEXT_ENGINE (engine); + + g_return_if_fail (start_offset < end_offset); + + if (ce->priv->disabled) + return; + + invalidate_region (ce, start_offset, end_offset - start_offset); + + /* If end_offset is at the start of a line (enter key pressed) then + * we need to invalidate the whole new line, otherwise it may not be + * highlighted because the engine analyzes the previous line, end + * context there is none, start context at this line is none too, + * and the engine stops. */ + gtk_text_buffer_get_iter_at_offset (ce->priv->buffer, &iter, end_offset); + if (gtk_text_iter_starts_line (&iter) && !gtk_text_iter_ends_line (&iter)) + { + gtk_text_iter_forward_to_line_end (&iter); + invalidate_region (ce, gtk_text_iter_get_offset (&iter), 0); + } +} + +/** + * fix_offset_delete_one_: + * + * @offset: segment. + * @start: start of deleted text. + * @length: length of deleted text. + * + * Returns: new offset depending on location of @offset + * relative to deleted text. + * Called only from fix_offsets_delete_(). + */ +static inline gint +fix_offset_delete_one_ (gint offset, + gint start, + gint length) +{ + if (offset > start) + { + if (offset >= start + length) + offset -= length; + else + offset = start; + } + + return offset; +} + +/** + * fix_offsets_delete_: + * + * @segment: segment. + * @start: start offset. + * @length: length of deleted text. + * @hint: some segment somewhere near deleted text to optimize search. + * + * Recursively updates offsets after deleting text. To be called + * only from delete_range_(). + */ +static void +fix_offsets_delete_ (Segment *segment, + gint offset, + gint length, + Segment *hint) +{ + Segment *child; + SubPattern *sp; + + g_return_if_fail (segment->end_at > offset); + + if (hint != NULL) + while (hint != NULL && hint->parent != segment) + hint = hint->parent; + + if (hint == NULL) + hint = segment->children; + + for (child = hint; child != NULL; child = child->next) + { + if (child->end_at <= offset) + continue; + fix_offsets_delete_ (child, offset, length, NULL); + } + + for (child = hint ? hint->prev : NULL; child != NULL; child = child->prev) + { + if (child->end_at <= offset) + break; + fix_offsets_delete_ (child, offset, length, NULL); + } + + for (sp = segment->sub_patterns; sp != NULL; sp = sp->next) + { + sp->start_at = fix_offset_delete_one_ (sp->start_at, offset, length); + sp->end_at = fix_offset_delete_one_ (sp->end_at, offset, length); + } + + segment->start_at = fix_offset_delete_one_ (segment->start_at, offset, length); + segment->end_at = fix_offset_delete_one_ (segment->end_at, offset, length); +} + +/** + * delete_range_: + * + * @ce: a #GtkSourceContextEngine. + * @start: the start of deleted area. + * @end: the end of deleted area. + * + * Updates segment tree after deletion: removes segments at deleted + * interval, updates tree offsets, etc. + * It's called only from update_tree(). + */ +static void +delete_range_ (GtkSourceContextEngine *ce, + gint start, + gint end) +{ + g_return_if_fail (start < end); + + /* FIXME adjacent invalid segments? */ + erase_segments (ce, start, end, NULL); + fix_offsets_delete_ (ce->priv->root_segment, start, end - start, ce->priv->hint); + + /* no need to invalidate at start, update_tree will do it */ + + CHECK_TREE (ce); +} + +/** + * gtk_source_context_engine_text_deleted: + * + * @ce: a #GtkSourceContextEngine. + * @offset: the start of deleted text. + * @length: the length (in characters) of deleted text. + * + * Called from GtkTextBuffer::delete_range. + */ +static void +gtk_source_context_engine_text_deleted (GtkSourceEngine *engine, + gint offset, + gint length) +{ + GtkSourceContextEngine *ce = GTK_SOURCE_CONTEXT_ENGINE (engine); + + g_return_if_fail (length > 0); + + if (ce->priv->disabled) + return; + + invalidate_region (GTK_SOURCE_CONTEXT_ENGINE (engine), + offset, + - length); +} + +/** + * get_invalid_segment: + * + * @ce: a #GtkSourceContextEngine. + * + * Returns: first invalid segment, or %NULL. + */ +static Segment * +get_invalid_segment (GtkSourceContextEngine *ce) +{ + g_return_val_if_fail (ce->priv->invalid_region.empty, NULL); + return ce->priv->invalid ? ce->priv->invalid->data : NULL; +} + +/** + * get_invalid_line: + * + * @ce: a #GtkSourceContextEngine. + * + * Returns: first invalid line, or -1. + */ +static gint +get_invalid_line (GtkSourceContextEngine *ce) +{ + GtkTextIter iter; + gint offset = G_MAXINT; + + if (!ce->priv->invalid_region.empty) + { + gint tmp; + gtk_text_buffer_get_iter_at_mark (ce->priv->buffer, + &iter, + ce->priv->invalid_region.start); + tmp = gtk_text_iter_get_offset (&iter); + offset = MIN (offset, tmp); + } + + if (ce->priv->invalid) + { + Segment *segment = ce->priv->invalid->data; + offset = MIN (offset, segment->start_at); + } + + if (offset == G_MAXINT) + return -1; + + gtk_text_buffer_get_iter_at_offset (ce->priv->buffer, &iter, offset); + return gtk_text_iter_get_line (&iter); +} + +/** + * update_tree: + * + * @ce: a #GtkSourceContextEngine. + * + * Modifies syntax tree according to data in invalid_region. + */ +static void +update_tree (GtkSourceContextEngine *ce) +{ + InvalidRegion *region = &ce->priv->invalid_region; + gint start, end, delta; + gint erase_start, erase_end; + GtkTextIter iter; + + if (region->empty) + return; + + gtk_text_buffer_get_iter_at_mark (ce->priv->buffer, &iter, region->start); + start = gtk_text_iter_get_offset (&iter); + gtk_text_buffer_get_iter_at_mark (ce->priv->buffer, &iter, region->end); + end = gtk_text_iter_get_offset (&iter); + + delta = region->delta; + + g_assert (start <= MIN (end, end - delta)); + + /* Here start and end are actual offsets in the buffer (they do not match offsets + * in the tree if delta is not zero); delta is how much was inserted/removed. + * First, we insert/delete range from the tree, to make offsets in tree + * match offsets in the buffer. Then, create an invalid segment for the rest + * of the area if needed. */ + + if (delta > 0) + insert_range (ce, start, delta); + else if (delta < 0) + delete_range_ (ce, end, end - delta); + + if (delta <= 0) + { + erase_start = start; + erase_end = end; + } + else + { + erase_start = start + delta; + erase_end = end; + } + + if (erase_start < erase_end) + { + erase_segments (ce, erase_start, erase_end, NULL); + create_segment (ce, ce->priv->root_segment, NULL, erase_start, erase_end, FALSE, NULL); + } + else if (get_invalid_at (ce, start) == NULL) + { + insert_range (ce, start, 0); + } + + region->empty = TRUE; + +#ifdef ENABLE_CHECK_TREE + g_assert (get_invalid_at (ce, start) != NULL); + CHECK_TREE (ce); +#endif +} + +/** + * gtk_source_context_engine_update_highlight: + * + * @ce: a #GtkSourceContextEngine. + * @start: start of area to update. + * @end: start of area to update. + * @synchronous: whether it should block until everything + * is analyzed/highlighted. + * + * GtkSourceEngine::update_highlight method. + * + * Makes sure the area is analyzed and highlighted. If @asynchronous + * is %FALSE, then it queues idle worker. + */ +static void +gtk_source_context_engine_update_highlight (GtkSourceEngine *engine, + const GtkTextIter *start, + const GtkTextIter *end, + gboolean synchronous) +{ + gint invalid_line; + gint end_line; + GtkSourceContextEngine *ce = GTK_SOURCE_CONTEXT_ENGINE (engine); + + if (!ce->priv->highlight || ce->priv->disabled) + return; + + invalid_line = get_invalid_line (ce); + end_line = gtk_text_iter_get_line (end); + + if (gtk_text_iter_starts_line (end) && end_line > 0) + end_line -= 1; + + if (invalid_line < 0 || invalid_line > end_line) + { + ensure_highlighted (ce, start, end); + } + else if (synchronous) + { + /* analyze whole region */ + update_syntax (ce, end, 0); + ensure_highlighted (ce, start, end); + } + else + { + if (gtk_text_iter_get_line (start) >= invalid_line) + { + gtk_text_region_add (ce->priv->highlight_requests, start, end); + } + else + { + GtkTextIter valid_end = *start; + gtk_text_iter_set_line (&valid_end, invalid_line); + ensure_highlighted (ce, start, &valid_end); + gtk_text_region_add (ce->priv->highlight_requests, &valid_end, end); + } + + install_first_update (ce); + } +} + +/** + * enable_highlight: + * + * @ce: a #GtkSourceContextEngine. + * @enable: whether to enable highlighting. + * + * Whether to highlight (i.e. apply tags) analyzed area. + * Note that this does not turn on/off the analyzis stuff, + * it affects only text tags. + */ +static void +enable_highlight (GtkSourceContextEngine *ce, + gboolean enable) +{ + GtkTextIter start, end; + + if (!enable == !ce->priv->highlight) + return; + + ce->priv->highlight = enable != 0; + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (ce->priv->buffer), + &start, &end); + + if (enable) + refresh_range (ce, &start, &end, TRUE); + else + unhighlight_region (ce, &start, &end); +} + +static void +buffer_notify_highlight_syntax_cb (GtkSourceContextEngine *ce) +{ + gboolean highlight; + g_object_get (ce->priv->buffer, "highlight-syntax", &highlight, NULL); + enable_highlight (ce, highlight); +} + + +/* IDLE WORKER CODE ------------------------------------------------------- */ + +/** + * all_analyzed: + * + * @ce: a #GtkSourceContextEngine. + * + * Returns: whether everything is analyzed (but it doesn't care about the tags). + */ +static gboolean +all_analyzed (GtkSourceContextEngine *ce) +{ + return ce->priv->invalid == NULL && ce->priv->invalid_region.empty; +} + +/** + * idle_worker: + * + * @ce: #GtkSourceContextEngine. + * + * Analyzes a batch in idle. Stops when + * whole buffer is analyzed. + */ +static gboolean +idle_worker (GtkSourceContextEngine *ce) +{ + gboolean retval = TRUE; + + g_return_val_if_fail (ce->priv->buffer != NULL, FALSE); + + gdk_threads_enter (); + + /* analyze batch of text */ + update_syntax (ce, NULL, INCREMENTAL_UPDATE_TIME_SLICE); + CHECK_TREE (ce); + + if (all_analyzed (ce)) + { + ce->priv->incremental_update = 0; + retval = FALSE; + } + + gdk_threads_leave (); + + return retval; +} + +/** + * first_update_callback: + * + * @ce: a #GtkSourceContextEngine. + * + * Same as idle_worker, except: it runs once, and install idle_worker + * if not everything was analyzed at once. + */ +static gboolean +first_update_callback (GtkSourceContextEngine *ce) +{ + g_return_val_if_fail (ce->priv->buffer != NULL, FALSE); + + gdk_threads_enter (); + + /* analyze batch of text */ + update_syntax (ce, NULL, FIRST_UPDATE_TIME_SLICE); + CHECK_TREE (ce); + + ce->priv->first_update = 0; + + if (!all_analyzed (ce)) + install_idle_worker (ce); + + gdk_threads_leave (); + + return FALSE; +} + +/** + * install_idle_worker: + * + * @ce: #GtkSourceContextEngine. + * + * Schedules reanalyzing buffer in idle. + * Always safe to call. + */ +static void +install_idle_worker (GtkSourceContextEngine *ce) +{ + if (ce->priv->first_update == 0 && ce->priv->incremental_update == 0) + ce->priv->incremental_update = + g_idle_add_full (INCREMENTAL_UPDATE_PRIORITY, + (GSourceFunc) idle_worker, ce, NULL); +} + +/** + * install_first_update: + * + * @ce: #GtkSourceContextEngine. + * + * Schedules first_update_callback call. + * Always safe to call. + */ +static void +install_first_update (GtkSourceContextEngine *ce) +{ + if (ce->priv->first_update == 0) + { + if (ce->priv->incremental_update != 0) + { + g_source_remove (ce->priv->incremental_update); + ce->priv->incremental_update = 0; + } + + ce->priv->first_update = + g_idle_add_full (FIRST_UPDATE_PRIORITY, + (GSourceFunc) first_update_callback, + ce, NULL); + } +} + +/* GtkSourceContextEngine class ------------------------------------------- */ + +G_DEFINE_TYPE (GtkSourceContextEngine, _gtk_source_context_engine, GTK_TYPE_SOURCE_ENGINE) + +static GQuark +gtk_source_context_engine_error_quark (void) +{ + static GQuark err_q = 0; + if (err_q == 0) + err_q = g_quark_from_static_string ("gtk-source-context-engine-error-quark"); + return err_q; +} + +static void +remove_tags_hash_cb (G_GNUC_UNUSED gpointer style, + GSList *tags, + GtkTextTagTable *table) +{ + GSList *l = tags; + + while (l != NULL) + { + gtk_text_tag_table_remove (table, l->data); + g_object_unref (l->data); + l = l->next; + } + + g_slist_free (tags); +} + +/** + * destroy_tags_hash: + * + * @ce: #GtkSourceContextEngine. + * + * Destroys syntax tags cache. + */ +static void +destroy_tags_hash (GtkSourceContextEngine *ce) +{ + g_hash_table_foreach (ce->priv->tags, (GHFunc) remove_tags_hash_cb, + gtk_text_buffer_get_tag_table (ce->priv->buffer)); + g_hash_table_destroy (ce->priv->tags); + ce->priv->tags = NULL; +} + +/** + * gtk_source_context_engine_attach_buffer: + * + * @ce: #GtkSourceContextEngine. + * @buffer: buffer. + * + * Detaches engine from previous buffer, and attaches to @buffer if + * it's not %NULL. + */ +static void +gtk_source_context_engine_attach_buffer (GtkSourceEngine *engine, + GtkTextBuffer *buffer) +{ + GtkSourceContextEngine *ce = GTK_SOURCE_CONTEXT_ENGINE (engine); + + g_return_if_fail (!buffer || GTK_IS_TEXT_BUFFER (buffer)); + + if (ce->priv->buffer == buffer) + return; + + /* Detach previous buffer if there is one. */ + if (ce->priv->buffer != NULL) + { + g_signal_handlers_disconnect_by_func (ce->priv->buffer, + (gpointer) buffer_notify_highlight_syntax_cb, + ce); + + if (ce->priv->first_update != 0) + g_source_remove (ce->priv->first_update); + if (ce->priv->incremental_update != 0) + g_source_remove (ce->priv->incremental_update); + ce->priv->first_update = 0; + ce->priv->incremental_update = 0; + + if (ce->priv->root_segment != NULL) + segment_destroy (ce, ce->priv->root_segment); + if (ce->priv->root_context != NULL) + context_unref (ce->priv->root_context); + g_assert (!ce->priv->invalid); + g_slist_free (ce->priv->invalid); + ce->priv->root_segment = NULL; + ce->priv->root_context = NULL; + ce->priv->invalid = NULL; + + if (ce->priv->invalid_region.start != NULL) + gtk_text_buffer_delete_mark (ce->priv->buffer, + ce->priv->invalid_region.start); + if (ce->priv->invalid_region.end != NULL) + gtk_text_buffer_delete_mark (ce->priv->buffer, + ce->priv->invalid_region.end); + ce->priv->invalid_region.start = NULL; + ce->priv->invalid_region.end = NULL; + + /* this deletes tags from the tag table, therefore there is no need + * in removing tags from the text (it may be very slow). + * FIXME: don't we want to just destroy and forget everything when + * the buffer is destroyed? Removing tags is still slower than doing + * nothing. Caveat: if tag table is shared with other buffer, we do + * need to remove tags. */ + destroy_tags_hash (ce); + ce->priv->n_tags = 0; + + if (ce->priv->refresh_region != NULL) + gtk_text_region_destroy (ce->priv->refresh_region, FALSE); + if (ce->priv->highlight_requests != NULL) + gtk_text_region_destroy (ce->priv->highlight_requests, FALSE); + ce->priv->refresh_region = NULL; + ce->priv->highlight_requests = NULL; + } + + ce->priv->buffer = buffer; + + if (buffer != NULL) + { + gchar *root_id; + ContextDefinition *main_definition; + GtkTextIter start, end; + + /* Create the root context. */ + root_id = g_strdup_printf ("%s:%s", ENGINE_ID (ce), ENGINE_ID (ce)); + main_definition = LOOKUP_DEFINITION (ce->priv->ctx_data, root_id); + g_free (root_id); + + /* If we don't abort here, we will crash later (#485661). But it should + * never happen, _gtk_source_context_data_finish_parse checks main context. */ + g_assert (main_definition != NULL); + + ce->priv->root_context = context_new (NULL, main_definition, NULL, NULL, FALSE); + ce->priv->root_segment = create_segment (ce, NULL, ce->priv->root_context, 0, 0, TRUE, NULL); + + ce->priv->tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + gtk_text_buffer_get_bounds (buffer, &start, &end); + ce->priv->invalid_region.start = gtk_text_buffer_create_mark (buffer, NULL, + &start, TRUE); + ce->priv->invalid_region.end = gtk_text_buffer_create_mark (buffer, NULL, + &end, FALSE); + + if (gtk_text_buffer_get_char_count (buffer) != 0) + { + ce->priv->invalid_region.empty = FALSE; + ce->priv->invalid_region.delta = gtk_text_buffer_get_char_count (buffer); + } + else + { + ce->priv->invalid_region.empty = TRUE; + ce->priv->invalid_region.delta = 0; + } + + g_object_get (ce->priv->buffer, "highlight-syntax", &ce->priv->highlight, NULL); + ce->priv->refresh_region = gtk_text_region_new (buffer); + ce->priv->highlight_requests = gtk_text_region_new (buffer); + + g_signal_connect_swapped (buffer, + "notify::highlight-syntax", + G_CALLBACK (buffer_notify_highlight_syntax_cb), + ce); + + install_first_update (ce); + } +} + +/** + * disable_highlighting: + * + * @ce: #GtkSourceContextEngine. + * + * Dsiables highlighting in case of errors (currently if highlighting + * a single line took too long, so that highlighting doesn't freeze + * text editor). + */ +static void +disable_highlighting (GtkSourceContextEngine *ce) +{ + if (!ce->priv->disabled) + { + ce->priv->disabled = TRUE; + gtk_source_context_engine_attach_buffer (GTK_SOURCE_ENGINE (ce), NULL); + /* FIXME maybe emit some signal here? */ + } +} + +static void +set_tag_style_hash_cb (const char *style, + GSList *tags, + GtkSourceContextEngine *ce) +{ + while (tags != NULL) + { + set_tag_style (ce, tags->data, style); + tags = tags->next; + } +} + +/** + * gtk_source_context_engine_set_style_scheme: + * + * @engine: #GtkSourceContextEngine. + * @scheme: #GtkSourceStyleScheme to set. + * + * GtkSourceEngine::set_style_scheme method. + * Sets current style scheme, updates tag styles and everything. + */ +static void +gtk_source_context_engine_set_style_scheme (GtkSourceEngine *engine, + GtkSourceStyleScheme *scheme) +{ + GtkSourceContextEngine *ce; + + g_return_if_fail (GTK_IS_SOURCE_CONTEXT_ENGINE (engine)); + g_return_if_fail (GTK_IS_SOURCE_STYLE_SCHEME (scheme)); + + ce = GTK_SOURCE_CONTEXT_ENGINE (engine); + + if (scheme == ce->priv->style_scheme) + return; + + if (ce->priv->style_scheme != NULL) + g_object_unref (ce->priv->style_scheme); + + ce->priv->style_scheme = g_object_ref (scheme); + g_hash_table_foreach (ce->priv->tags, (GHFunc) set_tag_style_hash_cb, ce); +} + +static void +gtk_source_context_engine_finalize (GObject *object) +{ + GtkSourceContextEngine *ce = GTK_SOURCE_CONTEXT_ENGINE (object); + + if (ce->priv->buffer != NULL) + { + g_critical ("finalizing engine with attached buffer"); + /* Disconnect the buffer (if there is one), which destroys almost + * everything. */ + gtk_source_context_engine_attach_buffer (GTK_SOURCE_ENGINE (ce), NULL); + } + +#ifdef ENABLE_MEMORY_DEBUG + if (ce->priv->mem_usage_timeout) + g_source_remove (ce->priv->mem_usage_timeout); +#endif + + g_assert (!ce->priv->tags); + g_assert (!ce->priv->root_context); + g_assert (!ce->priv->root_segment); + g_assert (!ce->priv->first_update); + g_assert (!ce->priv->incremental_update); + + _gtk_source_context_data_unref (ce->priv->ctx_data); + + if (ce->priv->style_scheme != NULL) + g_object_unref (ce->priv->style_scheme); + + G_OBJECT_CLASS (_gtk_source_context_engine_parent_class)->finalize (object); +} + +static void +_gtk_source_context_engine_class_init (GtkSourceContextEngineClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkSourceEngineClass *engine_class = GTK_SOURCE_ENGINE_CLASS (klass); + + object_class->finalize = gtk_source_context_engine_finalize; + + engine_class->attach_buffer = gtk_source_context_engine_attach_buffer; + engine_class->text_inserted = gtk_source_context_engine_text_inserted; + engine_class->text_deleted = gtk_source_context_engine_text_deleted; + engine_class->update_highlight = gtk_source_context_engine_update_highlight; + engine_class->set_style_scheme = gtk_source_context_engine_set_style_scheme; + + g_type_class_add_private (object_class, sizeof (GtkSourceContextEnginePrivate)); +} + +static void +_gtk_source_context_engine_init (GtkSourceContextEngine *ce) +{ + ce->priv = G_TYPE_INSTANCE_GET_PRIVATE (ce, GTK_TYPE_SOURCE_CONTEXT_ENGINE, + GtkSourceContextEnginePrivate); +} + +GtkSourceContextEngine * +_gtk_source_context_engine_new (GtkSourceContextData *ctx_data) +{ + GtkSourceContextEngine *ce; + + g_return_val_if_fail (ctx_data != NULL, NULL); + g_return_val_if_fail (ctx_data->lang != NULL, NULL); + + ce = g_object_new (GTK_TYPE_SOURCE_CONTEXT_ENGINE, NULL); + ce->priv->ctx_data = _gtk_source_context_data_ref (ctx_data); + +#ifdef ENABLE_MEMORY_DEBUG + ce->priv->mem_usage_timeout = + g_timeout_add (5000, (GSourceFunc) mem_usage_timeout, ce); +#endif + + return ce; +} + +/** + * _gtk_source_context_data_new: + * + * @lang: #GtkSourceLanguage. + * + * Creates new context definition set. It does not set lang->priv->ctx_data, + * that's lang business. + */ +GtkSourceContextData * +_gtk_source_context_data_new (GtkSourceLanguage *lang) +{ + GtkSourceContextData *ctx_data; + + g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (lang), NULL); + + ctx_data = g_slice_new0 (GtkSourceContextData); + ctx_data->ref_count = 1; + ctx_data->lang = lang; + ctx_data->definitions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) context_definition_unref); + + return ctx_data; +} + +GtkSourceContextData * +_gtk_source_context_data_ref (GtkSourceContextData *ctx_data) +{ + g_return_val_if_fail (ctx_data != NULL, NULL); + ctx_data->ref_count++; + return ctx_data; +} + +/** + * _gtk_source_context_data_unref: + * + * @ctx_data: #GtkSourceContextData. + * + * Decreases reference count in ctx_data. When reference count + * drops to zero, ctx_data is freed, and ctx_data->lang->priv->ctx_data + * is unset. + */ +void +_gtk_source_context_data_unref (GtkSourceContextData *ctx_data) +{ + g_return_if_fail (ctx_data != NULL); + + if (--ctx_data->ref_count == 0) + { + if (ctx_data->lang != NULL && ctx_data->lang->priv != NULL && + ctx_data->lang->priv->ctx_data == ctx_data) + ctx_data->lang->priv->ctx_data = NULL; + g_hash_table_destroy (ctx_data->definitions); + g_slice_free (GtkSourceContextData, ctx_data); + } +} + +/* REGEX HANDLING --------------------------------------------------------- */ + +static Regex * +regex_ref (Regex *regex) +{ + if (regex != NULL) + regex->ref_count++; + return regex; +} + +static void +regex_unref (Regex *regex) +{ + if (regex != NULL && --regex->ref_count == 0) + { + if (regex->resolved) + { + g_regex_unref (regex->u.regex.regex); + if (regex->u.regex.match) + g_match_info_free (regex->u.regex.match); + } + else + g_free (regex->u.info.pattern); + g_slice_free (Regex, regex); + } +} + +/** + * find_single_byte_escape: + * + * @string: the pattern. + * + * Checks whether pattern contains \C escape sequence, + * which means "single byte" in pcre and naturally leads + * to crash if used for highlighting. + */ +static gboolean +find_single_byte_escape (const gchar *string) +{ + const char *p = string; + + while ((p = strstr (p, "\\C"))) + { + const char *slash; + gboolean found; + + if (p == string) + return TRUE; + + found = TRUE; + slash = p - 1; + + while (slash >= string && *slash == '\\') + { + found = !found; + slash--; + } + + if (found) + return TRUE; + + p += 2; + } + + return FALSE; +} + +/** + * regex_new: + * + * @pattern: the regular expression. + * @flags: compile options for @pattern. + * @error: location to store the error occuring, or %NULL to ignore errors. + * + * Creates a new regex. + * + * Returns: a newly-allocated #Regex. + */ +static Regex * +regex_new (const gchar *pattern, + GRegexCompileFlags flags, + GError **error) +{ + Regex *regex; + static GRegex *start_ref_re = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (find_single_byte_escape (pattern)) + { + g_set_error (error, GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_REGEX, + _("using \\C is not supported in language definitions")); + return NULL; + } + + regex = g_slice_new0 (Regex); + regex->ref_count = 1; + + if (start_ref_re == NULL) + start_ref_re = g_regex_new (START_REF_REGEX, + /* http://bugzilla.gnome.org/show_bug.cgi?id=455640 + * we don't care about line ends anyway */ + G_REGEX_OPTIMIZE | G_REGEX_NEWLINE_LF, + 0, + NULL); + + if (g_regex_match (start_ref_re, pattern, 0, NULL)) + { + regex->resolved = FALSE; + regex->u.info.pattern = g_strdup (pattern); + regex->u.info.flags = flags; + } + else + { + regex->resolved = TRUE; + regex->u.regex.regex = g_regex_new (pattern, + flags | G_REGEX_OPTIMIZE | G_REGEX_NEWLINE_LF, 0, + error); + + if (regex->u.regex.regex == NULL) + { + g_slice_free (Regex, regex); + regex = NULL; + } + } + + return regex; +} + +/** + * sub_pattern_to_int: + * + * @name: the string from lang file. + * + * Tries to convert @name to a number and assumes + * it's a name if that fails. Used for references in + * subpattern contexts (e.g. \%{1@start} or \%{blah@start}). + */ +static gint +sub_pattern_to_int (const gchar *name) +{ + guint64 number; + gchar *end_name; + + if (*name == 0) + return -1; + + errno = 0; + number = g_ascii_strtoull (name, &end_name, 10); + + if (errno !=0 || number > G_MAXINT || *end_name != 0) + return -1; + + return number; +} + +struct RegexResolveData { + Regex *start_regex; + const gchar *matched_text; +}; + +static gboolean +replace_start_regex (const GMatchInfo *match_info, + GString *expanded_regex, + gpointer user_data) +{ + gchar *num_string, *subst, *subst_escaped, *escapes; + gint num; + struct RegexResolveData *data = user_data; + + escapes = g_match_info_fetch (match_info, 1); + num_string = g_match_info_fetch (match_info, 2); + num = sub_pattern_to_int (num_string); + + if (num < 0) + subst = g_match_info_fetch_named (data->start_regex->u.regex.match, + num_string); + else + subst = g_match_info_fetch (data->start_regex->u.regex.match, + num); + + if (subst != NULL) + { + subst_escaped = g_regex_escape_string (subst, -1); + } + else + { + g_warning ("Invalid group: %s", num_string); + subst_escaped = g_strdup (""); + } + + g_string_append (expanded_regex, escapes); + g_string_append (expanded_regex, subst_escaped); + + g_free (escapes); + g_free (num_string); + g_free (subst); + g_free (subst_escaped); + + return FALSE; +} + +/** + * regex_resolve: + * + * @regex: a #Regex. + * @start_regex: a #Regex. + * @matched_text: the text matched against @start_regex. + * + * If the regular expression does not contain references to the start + * regular expression, the functions increases the reference count + * of @regex and returns it. + * + * If the regular expression contains references to the start regular + * expression in the form "\%{start_sub_pattern@start}", it replaces + * them (they are extracted from @start_regex and @matched_text) and + * returns the new regular expression. + * + * Returns: a #Regex. + */ +static Regex * +regex_resolve (Regex *regex, + Regex *start_regex, + const gchar *matched_text) +{ + GRegex *start_ref; + gchar *expanded_regex; + Regex *new_regex; + struct RegexResolveData data; + + if (regex == NULL || regex->resolved) + return regex_ref (regex); + + start_ref = g_regex_new (START_REF_REGEX, G_REGEX_NEWLINE_LF, 0, NULL); + data.start_regex = start_regex; + data.matched_text = matched_text; + expanded_regex = g_regex_replace_eval (start_ref, + regex->u.info.pattern, + -1, 0, 0, + replace_start_regex, + &data, NULL); + new_regex = regex_new (expanded_regex, regex->u.info.flags, NULL); + + if (new_regex == NULL || !new_regex->resolved) + { + regex_unref (new_regex); + g_warning ("Regular expression %s cannot be expanded.", + regex->u.info.pattern); + /* Returns a regex that nevers matches. */ + new_regex = regex_new ("$never-match^", 0, NULL); + } + + g_free (expanded_regex); + g_regex_unref (start_ref); + return new_regex; +} + +static gboolean +regex_match (Regex *regex, + const gchar *line, + gint byte_length, + gint byte_pos) +{ + gboolean result; + + g_assert (regex->resolved); + + if (regex->u.regex.match) + { + g_match_info_free (regex->u.regex.match); + regex->u.regex.match = NULL; + } + + result = g_regex_match_full (regex->u.regex.regex, line, + byte_length, byte_pos, + 0, ®ex->u.regex.match, + NULL); + + return result; +} + +static gchar * +regex_fetch (Regex *regex, + gint num) +{ + g_assert (regex->resolved); + return g_match_info_fetch (regex->u.regex.match, num); +} + +static void +regex_fetch_pos (Regex *regex, + const gchar *text, + gint num, + gint *start_pos, /* character offsets */ + gint *end_pos) /* character offsets */ +{ + gint byte_start_pos, byte_end_pos; + + g_assert (regex->resolved); + + if (!g_match_info_fetch_pos (regex->u.regex.match, num, &byte_start_pos, &byte_end_pos)) + { + if (start_pos != NULL) + *start_pos = -1; + if (end_pos != NULL) + *end_pos = -1; + } + else + { + if (start_pos != NULL) + *start_pos = g_utf8_pointer_to_offset (text, text + byte_start_pos); + if (end_pos != NULL) + *end_pos = g_utf8_pointer_to_offset (text, text + byte_end_pos); + } +} + +static void +regex_fetch_pos_bytes (Regex *regex, + gint num, + gint *start_pos_p, /* byte offsets */ + gint *end_pos_p) /* byte offsets */ +{ + gint start_pos; + gint end_pos; + + g_assert (regex->resolved); + + if (!g_match_info_fetch_pos (regex->u.regex.match, num, &start_pos, &end_pos)) + { + start_pos = -1; + end_pos = -1; + } + + if (start_pos_p != NULL) + *start_pos_p = start_pos; + if (end_pos_p != NULL) + *end_pos_p = end_pos; +} + +static void +regex_fetch_named_pos (Regex *regex, + const gchar *text, + const gchar *name, + gint *start_pos, /* character offsets */ + gint *end_pos) /* character offsets */ +{ + gint byte_start_pos, byte_end_pos; + + g_assert (regex->resolved); + + if (!g_match_info_fetch_named_pos (regex->u.regex.match, name, &byte_start_pos, &byte_end_pos)) + { + if (start_pos != NULL) + *start_pos = -1; + if (end_pos != NULL) + *end_pos = -1; + } + else + { + if (start_pos != NULL) + *start_pos = g_utf8_pointer_to_offset (text, text + byte_start_pos); + if (end_pos != NULL) + *end_pos = g_utf8_pointer_to_offset (text, text + byte_end_pos); + } +} + +static const gchar * +regex_get_pattern (Regex *regex) +{ + g_return_val_if_fail (regex && regex->resolved, ""); + return g_regex_get_pattern (regex->u.regex.regex); +} + +/* SYNTAX TREE ------------------------------------------------------------ */ + +/** + * apply_sub_patterns: + * + * @contextstate: a #Context. + * @line_starts_at: beginning offset of the line. + * @line: the line to analyze. + * @line_pos: the position inside @line. + * @line_length: the length of @line. + * @regex: regex that matched. + * @where: kind of sub patterns to apply. + * + * Applies sub patterns of kind @where to the matched text. + */ +static void +apply_sub_patterns (Segment *state, + LineInfo *line, + Regex *regex, + SubPatternWhere where) +{ + GSList *sub_pattern_list = state->context->definition->sub_patterns; + + if (SEGMENT_IS_CONTAINER (state)) + { + gint start_pos; + gint end_pos; + + regex_fetch_pos (regex, line->text, 0, &start_pos, &end_pos); + + if (where == SUB_PATTERN_WHERE_START) + { + if (line->start_at + start_pos != state->start_at) + g_critical ("oops"); + else if (line->start_at + end_pos > state->end_at) + g_critical ("oops"); + else + state->start_len = line->start_at + end_pos - state->start_at; + } + else + { + if (line->start_at + start_pos < state->start_at) + g_critical ("oops"); + else if (line->start_at + end_pos != state->end_at) + g_critical ("oops"); + else + state->end_len = state->end_at - line->start_at - start_pos; + } + } + + while (sub_pattern_list != NULL) + { + SubPatternDefinition *sp_def = sub_pattern_list->data; + + if (sp_def->where == where) + { + gint start_pos; + gint end_pos; + + if (sp_def->is_named) + regex_fetch_named_pos (regex, + line->text, + sp_def->u.name, + &start_pos, + &end_pos); + else + regex_fetch_pos (regex, + line->text, + sp_def->u.num, + &start_pos, + &end_pos); + + if (start_pos >= 0 && start_pos != end_pos) + { + sub_pattern_new (state, + line->start_at + start_pos, + line->start_at + end_pos, + sp_def); + } + } + + sub_pattern_list = sub_pattern_list->next; + } +} + +/** + * can_apply_match: + * + * @state: the current state of the parser. + * @line: the line to analyze. + * @match_start: start position of match, bytes. + * @match_end: where to put end of match, bytes. + * @where: kind of sub patterns to apply. + * + * See apply_match(), this function is a helper function + * called from where, it doesn't modify syntax tree. + * + * Returns: %TRUE if the match can be applied. + */ +static gboolean +can_apply_match (Context *state, + LineInfo *line, + gint match_start, + gint *match_end, + Regex *regex) +{ + gint end_match_pos; + gboolean ancestor_ends; + gint pos; + + ancestor_ends = FALSE; + /* end_match_pos is the position of the end of the matched regex. */ + regex_fetch_pos_bytes (regex, 0, NULL, &end_match_pos); + + g_assert (end_match_pos <= line->byte_length); + + /* Verify if an ancestor ends in the matched text. */ + if (ANCESTOR_CAN_END_CONTEXT (state) && + /* there is no middle of zero-length match */ + match_start < end_match_pos) + { + pos = match_start + 1; + + while (pos < end_match_pos) + { + if (ancestor_context_ends_here (state, line, pos)) + { + ancestor_ends = TRUE; + break; + } + + pos = g_utf8_next_char (line->text + pos) - line->text; + } + } + else + { + pos = end_match_pos; + } + + if (ancestor_ends) + { + /* An ancestor ends in the middle of the match, we verify + * if the regex matches against the available string before + * the end of the ancestor. + * For instance in C a net-address context matches even if + * it contains the end of a multi-line comment. */ + if (!regex_match (regex, line->text, pos, match_start)) + { + /* This match is not valid, so we can try to match + * the next definition, so the position should not + * change. */ + return FALSE; + } + } + + *match_end = pos; + return TRUE; +} + +static gint +line_pos_to_offset (LineInfo *line, + gint pos) +{ + if (line->char_length != line->byte_length) + pos = g_utf8_pointer_to_offset (line->text, line->text + pos); + return line->start_at + pos; +} + +/** + * apply_match: + * + * @state: the current state of the parser. + * @line: the line to analyze. + * @line_pos: position in the line, bytes. + * @regex: regex that matched. + * @where: kind of sub patterns to apply. + * + * Moves @line_pos after the matched text. @line_pos is not + * updated and the function returns %FALSE if the match cannot be + * applied because an ancestor ends in the middle of the matched + * text. + * + * If the match can be applied the function applies the appropriate + * sub patterns. + * + * Returns: %TRUE if the match can be applied. + */ +static gboolean +apply_match (Segment *state, + LineInfo *line, + gint *line_pos, + Regex *regex, + SubPatternWhere where) +{ + gint match_end; + + if (!can_apply_match (state->context, line, *line_pos, &match_end, regex)) + return FALSE; + + segment_extend (state, line_pos_to_offset (line, match_end)); + apply_sub_patterns (state, line, regex, where); + *line_pos = match_end; + + return TRUE; +} + +/** + * create_reg_all: + * + * @context: context. + * @definition: context definition. + * + * Creates regular expression for all possible transitions: it + * combines terminating regex, terminating regexes of parent + * contexts if those can terminate this one, and start regexes + * of child contexts. + * + * It takes as an argument actual context or a context definition. In + * case when context end depends on start (\%{foo@start} references), + * it must use the context, definition is not enough. If there are no + * those references, then the reg_all is created right in the definition + * when no contexts exist yet. This is why this function has its funny + * arguments. + * + * Returns: resulting regex or %NULL when pcre failed to compile the regex. + */ +static Regex * +create_reg_all (Context *context, + ContextDefinition *definition) +{ + DefinitionsIter iter; + DefinitionChild *child_def; + GString *all; + Regex *regex; + GError *error = NULL; + + g_return_val_if_fail ((context == NULL && definition != NULL) || + (context != NULL && definition == NULL), NULL); + + if (definition == NULL) + definition = context->definition; + + all = g_string_new ("("); + + /* Closing regex. */ + if (definition->type == CONTEXT_TYPE_CONTAINER && + definition->u.start_end.end != NULL) + { + Regex *end; + + if (definition->u.start_end.end->resolved) + { + end = definition->u.start_end.end; + } + else + { + g_return_val_if_fail (context && context->end, NULL); + end = context->end; + } + + g_string_append (all, regex_get_pattern (end)); + g_string_append (all, "|"); + } + + /* Ancestors. */ + if (context != NULL) + { + Context *tmp = context; + + while (ANCESTOR_CAN_END_CONTEXT (tmp)) + { + if (!CONTEXT_EXTENDS_PARENT (tmp)) + { + gboolean append = TRUE; + + /* Code as it is seems to be right, and seems working right. + * Remove FIXME's below if everything is fine. */ + + if (tmp->parent->end != NULL) + g_string_append (all, regex_get_pattern (tmp->parent->end)); + /* FIXME ? + * The old code insisted on having tmp->parent->end != NULL here, + * though e.g. in case line-comment -> email-address it's not the case. + * Apparently using $ fixes the problem. */ + else if (CONTEXT_END_AT_LINE_END (tmp->parent)) + g_string_append (all, "$"); + /* FIXME it's not clear whether it can happen, maybe we need assert here + * or parser need to check it */ + else + { + /* g_critical ("oops"); */ + append = FALSE; + } + + if (append) + g_string_append (all, "|"); + } + + tmp = tmp->parent; + } + } + + /* Children. */ + definition_iter_init (&iter, definition); + while ((child_def = definition_iter_next (&iter)) != NULL) + { + Regex *child_regex = NULL; + + g_return_val_if_fail (child_def->resolved, NULL); + + switch (child_def->u.definition->type) + { + case CONTEXT_TYPE_CONTAINER: + child_regex = child_def->u.definition->u.start_end.start; + break; + case CONTEXT_TYPE_SIMPLE: + child_regex = child_def->u.definition->u.match; + break; + default: + g_return_val_if_reached (NULL); + } + + if (child_regex != NULL) + { + g_string_append (all, regex_get_pattern (child_regex)); + g_string_append (all, "|"); + } + } + definition_iter_destroy (&iter); + + if (all->len > 1) + g_string_truncate (all, all->len - 1); + g_string_append (all, ")"); + + regex = regex_new (all->str, 0, &error); + + if (regex == NULL) + { + /* regex_new could fail, for instance if there are different + * named sub-patterns with the same name or if resulting regex is + * too long. In this case fixing lang file helps (e.g. renaming + * subpatterns, making huge keywords use bigger prefixes, etc.) */ + g_warning (_("Cannot create a regex for all the transitions, " + "the syntax highlighting process will be slower " + "than usual.\nThe error was: %s"), error->message); + g_error_free (error); + } + + g_string_free (all, TRUE); + return regex; +} + +static Context * +context_ref (Context *context) +{ + if (context != NULL) + context->ref_count++; + return context; +} + +/* does not copy style */ +static Context * +context_new (Context *parent, + ContextDefinition *definition, + const gchar *line_text, + const gchar *style, + gboolean ignore_children_style) +{ + Context *context; + + context = g_slice_new0 (Context); + context->ref_count = 1; + context->definition = definition; + context->parent = parent; + + context->style = style; + context->ignore_children_style = ignore_children_style; + + if (parent != NULL && parent->ignore_children_style) + { + context->ignore_children_style = TRUE; + context->style = NULL; + } + + if (!parent || (parent->all_ancestors_extend && CONTEXT_EXTENDS_PARENT (parent))) + { + context->all_ancestors_extend = TRUE; + } + + if (line_text && + definition->type == CONTEXT_TYPE_CONTAINER && + definition->u.start_end.end) + { + context->end = regex_resolve (definition->u.start_end.end, + definition->u.start_end.start, + line_text); + } + + /* Create reg_all. If it is possibile we share the same reg_all + * for more contexts storing it in the definition. */ + if (ANCESTOR_CAN_END_CONTEXT (context) || + (definition->type == CONTEXT_TYPE_CONTAINER && + definition->u.start_end.end != NULL && + !definition->u.start_end.end->resolved)) + { + context->reg_all = create_reg_all (context, NULL); + } + else + { + if (!definition->reg_all) + definition->reg_all = create_reg_all (NULL, definition); + context->reg_all = regex_ref (definition->reg_all); + } + +#ifdef ENABLE_DEBUG + { + GString *str = g_string_new (definition->id); + Context *tmp = context->parent; + while (tmp != NULL) + { + g_string_prepend (str, "/"); + g_string_prepend (str, tmp->definition->id); + tmp = tmp->parent; + } + g_print ("created context %s: %s\n", definition->id, str->str); + g_string_free (str, TRUE); + } +#endif + + return context; +} + +static void +context_unref_hash_cb (G_GNUC_UNUSED gpointer text, + Context *context) +{ + context->parent = NULL; + context_unref (context); +} + +static gboolean +remove_context_cb (G_GNUC_UNUSED gpointer text, + Context *context, + Context *target) +{ + return context == target; +} + +static void +context_remove_child (Context *parent, + Context *context) +{ + ContextPtr *ptr, *prev = NULL; + gboolean delete = TRUE; + + g_assert (context->parent == parent); + + for (ptr = parent->children; ptr; ptr = ptr->next) + { + if (ptr->definition == context->definition) + break; + prev = ptr; + } + + if (!ptr) + g_error ("error"); + + if (!ptr->fixed) + { + g_hash_table_foreach_remove (ptr->u.hash, + (GHRFunc) remove_context_cb, + context); + + if (g_hash_table_size (ptr->u.hash) != 0) + delete = FALSE; + } + + if (delete) + { + if (prev != NULL) + prev->next = ptr->next; + else + parent->children = ptr->next; + + if (!ptr->fixed) + g_hash_table_destroy (ptr->u.hash); + +#ifdef ENABLE_DEBUG + memset (ptr, 1, sizeof (ContextPtr)); +#else + g_slice_free (ContextPtr, ptr); +#endif + } +} + +/** + * context_unref: + * + * @context: the context. + * + * Decreases reference count and removes @context + * from the tree when it drops to zero. + */ +static void +context_unref (Context *context) +{ + ContextPtr *children; + + if (context == NULL || --context->ref_count != 0) + return; + + DEBUG (g_print ("destroying context %s\n", context->definition->id)); + + children = context->children; + context->children = NULL; + + while (children != NULL) + { + ContextPtr *ptr = children; + + children = children->next; + + if (ptr->fixed) + { + ptr->u.context->parent = NULL; + context_unref (ptr->u.context); + } + else + { + g_hash_table_foreach (ptr->u.hash, + (GHFunc) context_unref_hash_cb, + NULL); + g_hash_table_destroy (ptr->u.hash); + } + +#ifdef ENABLE_DEBUG + memset (ptr, 1, sizeof (ContextPtr)); +#else + g_slice_free (ContextPtr, ptr); +#endif + } + + if (context->parent != NULL) + context_remove_child (context->parent, context); + + regex_unref (context->end); + regex_unref (context->reg_all); + g_free (context->subpattern_tags); + g_slice_free (Context, context); +} + +static void +context_freeze_hash_cb (G_GNUC_UNUSED gpointer text, + Context *context) +{ + context_freeze (context); +} + +/** + * context_freeze: + * + * @context: the context. + * + * Recursively increments reference count in context and its children, + * and marks them, so context_thaw is able to correctly decrement + * reference count. + * This function is for update_syntax: we want to preserve existing + * contexts when possible, and update_syntax erases contexts from + * reanalyzed lines; so to avoid destructing and recreating contexts + * every time, we need to increment reference count on existing contexts, + * and decrement it when we are done with analysis, so no more needed + * contexts go away. Keeping a list of referenced contexts is painful + * or slow, so we just reference all contexts present at the moment. + * + * Note this is not reentrant, context_freeze()/context_thaw() pair is called + * only from update_syntax(). + */ +static void +context_freeze (Context *ctx) +{ + ContextPtr *ptr; + + g_assert (!ctx->frozen); + ctx->frozen = TRUE; + context_ref (ctx); + + for (ptr = ctx->children; ptr != NULL; ptr = ptr->next) + { + if (ptr->fixed) + { + context_freeze (ptr->u.context); + } + else + { + g_hash_table_foreach (ptr->u.hash, + (GHFunc) context_freeze_hash_cb, + NULL); + } + } +} + +static void +get_child_contexts_hash_cb (G_GNUC_UNUSED gpointer text, + Context *context, + GSList **list) +{ + *list = g_slist_prepend (*list, context); +} + +/** + * context_thaw: + * + * @context: the context. + * + * Recursively decrements reference count in context and its children, + * if it was incremented by context_freeze(). + */ +static void +context_thaw (Context *ctx) +{ + ContextPtr *ptr; + + if (!ctx->frozen) + return; + + for (ptr = ctx->children; ptr != NULL; ) + { + ContextPtr *next = ptr->next; + + if (ptr->fixed) + { + context_thaw (ptr->u.context); + } + else + { + GSList *children = NULL; + g_hash_table_foreach (ptr->u.hash, + (GHFunc) get_child_contexts_hash_cb, + &children); + g_slist_foreach (children, (GFunc) context_thaw, NULL); + g_slist_free (children); + } + + ptr = next; + } + + ctx->frozen = FALSE; + context_unref (ctx); +} + +static Context * +create_child_context (Context *parent, + DefinitionChild *child_def, + const gchar *line_text) +{ + Context *context; + ContextPtr *ptr; + gchar *match = NULL; + ContextDefinition *definition = child_def->u.definition; + + g_return_val_if_fail (parent != NULL, NULL); + + for (ptr = parent->children; + ptr != NULL && ptr->definition != definition; + ptr = ptr->next) ; + + if (ptr == NULL) + { + ptr = g_slice_new0 (ContextPtr); + ptr->next = parent->children; + parent->children = ptr; + ptr->definition = definition; + + if (definition->type != CONTEXT_TYPE_CONTAINER || + !definition->u.start_end.end || + definition->u.start_end.end->resolved) + { + ptr->fixed = TRUE; + } + + if (!ptr->fixed) + ptr->u.hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + } + + if (ptr->fixed) + { + context = ptr->u.context; + } + else + { + match = regex_fetch (definition->u.start_end.start, 0); + g_return_val_if_fail (match != NULL, NULL); + context = g_hash_table_lookup (ptr->u.hash, match); + } + + if (context != NULL) + { + g_free (match); + return context_ref (context); + } + + context = context_new (parent, + definition, + line_text, + child_def->override_style ? child_def->style : + child_def->u.definition->default_style, + child_def->override_style ? child_def->override_style_deep : FALSE); + g_return_val_if_fail (context != NULL, NULL); + + if (ptr->fixed) + ptr->u.context = context; + else + g_hash_table_insert (ptr->u.hash, match, context); + + return context; +} + +/** + * segment_new: + * + * @ce: the engine. + * @parent: parent segment (%NULL for the root segment). + * @context: context for this segment (%NULL for invalid segments). + * @start_at: start offset in the buffer, characters. + * @end_at: end offset in the buffer, characters. + * @is_start: is_start flag. + * + * Creates a new segment structure. It doesn't take care about + * parent or siblings, create_segment() is the function to + * create new segments in the tree. + * + * Returns: newly created segment. + */ +static Segment * +segment_new (GtkSourceContextEngine *ce, + Segment *parent, + Context *context, + gint start_at, + gint end_at, + gboolean is_start) +{ + Segment *segment; + +#ifdef ENABLE_CHECK_TREE + g_assert (!is_start || context != NULL); +#endif + + segment = g_slice_new0 (Segment); + segment->parent = parent; + segment->context = context_ref (context); + segment->start_at = start_at; + segment->end_at = end_at; + segment->is_start = is_start; + + if (context == NULL) + add_invalid (ce, segment); + + return segment; +} + +static void +find_segment_position_forward_ (Segment *segment, + gint start_at, + gint end_at, + Segment **prev, + Segment **next) +{ + g_assert (segment->start_at <= start_at); + + while (segment != NULL) + { + if (segment->end_at == start_at) + { + while (segment->next != NULL && segment->next->start_at == start_at) + segment = segment->next; + + *prev = segment; + *next = segment->next; + + break; + } + + if (segment->start_at == end_at) + { + *next = segment; + *prev = segment->prev; + break; + } + + if (segment->start_at > end_at) + { + *next = segment; + break; + } + + if (segment->end_at < start_at) + *prev = segment; + + segment = segment->next; + } +} + +static void +find_segment_position_backward_ (Segment *segment, + gint start_at, + G_GNUC_UNUSED gint end_at, + Segment **prev, + Segment **next) +{ + g_assert (start_at < segment->end_at); + + while (segment != NULL) + { + if (segment->end_at <= start_at) + { + *prev = segment; + break; + } + + g_assert (segment->start_at >= end_at); + + *next = segment; + segment = segment->prev; + } +} + +/** + * find_segment_position: + * + * @parent: parent segment (not %NULL). + * @hint: segment somewhere near new segment position. + * @start_at: start offset. + * @end_at: end offset. + * @prev: location to return previous sibling. + * @next: location to return next sibling. + * + * Finds siblings of a new segment to be created at interval + * (start_at, end_at). It uses hint to avoid walking whole + * parent->children list. + */ +static void +find_segment_position (Segment *parent, + Segment *hint, + gint start_at, + gint end_at, + Segment **prev, + Segment **next) +{ + Segment *tmp; + + g_assert (parent->start_at <= start_at && end_at <= parent->end_at); + g_assert (!hint || hint->parent == parent); + + *prev = *next = NULL; + + if (parent->children == NULL) + return; + + if (parent->children->next == NULL) + { + tmp = parent->children; + + if (start_at >= tmp->end_at) + *prev = tmp; + else + *next = tmp; + + return; + } + + if (hint == NULL) + hint = parent->children; + + if (hint->end_at <= start_at) + find_segment_position_forward_ (hint, start_at, end_at, prev, next); + else + find_segment_position_backward_ (hint, start_at, end_at, prev, next); +} + +/** + * create_segment: + * + * @ce: the engine. + * @parent: parent segment (%NULL for the root segment). + * @context: context for this segment (%NULL for invalid segments). + * @start_at: start offset, characters. + * @end_at: end offset, characters. + * @is_start: is_start flag. + * @hint: a segment somewhere near new one, to omtimize search. + * + * Creates a new segment and inserts it into the tree. + * + * Returns: newly created segment. + */ +static Segment * +create_segment (GtkSourceContextEngine *ce, + Segment *parent, + Context *context, + gint start_at, + gint end_at, + gboolean is_start, + Segment *hint) +{ + Segment *segment; + + g_assert (!parent || (parent->start_at <= start_at && end_at <= parent->end_at)); + + segment = segment_new (ce, parent, context, start_at, end_at, is_start); + + if (parent != NULL) + { + Segment *prev, *next; + + if (hint == NULL) + { + hint = ce->priv->hint; + while (hint != NULL && hint->parent != parent) + hint = hint->parent; + } + + find_segment_position (parent, hint, + start_at, end_at, + &prev, &next); + + g_assert ((!parent->children && !prev && !next) || + (parent->children && (prev || next))); + g_assert (!prev || prev->next == next); + g_assert (!next || next->prev == prev); + + segment->next = next; + segment->prev = prev; + + if (next != NULL) + next->prev = segment; + else + parent->last_child = segment; + + if (prev != NULL) + prev->next = segment; + else + parent->children = segment; + + CHECK_SEGMENT_LIST (parent); + CHECK_TREE (ce); + } + + return segment; +} + +/** + * segment_extend: + * + * @state: the semgent. + * @end_at: new end offset, characters. + * + * Updates end offset in the segment and its ancestors. + */ +static void +segment_extend (Segment *state, + gint end_at) +{ + while (state != NULL && state->end_at < end_at) + { + state->end_at = end_at; + state = state->parent; + } + CHECK_SEGMENT_LIST (state->parent); +} + +static void +segment_destroy_children (GtkSourceContextEngine *ce, + Segment *segment) +{ + Segment *child; + SubPattern *sp; + + g_return_if_fail (segment != NULL); + + child = segment->children; + segment->children = NULL; + segment->last_child = NULL; + + while (child != NULL) + { + Segment *next = child->next; + segment_destroy (ce, child); + child = next; + } + + sp = segment->sub_patterns; + segment->sub_patterns = NULL; + + while (sp != NULL) + { + SubPattern *next = sp->next; + sub_pattern_free (sp); + sp = next; + } +} + +/** + * segment_destroy: + * + * @ce: the engine. + * @context: the segment to destroy. + * + * Recursively frees given segment. It removes the segment + * from ce structure, but it doesn't update parent and + * siblings. segment_remove() is the function that takes + * care of everything. + */ +static void +segment_destroy (GtkSourceContextEngine *ce, + Segment *segment) +{ + g_return_if_fail (segment != NULL); + + segment_destroy_children (ce, segment); + + /* segment neighbours and parent may be invalid here, + * so we only can unset the hint */ + if (ce->priv->hint == segment) + ce->priv->hint = NULL; + if (ce->priv->hint2 == segment) + ce->priv->hint2 = NULL; + + if (SEGMENT_IS_INVALID (segment)) + remove_invalid (ce, segment); + + context_unref (segment->context); + +#ifdef ENABLE_DEBUG + g_assert (!g_slist_find (ce->priv->invalid, segment)); + memset (segment, 1, sizeof (Segment)); +#else + g_slice_free (Segment, segment); +#endif +} + +/** + * container_context_starts_here: + * + * See child_starts_here(). + */ +static gboolean +container_context_starts_here (GtkSourceContextEngine *ce, + Segment *state, + DefinitionChild *child_def, + LineInfo *line, + gint *line_pos, /* bytes */ + Segment **new_state) +{ + Context *new_context; + Segment *new_segment; + gint match_end; + ContextDefinition *definition = child_def->u.definition; + + g_assert (*line_pos <= line->byte_length); + + /* We can have a container context definition (i.e. the main + * language definition) without start_end.start. */ + if (definition->u.start_end.start == NULL) + return FALSE; + + if (!regex_match (definition->u.start_end.start, + line->text, line->byte_length, *line_pos)) + { + return FALSE; + } + + new_context = create_child_context (state->context, child_def, line->text); + g_return_val_if_fail (new_context != NULL, FALSE); + + if (!can_apply_match (new_context, line, *line_pos, &match_end, + definition->u.start_end.start)) + { + context_unref (new_context); + return FALSE; + } + + g_assert (match_end <= line->byte_length); + + segment_extend (state, line_pos_to_offset (line, match_end)); + new_segment = create_segment (ce, state, new_context, + line_pos_to_offset (line, *line_pos), + line_pos_to_offset (line, match_end), + TRUE, + ce->priv->hint2); + + /* This new context could end at the same position (i.e. have zero length), + * and then we get an infinite loop. We can't possibly know about it at this point + * (since we need to know that the context indeed *ends* here, and that's + * discovered only later) so we look at the previous sibling: if it's the same, + * and has zero length then we remove the segment. We do it this way instead of + * checking before creating the segment because it's more convenient. */ + if (*line_pos == match_end && + new_segment->prev != NULL && + new_segment->prev->context == new_segment->context && + new_segment->prev->start_at == new_segment->prev->end_at && + new_segment->prev->start_at == line_pos_to_offset (line, *line_pos)) + { + segment_remove (ce, new_segment); + return FALSE; + } + + apply_sub_patterns (new_segment, line, + definition->u.start_end.start, + SUB_PATTERN_WHERE_START); + *line_pos = match_end; + *new_state = new_segment; + ce->priv->hint2 = NULL; + context_unref (new_context); + return TRUE; +} + +/** + * simple_context_starts_here: + * + * See child_starts_here(). + */ +static gboolean +simple_context_starts_here (GtkSourceContextEngine *ce, + Segment *state, + DefinitionChild *child_def, + LineInfo *line, + gint *line_pos, /* bytes */ + Segment **new_state) +{ + gint match_end; + Context *new_context; + ContextDefinition *definition = child_def->u.definition; + + g_return_val_if_fail (definition->u.match != NULL, FALSE); + + g_assert (*line_pos <= line->byte_length); + + if (!regex_match (definition->u.match, line->text, line->byte_length, *line_pos)) + return FALSE; + + new_context = create_child_context (state->context, child_def, line->text); + g_return_val_if_fail (new_context != NULL, FALSE); + + if (!can_apply_match (new_context, line, *line_pos, &match_end, definition->u.match)) + { + context_unref (new_context); + return FALSE; + } + + /* If length of the match is zero, then we get zero-length segment and return to + * the same state, so it's an infinite loop. But, if this child ends parent, we + * do want to terminate parent. Still, if match is at the beginning of the parent + * then we get an infinite loop again, so we check that (NOTE it really should destroy + * parent context then, but then we again can get parent context be recreated here and + * so on). */ + if (*line_pos == match_end && + (!CONTEXT_ENDS_PARENT (new_context) || + line_pos_to_offset (line, *line_pos) == state->start_at)) + { + context_unref (new_context); + return FALSE; + } + + g_assert (match_end <= line->byte_length); + segment_extend (state, line_pos_to_offset (line, match_end)); + + if (*line_pos != match_end) + { + /* Normal non-zero-length match, create a child segment */ + Segment *new_segment; + new_segment = create_segment (ce, state, new_context, + line_pos_to_offset (line, *line_pos), + line_pos_to_offset (line, match_end), + TRUE, + ce->priv->hint2); + apply_sub_patterns (new_segment, line, definition->u.match, SUB_PATTERN_WHERE_DEFAULT); + ce->priv->hint2 = new_segment; + } + + /* Terminate parent if needed */ + if (CONTEXT_ENDS_PARENT (new_context)) + { + do + { + ce->priv->hint2 = state; + state = state->parent; + } + while (SEGMENT_ENDS_PARENT (state)); + } + + *line_pos = match_end; + *new_state = state; + context_unref (new_context); + return TRUE; +} + +/** + * child_starts_here: + * + * @ce: the engine. + * @state: current state. + * @child_def: the child. + * @line: line to analyze. + * @line_pos: the position inside @line, bytes. + * @new_state: where to store the new state. + * + * Verifies if a context of the type in @curr_definition starts at + * @line_pos in @line. If the contexts start here @new_state and + * @line_pos are updated. + * + * Returns: %TRUE if the context starts here. + */ +static gboolean +child_starts_here (GtkSourceContextEngine *ce, + Segment *state, + DefinitionChild *child_def, + LineInfo *line, + gint *line_pos, + Segment **new_state) +{ + g_return_val_if_fail (child_def->resolved, FALSE); + + switch (child_def->u.definition->type) + { + case CONTEXT_TYPE_SIMPLE: + return simple_context_starts_here (ce, + state, + child_def, + line, + line_pos, + new_state); + case CONTEXT_TYPE_CONTAINER: + return container_context_starts_here (ce, + state, + child_def, + line, + line_pos, + new_state); + default: + g_return_val_if_reached (FALSE); + } +} + +/** + * segment_ends_here: + * + * @state: the segment. + * @line: analyzed line. + * @pos: the position inside @line, bytes. + * + * Checks whether given segment ends at pos. Unlike + * child_starts_here() it doesn't modify tree, it merely + * calls regex_match() for the end regex. + */ +static gboolean +segment_ends_here (Segment *state, + LineInfo *line, + gint pos) +{ + g_assert (SEGMENT_IS_CONTAINER (state)); + + return state->context->definition->u.start_end.end && + regex_match (state->context->end, + line->text, + line->byte_length, + pos); +} + +/** + * ancestor_context_ends_here: + * + * @state: current context. + * @line: the line to analyze. + * @line_pos: the position inside @line, bytes. + * + * Verifies if some ancestor context ends at the current position. + * This function only checks conetxts and does not modify the tree, + * it's used by ancestor_ends_here(). + * + * Returns: the ancestor context that terminates here or %NULL. + */ +static Context * +ancestor_context_ends_here (Context *state, + LineInfo *line, + gint line_pos) +{ + Context *current_context; + GSList *current_context_list; + GSList *check_ancestors; + Context *terminating_context; + + /* A context can be terminated by the parent if extend_parent is + * FALSE, so we need to verify the end of all the parents of + * not-extending contexts. The list is ordered by ascending + * depth. */ + check_ancestors = NULL; + current_context = state; + while (ANCESTOR_CAN_END_CONTEXT (current_context)) + { + if (!CONTEXT_EXTENDS_PARENT (current_context)) + check_ancestors = g_slist_prepend (check_ancestors, + current_context->parent); + current_context = current_context->parent; + } + + /* The first context that ends here terminates its descendants. */ + terminating_context = NULL; + current_context_list = check_ancestors; + while (current_context_list != NULL) + { + current_context = current_context_list->data; + + if (current_context->end && + current_context->end->u.regex.regex && + regex_match (current_context->end, + line->text, + line->byte_length, + line_pos)) + { + terminating_context = current_context; + break; + } + + current_context_list = current_context_list->next; + } + g_slist_free (check_ancestors); + + return terminating_context; +} + +/** + * ancestor_ends_here: + * + * @state: current state. + * @line: the line to analyze. + * @line_pos: the position inside @line, bytes. + * @new_state: where to store the new state. + * + * Verifies if some ancestor context ends at given position. If + * state changed and @new_state is not %NULL, then the new state is stored + * in @new_state, and descendants of @new_state are closed, so the + * terminating segment becomes current state. + * + * Returns: %TRUE if an ancestor ends at the given position. + */ +static gboolean +ancestor_ends_here (Segment *state, + LineInfo *line, + gint line_pos, + Segment **new_state) +{ + Context *terminating_context; + + terminating_context = ancestor_context_ends_here (state->context, line, line_pos); + + if (new_state != NULL && terminating_context != NULL) + { + /* We have found a context that ends here, so we close + * all the descendants. terminating_segment will be + * closed by next next_segment() call from analyze_line. */ + Segment *current_segment = state; + + while (current_segment->context != terminating_context) + current_segment = current_segment->parent; + + *new_state = current_segment; + g_assert (*new_state != NULL); + } + + return terminating_context != NULL; +} + +/** + * next_segment: + * + * @ce: #GtkSourceContextEngine. + * @state: current state. + * @line: analyzed line. + * @line_pos: position inside @line, bytes. + * @new_state: where to store the new state. + * @hint: child of @state used to optimize tree operations. + * + * Verifies if a context starts or ends in @line at @line_pos of after it. + * If the contexts starts or ends here @new_state and @line_pos are updated. + * + * Returns: %FALSE is there are no more contexts in @line. + */ +static gboolean +next_segment (GtkSourceContextEngine *ce, + Segment *state, + LineInfo *line, + gint *line_pos, + Segment **new_state) +{ + gint pos = *line_pos; + + g_assert (!ce->priv->hint2 || ce->priv->hint2->parent == state); + g_assert (pos <= line->byte_length); + + while (pos <= line->byte_length) + { + DefinitionsIter def_iter; + gboolean context_end_found; + DefinitionChild *child_def; + + if (state->context->reg_all) + { + if (!regex_match (state->context->reg_all, + line->text, + line->byte_length, + pos)) + { + return FALSE; + } + + regex_fetch_pos_bytes (state->context->reg_all, + 0, &pos, NULL); + } + + /* Does an ancestor end here? */ + if (ANCESTOR_CAN_END_CONTEXT (state->context) && + ancestor_ends_here (state, line, pos, new_state)) + { + g_assert (pos <= line->byte_length); + segment_extend (state, line_pos_to_offset (line, pos)); + *line_pos = pos; + return TRUE; + } + + /* Does the current context end here? */ + context_end_found = segment_ends_here (state, line, pos); + + /* Iter over the definitions we can find in the current + * context. */ + definition_iter_init (&def_iter, state->context->definition); + while ((child_def = definition_iter_next (&def_iter)) != NULL) + { + gboolean try_this = TRUE; + + g_return_val_if_fail (child_def->resolved, FALSE); + + /* If the child definition does not extend the parent + * and the current context could end here we do not + * need to examine this child. */ + if (!HAS_OPTION (child_def->u.definition, EXTEND_PARENT) && context_end_found) + try_this = FALSE; + + if (HAS_OPTION (child_def->u.definition, FIRST_LINE_ONLY) && line->start_at != 0) + try_this = FALSE; + + if (HAS_OPTION (child_def->u.definition, ONCE_ONLY)) + { + Segment *prev; + + for (prev = state->children; prev != NULL; prev = prev->next) + { + if (prev->context != NULL && + prev->context->definition == child_def->u.definition) + { + try_this = FALSE; + break; + } + } + } + + if (try_this) + { + /* Does this child definition start a new + * context at the current position? */ + if (child_starts_here (ce, state, child_def, + line, &pos, new_state)) + { + g_assert (pos <= line->byte_length); + *line_pos = pos; + definition_iter_destroy (&def_iter); + return TRUE; + } + } + + /* This child does not start here, so we analyze + * another definition. */ + } + definition_iter_destroy (&def_iter); + + if (context_end_found) + { + /* We have found that the current context could end + * here and that it cannot be extended by a child. + * Still, it may happen that parent context ends in + * the middle of the end regex match, apply_match() + * checks this. */ + if (apply_match (state, line, &pos, state->context->end, SUB_PATTERN_WHERE_END)) + { + g_assert (pos <= line->byte_length); + + while (SEGMENT_ENDS_PARENT (state)) + state = state->parent; + + *new_state = state->parent; + ce->priv->hint2 = state; + *line_pos = pos; + return TRUE; + } + } + + /* Nothing new at this position, go to next char. */ + pos = g_utf8_next_char (line->text + pos) - line->text; + } + + return FALSE; +} + +/** + * check_line_end: + * + * @state: current state. + * @hint: child of @state used in analyze_line() and next_segment(). + * + * Closes the contexts that cannot contain end of lines if needed. + * Updates hint if new state is different from @state. + * + * Returns: the new state. + */ +static Segment * +check_line_end (GtkSourceContextEngine *ce, + Segment *state) +{ + Segment *current_segment; + Segment *terminating_segment; + + g_assert (!ce->priv->hint2 || ce->priv->hint2->parent == state); + + /* A context can be terminated by the parent if extend_parent is + * FALSE, so we need to verify the end of all the parents of + * not-extending contexts. */ + terminating_segment = NULL; + current_segment = state; + + while (current_segment != NULL) + { + if (SEGMENT_END_AT_LINE_END (current_segment)) + terminating_segment = current_segment; + else if (!ANCESTOR_CAN_END_CONTEXT(current_segment->context)) + break; + current_segment = current_segment->parent; + } + + if (terminating_segment != NULL) + { + ce->priv->hint2 = terminating_segment; + return terminating_segment->parent; + } + else + { + return state; + } +} + +static void +delete_zero_length_segments (GtkSourceContextEngine *ce, + GList *list) +{ + while (list != NULL) + { + Segment *s = list->data; + + if (s->start_at == s->end_at) + { + GList *l; + + for (l = list->next; l != NULL; ) + { + GList *next = l->next; + Segment *s2 = l->data; + gboolean child = FALSE; + + while (s2 != NULL) + { + if (s2 == s) + { + child = TRUE; + break; + } + + s2 = s2->parent; + } + + if (child) + list = g_list_delete_link (list, l); + + l = next; + } + + if (ce->priv->hint2 != NULL) + { + Segment *s2 = ce->priv->hint2; + gboolean child = FALSE; + + while (s2 != NULL) + { + if (s2 == s) + { + child = TRUE; + break; + } + + s2 = s2->parent; + } + + if (child) + ce->priv->hint2 = s->parent; + } + + segment_remove (ce, s); + } + + list = g_list_delete_link (list, list); + } +} + +/** + * analyze_line: + * + * @ce: #GtkSourceContextEngine. + * @state: the state at the beginning of line. + * @line: the line. + * @hint: a child of @state around start of line, to make it faster. + * + * Finds contexts at the line and updates the syntax tree on it. + * + * Returns: starting state at the next line. + */ +static Segment * +analyze_line (GtkSourceContextEngine *ce, + Segment *state, + LineInfo *line) +{ + gint line_pos = 0; + GList *end_segments = NULL; + GTimer *timer; + + g_assert (SEGMENT_IS_CONTAINER (state)); + + if (ce->priv->hint2 == NULL || ce->priv->hint2->parent != state) + ce->priv->hint2 = state->last_child; + g_assert (!ce->priv->hint2 || ce->priv->hint2->parent == state); + + timer = g_timer_new (); + + /* Find the contexts in the line. */ + while (line_pos <= line->byte_length) + { + Segment *new_state = NULL; + + if (!next_segment (ce, state, line, &line_pos, &new_state)) + break; + + if (g_timer_elapsed (timer, NULL) * 1000 > MAX_TIME_FOR_ONE_LINE) + { + g_critical (_("Highlighting a single line took too much time, " + "syntax highlighting will be disabled")); + disable_highlighting (ce); + break; + } + + g_assert (new_state != NULL); + g_assert (SEGMENT_IS_CONTAINER (new_state)); + + state = new_state; + + if (ce->priv->hint2 == NULL || ce->priv->hint2->parent != state) + ce->priv->hint2 = state->last_child; + g_assert (!ce->priv->hint2 || ce->priv->hint2->parent == state); + + /* XXX this a temporary workaround for zero-length segments in the end + * of line. there are no zero-length segments in the middle because it goes + * into infinite loop in that case. */ + /* state may be extended later, so not all elements of new_segments + * really have zero length */ + if (state->start_at == line->char_length) + end_segments = g_list_prepend (end_segments, state); + } + + g_timer_destroy (timer); + if (ce->priv->disabled) + return NULL; + + /* Extend current state to the end of line. */ + segment_extend (state, line->start_at + line->char_length); + g_assert (line_pos <= line->byte_length); + + /* Verify if we need to close the context because we are at + * the end of the line. */ + if (ANCESTOR_CAN_END_CONTEXT (state->context) || + SEGMENT_END_AT_LINE_END (state)) + { + state = check_line_end (ce, state); + } + + /* Extend the segment to the beginning of next line. */ + g_assert (SEGMENT_IS_CONTAINER (state)); + segment_extend (state, NEXT_LINE_OFFSET (line)); + + /* if it's the last line, don't bother with zero length segments */ + if (!line->eol_length) + g_list_free (end_segments); + else + delete_zero_length_segments (ce, end_segments); + + CHECK_TREE (ce); + + return state; +} + +/** + * get_line_info: + * + * @buffer: #GtkTextBuffer. + * @line_start: iterator pointing to the beginning of line. + * @line_start: iterator pointing to the beginning of next line or to the end + * of this line if it's the last line in @buffer. + * @line: #LineInfo structure to be filled. + * + * Retrieves line text from the buffer, finds line terminator and fills + * @line structure. + */ +static void +get_line_info (GtkTextBuffer *buffer, + const GtkTextIter *line_start, + const GtkTextIter *line_end, + LineInfo *line) +{ + g_assert (!gtk_text_iter_equal (line_start, line_end)); + + line->text = gtk_text_buffer_get_slice (buffer, line_start, line_end, TRUE); + line->start_at = gtk_text_iter_get_offset (line_start); + + if (!gtk_text_iter_starts_line (line_end)) + { + line->eol_length = 0; + line->char_length = g_utf8_strlen (line->text, -1); + line->byte_length = strlen (line->text); + } + else + { + gint eol_index, next_line_index; + + pango_find_paragraph_boundary (line->text, -1, + &eol_index, + &next_line_index); + + g_assert (eol_index < next_line_index); + + line->char_length = g_utf8_strlen (line->text, eol_index); + line->eol_length = g_utf8_strlen (line->text + eol_index, -1); + line->byte_length = eol_index; + } + + g_assert (gtk_text_iter_get_offset (line_end) == + line->start_at + line->char_length + line->eol_length); +} + +/** + * line_info_destroy: + * + * @line: #LineInfo. + * + * Destroys data allocated by get_line_info(). + */ +static void +line_info_destroy (LineInfo *line) +{ + g_free (line->text); +} + +/** + * segment_tree_zero_len: + * + * @ce: #GtkSoucreContextEngine. + * + * Erases syntax tree and sets root segment length to zero. + * It's a shortcut for case when all the text is deleted from + * the buffer. + */ +static void +segment_tree_zero_len (GtkSourceContextEngine *ce) +{ + Segment *root = ce->priv->root_segment; + segment_destroy_children (ce, root); + root->start_at = root->end_at = 0; + CHECK_TREE (ce); +} + +#ifdef ENABLE_CHECK_TREE +static Segment * +get_segment_at_offset_slow_ (Segment *segment, + gint offset) +{ + Segment *child; + +start: + if (segment->parent == NULL && offset == segment->end_at) + return segment; + + if (segment->start_at > offset) + { + g_assert (segment->parent != NULL); + segment = segment->parent; + goto start; + } + + if (segment->start_at == offset) + { + if (segment->children != NULL && segment->children->start_at == offset) + { + segment = segment->children; + goto start; + } + + return segment; + } + + if (segment->end_at <= offset && segment->parent != NULL) + { + if (segment->next != NULL) + { + if (segment->next->start_at > offset) + return segment->parent; + + segment = segment->next; + } + else + { + segment = segment->parent; + } + + goto start; + } + + for (child = segment->children; child != NULL; child = child->next) + { + if (child->start_at == offset) + { + segment = child; + goto start; + } + + if (child->end_at <= offset) + continue; + + if (child->start_at > offset) + break; + + segment = child; + goto start; + } + + return segment; +} +#endif /* ENABLE_CHECK_TREE */ + +#define SEGMENT_IS_ZERO_LEN_AT(s,o) ((s)->start_at == (o) && (s)->end_at == (o)) +#define SEGMENT_CONTAINS(s,o) ((s)->start_at <= (o) && (s)->end_at > (o)) +#define SEGMENT_DISTANCE(s,o) (MIN (ABS ((s)->start_at - (o)), ABS ((s)->end_at - (o)))) +static Segment * +get_segment_in_ (Segment *segment, + gint offset) +{ + Segment *child; + + g_assert (segment->start_at <= offset && segment->end_at > offset); + + if (segment->children == NULL) + return segment; + + if (segment->children == segment->last_child) + { + if (SEGMENT_IS_ZERO_LEN_AT (segment->children, offset)) + return segment->children; + + if (SEGMENT_CONTAINS (segment->children, offset)) + return get_segment_in_ (segment->children, offset); + + return segment; + } + + if (segment->children->start_at > offset || segment->last_child->end_at < offset) + return segment; + + if (SEGMENT_DISTANCE (segment->children, offset) >= SEGMENT_DISTANCE (segment->last_child, offset)) + { + for (child = segment->children; child; child = child->next) + { + if (child->start_at > offset) + return segment; + + if (SEGMENT_IS_ZERO_LEN_AT (child, offset)) + return child; + + if (SEGMENT_CONTAINS (child, offset)) + return get_segment_in_ (child, offset); + } + } + else + { + for (child = segment->last_child; child; child = child->prev) + { + if (SEGMENT_IS_ZERO_LEN_AT (child, offset)) + { + while (child->prev != NULL && SEGMENT_IS_ZERO_LEN_AT (child->prev, offset)) + child = child->prev; + return child; + } + + if (child->end_at <= offset) + return segment; + + if (SEGMENT_CONTAINS (child, offset)) + return get_segment_in_ (child, offset); + } + } + + return segment; +} + +/* assumes zero-length segments can't have children */ +static Segment * +get_segment_ (Segment *segment, + gint offset) +{ + if (segment->parent != NULL) + { + if (!SEGMENT_CONTAINS (segment->parent, offset)) + return get_segment_ (segment->parent, offset); + } + else + { + g_assert (offset >= segment->start_at); + g_assert (offset <= segment->end_at); + } + + if (SEGMENT_CONTAINS (segment, offset)) + return get_segment_in_ (segment, offset); + + if (SEGMENT_IS_ZERO_LEN_AT (segment, offset)) + { + while (segment->prev != NULL && SEGMENT_IS_ZERO_LEN_AT (segment->prev, offset)) + segment = segment->prev; + return segment; + } + + if (offset < segment->start_at) + { + while (segment->prev != NULL && segment->prev->start_at > offset) + segment = segment->prev; + + g_assert (!segment->prev || segment->prev->start_at <= offset); + + if (segment->prev == NULL) + return segment->parent; + + if (segment->prev->end_at > offset) + return get_segment_in_ (segment->prev, offset); + + if (segment->prev->end_at == offset) + { + if (SEGMENT_IS_ZERO_LEN_AT (segment->prev, offset)) + { + segment = segment->prev; + while (segment->prev != NULL && SEGMENT_IS_ZERO_LEN_AT (segment->prev, offset)) + segment = segment->prev; + return segment; + } + + return segment->parent; + } + + /* segment->prev->end_at < offset */ + return segment->parent; + } + + /* offset >= segment->end_at, not zero-length */ + + while (segment->next != NULL) + { + if (SEGMENT_IS_ZERO_LEN_AT (segment->next, offset)) + return segment->next; + + if (segment->next->end_at > offset) + { + if (segment->next->start_at <= offset) + return get_segment_in_ (segment->next, offset); + else + return segment->parent; + } + + segment = segment->next; + } + + return segment->parent; +} +#undef SEGMENT_IS_ZERO_LEN_AT +#undef SEGMENT_CONTAINS +#undef SEGMENT_DISTANCE + +/** + * get_segment_at_offset: + * + * @ce: #GtkSoucreContextEngine. + * @hint: segment to start search from or %NULL. + * @offset: the offset, characters. + * + * Finds the deepest segment "at @offset". + * More precisely, it returns toplevel segment if + * @offset is equal to length of buffer; or non-zero-length + * segment which contains character at @offset; or zero-length + * segment at @offset. In case when there are several zero-length + * segments, it returns the first one. + */ +static Segment * +get_segment_at_offset (GtkSourceContextEngine *ce, + Segment *hint, + gint offset) +{ + Segment *result; + + if (offset == ce->priv->root_segment->end_at) + return ce->priv->root_segment; + +#ifdef ENABLE_DEBUG + /* if you see this message (often), then something is + * wrong with the hints business, i.e. optimizations + * do not work quite like they should */ + if (hint == NULL || hint == ce->priv->root_segment) + { + static int c; + g_print ("searching from root %d\n", ++c); + } +#endif + + result = get_segment_ (hint ? hint : ce->priv->root_segment, offset); + +#ifdef ENABLE_CHECK_TREE + g_assert (result == get_segment_at_offset_slow_ (hint, offset)); +#endif + + return result; +} + +/** + * segment_remove: + * + * @ce: #GtkSoucreContextEngine. + * @segment: segment to remove. + * + * Removes the segment from syntax tree and frees it. + * It correctly updates parent's children list, not + * like segment_destroy() where caller has to take care + * of tree integrity. + */ +static void +segment_remove (GtkSourceContextEngine *ce, + Segment *segment) +{ + if (segment->next != NULL) + segment->next->prev = segment->prev; + else + segment->parent->last_child = segment->prev; + + if (segment->prev != NULL) + segment->prev->next = segment->next; + else + segment->parent->children = segment->next; + + /* if ce->priv->hint is being deleted, set it to some + * neighbour segment */ + if (ce->priv->hint == segment) + { + if (segment->next != NULL) + ce->priv->hint = segment->next; + else if (segment->prev != NULL) + ce->priv->hint = segment->prev; + else + ce->priv->hint = segment->parent; + } + + /* if ce->priv->hint2 is being deleted, set it to some + * neighbour segment */ + if (ce->priv->hint2 == segment) + { + if (segment->next != NULL) + ce->priv->hint2 = segment->next; + else if (segment->prev != NULL) + ce->priv->hint2 = segment->prev; + else + ce->priv->hint2 = segment->parent; + } + + segment_destroy (ce, segment); +} + +static void +segment_erase_middle_ (GtkSourceContextEngine *ce, + Segment *segment, + gint start, + gint end) +{ + Segment *new_segment, *child; + SubPattern *sp; + + new_segment = segment_new (ce, + segment->parent, + segment->context, + end, + segment->end_at, + FALSE); + segment->end_at = start; + + new_segment->next = segment->next; + segment->next = new_segment; + new_segment->prev = segment; + + if (new_segment->next != NULL) + new_segment->next->prev = new_segment; + else + new_segment->parent->last_child = new_segment; + + child = segment->children; + segment->children = NULL; + segment->last_child = NULL; + + while (child != NULL) + { + Segment *append_to; + Segment *next = child->next; + + if (child->start_at < start) + { + g_assert (child->end_at <= start); + append_to = segment; + } + else + { + g_assert (child->start_at >= end); + append_to = new_segment; + } + + child->parent = append_to; + + if (append_to->last_child != NULL) + { + append_to->last_child->next = child; + child->prev = append_to->last_child; + child->next = NULL; + append_to->last_child = child; + } + else + { + child->next = child->prev = NULL; + append_to->last_child = child; + append_to->children = child; + } + + child = next; + } + + sp = segment->sub_patterns; + segment->sub_patterns = NULL; + + while (sp != NULL) + { + SubPattern *next = sp->next; + Segment *append_to; + + if (sp->start_at < start) + { + sp->end_at = MIN (sp->end_at, start); + append_to = segment; + } + else + { + g_assert (sp->end_at > end); + sp->start_at = MAX (sp->start_at, end); + append_to = new_segment; + } + + sp->next = append_to->sub_patterns; + append_to->sub_patterns = sp; + + sp = next; + } + + CHECK_SEGMENT_CHILDREN (segment); + CHECK_SEGMENT_CHILDREN (new_segment); +} + +/** + * segment_erase_range_: + * + * @ce: #GtkSourceContextEngine. + * @segment: the segment. + * @start: start offset of range to erase, characters. + * @end: end offset of range to erase, characters. + * + * Recurisvely removes segments from [@start, @end] interval + * starting from @segment. If @segment belongs to the range, + * or it's a zero-length segment at @end offset, and it's not + * the toplevel segment, then it's removed from the tree. + * If @segment intersects with the range (unless it's the toplevel + * segment), then its ends are adjusted appropriately, and it's + * split into two if it completely contains the range. + */ +static void +segment_erase_range_ (GtkSourceContextEngine *ce, + Segment *segment, + gint start, + gint end) +{ + g_assert (start < end); + + if (segment->start_at == segment->end_at) + { + if (segment->start_at >= start && segment->start_at <= end) + segment_remove (ce, segment); + return; + } + + if (segment->start_at > end || segment->end_at < start) + return; + + if (segment->start_at >= start && segment->end_at <= end && segment->parent) + { + segment_remove (ce, segment); + return; + } + + if (segment->start_at == end) + { + Segment *child = segment->children; + + while (child != NULL && child->start_at == end) + { + Segment *next = child->next; + segment_erase_range_ (ce, child, start, end); + child = next; + } + } + else if (segment->end_at == start) + { + Segment *child = segment->last_child; + + while (child != NULL && child->end_at == start) + { + Segment *prev = child->prev; + segment_erase_range_ (ce, child, start, end); + child = prev; + } + } + else + { + Segment *child = segment->children; + + while (child != NULL) + { + Segment *next = child->next; + segment_erase_range_ (ce, child, start, end); + child = next; + } + } + + if (segment->sub_patterns != NULL) + { + SubPattern *sp; + + sp = segment->sub_patterns; + segment->sub_patterns = NULL; + + while (sp != NULL) + { + SubPattern *next = sp->next; + + if (sp->start_at >= start && sp->end_at <= end) + sub_pattern_free (sp); + else + segment_add_subpattern (segment, sp); + + sp = next; + } + } + + if (segment->parent != NULL) + { + /* Now all children and subpatterns are cleaned up, + * so we only need to split segment properly if its middle + * was erased. Otherwise, only ends need to be adjusted. */ + if (segment->start_at < start && segment->end_at > end) + { + segment_erase_middle_ (ce, segment, start, end); + } + else + { + g_assert ((segment->start_at >= start && segment->end_at > end) || + (segment->start_at < start && segment->end_at <= end)); + + if (segment->end_at > end) + { + /* If we erase the beginning, we need to clear + * is_start flag. */ + segment->start_at = end; + segment->is_start = FALSE; + } + else + { + segment->end_at = start; + } + } + } +} + +/** + * segment_merge: + * + * @ce: #GtkSourceContextEngine. + * @first: first segment. + * @second: second segment. + * + * Merges adjacent segments @first and @second given + * their contexts are equal. + */ +static void +segment_merge (GtkSourceContextEngine *ce, + Segment *first, + Segment *second) +{ + Segment *parent; + + if (first == second) + return; + + g_assert (!SEGMENT_IS_INVALID (first)); + g_assert (first->context == second->context); + g_assert (first->end_at == second->start_at); + + if (first->parent != second->parent) + segment_merge (ce, first->parent, second->parent); + + parent = first->parent; + + g_assert (first->next == second); + g_assert (first->parent == second->parent); + g_assert (second != parent->children); + + if (second == parent->last_child) + parent->last_child = first; + first->next = second->next; + if (second->next != NULL) + second->next->prev = first; + + first->end_at = second->end_at; + + if (second->children != NULL) + { + Segment *child; + + for (child = second->children; child != NULL; child = child->next) + child->parent = first; + + if (first->children == NULL) + { + g_assert (!first->last_child); + first->children = second->children; + first->last_child = second->last_child; + } + else + { + first->last_child->next = second->children; + second->children->prev = first->last_child; + first->last_child = second->last_child; + } + } + + if (second->sub_patterns != NULL) + { + if (first->sub_patterns == NULL) + { + first->sub_patterns = second->sub_patterns; + } + else + { + while (second->sub_patterns != NULL) + { + SubPattern *sp = second->sub_patterns; + second->sub_patterns = sp->next; + sp->next = first->sub_patterns; + first->sub_patterns = sp; + } + } + } + + second->children = NULL; + second->last_child = NULL; + second->sub_patterns = NULL; + + segment_destroy (ce, second); +} + +/** + * erase_segments: + * + * @ce: #GtkSourceContextEngine. + * @start: start offset of region to erase, characters. + * @end: end offset of region to erase, characters. + * @hint: segment around @start to make it faster. + * + * Erases all non-toplevel segments in the interval + * [@start, @end]. Its action on the tree is roughly + * equivalent to segment_erase_range_(ce->priv->root_segment, start, end) + * (but that does not accept toplevel segment). + */ +static void +erase_segments (GtkSourceContextEngine *ce, + gint start, + gint end, + Segment *hint) +{ + Segment *root = ce->priv->root_segment; + Segment *child, *hint_prev; + + if (root->children == NULL) + return; + + if (hint == NULL) + hint = ce->priv->hint; + + if (hint != NULL) + while (hint != NULL && hint->parent != ce->priv->root_segment) + hint = hint->parent; + + if (hint == NULL) + hint = root->children; + + hint_prev = hint->prev; + + child = hint; + while (child != NULL) + { + Segment *next = child->next; + + if (child->end_at < start) + { + child = next; + + if (next != NULL) + ce->priv->hint = next; + + continue; + } + + if (child->start_at > end) + { + ce->priv->hint = child; + break; + } + + segment_erase_range_ (ce, child, start, end); + child = next; + } + + child = hint_prev; + while (child != NULL) + { + Segment *prev = child->prev; + + if (ce->priv->hint == NULL) + ce->priv->hint = child; + + if (child->start_at > end) + { + child = prev; + continue; + } + + if (child->end_at < start) + { + break; + } + + segment_erase_range_ (ce, child, start, end); + child = prev; + } + + CHECK_TREE (ce); +} + +/** + * update_syntax: + * + * @ce: #GtkSourceContextEngine. + * @end: desired end of region to analyze or %NULL. + * @time: maximal amount of time in milliseconds allowed to spend here + * or 0 for 'unlimited'. + * + * Updates syntax tree. If @end is not %NULL, then it analyzes + * (reanalyzes invalid areas in) region from start of buffer + * to @end. Otherwise, it analyzes batch of text starting at + * first invalid line. + * In order to avoid blocking ui it uses a timer and stops + * when time elapsed is greater than @time, so analyzed region is + * not necessarily what's requested (unless @time is 0). + */ +/* XXX it must be refactored. */ +static void +update_syntax (GtkSourceContextEngine *ce, + const GtkTextIter *end, + gint time) +{ + Segment *invalid; + GtkTextIter start_iter, end_iter; + GtkTextIter line_start, line_end; + gint start_offset, end_offset; + gint line_start_offset, line_end_offset; + gint analyzed_end; + GtkTextBuffer *buffer = ce->priv->buffer; + Segment *state = ce->priv->root_segment; + GTimer *timer; + + context_freeze (ce->priv->root_context); + update_tree (ce); + + if (!gtk_text_buffer_get_char_count (buffer)) + { + segment_tree_zero_len (ce); + goto out; + } + + invalid = get_invalid_segment (ce); + + if (invalid == NULL) + goto out; + + if (end != NULL && invalid->start_at >= gtk_text_iter_get_offset (end)) + goto out; + + if (end != NULL) + { + end_offset = gtk_text_iter_get_offset (end); + start_offset = MIN (end_offset, invalid->start_at); + } + else + { + start_offset = invalid->start_at; + end_offset = gtk_text_buffer_get_char_count (buffer); + } + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start_offset); + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end_offset); + + if (!gtk_text_iter_starts_line (&start_iter)) + { + gtk_text_iter_set_line_offset (&start_iter, 0); + start_offset = gtk_text_iter_get_offset (&start_iter); + } + + if (!gtk_text_iter_starts_line (&end_iter)) + { + gtk_text_iter_forward_line (&end_iter); + end_offset = gtk_text_iter_get_offset (&end_iter); + } + + /* This happens after deleting all text on last line. */ + if (start_offset == end_offset) + { + g_assert (end_offset == gtk_text_buffer_get_char_count (buffer)); + g_assert (g_slist_length (ce->priv->invalid) == 1); + segment_remove (ce, invalid); + CHECK_TREE (ce); + goto out; + } + + + /* Main loop */ + + line_start = start_iter; + line_start_offset = start_offset; + line_end = line_start; + gtk_text_iter_forward_line (&line_end); + line_end_offset = gtk_text_iter_get_offset (&line_end); + analyzed_end = line_end_offset; + + timer = g_timer_new (); + + while (TRUE) + { + LineInfo line; + gboolean next_line_invalid = FALSE; + gboolean need_invalidate_next = FALSE; + + /* Last buffer line. */ + if (line_start_offset == line_end_offset) + { + g_assert (line_start_offset == gtk_text_buffer_get_char_count (buffer)); + break; + } + + /* Analyze the line */ + erase_segments (ce, line_start_offset, line_end_offset, ce->priv->hint); + get_line_info (buffer, &line_start, &line_end, &line); + +#ifdef ENABLE_CHECK_TREE + { + Segment *inv = get_invalid_segment (ce); + g_assert (inv == NULL || inv->start_at >= line_end_offset); + } +#endif + + if (line_start_offset == 0) + state = ce->priv->root_segment; + else + state = get_segment_at_offset (ce, + ce->priv->hint ? ce->priv->hint : state, + line_start_offset - 1); + g_assert (state->context != NULL); + + ce->priv->hint2 = ce->priv->hint; + + if (ce->priv->hint2 != NULL && ce->priv->hint2->parent != state) + ce->priv->hint2 = NULL; + + state = analyze_line (ce, state, &line); + + /* At this point analyze_line() could have disabled highlighting */ + if (ce->priv->disabled) + return; + +#ifdef ENABLE_CHECK_TREE + { + Segment *inv = get_invalid_segment (ce); + g_assert (inv == NULL || inv->start_at >= line_end_offset); + } +#endif + + /* XXX this is wrong */ + /* I don't know anymore why it's wrong, I guess it means + * "may be inefficient" */ + if (ce->priv->hint2 != NULL) + ce->priv->hint = ce->priv->hint2; + else + ce->priv->hint = state; + + line_info_destroy (&line); + + gtk_text_region_add (ce->priv->refresh_region, &line_start, &line_end); + analyzed_end = line_end_offset; + invalid = get_invalid_segment (ce); + + if (invalid != NULL) + { + GtkTextIter iter; + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, invalid->start_at); + gtk_text_iter_set_line_offset (&iter, 0); + + if (gtk_text_iter_get_offset (&iter) == line_end_offset) + next_line_invalid = TRUE; + } + + if (!next_line_invalid) + { + Segment *old_state, *hint; + + hint = ce->priv->hint ? ce->priv->hint : state; + old_state = get_segment_at_offset (ce, hint, line_end_offset); + + /* We can merge old and new stuff if: contexts are the same, + * and the segment on the next line is continuation of the + * segment from previous line. */ + if (old_state != state && + (old_state->context != state->context || state->is_start)) + { + need_invalidate_next = TRUE; + next_line_invalid = TRUE; + } + else + { + segment_merge (ce, state, old_state); + CHECK_TREE (ce); + } + } + + if ((time != 0 && g_timer_elapsed (timer, NULL) * 1000 > time) || + line_end_offset >= end_offset || + (invalid == NULL && !next_line_invalid)) + { + if (need_invalidate_next) + insert_range (ce, line_end_offset, 0); + break; + } + + if (next_line_invalid) + { + line_start_offset = line_end_offset; + line_start = line_end; + gtk_text_iter_forward_line (&line_end); + line_end_offset = gtk_text_iter_get_offset (&line_end); + } + else + { + gtk_text_buffer_get_iter_at_offset (buffer, &line_start, invalid->start_at); + gtk_text_iter_set_line_offset (&line_start, 0); + line_start_offset = gtk_text_iter_get_offset (&line_start); + line_end = line_start; + gtk_text_iter_forward_line (&line_end); + line_end_offset = gtk_text_iter_get_offset (&line_end); + } + } + + if (analyzed_end == gtk_text_buffer_get_char_count (buffer)) + { + g_assert (g_slist_length (ce->priv->invalid) <= 1); + + if (ce->priv->invalid != NULL) + { + invalid = get_invalid_segment (ce); + segment_remove (ce, invalid); + CHECK_TREE (ce); + } + } + + if (!all_analyzed (ce)) + install_idle_worker (ce); + + gtk_text_iter_set_offset (&end_iter, analyzed_end); + refresh_range (ce, &start_iter, &end_iter, FALSE); + + PROFILE (g_print ("analyzed %d chars from %d to %d in %fms\n", + analyzed_end - start_offset, start_offset, analyzed_end, + g_timer_elapsed (timer, NULL) * 1000)); + + g_timer_destroy (timer); + +out: + /* must call context_thaw, so this is the only return point */ + context_thaw (ce->priv->root_context); +} + + +/* DEFINITIONS MANAGEMENT ------------------------------------------------- */ + +static DefinitionChild * +definition_child_new (ContextDefinition *definition, + const gchar *child_id, + const gchar *style, + gboolean override_style, + gboolean is_ref_all, + gboolean original_ref) +{ + DefinitionChild *ch; + + g_return_val_if_fail (child_id != NULL, NULL); + + ch = g_slice_new0 (DefinitionChild); + + if (original_ref) + ch->u.id = g_strdup_printf ("@%s", child_id); + else + ch->u.id = g_strdup (child_id); + + ch->style = g_strdup (style); + ch->is_ref_all = is_ref_all; + ch->resolved = FALSE; + ch->override_style = override_style; + ch->override_style_deep = (override_style && style == NULL); + + definition->children = g_slist_append (definition->children, ch); + + return ch; +} + +static void +definition_child_free (DefinitionChild *ch) +{ + if (!ch->resolved) + g_free (ch->u.id); + g_free (ch->style); + +#ifdef ENABLE_DEBUG + memset (ch, 1, sizeof (DefinitionChild)); +#else + g_slice_free (DefinitionChild, ch); +#endif +} + +static ContextDefinition * +context_definition_new (const gchar *id, + ContextType type, + const gchar *match, + const gchar *start, + const gchar *end, + const gchar *style, + GtkSourceContextFlags flags, + GError **error) +{ + ContextDefinition *definition; + gboolean regex_error = FALSE; + gboolean unresolved_error = FALSE; + + g_return_val_if_fail (id != NULL, NULL); + + switch (type) + { + case CONTEXT_TYPE_SIMPLE: + g_return_val_if_fail (match != NULL, NULL); + g_return_val_if_fail (!end && !start, NULL); + break; + case CONTEXT_TYPE_CONTAINER: + g_return_val_if_fail (!match, NULL); + g_return_val_if_fail (!end || start, NULL); + break; + } + + definition = g_slice_new0 (ContextDefinition); + + if (match != NULL) + { + definition->u.match = regex_new (match, G_REGEX_ANCHORED, error); + + if (definition->u.match == NULL) + { + regex_error = TRUE; + } + else if (!definition->u.match->resolved) + { + regex_error = TRUE; + unresolved_error = TRUE; + regex_unref (definition->u.match); + definition->u.match = NULL; + } + } + + if (start != NULL) + { + definition->u.start_end.start = regex_new (start, G_REGEX_ANCHORED, error); + + if (definition->u.start_end.start == NULL) + { + regex_error = TRUE; + } + else if (!definition->u.start_end.start->resolved) + { + regex_error = TRUE; + unresolved_error = TRUE; + regex_unref (definition->u.start_end.start); + definition->u.start_end.start = NULL; + } + } + + if (end != NULL && !regex_error) + { + definition->u.start_end.end = regex_new (end, G_REGEX_ANCHORED, error); + + if (definition->u.start_end.end == NULL) + regex_error = TRUE; + } + + if (unresolved_error) + { + g_set_error (error, + GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_START_REF, + _("context '%s' cannot contain a \\%%{...@start} command"), + id); + regex_error = TRUE; + } + + if (regex_error) + { + g_slice_free (ContextDefinition, definition); + return NULL; + } + + definition->ref_count = 1; + definition->id = g_strdup (id); + definition->default_style = g_strdup (style); + definition->type = type; + definition->flags = flags; + definition->children = NULL; + definition->sub_patterns = NULL; + definition->n_sub_patterns = 0; + + return definition; +} + +static ContextDefinition * +context_definition_ref (ContextDefinition *definition) +{ + g_return_val_if_fail (definition != NULL, NULL); + definition->ref_count += 1; + return definition; +} + +static void +context_definition_unref (ContextDefinition *definition) +{ + GSList *sub_pattern_list; + + if (definition == NULL || --definition->ref_count != 0) + return; + + switch (definition->type) + { + case CONTEXT_TYPE_SIMPLE: + regex_unref (definition->u.match); + break; + case CONTEXT_TYPE_CONTAINER: + regex_unref (definition->u.start_end.start); + regex_unref (definition->u.start_end.end); + break; + } + + sub_pattern_list = definition->sub_patterns; + while (sub_pattern_list != NULL) + { + SubPatternDefinition *sp_def = sub_pattern_list->data; +#ifdef NEED_DEBUG_ID + g_free (sp_def->id); +#endif + g_free (sp_def->style); + if (sp_def->is_named) + g_free (sp_def->u.name); + g_slice_free (SubPatternDefinition, sp_def); + sub_pattern_list = sub_pattern_list->next; + } + g_slist_free (definition->sub_patterns); + + g_free (definition->id); + g_free (definition->default_style); + regex_unref (definition->reg_all); + + g_slist_foreach (definition->children, (GFunc) definition_child_free, NULL); + g_slist_free (definition->children); + g_slice_free (ContextDefinition, definition); +} + +static void +definition_iter_init (DefinitionsIter *iter, + ContextDefinition *definition) +{ + iter->children_stack = g_slist_prepend (NULL, definition->children); +} + +static void +definition_iter_destroy (DefinitionsIter *iter) +{ + g_slist_free (iter->children_stack); +} + +static DefinitionChild * +definition_iter_next (DefinitionsIter *iter) +{ + GSList *children_list; + + if (iter->children_stack == NULL) + return NULL; + + children_list = iter->children_stack->data; + if (children_list == NULL) + { + iter->children_stack = g_slist_delete_link (iter->children_stack, + iter->children_stack); + return definition_iter_next (iter); + } + else + { + DefinitionChild *curr_child = children_list->data; + ContextDefinition *definition = curr_child->u.definition; + + g_return_val_if_fail (curr_child->resolved, NULL); + + children_list = g_slist_next (children_list); + iter->children_stack->data = children_list; + + if (curr_child->is_ref_all) + { + iter->children_stack = g_slist_prepend (iter->children_stack, + definition->children); + return definition_iter_next (iter); + } + else + { + return curr_child; + } + } +} + +gboolean +_gtk_source_context_data_define_context (GtkSourceContextData *ctx_data, + const gchar *id, + const gchar *parent_id, + const gchar *match_regex, + const gchar *start_regex, + const gchar *end_regex, + const gchar *style, + GtkSourceContextFlags flags, + GError **error) +{ + ContextDefinition *definition, *parent = NULL; + ContextType type; + gchar *original_id; + gboolean wrong_args = FALSE; + + g_return_val_if_fail (ctx_data != NULL, FALSE); + g_return_val_if_fail (id != NULL, FALSE); + + /* If the id is already present in the hashtable it is a duplicate, + * so we report the error (probably there is a duplicate id in the + * XML lang file) */ + if (LOOKUP_DEFINITION (ctx_data, id) != NULL) + { + g_set_error (error, + GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_DUPLICATED_ID, + _("duplicated context id '%s'"), id); + return FALSE; + } + + if (match_regex != NULL) + type = CONTEXT_TYPE_SIMPLE; + else + type = CONTEXT_TYPE_CONTAINER; + + /* Check if the arguments passed are exactly what we expect, no more, no less. */ + switch (type) + { + case CONTEXT_TYPE_SIMPLE: + if (start_regex != NULL || end_regex != NULL) + wrong_args = TRUE; + break; + case CONTEXT_TYPE_CONTAINER: + if (match_regex != NULL) + wrong_args = TRUE; + break; + } + + if (wrong_args) + { + g_set_error (error, + GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_ARGS, + /* do not translate, parser should take care of this */ + "insufficient or redundant arguments creating " + "the context '%s'", id); + return FALSE; + } + + if (parent_id == NULL) + { + parent = NULL; + } + else + { + parent = LOOKUP_DEFINITION (ctx_data, parent_id); + g_return_val_if_fail (parent != NULL, FALSE); + } + + definition = context_definition_new (id, type, match_regex, + start_regex, end_regex, style, + flags, error); + if (definition == NULL) + return FALSE; + + g_hash_table_insert (ctx_data->definitions, g_strdup (id), definition); + original_id = g_strdup_printf ("@%s", id); + g_hash_table_insert (ctx_data->definitions, original_id, + context_definition_ref (definition)); + + if (parent != NULL) + definition_child_new (parent, id, NULL, FALSE, FALSE, FALSE); + + return TRUE; +} + +gboolean +_gtk_source_context_data_add_sub_pattern (GtkSourceContextData *ctx_data, + const gchar *id, + const gchar *parent_id, + const gchar *name, + const gchar *where, + const gchar *style, + GError **error) +{ + ContextDefinition *parent; + SubPatternDefinition *sp_def; + SubPatternWhere where_num; + gint number; + + g_return_val_if_fail (ctx_data != NULL, FALSE); + g_return_val_if_fail (id != NULL, FALSE); + g_return_val_if_fail (parent_id != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + /* If the id is already present in the hashtable it is a duplicate, + * so we report the error (probably there is a duplicate id in the + * XML lang file) */ + if (LOOKUP_DEFINITION (ctx_data, id) != NULL) + { + g_set_error (error, + GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_DUPLICATED_ID, + _("duplicated context id '%s'"), id); + return FALSE; + } + + parent = LOOKUP_DEFINITION (ctx_data, parent_id); + g_return_val_if_fail (parent != NULL, FALSE); + + if (!where || !where[0] || !strcmp (where, "default")) + where_num = SUB_PATTERN_WHERE_DEFAULT; + else if (!strcmp (where, "start")) + where_num = SUB_PATTERN_WHERE_START; + else if (!strcmp (where, "end")) + where_num = SUB_PATTERN_WHERE_END; + else + where_num = (SubPatternWhere) -1; + + if ((parent->type == CONTEXT_TYPE_SIMPLE && where_num != SUB_PATTERN_WHERE_DEFAULT) || + (parent->type == CONTEXT_TYPE_CONTAINER && where_num == SUB_PATTERN_WHERE_DEFAULT)) + { + where_num = (SubPatternWhere) -1; + } + + if (where_num == (SubPatternWhere) -1) + { + g_set_error (error, + GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_WHERE, + /* do not translate, parent takes care of this */ + "invalid location ('%s') for sub pattern '%s'", + where, id); + return FALSE; + } + + sp_def = g_slice_new0 (SubPatternDefinition); +#ifdef NEED_DEBUG_ID + sp_def->id = g_strdup (id); +#endif + sp_def->style = g_strdup (style); + sp_def->where = where_num; + number = sub_pattern_to_int (name); + + if (number < 0) + { + sp_def->is_named = TRUE; + sp_def->u.name = g_strdup (name); + } + else + { + sp_def->is_named = FALSE; + sp_def->u.num = number; + } + + parent->sub_patterns = g_slist_append (parent->sub_patterns, sp_def); + sp_def->index = parent->n_sub_patterns++; + + return TRUE; +} + +/** + * context_is_pure_container: + * + * @def: context definition. + * + * Checks whether context is a container with no start regex. + * References to such contexts are implicitly translated to + * wildcard references (context_id:*). + */ +static gboolean +context_is_pure_container (ContextDefinition *def) +{ + return def->type == CONTEXT_TYPE_CONTAINER && + def->u.start_end.start == NULL; +} + +gboolean +_gtk_source_context_data_add_ref (GtkSourceContextData *ctx_data, + const gchar *parent_id, + const gchar *ref_id, + GtkSourceContextRefOptions options, + const gchar *style, + gboolean all, + GError **error) +{ + ContextDefinition *parent; + ContextDefinition *ref; + gboolean override_style = FALSE; + + g_return_val_if_fail (parent_id != NULL, FALSE); + g_return_val_if_fail (ref_id != NULL, FALSE); + g_return_val_if_fail (ctx_data != NULL, FALSE); + + ref = LOOKUP_DEFINITION (ctx_data, ref_id); + parent = LOOKUP_DEFINITION (ctx_data, parent_id); + g_return_val_if_fail (parent != NULL, FALSE); + + if (parent->type != CONTEXT_TYPE_CONTAINER) + { + g_set_error (error, + GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_PARENT, + /* do not translate, parent takes care of this */ + "invalid parent type for the context '%s'", + ref_id); + return FALSE; + } + + if (ref != NULL && context_is_pure_container (ref)) + all = TRUE; + + if (all && (options & (GTK_SOURCE_CONTEXT_IGNORE_STYLE | GTK_SOURCE_CONTEXT_OVERRIDE_STYLE))) + { + g_set_error (error, GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_STYLE, + _("style override used with wildcard context reference" + " in language '%s' in ref '%s'"), + ctx_data->lang->priv->id, ref_id); + return FALSE; + } + + if (options & (GTK_SOURCE_CONTEXT_IGNORE_STYLE | GTK_SOURCE_CONTEXT_OVERRIDE_STYLE)) + override_style = TRUE; + + definition_child_new (parent, ref_id, style, override_style, all, + (options & GTK_SOURCE_CONTEXT_REF_ORIGINAL) != 0); + + return TRUE; +} + +/** + * resolve_reference: + * + * Checks whether all children of a context definition refer to valid + * contexts. Called from _gtk_source_context_data_finish_parse. + */ +struct ResolveRefData { + GtkSourceContextData *ctx_data; + GError *error; +}; + +static void +resolve_reference (G_GNUC_UNUSED const gchar *id, + ContextDefinition *definition, + gpointer user_data) +{ + GSList *l; + + struct ResolveRefData *data = user_data; + + if (data->error != NULL) + return; + + for (l = definition->children; l != NULL && data->error == NULL; l = l->next) + { + ContextDefinition *ref; + DefinitionChild *child_def = l->data; + + if (child_def->resolved) + continue; + + ref = LOOKUP_DEFINITION (data->ctx_data, child_def->u.id); + + if (ref != NULL) + { + g_free (child_def->u.id); + child_def->u.definition = ref; + child_def->resolved = TRUE; + + if (context_is_pure_container (ref)) + { + if (child_def->override_style) + { + g_set_error (&data->error, GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_STYLE, + _("style override used with wildcard context reference" + " in language '%s' in ref '%s'"), + data->ctx_data->lang->priv->id, ref->id); + } + else + { + child_def->is_ref_all = TRUE; + } + } + } + else + { + g_set_error (&data->error, GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_REF, + _("invalid context reference '%s'"), child_def->u.id); + } + } +} + +static gboolean +process_replace (GtkSourceContextData *ctx_data, + const gchar *id, + const gchar *replace_with, + GError **error) +{ + ContextDefinition *to_replace, *new; + + to_replace = LOOKUP_DEFINITION (ctx_data, id); + + if (to_replace == NULL) + { + g_set_error (error, GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_REF, + _("unknown context '%s'"), id); + return FALSE; + } + + new = LOOKUP_DEFINITION (ctx_data, replace_with); + + if (new == NULL) + { + g_set_error (error, GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_INVALID_REF, + _("unknown context '%s'"), replace_with); + return FALSE; + } + + g_hash_table_insert (ctx_data->definitions, g_strdup (id), context_definition_ref (new)); + + return TRUE; +} + +GtkSourceContextReplace * +_gtk_source_context_replace_new (const gchar *to_replace_id, + const gchar *replace_with_id) +{ + GtkSourceContextReplace *repl; + + g_return_val_if_fail (to_replace_id != NULL, NULL); + g_return_val_if_fail (replace_with_id != NULL, NULL); + + repl = g_slice_new (GtkSourceContextReplace); + repl->id = g_strdup (to_replace_id); + repl->replace_with = g_strdup (replace_with_id); + + return repl; +} + +void +_gtk_source_context_replace_free (GtkSourceContextReplace *repl) +{ + if (repl != NULL) + { + g_free (repl->id); + g_free (repl->replace_with); + g_slice_free (GtkSourceContextReplace, repl); + } +} + +/** + * _gtk_source_context_data_finish_parse: + * + * @ctx_data: #GtkSourceContextData. + * @overrides: list of #GtkSourceContextOverride objects. + * @error: error structure to be filled in when failed. + * + * Checks all context references and applies overrides. Lang file may + * use cross-references between contexts, e.g. context A may include + * context B, and context B in turn include context A. Hence during + * parsing it just records referenced context id, and then it needs to + * check the references and replace them with actual context definitions + * (which in turn may be overridden using or tags). + * May be called any number of times, must be called after parsing is + * done. + * + * Returns: %TRUE on success, %FALSE if there were unresolved + * references. + */ +gboolean +_gtk_source_context_data_finish_parse (GtkSourceContextData *ctx_data, + GList *overrides, + GError **error) +{ + struct ResolveRefData data; + gchar *root_id; + ContextDefinition *main_definition; + + g_return_val_if_fail (ctx_data != NULL, FALSE); + g_return_val_if_fail (ctx_data->lang != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + while (overrides != NULL) + { + GtkSourceContextReplace *repl = overrides->data; + + g_return_val_if_fail (repl != NULL, FALSE); + + if (!process_replace (ctx_data, repl->id, repl->replace_with, error)) + return FALSE; + + overrides = overrides->next; + } + + data.ctx_data = ctx_data; + data.error = NULL; + + g_hash_table_foreach (ctx_data->definitions, (GHFunc) resolve_reference, &data); + + if (data.error != NULL) + { + g_propagate_error (error, data.error); + return FALSE; + } + + /* Sanity check: user may have screwed up the files by now (#485661) */ + root_id = g_strdup_printf ("%s:%s", ctx_data->lang->priv->id, ctx_data->lang->priv->id); + main_definition = LOOKUP_DEFINITION (ctx_data, root_id); + g_free (root_id); + + if (main_definition == NULL) + { + g_set_error (error, GTK_SOURCE_CONTEXT_ENGINE_ERROR, + GTK_SOURCE_CONTEXT_ENGINE_ERROR_BAD_FILE, + _("Missing main language " + "definition (id = \"%s\".)"), + ctx_data->lang->priv->id); + return FALSE; + } + + return TRUE; +} + +static void +add_escape_ref (ContextDefinition *definition, + GtkSourceContextData *ctx_data) +{ + GError *error = NULL; + + if (definition->type != CONTEXT_TYPE_CONTAINER) + return; + + _gtk_source_context_data_add_ref (ctx_data, definition->id, + "gtk-source-context-engine-escape", + 0, NULL, FALSE, &error); + + if (error) + goto out; + + _gtk_source_context_data_add_ref (ctx_data, definition->id, + "gtk-source-context-engine-line-escape", + 0, NULL, FALSE, &error); + +out: + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + } +} + +static void +prepend_definition (G_GNUC_UNUSED gchar *id, + ContextDefinition *definition, + GSList **list) +{ + *list = g_slist_prepend (*list, definition); +} + +/* Only for lang files version 1, do not use it */ +/* It's called after lang file is parsed. It creates two special contexts + contexts and puts them into every container context defined. These contexts + are 'x.' and 'x$', where 'x' is the escape char. In this way, patterns from + lang files are matched only if match doesn't start with escaped char, and + escaped char in the end of line means that the current contexts extends to the + next line. */ +void +_gtk_source_context_data_set_escape_char (GtkSourceContextData *ctx_data, + gunichar escape_char) +{ + GError *error = NULL; + char buf[10]; + gint len; + char *escaped, *pattern; + GSList *definitions = NULL; + + g_return_if_fail (ctx_data != NULL); + g_return_if_fail (escape_char != 0); + + len = g_unichar_to_utf8 (escape_char, buf); + g_return_if_fail (len > 0); + + escaped = g_regex_escape_string (buf, 1); + pattern = g_strdup_printf ("%s.", escaped); + + g_hash_table_foreach (ctx_data->definitions, (GHFunc) prepend_definition, &definitions); + definitions = g_slist_reverse (definitions); + + if (!_gtk_source_context_data_define_context (ctx_data, "gtk-source-context-engine-escape", + NULL, pattern, NULL, NULL, NULL, + GTK_SOURCE_CONTEXT_EXTEND_PARENT, + &error)) + goto out; + + g_free (pattern); + pattern = g_strdup_printf ("%s$", escaped); + + if (!_gtk_source_context_data_define_context (ctx_data, "gtk-source-context-engine-line-escape", + NULL, NULL, pattern, "^", NULL, + GTK_SOURCE_CONTEXT_EXTEND_PARENT, + &error)) + goto out; + + g_slist_foreach (definitions, (GFunc) add_escape_ref, ctx_data); + +out: + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_free (pattern); + g_free (escaped); + g_slist_free (definitions); +} + + +/* DEBUG CODE ------------------------------------------------------------- */ + +#ifdef ENABLE_CHECK_TREE +static void +check_segment (GtkSourceContextEngine *ce, + Segment *segment) +{ + Segment *child; + + g_assert (segment != NULL); + g_assert (segment->start_at <= segment->end_at); + g_assert (!segment->next || segment->next->start_at >= segment->end_at); + + if (SEGMENT_IS_INVALID (segment)) + g_assert (g_slist_find (ce->priv->invalid, segment) != NULL); + else + g_assert (g_slist_find (ce->priv->invalid, segment) == NULL); + + if (segment->children != NULL) + g_assert (!SEGMENT_IS_INVALID (segment) && SEGMENT_IS_CONTAINER (segment)); + + for (child = segment->children; child != NULL; child = child->next) + { + g_assert (child->parent == segment); + g_assert (child->start_at >= segment->start_at); + g_assert (child->end_at <= segment->end_at); + g_assert (child->prev || child == segment->children); + g_assert (child->next || child == segment->last_child); + check_segment (ce, child); + } +} + +struct CheckContextData { + Context *parent; + ContextDefinition *definition; +}; + +static void +check_context_hash_cb (const char *text, + Context *context, + gpointer user_data) +{ + struct CheckContextData *data = user_data; + + g_assert (text != NULL); + g_assert (context != NULL); + g_assert (context->definition == data->definition); + g_assert (context->parent == data->parent); +} + +static void +check_context (Context *context) +{ + ContextPtr *ptr; + + for (ptr = context->children; ptr != NULL; ptr = ptr->next) + { + if (ptr->fixed) + { + g_assert (ptr->u.context->parent == context); + g_assert (ptr->u.context->definition == ptr->definition); + check_context (ptr->u.context); + } + else + { + struct CheckContextData data; + data.parent = context; + data.definition = ptr->definition; + g_hash_table_foreach (ptr->u.hash, + (GHFunc) check_context_hash_cb, + &data); + } + } +} + +static void +check_regex (void) +{ + static gboolean done; + + if (!done) + { + g_assert (!find_single_byte_escape ("gfregerg")); + g_assert (!find_single_byte_escape ("\\\\C")); + g_assert (find_single_byte_escape ("\\C")); + g_assert (find_single_byte_escape ("ewfwefwefwef\\Cwefwefwefwe")); + g_assert (find_single_byte_escape ("ewfwefwefwef\\\\Cwefw\\Cefwefwe")); + g_assert (!find_single_byte_escape ("ewfwefwefwef\\\\Cwefw\\\\Cefwefwe")); + + done = TRUE; + } +} + +static void +check_tree (GtkSourceContextEngine *ce) +{ + Segment *root = ce->priv->root_segment; + + check_regex (); + + g_assert (root->start_at == 0); + + if (ce->priv->invalid_region.empty) + g_assert (root->end_at == gtk_text_buffer_get_char_count (ce->priv->buffer)); + + g_assert (!root->parent); + check_segment (ce, root); + + g_assert (!ce->priv->root_context->parent); + g_assert (root->context == ce->priv->root_context); + check_context (ce->priv->root_context); +} + +static void +check_segment_children (Segment *segment) +{ + Segment *ch; + + g_assert (segment != NULL); + check_segment_list (segment->parent); + + for (ch = segment->children; ch != NULL; ch = ch->next) + { + g_assert (ch->parent == segment); + g_assert (ch->start_at <= ch->end_at); + g_assert (!ch->next || ch->next->start_at >= ch->end_at); + g_assert (ch->start_at >= segment->start_at); + g_assert (ch->end_at <= segment->end_at); + g_assert (ch->prev || ch == segment->children); + g_assert (ch->next || ch == segment->last_child); + } +} + +static void +check_segment_list (Segment *segment) +{ + Segment *ch; + + if (segment == NULL) + return; + + for (ch = segment->children; ch != NULL; ch = ch->next) + { + g_assert (ch->parent == segment); + g_assert (ch->start_at <= ch->end_at); + g_assert (!ch->next || ch->next->start_at >= ch->end_at); + g_assert (ch->prev || ch == segment->children); + g_assert (ch->next || ch == segment->last_child); + } +} +#endif /* ENABLE_CHECK_TREE */ + + +#ifdef ENABLE_MEMORY_DEBUG +typedef struct { + GSList *def_regexes; + GSList *ctx_regexes; + gsize def_mem; + gsize ctx_mem; + guint n_ctx; +} MemInfo; + +typedef struct +{ + gpointer key; + gpointer value; + gpointer next; +} HashNodeStruct; + +typedef struct +{ + gint size; + gint nnodes; + HashNodeStruct **nodes; + GHashFunc hash_func; + GEqualFunc key_equal_func; + volatile guint ref_count; + GDestroyNotify key_destroy_func; + GDestroyNotify value_destroy_func; +} HashTableStruct; + +static gsize +get_hash_table_mem (GHashTable *ht) +{ + return sizeof (HashTableStruct) + + sizeof (HashNodeStruct) * g_hash_table_size (ht); +} + +static void +add_regex_mem (MemInfo *info, + Regex *regex, + gboolean def) +{ + if (!regex) + return; + + if (def) + { + if (!g_slist_find (info->def_regexes, regex)) + info->def_regexes = g_slist_prepend (info->def_regexes, regex); + } + else + { + if (!g_slist_find (info->def_regexes, regex) && + !g_slist_find (info->ctx_regexes, regex)) + info->ctx_regexes = g_slist_prepend (info->ctx_regexes, regex); + } +} + +static gsize +get_str_mem (const gchar *string) +{ + return string ? strlen (string) + 1 : 0; +} + +static void +get_def_mem (ContextDefinition *def, + MemInfo *info) +{ + GSList *l; + + info->def_mem += sizeof (ContextDefinition); + info->def_mem += get_str_mem (def->id); + info->def_mem += get_str_mem (def->default_style); + + if (def->type == CONTEXT_TYPE_CONTAINER) + { + add_regex_mem (info, def->u.start_end.start, TRUE); + add_regex_mem (info, def->u.start_end.end, TRUE); + } + else + { + add_regex_mem (info, def->u.match, TRUE); + } + + for (l = def->children; l != NULL; l = l->next) + { + DefinitionChild *child_def = l->data; + + info->def_mem += sizeof (DefinitionChild); + info->def_mem += get_str_mem (child_def->style); + + if (child_def->resolved) + info->def_mem += get_str_mem (child_def->u.id); + } + + for (l = def->sub_patterns; l != NULL; l = l->next) + { + SubPatternDefinition *sp_def = l->data; + + info->def_mem += sizeof (SubPatternDefinition); + info->def_mem += get_str_mem (sp_def->style); +#ifdef NEED_DEBUG_ID + info->def_mem += get_str_mem (sp_def->id); +#endif + + if (sp_def->is_named) + info->def_mem += get_str_mem (sp_def->u.name); + } + + add_regex_mem (info, def->reg_all, TRUE); +} + +static void get_context_mem (Context *ctx, MemInfo *info); + +static void +get_context_mem_cb (const char *id, + Context *ctx, + MemInfo *info) +{ + info->ctx_mem += get_str_mem (id); + get_context_mem (ctx, info); +} + +static void +get_context_ptr_mem (ContextPtr *ptr, + MemInfo *info) +{ + if (ptr) + { + info->ctx_mem += sizeof (ContextPtr); + + if (ptr->fixed) + get_context_mem (ptr->u.context, info); + else + { + info->ctx_mem += get_hash_table_mem (ptr->u.hash); + g_hash_table_foreach (ptr->u.hash, (GHFunc) get_context_mem_cb, info); + } + + get_context_ptr_mem (ptr->next, info); + } +} + +static void +get_context_mem (Context *ctx, + MemInfo *info) +{ + if (ctx) + { + info->ctx_mem += sizeof (Context); + add_regex_mem (info, ctx->end, FALSE); + add_regex_mem (info, ctx->reg_all, FALSE); + get_context_ptr_mem (ctx->children, info); + info->ctx_mem += ctx->definition->n_sub_patterns * sizeof (GtkTextTag*); + info->n_ctx += 1; + } +} + +static void +get_def_mem_cb (const char *id, + ContextDefinition *def, + MemInfo *info) +{ + info->def_mem += get_str_mem (id); + get_def_mem (def, info); +} + +static void +get_definitions_mem (GtkSourceContextEngine *ce, + MemInfo *info) +{ + info->def_mem += sizeof (GtkSourceContextData); + info->def_mem += get_hash_table_mem (ce->priv->ctx_data->definitions); + g_hash_table_foreach (ce->priv->ctx_data->definitions, + (GHFunc) get_def_mem_cb, + info); +} + +static gsize +get_regex_mem (Regex *regex) +{ + gsize mem = 0; + + if (!regex) + return 0; + + mem += sizeof (Regex); + + if (regex->resolved) + mem += _egg_regex_get_memory (regex->u.regex.regex); + else + mem += get_str_mem (regex->u.info.pattern); + + return mem; +} + +static gboolean +mem_usage_timeout (GtkSourceContextEngine *ce) +{ + GSList *l; + MemInfo info = {NULL, NULL, 0, 0, 0}; + + get_definitions_mem (ce, &info); + get_context_mem (ce->priv->root_context, &info); + + for (l = info.def_regexes; l != NULL; l = l->next) + info.def_mem += get_regex_mem (l->data); + + for (l = info.ctx_regexes; l != NULL; l = l->next) + info.ctx_mem += get_regex_mem (l->data); + + g_print ("%s: definitions: %d bytes, contexts: %d bytes in %d contexts\n", + ENGINE_ID (ce), info.def_mem, info.ctx_mem, info.n_ctx); + + g_slist_free (info.def_regexes); + g_slist_free (info.ctx_regexes); + + return TRUE; +} +#endif /* ENABLE_MEMORY_DEBUG */ diff --git a/moo/gtksourceview/gtksourcecontextengine.h b/moo/gtksourceview/gtksourcecontextengine.h new file mode 100644 index 00000000..f83fb9f7 --- /dev/null +++ b/moo/gtksourceview/gtksourcecontextengine.h @@ -0,0 +1,124 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- + * gtksourcecontextengine.h + * + * Copyright (C) 2003 - Gustavo Giráldez + * Copyright (C) 2005 - Marco Barisione, Emanuele Aina + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_SOURCE_CONTEXT_ENGINE_H__ +#define __GTK_SOURCE_CONTEXT_ENGINE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_SOURCE_CONTEXT_ENGINE (_gtk_source_context_engine_get_type ()) +#define GTK_SOURCE_CONTEXT_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SOURCE_CONTEXT_ENGINE, GtkSourceContextEngine)) +#define GTK_SOURCE_CONTEXT_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SOURCE_CONTEXT_ENGINE, GtkSourceContextEngineClass)) +#define GTK_IS_SOURCE_CONTEXT_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SOURCE_CONTEXT_ENGINE)) +#define GTK_IS_SOURCE_CONTEXT_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SOURCE_CONTEXT_ENGINE)) +#define GTK_SOURCE_CONTEXT_ENGINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SOURCE_CONTEXT_ENGINE, GtkSourceContextEngineClass)) + +typedef struct _GtkSourceContextData GtkSourceContextData; +typedef struct _GtkSourceContextReplace GtkSourceContextReplace; + +typedef struct _GtkSourceContextEngine GtkSourceContextEngine; +typedef struct _GtkSourceContextEngineClass GtkSourceContextEngineClass; +typedef struct _GtkSourceContextEnginePrivate GtkSourceContextEnginePrivate; + +struct _GtkSourceContextEngine +{ + GtkSourceEngine parent_instance; + + /*< private >*/ + GtkSourceContextEnginePrivate *priv; +}; + +struct _GtkSourceContextEngineClass +{ + GtkSourceEngineClass parent_class; +}; + +typedef enum { + GTK_SOURCE_CONTEXT_EXTEND_PARENT = 1 << 0, + GTK_SOURCE_CONTEXT_END_PARENT = 1 << 1, + GTK_SOURCE_CONTEXT_END_AT_LINE_END = 1 << 2, + GTK_SOURCE_CONTEXT_FIRST_LINE_ONLY = 1 << 3, + GTK_SOURCE_CONTEXT_ONCE_ONLY = 1 << 4, + GTK_SOURCE_CONTEXT_STYLE_INSIDE = 1 << 5 +} GtkSourceContextFlags; + +typedef enum { + GTK_SOURCE_CONTEXT_IGNORE_STYLE = 1 << 0, + GTK_SOURCE_CONTEXT_OVERRIDE_STYLE = 1 << 1, + GTK_SOURCE_CONTEXT_REF_ORIGINAL = 1 << 2 +} GtkSourceContextRefOptions; + +GType _gtk_source_context_engine_get_type (void) G_GNUC_CONST; + +GtkSourceContextData *_gtk_source_context_data_new (GtkSourceLanguage *lang); +GtkSourceContextData *_gtk_source_context_data_ref (GtkSourceContextData *data); +void _gtk_source_context_data_unref (GtkSourceContextData *data); + +GtkSourceContextEngine *_gtk_source_context_engine_new (GtkSourceContextData *data); + +gboolean _gtk_source_context_data_define_context + (GtkSourceContextData *data, + const gchar *id, + const gchar *parent_id, + const gchar *match_regex, + const gchar *start_regex, + const gchar *end_regex, + const gchar *style, + GtkSourceContextFlags flags, + GError **error); + +gboolean _gtk_source_context_data_add_sub_pattern + (GtkSourceContextData *data, + const gchar *id, + const gchar *parent_id, + const gchar *name, + const gchar *where, + const gchar *style, + GError **error); + +gboolean _gtk_source_context_data_add_ref (GtkSourceContextData *data, + const gchar *parent_id, + const gchar *ref_id, + GtkSourceContextRefOptions options, + const gchar *style, + gboolean all, + GError **error); + +GtkSourceContextReplace * + _gtk_source_context_replace_new (const gchar *to_replace_id, + const gchar *replace_with_id); +void _gtk_source_context_replace_free (GtkSourceContextReplace *repl); + +gboolean _gtk_source_context_data_finish_parse (GtkSourceContextData *data, + GList *overrides, + GError **error); + +/* Only for lang files version 1, do not use it */ +void _gtk_source_context_data_set_escape_char + (GtkSourceContextData *data, + gunichar esc_char); + +G_END_DECLS + +#endif /* __GTK_SOURCE_CONTEXT_ENGINE_H__ */ diff --git a/moo/gtksourceview/gtksourceengine.c b/moo/gtksourceview/gtksourceengine.c new file mode 100644 index 00000000..49622afd --- /dev/null +++ b/moo/gtksourceview/gtksourceengine.c @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- + * + * gtksourceengine.c - Abstract base class for highlighting engines + * + * Copyright (C) 2003 - Gustavo Giráldez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gtksourcebuffer.h" +#include "gtksourceengine.h" + + +G_DEFINE_TYPE (GtkSourceEngine, _gtk_source_engine, G_TYPE_OBJECT) + + +static void +_gtk_source_engine_class_init (GtkSourceEngineClass *klass) +{ + klass->attach_buffer = NULL; +} + + +static void +_gtk_source_engine_init (G_GNUC_UNUSED GtkSourceEngine *engine) +{ +} + +void +_gtk_source_engine_attach_buffer (GtkSourceEngine *engine, + GtkTextBuffer *buffer) +{ + g_return_if_fail (GTK_IS_SOURCE_ENGINE (engine)); + g_return_if_fail (GTK_SOURCE_ENGINE_GET_CLASS (engine)->attach_buffer != NULL); + + GTK_SOURCE_ENGINE_GET_CLASS (engine)->attach_buffer (engine, buffer); +} + +void +_gtk_source_engine_text_inserted (GtkSourceEngine *engine, + gint start_offset, + gint end_offset) +{ + g_return_if_fail (GTK_IS_SOURCE_ENGINE (engine)); + g_return_if_fail (GTK_SOURCE_ENGINE_GET_CLASS (engine)->text_inserted != NULL); + + GTK_SOURCE_ENGINE_GET_CLASS (engine)->text_inserted (engine, + start_offset, + end_offset); +} + +void +_gtk_source_engine_text_deleted (GtkSourceEngine *engine, + gint offset, + gint length) +{ + g_return_if_fail (GTK_IS_SOURCE_ENGINE (engine)); + g_return_if_fail (GTK_SOURCE_ENGINE_GET_CLASS (engine)->text_deleted != NULL); + + GTK_SOURCE_ENGINE_GET_CLASS (engine)->text_deleted (engine, + offset, + length); +} + +void +_gtk_source_engine_update_highlight (GtkSourceEngine *engine, + const GtkTextIter *start, + const GtkTextIter *end, + gboolean synchronous) +{ + g_return_if_fail (GTK_IS_SOURCE_ENGINE (engine)); + g_return_if_fail (start != NULL && end != NULL); + g_return_if_fail (GTK_SOURCE_ENGINE_GET_CLASS (engine)->update_highlight != NULL); + + GTK_SOURCE_ENGINE_GET_CLASS (engine)->update_highlight (engine, + start, + end, + synchronous); +} + +void +_gtk_source_engine_set_style_scheme (GtkSourceEngine *engine, + GtkSourceStyleScheme *scheme) +{ + g_return_if_fail (GTK_IS_SOURCE_ENGINE (engine)); + g_return_if_fail (GTK_IS_SOURCE_STYLE_SCHEME (scheme)); + g_return_if_fail (GTK_SOURCE_ENGINE_GET_CLASS (engine)->set_style_scheme != NULL); + + GTK_SOURCE_ENGINE_GET_CLASS (engine)->set_style_scheme (engine, scheme); +} diff --git a/moo/gtksourceview/gtksourceengine.h b/moo/gtksourceview/gtksourceengine.h new file mode 100644 index 00000000..6c44be10 --- /dev/null +++ b/moo/gtksourceview/gtksourceengine.h @@ -0,0 +1,87 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- + * + * gtksourceengine.h - Abstract base class for highlighting engines + * + * Copyright (C) 2003 - Gustavo Giráldez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_SOURCE_ENGINE_H__ +#define __GTK_SOURCE_ENGINE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_SOURCE_ENGINE (_gtk_source_engine_get_type ()) +#define GTK_SOURCE_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SOURCE_ENGINE, GtkSourceEngine)) +#define GTK_SOURCE_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SOURCE_ENGINE, GtkSourceEngineClass)) +#define GTK_IS_SOURCE_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SOURCE_ENGINE)) +#define GTK_IS_SOURCE_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SOURCE_ENGINE)) +#define GTK_SOURCE_ENGINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SOURCE_ENGINE, GtkSourceEngineClass)) + +typedef struct _GtkSourceEngine GtkSourceEngine; +typedef struct _GtkSourceEngineClass GtkSourceEngineClass; + +struct _GtkSourceEngine +{ + GObject parent_instance; +}; + +struct _GtkSourceEngineClass +{ + GObjectClass parent_class; + + void (* attach_buffer) (GtkSourceEngine *engine, + GtkTextBuffer *buffer); + + void (* text_inserted) (GtkSourceEngine *engine, + gint start_offset, + gint end_offset); + void (* text_deleted) (GtkSourceEngine *engine, + gint offset, + gint length); + + void (* update_highlight) (GtkSourceEngine *engine, + const GtkTextIter *start, + const GtkTextIter *end, + gboolean synchronous); + + void (* set_style_scheme) (GtkSourceEngine *engine, + GtkSourceStyleScheme *scheme); +}; + +GType _gtk_source_engine_get_type (void) G_GNUC_CONST; + +void _gtk_source_engine_attach_buffer (GtkSourceEngine *engine, + GtkTextBuffer *buffer); +void _gtk_source_engine_text_inserted (GtkSourceEngine *engine, + gint start_offset, + gint end_offset); +void _gtk_source_engine_text_deleted (GtkSourceEngine *engine, + gint offset, + gint length); +void _gtk_source_engine_update_highlight (GtkSourceEngine *engine, + const GtkTextIter *start, + const GtkTextIter *end, + gboolean synchronous); +void _gtk_source_engine_set_style_scheme (GtkSourceEngine *engine, + GtkSourceStyleScheme *scheme); + +G_END_DECLS + +#endif /* __GTK_SOURCE_ENGINE_H__ */ diff --git a/moo/gtksourceview/gtksourceiter.c b/moo/gtksourceview/gtksourceiter.c new file mode 100644 index 00000000..9a50d3a9 --- /dev/null +++ b/moo/gtksourceview/gtksourceiter.c @@ -0,0 +1,801 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * gtksourceiter.h + * + * Copyright (C) 2000 - 2005 Paolo Maggi + * Copyright (C) 2002, 2003 Jeroen Zwartepoorte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * Parts of this file are copied from the gedit and glimmer project. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include "gtksourceiter.h" + +#define GTK_TEXT_UNKNOWN_CHAR 0xFFFC + +#if GLIB_CHECK_VERSION(2,30,0) && !defined(G_UNICODE_COMBINING_MARK) +#define G_UNICODE_COMBINING_MARK G_UNICODE_SPACING_MARK +#endif + +/* this function acts like g_utf8_offset_to_pointer() except that if it finds a + * decomposable character it consumes the decomposition length from the given + * offset. So it's useful when the offset was calculated for the normalized + * version of str, but we need a pointer to str itself. */ +static const gchar * +pointer_from_offset_skipping_decomp (const gchar *str, gint offset) +{ + gchar *casefold, *normal; + const gchar *p, *q; + + p = str; + while (offset > 0) + { + q = g_utf8_next_char (p); + casefold = g_utf8_casefold (p, q - p); + normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + offset -= g_utf8_strlen (normal, -1); + g_free (casefold); + g_free (normal); + p = q; + } + return p; +} + +static gboolean +exact_prefix_cmp (const gchar *string, + const gchar *prefix, + guint prefix_len) +{ + GUnicodeType type; + + if (strncmp (string, prefix, prefix_len) != 0) + return FALSE; + if (string[prefix_len] == '\0') + return TRUE; + + type = g_unichar_type (g_utf8_get_char (string + prefix_len)); + + /* If string contains prefix, check that prefix is not followed + * by a unicode mark symbol, e.g. that trailing 'a' in prefix + * is not part of two-char a-with-hat symbol in string. */ + return type != G_UNICODE_COMBINING_MARK && + type != G_UNICODE_ENCLOSING_MARK && + type != G_UNICODE_NON_SPACING_MARK; +} + +static const gchar * +utf8_strcasestr (const gchar *haystack, const gchar *needle) +{ + gsize needle_len; + gsize haystack_len; + const gchar *ret = NULL; + gchar *p; + gchar *casefold; + gchar *caseless_haystack; + gint i; + + g_return_val_if_fail (haystack != NULL, NULL); + g_return_val_if_fail (needle != NULL, NULL); + + casefold = g_utf8_casefold (haystack, -1); + caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + needle_len = g_utf8_strlen (needle, -1); + haystack_len = g_utf8_strlen (caseless_haystack, -1); + + if (needle_len == 0) + { + ret = (gchar *)haystack; + goto finally_1; + } + + if (haystack_len < needle_len) + { + ret = NULL; + goto finally_1; + } + + p = (gchar*)caseless_haystack; + needle_len = strlen (needle); + i = 0; + + while (*p) + { + if (exact_prefix_cmp (p, needle, needle_len)) + { + ret = pointer_from_offset_skipping_decomp (haystack, i); + goto finally_1; + } + + p = g_utf8_next_char (p); + i++; + } + +finally_1: + g_free (caseless_haystack); + + return ret; +} + +static const gchar * +utf8_strrcasestr (const gchar *haystack, const gchar *needle) +{ + gsize needle_len; + gsize haystack_len; + const gchar *ret = NULL; + gchar *p; + gchar *casefold; + gchar *caseless_haystack; + gint i; + + g_return_val_if_fail (haystack != NULL, NULL); + g_return_val_if_fail (needle != NULL, NULL); + + casefold = g_utf8_casefold (haystack, -1); + caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + needle_len = g_utf8_strlen (needle, -1); + haystack_len = g_utf8_strlen (caseless_haystack, -1); + + if (needle_len == 0) + { + ret = (gchar *)haystack; + goto finally_1; + } + + if (haystack_len < needle_len) + { + ret = NULL; + goto finally_1; + } + + i = haystack_len - needle_len; + p = g_utf8_offset_to_pointer (caseless_haystack, i); + needle_len = strlen (needle); + + while (p >= caseless_haystack) + { + if (exact_prefix_cmp (p, needle, needle_len)) + { + ret = pointer_from_offset_skipping_decomp (haystack, i); + goto finally_1; + } + + p = g_utf8_prev_char (p); + i--; + } + +finally_1: + g_free (caseless_haystack); + + return ret; +} + +static gboolean +utf8_caselessnmatch (const char *s1, const char *s2, + gssize n1, gssize n2) +{ + gchar *casefold; + gchar *normalized_s1; + gchar *normalized_s2; + gint len_s1; + gint len_s2; + gboolean ret = FALSE; + + g_return_val_if_fail (s1 != NULL, FALSE); + g_return_val_if_fail (s2 != NULL, FALSE); + g_return_val_if_fail (n1 > 0, FALSE); + g_return_val_if_fail (n2 > 0, FALSE); + + casefold = g_utf8_casefold (s1, n1); + normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + casefold = g_utf8_casefold (s2, n2); + normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + + len_s1 = strlen (normalized_s1); + len_s2 = strlen (normalized_s2); + + if (len_s1 < len_s2) + goto finally_2; + + ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0); + +finally_2: + g_free (normalized_s1); + g_free (normalized_s2); + + return ret; +} + +/* FIXME: total horror */ +static gboolean +char_is_invisible (const GtkTextIter *iter) +{ + GSList *tags; + gboolean invisible = FALSE; + tags = gtk_text_iter_get_tags (iter); + while (tags) + { + gboolean this_invisible, invisible_set; + g_object_get (tags->data, "invisible", &this_invisible, + "invisible-set", &invisible_set, NULL); + if (invisible_set) + invisible = this_invisible; + tags = g_slist_delete_link (tags, tags); + } + return invisible; +} + +static void +forward_chars_with_skipping (GtkTextIter *iter, + gint count, + gboolean skip_invisible, + gboolean skip_nontext, + gboolean skip_decomp) +{ + gint i; + + g_return_if_fail (count >= 0); + + i = count; + + while (i > 0) + { + gboolean ignored = FALSE; + + /* minimal workaround to avoid the infinite loop of bug #168247. + * It doesn't fix the problemjust the symptom... + */ + if (gtk_text_iter_is_end (iter)) + return; + + if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR) + ignored = TRUE; + + /* FIXME: char_is_invisible() gets list of tags for each char there, + and checks every tag. It doesn't sound like a good idea. */ + if (!ignored && skip_invisible && char_is_invisible (iter)) + ignored = TRUE; + + if (!ignored && skip_decomp) + { + /* being UTF8 correct sucks; this accounts for extra + offsets coming from canonical decompositions of + UTF8 characters (e.g. accented characters) which + g_utf8_normalize() performs */ + gchar *normal; + gchar *casefold; + gchar buffer[6]; + gint buffer_len; + + buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer); + casefold = g_utf8_casefold (buffer, buffer_len); + normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + i -= (g_utf8_strlen (normal, -1) - 1); + g_free (normal); + g_free (casefold); + } + + gtk_text_iter_forward_char (iter); + + if (!ignored) + --i; + } +} + +static gboolean +lines_match (const GtkTextIter *start, + const gchar **lines, + gboolean visible_only, + gboolean slice, + GtkTextIter *match_start, + GtkTextIter *match_end) +{ + GtkTextIter next; + gchar *line_text; + const gchar *found; + gint offset; + + if (*lines == NULL || **lines == '\0') + { + if (match_start) + *match_start = *start; + if (match_end) + *match_end = *start; + return TRUE; + } + + next = *start; + gtk_text_iter_forward_line (&next); + + /* No more text in buffer, but *lines is nonempty */ + if (gtk_text_iter_equal (start, &next)) + return FALSE; + + if (slice) + { + if (visible_only) + line_text = gtk_text_iter_get_visible_slice (start, &next); + else + line_text = gtk_text_iter_get_slice (start, &next); + } + else + { + if (visible_only) + line_text = gtk_text_iter_get_visible_text (start, &next); + else + line_text = gtk_text_iter_get_text (start, &next); + } + + if (match_start) /* if this is the first line we're matching */ + { + found = utf8_strcasestr (line_text, *lines); + } + else + { + /* If it's not the first line, we have to match from the + * start of the line. + */ + if (utf8_caselessnmatch (line_text, *lines, strlen (line_text), + strlen (*lines))) + found = line_text; + else + found = NULL; + } + + if (found == NULL) + { + g_free (line_text); + return FALSE; + } + + /* Get offset to start of search string */ + offset = g_utf8_strlen (line_text, found - line_text); + + next = *start; + + /* If match start needs to be returned, set it to the + * start of the search string. + */ + forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE); + if (match_start) + { + *match_start = next; + } + + /* Go to end of search string */ + forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE); + + g_free (line_text); + + ++lines; + + if (match_end) + *match_end = next; + + /* pass NULL for match_start, since we don't need to find the + * start again. + */ + return lines_match (&next, lines, visible_only, slice, NULL, match_end); +} + +static gboolean +backward_lines_match (const GtkTextIter *start, + const gchar **lines, + gboolean visible_only, + gboolean slice, + GtkTextIter *match_start, + GtkTextIter *match_end) +{ + GtkTextIter line, next; + gchar *line_text; + const gchar *found; + gint offset; + + if (*lines == NULL || **lines == '\0') + { + if (match_start) + *match_start = *start; + if (match_end) + *match_end = *start; + return TRUE; + } + + line = next = *start; + if (gtk_text_iter_get_line_offset (&next) == 0) + { + if (!gtk_text_iter_backward_line (&next)) + return FALSE; + } + else + gtk_text_iter_set_line_offset (&next, 0); + + if (slice) + { + if (visible_only) + line_text = gtk_text_iter_get_visible_slice (&next, &line); + else + line_text = gtk_text_iter_get_slice (&next, &line); + } + else + { + if (visible_only) + line_text = gtk_text_iter_get_visible_text (&next, &line); + else + line_text = gtk_text_iter_get_text (&next, &line); + } + + if (match_start) /* if this is the first line we're matching */ + { + found = utf8_strrcasestr (line_text, *lines); + } + else + { + /* If it's not the first line, we have to match from the + * start of the line. + */ + if (utf8_caselessnmatch (line_text, *lines, strlen (line_text), + strlen (*lines))) + found = line_text; + else + found = NULL; + } + + if (found == NULL) + { + g_free (line_text); + return FALSE; + } + + /* Get offset to start of search string */ + offset = g_utf8_strlen (line_text, found - line_text); + + forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE); + + /* If match start needs to be returned, set it to the + * start of the search string. + */ + if (match_start) + { + *match_start = next; + } + + /* Go to end of search string */ + forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE); + + g_free (line_text); + + ++lines; + + if (match_end) + *match_end = next; + + /* try to match the rest of the lines forward, passing NULL + * for match_start so lines_match will try to match the entire + * line */ + return lines_match (&next, lines, visible_only, + slice, NULL, match_end); +} + +/* strsplit () that retains the delimiter as part of the string. */ +static gchar ** +breakup_string (const char *string, + const char *delimiter, + gint max_tokens) +{ + GSList *string_list = NULL, *slist; + gchar **str_array, *s, *casefold, *new_string; + guint i, n = 1; + + g_return_val_if_fail (string != NULL, NULL); + g_return_val_if_fail (delimiter != NULL, NULL); + + if (max_tokens < 1) + max_tokens = G_MAXINT; + + s = strstr (string, delimiter); + if (s) + { + guint delimiter_len = strlen (delimiter); + + do + { + guint len; + + len = s - string + delimiter_len; + new_string = g_new (gchar, len + 1); + strncpy (new_string, string, len); + new_string[len] = 0; + casefold = g_utf8_casefold (new_string, -1); + g_free (new_string); + new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + string_list = g_slist_prepend (string_list, new_string); + n++; + string = s + delimiter_len; + s = strstr (string, delimiter); + } while (--max_tokens && s); + } + + if (*string) + { + n++; + casefold = g_utf8_casefold (string, -1); + new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); + g_free (casefold); + string_list = g_slist_prepend (string_list, new_string); + } + + str_array = g_new (gchar*, n); + + i = n - 1; + + str_array[i--] = NULL; + for (slist = string_list; slist; slist = slist->next) + str_array[i--] = slist->data; + + g_slist_free (string_list); + + return str_array; +} + +/** + * gtk_source_iter_forward_search: + * @iter: start of search. + * @str: a search string. + * @flags: flags affecting how the search is done. + * @match_start: return location for start of match, or %%NULL. + * @match_end: return location for end of match, or %%NULL. + * @limit: bound for the search, or %%NULL for the end of the buffer. + * + * Searches forward for @str. Any match is returned by setting + * @match_start to the first character of the match and @match_end to the + * first character after the match. The search will not continue past + * @limit. Note that a search is a linear or O(n) operation, so you + * may wish to use @limit to avoid locking up your UI on large + * buffers. + * + * If the #GTK_SOURCE_SEARCH_VISIBLE_ONLY flag is present, the match may + * have invisible text interspersed in @str. i.e. @str will be a + * possibly-noncontiguous subsequence of the matched range. similarly, + * if you specify #GTK_SOURCE_SEARCH_TEXT_ONLY, the match may have + * pixbufs or child widgets mixed inside the matched range. If these + * flags are not given, the match must be exact; the special 0xFFFC + * character in @str will match embedded pixbufs or child widgets. + * If you specify the #GTK_SOURCE_SEARCH_CASE_INSENSITIVE flag, the text will + * be matched regardless of what case it is in. + * + * Same as gtk_text_iter_forward_search(), but supports case insensitive + * searching. + * + * Return value: whether a match was found. + **/ +gboolean +gtk_source_iter_forward_search (const GtkTextIter *iter, + const gchar *str, + GtkSourceSearchFlags flags, + GtkTextIter *match_start, + GtkTextIter *match_end, + const GtkTextIter *limit) +{ + gchar **lines = NULL; + GtkTextIter match; + gboolean retval = FALSE; + GtkTextIter search; + gboolean visible_only; + gboolean slice; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (str != NULL, FALSE); + + if ((flags & GTK_SOURCE_SEARCH_CASE_INSENSITIVE) == 0) + return gtk_text_iter_forward_search (iter, str, flags, + match_start, match_end, + limit); + + if (limit && gtk_text_iter_compare (iter, limit) >= 0) + return FALSE; + + if (*str == '\0') + { + /* If we can move one char, return the empty string there */ + match = *iter; + + if (gtk_text_iter_forward_char (&match)) + { + if (limit && gtk_text_iter_equal (&match, limit)) + return FALSE; + + if (match_start) + *match_start = match; + if (match_end) + *match_end = match; + return TRUE; + } + else + { + return FALSE; + } + } + + visible_only = (flags & GTK_SOURCE_SEARCH_VISIBLE_ONLY) != 0; + slice = (flags & GTK_SOURCE_SEARCH_TEXT_ONLY) == 0; + + /* locate all lines */ + lines = breakup_string (str, "\n", -1); + + search = *iter; + + do + { + /* This loop has an inefficient worst-case, where + * gtk_text_iter_get_text () is called repeatedly on + * a single line. + */ + GtkTextIter end; + + if (limit && gtk_text_iter_compare (&search, limit) >= 0) + break; + + if (lines_match (&search, (const gchar**)lines, + visible_only, slice, &match, &end)) + { + if (limit == NULL || + (limit && gtk_text_iter_compare (&end, limit) <= 0)) + { + retval = TRUE; + + if (match_start) + *match_start = match; + if (match_end) + *match_end = end; + } + break; + } + } while (gtk_text_iter_forward_line (&search)); + + g_strfreev ((gchar**)lines); + + return retval; +} + +/** + * gtk_source_iter_backward_search: + * @iter: a #GtkTextIter where the search begins. + * @str: search string. + * @flags: bitmask of flags affecting the search. + * @match_start: return location for start of match, or %%NULL. + * @match_end: return location for end of match, or %%NULL. + * @limit: location of last possible @match_start, or %%NULL for start of buffer. + * + * Same as gtk_text_iter_backward_search(), but supports case insensitive + * searching. + * + * Return value: whether a match was found. + **/ +gboolean +gtk_source_iter_backward_search (const GtkTextIter *iter, + const gchar *str, + GtkSourceSearchFlags flags, + GtkTextIter *match_start, + GtkTextIter *match_end, + const GtkTextIter *limit) +{ + gchar **lines = NULL; + GtkTextIter match; + gboolean retval = FALSE; + GtkTextIter search; + gboolean visible_only; + gboolean slice; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (str != NULL, FALSE); + + if ((flags & GTK_SOURCE_SEARCH_CASE_INSENSITIVE) == 0) + return gtk_text_iter_backward_search (iter, str, flags, + match_start, match_end, + limit); + + if (limit && gtk_text_iter_compare (iter, limit) <= 0) + return FALSE; + + if (*str == '\0') + { + /* If we can move one char, return the empty string there */ + match = *iter; + + if (gtk_text_iter_backward_char (&match)) + { + if (limit && gtk_text_iter_equal (&match, limit)) + return FALSE; + + if (match_start) + *match_start = match; + if (match_end) + *match_end = match; + return TRUE; + } + else + { + return FALSE; + } + } + + visible_only = (flags & GTK_SOURCE_SEARCH_VISIBLE_ONLY) != 0; + slice = (flags & GTK_SOURCE_SEARCH_TEXT_ONLY) == 0; + + /* locate all lines */ + lines = breakup_string (str, "\n", -1); + + search = *iter; + + while (TRUE) + { + /* This loop has an inefficient worst-case, where + * gtk_text_iter_get_text () is called repeatedly on + * a single line. + */ + GtkTextIter end; + + if (limit && gtk_text_iter_compare (&search, limit) <= 0) + break; + + if (backward_lines_match (&search, (const gchar**)lines, + visible_only, slice, &match, &end)) + { + if (limit == NULL || (limit && + gtk_text_iter_compare (&end, limit) > 0)) + { + retval = TRUE; + + if (match_start) + *match_start = match; + if (match_end) + *match_end = end; + } + break; + } + + if (gtk_text_iter_get_line_offset (&search) == 0) + { + if (!gtk_text_iter_backward_line (&search)) + break; + } + else + { + gtk_text_iter_set_line_offset (&search, 0); + } + } + + g_strfreev ((gchar**)lines); + + return retval; +} + +/* + * gtk_source_iter_find_matching_bracket is implemented in gtksourcebuffer.c + */ diff --git a/moo/gtksourceview/gtksourceiter.h b/moo/gtksourceview/gtksourceiter.h new file mode 100644 index 00000000..8fb6338a --- /dev/null +++ b/moo/gtksourceview/gtksourceiter.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * gtksourceiter.h + * + * Copyright (C) 2000, 2002 Paolo Maggi + * Copyright (C) 2002, 2003 Jeroen Zwartepoorte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_SOURCE_ITER_H__ +#define __GTK_SOURCE_ITER_H__ + +#include + +G_BEGIN_DECLS + +typedef enum +{ + GTK_SOURCE_SEARCH_VISIBLE_ONLY = 1 << 0, + GTK_SOURCE_SEARCH_TEXT_ONLY = 1 << 1, + GTK_SOURCE_SEARCH_CASE_INSENSITIVE = 1 << 2 + /* Possible future plans: SEARCH_REGEXP */ +} GtkSourceSearchFlags; + +gboolean gtk_source_iter_forward_search (const GtkTextIter *iter, + const gchar *str, + GtkSourceSearchFlags flags, + GtkTextIter *match_start, + GtkTextIter *match_end, + const GtkTextIter *limit); + +gboolean gtk_source_iter_backward_search (const GtkTextIter *iter, + const gchar *str, + GtkSourceSearchFlags flags, + GtkTextIter *match_start, + GtkTextIter *match_end, + const GtkTextIter *limit); + +G_END_DECLS + +#endif /* __GTK_SOURCE_ITER_H__ */ diff --git a/moo/gtksourceview/gtksourcelanguage-parser-1.c b/moo/gtksourceview/gtksourcelanguage-parser-1.c new file mode 100644 index 00000000..92ee0199 --- /dev/null +++ b/moo/gtksourceview/gtksourcelanguage-parser-1.c @@ -0,0 +1,793 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- + * gtksourcelanguage-parser-ver1.c + * Language specification parser for 1.0 version .lang files + * + * Copyright (C) 2003 - Paolo Maggi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include "gtksourceview-i18n.h" +#include "gtksourcebuffer.h" +#include "gtksourcelanguage.h" +#include "gtksourcelanguage-private.h" + +static gchar * +fix_pattern (const gchar *pattern, + gboolean *end_at_line_end) +{ + char *slash; + + if (pattern == NULL) + return NULL; + + slash = strchr (pattern, '/'); + + if (slash != NULL) + { + GString *str; + + str = g_string_new_len (pattern, slash - pattern); + g_string_append (str, "\\/"); + pattern = slash + 1; + + while ((slash = strchr (pattern, '/')) != NULL) + { + g_string_append_len (str, pattern, slash - pattern); + g_string_append (str, "\\/"); + pattern = slash + 1; + } + + if (g_str_has_suffix (pattern, "\\n")) + g_string_append_len (str, pattern, strlen(pattern) - 2); + else + g_string_append (str, pattern); + + return g_string_free (str, FALSE); + } + else if (g_str_has_suffix (pattern, "\\n")) + { + if (end_at_line_end) + *end_at_line_end = TRUE; + return g_strndup (pattern, strlen (pattern) - 2); + } + else + { + return g_strdup (pattern); + } +} + +static gboolean +ctx_data_add_simple_pattern (GtkSourceContextData *ctx_data, + GtkSourceLanguage *language, + const gchar *id, + const gchar *style, + const gchar *pattern) +{ + gboolean result; + gchar *real_id, *root_id, *fixed; + GError *error = NULL; + + g_return_val_if_fail (id != NULL, FALSE); + + root_id = g_strdup_printf ("%s:%s", language->priv->id, language->priv->id); + real_id = g_strdup_printf ("%s:%s", language->priv->id, id); + + fixed = fix_pattern (pattern, NULL); + + result = _gtk_source_context_data_define_context (ctx_data, real_id, + root_id, + fixed, NULL, NULL, + style, + GTK_SOURCE_CONTEXT_EXTEND_PARENT | + GTK_SOURCE_CONTEXT_END_AT_LINE_END, + &error); + + if (error != NULL) + { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_free (fixed); + g_free (real_id); + g_free (root_id); + return result; +} + +static gboolean +ctx_data_add_syntax_pattern (GtkSourceContextData *ctx_data, + GtkSourceLanguage *language, + const gchar *id, + const gchar *style, + const gchar *pattern_start, + const gchar *pattern_end, + gboolean end_at_line_end) +{ + gboolean result; + gchar *real_id, *root_id; + gchar *fixed_start, *fixed_end; + GError *error = NULL; + GtkSourceContextFlags flags = GTK_SOURCE_CONTEXT_EXTEND_PARENT; + + g_return_val_if_fail (id != NULL, FALSE); + + root_id = g_strdup_printf ("%s:%s", language->priv->id, language->priv->id); + real_id = g_strdup_printf ("%s:%s", language->priv->id, id); + + fixed_start = fix_pattern (pattern_start, &end_at_line_end); + fixed_end = fix_pattern (pattern_end, &end_at_line_end); + + if (end_at_line_end) + flags |= GTK_SOURCE_CONTEXT_END_AT_LINE_END; + + result = _gtk_source_context_data_define_context (ctx_data, real_id, root_id, + NULL, + pattern_start, + pattern_end, + style, + flags, + &error); + + if (error != NULL) + { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_free (real_id); + g_free (root_id); + g_free (fixed_start); + g_free (fixed_end); + + return result; +} + +static gchar * +build_keyword_list (const GSList *keywords, + gboolean case_sensitive, + gboolean match_empty_string_at_beginning, + gboolean match_empty_string_at_end, + const gchar *beginning_regex, + const gchar *end_regex) +{ + GString *str; + + g_return_val_if_fail (keywords != NULL, NULL); + + str = g_string_new (""); + + if (keywords != NULL) + { + if (match_empty_string_at_beginning) + g_string_append (str, "\\b"); + + if (beginning_regex != NULL) + g_string_append (str, beginning_regex); + + if (case_sensitive) + g_string_append (str, "(?:"); + else + g_string_append (str, "(?i:"); + + /* TODO Make sure pcre can handle big lists, and split lists if necessary. + * See #110991 */ + while (keywords != NULL) + { + g_string_append (str, (gchar*) keywords->data); + + keywords = g_slist_next (keywords); + + if (keywords != NULL) + g_string_append (str, "|"); + } + g_string_append (str, ")"); + + if (end_regex != NULL) + g_string_append (str, end_regex); + + if (match_empty_string_at_end) + g_string_append (str, "\\b"); + } + + return g_string_free (str, FALSE); +} + +static void +parseLineComment (xmlNodePtr cur, + gchar *id, + xmlChar *style, + GtkSourceContextData *ctx_data, + GtkSourceLanguage *language) +{ + xmlNodePtr child; + + child = cur->xmlChildrenNode; + + if ((child != NULL) && !xmlStrcmp (child->name, (const xmlChar *)"start-regex")) + { + xmlChar *start_regex; + + start_regex = xmlNodeListGetString (child->doc, child->xmlChildrenNode, 1); + + ctx_data_add_syntax_pattern (ctx_data, language, id, + (gchar*) style, + (gchar*) start_regex, + NULL, TRUE); + + xmlFree (start_regex); + } + else + { + g_warning ("Missing start-regex in tag 'line-comment' (%s, line %ld)", + child->doc->name, xmlGetLineNo (child)); + } +} + +static void +parseBlockComment (xmlNodePtr cur, + gchar *id, + xmlChar *style, + GtkSourceContextData *ctx_data, + GtkSourceLanguage *language) +{ + xmlChar *start_regex = NULL; + xmlChar *end_regex = NULL; + + xmlNodePtr child; + + child = cur->xmlChildrenNode; + + while (child != NULL) + { + if (!xmlStrcmp (child->name, (const xmlChar *)"start-regex")) + { + start_regex = xmlNodeListGetString (child->doc, child->xmlChildrenNode, 1); + } + else + if (!xmlStrcmp (child->name, (const xmlChar *)"end-regex")) + { + end_regex = xmlNodeListGetString (child->doc, child->xmlChildrenNode, 1); + } + + child = child->next; + } + + if (start_regex == NULL) + { + g_warning ("Missing start-regex in tag 'block-comment' (%s, line %ld)", + child->doc->name, xmlGetLineNo (cur)); + + return; + } + + if (end_regex == NULL) + { + xmlFree (start_regex); + + g_warning ("Missing end-regex in tag 'block-comment' (%s, line %ld)", + child->doc->name, xmlGetLineNo (cur)); + + return; + } + + ctx_data_add_syntax_pattern (ctx_data, language, id, + (gchar*) style, + (gchar*) start_regex, + (gchar*) end_regex, + FALSE); + + xmlFree (start_regex); + xmlFree (end_regex); +} + +static void +parseString (xmlNodePtr cur, + gchar *id, + xmlChar *style, + GtkSourceContextData *ctx_data, + GtkSourceLanguage *language) +{ + xmlChar *start_regex = NULL; + xmlChar *end_regex = NULL; + + xmlChar *prop = NULL; + gboolean end_at_line_end = TRUE; + + xmlNodePtr child; + + prop = xmlGetProp (cur, BAD_CAST "end-at-line-end"); + if (prop != NULL) + { + if (!xmlStrcasecmp (prop, (const xmlChar *)"TRUE") || + !xmlStrcmp (prop, (const xmlChar *)"1")) + + end_at_line_end = TRUE; + else + end_at_line_end = FALSE; + + xmlFree (prop); + } + + child = cur->xmlChildrenNode; + + while (child != NULL) + { + if (!xmlStrcmp (child->name, (const xmlChar *)"start-regex")) + { + start_regex = xmlNodeListGetString (child->doc, child->xmlChildrenNode, 1); + } + else + if (!xmlStrcmp (child->name, (const xmlChar *)"end-regex")) + { + end_regex = xmlNodeListGetString (child->doc, child->xmlChildrenNode, 1); + } + + child = child->next; + } + + if (start_regex == NULL) + { + g_warning ("Missing start-regex in tag 'string' (%s, line %ld)", + child->doc->name, xmlGetLineNo (cur)); + + return; + } + + if (end_regex == NULL) + { + xmlFree (start_regex); + + g_warning ("Missing end-regex in tag 'string' (%s, line %ld)", + child->doc->name, xmlGetLineNo (cur)); + + return; + } + + ctx_data_add_syntax_pattern (ctx_data, language, id, + (gchar*) style, + (gchar*) start_regex, + (gchar*) end_regex, + end_at_line_end); + + xmlFree (start_regex); + xmlFree (end_regex); +} + +static void +parseKeywordList (xmlNodePtr cur, + gchar *id, + xmlChar *style, + GtkSourceContextData *ctx_data, + GtkSourceLanguage *language) +{ + gboolean case_sensitive = TRUE; + gboolean match_empty_string_at_beginning = TRUE; + gboolean match_empty_string_at_end = TRUE; + gchar *beginning_regex = NULL; + gchar *end_regex = NULL; + + GSList *list = NULL; + gchar *regex; + + xmlChar *prop; + + xmlNodePtr child; + + prop = xmlGetProp (cur, BAD_CAST "case-sensitive"); + if (prop != NULL) + { + if (!xmlStrcasecmp (prop, (const xmlChar *)"TRUE") || + !xmlStrcmp (prop, (const xmlChar *)"1")) + + case_sensitive = TRUE; + else + case_sensitive = FALSE; + + xmlFree (prop); + } + + prop = xmlGetProp (cur, BAD_CAST "match-empty-string-at-beginning"); + if (prop != NULL) + { + if (!xmlStrcasecmp (prop, (const xmlChar *)"TRUE") || + !xmlStrcmp (prop, (const xmlChar *)"1")) + + match_empty_string_at_beginning = TRUE; + else + match_empty_string_at_beginning = FALSE; + + xmlFree (prop); + } + + prop = xmlGetProp (cur, BAD_CAST "match-empty-string-at-end"); + if (prop != NULL) + { + if (!xmlStrcasecmp (prop, (const xmlChar *)"TRUE") || + !xmlStrcmp (prop, (const xmlChar *)"1")) + + match_empty_string_at_end = TRUE; + else + match_empty_string_at_end = FALSE; + + xmlFree (prop); + } + + prop = xmlGetProp (cur, BAD_CAST "beginning-regex"); + if (prop != NULL) + { + beginning_regex = g_strdup ((gchar *)prop); + + xmlFree (prop); + } + + prop = xmlGetProp (cur, BAD_CAST "end-regex"); + if (prop != NULL) + { + end_regex = g_strdup ((gchar *)prop); + + xmlFree (prop); + } + + child = cur->xmlChildrenNode; + + while (child != NULL) + { + if (!xmlStrcmp (child->name, BAD_CAST "keyword")) + { + xmlChar *keyword; + keyword = xmlNodeListGetString (child->doc, child->xmlChildrenNode, 1); + list = g_slist_prepend (list, keyword); + } + + child = child->next; + } + + list = g_slist_reverse (list); + + if (list == NULL) + { + g_warning ("No keywords in tag 'keyword-list' (%s, line %ld)", + child->doc->name, xmlGetLineNo (cur)); + + g_free (beginning_regex), + g_free (end_regex); + + return; + } + + regex = build_keyword_list (list, + case_sensitive, + match_empty_string_at_beginning, + match_empty_string_at_end, + beginning_regex, + end_regex); + + g_free (beginning_regex), + g_free (end_regex); + + g_slist_foreach (list, (GFunc) xmlFree, NULL); + g_slist_free (list); + + ctx_data_add_simple_pattern (ctx_data, language, id, (gchar*) style, regex); + + g_free (regex); +} + +static void +parsePatternItem (xmlNodePtr cur, + gchar *id, + xmlChar *style, + GtkSourceContextData *ctx_data, + GtkSourceLanguage *language) +{ + xmlNodePtr child; + + child = cur->xmlChildrenNode; + + if ((child != NULL) && !xmlStrcmp (child->name, (const xmlChar *)"regex")) + { + xmlChar *regex; + + regex = xmlNodeListGetString (child->doc, child->xmlChildrenNode, 1); + + ctx_data_add_simple_pattern (ctx_data, language, id, + (gchar*) style, + (gchar*) regex); + + xmlFree (regex); + } + else + { + g_warning ("Missing regex in tag 'pattern-item' (%s, line %ld)", + child->doc->name, xmlGetLineNo (child)); + } +} + +static void +parseSyntaxItem (xmlNodePtr cur, + const gchar *id, + xmlChar *style, + GtkSourceContextData *ctx_data, + GtkSourceLanguage *language) +{ + xmlChar *start_regex = NULL; + xmlChar *end_regex = NULL; + + xmlNodePtr child; + + child = cur->xmlChildrenNode; + + while (child != NULL) + { + if (!xmlStrcmp (child->name, (const xmlChar *)"start-regex")) + { + start_regex = xmlNodeListGetString (child->doc, child->xmlChildrenNode, 1); + } + else + if (!xmlStrcmp (child->name, (const xmlChar *)"end-regex")) + { + end_regex = xmlNodeListGetString (child->doc, child->xmlChildrenNode, 1); + } + + child = child->next; + } + + if (start_regex == NULL) + { + g_warning ("Missing start-regex in tag 'syntax-item' (%s, line %ld)", + child->doc->name, xmlGetLineNo (cur)); + + return; + } + + if (end_regex == NULL) + { + xmlFree (start_regex); + + g_warning ("Missing end-regex in tag 'syntax-item' (%s, line %ld)", + child->doc->name, xmlGetLineNo (cur)); + + return; + } + + ctx_data_add_syntax_pattern (ctx_data, language, id, + (gchar*) style, + (gchar*) start_regex, + (gchar*) end_regex, + FALSE); + + xmlFree (start_regex); + xmlFree (end_regex); +} + +static void +parseTag (GtkSourceLanguage *language, + xmlNodePtr cur, + GtkSourceContextData *ctx_data) +{ + xmlChar *name; + xmlChar *style; + xmlChar *id; + + name = xmlGetProp (cur, BAD_CAST "_name"); + if (name == NULL) + { + name = xmlGetProp (cur, BAD_CAST "name"); + id = xmlStrdup (name); + } + else + { + gchar *tmp1 = _gtk_source_language_translate_string (language, (gchar*) name); + xmlChar *tmp2 = xmlStrdup (BAD_CAST tmp1); + id = name; + name = tmp2; + g_free (tmp1); + } + + if (name == NULL) + { + return; + } + + style = xmlGetProp (cur, BAD_CAST "style"); + + if (!xmlStrcmp (cur->name, (const xmlChar*) "line-comment")) + { + parseLineComment (cur, (gchar*) id, style, ctx_data, language); + } + else if (!xmlStrcmp (cur->name, (const xmlChar*) "block-comment")) + { + parseBlockComment (cur, (gchar*) id, style, ctx_data, language); + } + else if (!xmlStrcmp (cur->name, (const xmlChar*) "string")) + { + parseString (cur, (gchar*) id, style, ctx_data, language); + } + else if (!xmlStrcmp (cur->name, (const xmlChar*) "keyword-list")) + { + parseKeywordList (cur, (gchar*) id, style, ctx_data, language); + } + else if (!xmlStrcmp (cur->name, (const xmlChar*) "pattern-item")) + { + parsePatternItem (cur, (gchar*) id, style, ctx_data, language); + } + else if (!xmlStrcmp (cur->name, (const xmlChar*) "syntax-item")) + { + parseSyntaxItem (cur, (gchar*) id, style, ctx_data, language); + } + else + { + g_print ("Unknown tag: %s\n", cur->name); + } + + xmlFree (name); + xmlFree (style); + xmlFree (id); +} + +static gboolean +define_root_context (GtkSourceContextData *ctx_data, + GtkSourceLanguage *language) +{ + gboolean result; + gchar *id; + GError *error = NULL; + + g_return_val_if_fail (language->priv->id != NULL, FALSE); + + id = g_strdup_printf ("%s:%s", language->priv->id, language->priv->id); + result = _gtk_source_context_data_define_context (ctx_data, id, + NULL, NULL, NULL, NULL, + NULL, + GTK_SOURCE_CONTEXT_EXTEND_PARENT, + &error); + + if (error != NULL) + { + g_warning ("%s", error->message); + g_error_free (error); + } + + g_free (id); + return result; +} + +gboolean +_gtk_source_language_file_parse_version1 (GtkSourceLanguage *language, + GtkSourceContextData *ctx_data) +{ + xmlDocPtr doc; + xmlNodePtr cur; + GMappedFile *mf; + gunichar esc_char = 0; + xmlChar *lang_version = NULL; + + xmlKeepBlanksDefault (0); + + mf = g_mapped_file_new (language->priv->lang_file_name, FALSE, NULL); + + if (mf == NULL) + { + doc = NULL; + } + else + { + doc = xmlParseMemory (g_mapped_file_get_contents (mf), + g_mapped_file_get_length (mf)); + + g_mapped_file_free (mf); + } + + if (doc == NULL) + { + g_warning ("Impossible to parse file '%s'", + language->priv->lang_file_name); + return FALSE; + } + + cur = xmlDocGetRootElement (doc); + + if (cur == NULL) + { + g_warning ("The lang file '%s' is empty", + language->priv->lang_file_name); + goto error; + } + + if (xmlStrcmp (cur->name, (const xmlChar *) "language") != 0) + { + g_warning ("File '%s' is of the wrong type", + language->priv->lang_file_name); + goto error; + } + + lang_version = xmlGetProp (cur, BAD_CAST "version"); + + if (lang_version == NULL || strcmp ("1.0", (char*) lang_version) != 0) + { + if (lang_version != NULL) + g_warning ("Wrong language version '%s' in file '%s', expected '%s'", + (char*) lang_version, language->priv->lang_file_name, "1.0"); + else + g_warning ("Language version missing in file '%s'", + language->priv->lang_file_name); + goto error; + } + + if (!define_root_context (ctx_data, language)) + { + g_warning ("Could not create root context for file '%s'", + language->priv->lang_file_name); + goto error; + } + + /* FIXME: check that the language name, version, etc. are the + * right ones - Paolo */ + + cur = xmlDocGetRootElement (doc); + cur = cur->xmlChildrenNode; + g_return_val_if_fail (cur != NULL, FALSE); + + while (cur != NULL) + { + if (!xmlStrcmp (cur->name, (const xmlChar *)"escape-char")) + { + xmlChar *escape; + + escape = xmlNodeListGetString (doc, cur->xmlChildrenNode, 1); + esc_char = g_utf8_get_char_validated ((gchar*) escape, -1); + + if (esc_char == (gunichar) -1 || esc_char == (gunichar) -2) + { + g_warning ("Invalid (non UTF8) escape character in file '%s'", + language->priv->lang_file_name); + esc_char = 0; + } + + xmlFree (escape); + } + else + { + parseTag (language, cur, ctx_data); + } + + cur = cur->next; + } + + if (esc_char != 0) + _gtk_source_context_data_set_escape_char (ctx_data, esc_char); + + _gtk_source_context_data_finish_parse (ctx_data, NULL, NULL); + _gtk_source_language_define_language_styles (language); + + xmlFreeDoc (doc); + xmlFree (lang_version); + return TRUE; + +error: + if (doc) + xmlFreeDoc (doc); + xmlFree (lang_version); + return FALSE; +} + diff --git a/moo/gtksourceview/gtksourcelanguage-parser-2.c b/moo/gtksourceview/gtksourcelanguage-parser-2.c new file mode 100644 index 00000000..243d646e --- /dev/null +++ b/moo/gtksourceview/gtksourcelanguage-parser-2.c @@ -0,0 +1,1770 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- + * gtksourcelanguage-parser-2.c + * Language specification parser for 2.0 version .lang files + * + * Copyright (C) 2003 - Gustavo Giráldez + * Copyright (C) 2005, 2006 - Emanuele Aina, Marco Barisione + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#undef DEBUG +#undef ENABLE_DEBUG + +#ifdef ENABLE_DEBUG +#define DEBUG(x) x +#else +#define DEBUG(x) +#endif + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef G_OS_WIN32 +#include +#endif +#include +#include +#include "gtksourceview-i18n.h" +#include "gtksourcebuffer.h" +#include "gtksourcelanguage.h" +#include "gtksourcelanguage-private.h" +#include "gtksourcecontextengine.h" +#include + +#define PARSER_ERROR (parser_error_quark ()) +#define ATTR_NO_STYLE "" + +typedef enum { + PARSER_ERROR_CANNOT_OPEN = 0, + PARSER_ERROR_CANNOT_VALIDATE, + PARSER_ERROR_INVALID_DOC, + PARSER_ERROR_WRONG_VERSION, + PARSER_ERROR_WRONG_ID, + PARSER_ERROR_WRONG_STYLE, + PARSER_ERROR_MALFORMED_REGEX, + PARSER_ERROR_MALFORMED_MAP_TO +} ParserError; + +struct _ParserState +{ + /* The args passed to _file_parse_version2() */ + xmlTextReader *reader; + char *filename; + GtkSourceLanguage *language; + GtkSourceContextData *ctx_data; + + gchar *language_decoration; + + /* A stack of id that representing parent contexts */ + GQueue *curr_parents; + + /* The id of the current language (used to decorate ids) */ + gchar *current_lang_id; + + /* An hash table with the defined regex as strings used to + * resolve references (the key is the id) */ + GHashTable *defined_regexes; + + /* Maps style ids to GtkSourceStyleInfo objects. + * Contains all the styles defined in the lang files parsed + * while parsing the main language file. For example, if the main + * language is C, also styles in def.lang, gtk-doc.lang, etc. are + * stored in this hash table. For styles defined in language files + * different from the main one, the name is _not_ stored, since it + * is not used in other places of the code. So, if the main language is + * C, only the name of styles defined in c.lang is stored, while for + * the styles defined in def.lang, etc. the name is not stored. */ + GHashTable *styles_mapping; + + /* The list of loaded languages (the item are xmlChar pointers), + * mapping is id -> id */ + GHashTable *loaded_lang_ids; + + /* The list of replacements. The queue object is owned by the caller, + * so parser_state only adds stuff to it */ + GQueue *replacements; + + /* A serial number incremented to get unique generated names */ + guint id_cookie; + + /* The default flags used by the regexes */ + GRegexCompileFlags regex_compile_flags; + + gchar *opening_delimiter; + gchar *closing_delimiter; + + GError *error; +}; + +typedef struct _ParserState ParserState; + + +static GQuark parser_error_quark (void); +static gboolean str_to_bool (const xmlChar *string); +static gchar *generate_new_id (ParserState *parser_state); +static gboolean id_is_decorated (const gchar *id, + gchar **lang_id); +static gchar *decorate_id (ParserState *parser_state, + const gchar *id); + +static ParserState *parser_state_new (GtkSourceLanguage *language, + GtkSourceContextData *ctx_data, + GHashTable *defined_regexes, + GHashTable *styles_mapping, + GQueue *replacements, + xmlTextReader *reader, + const char *filename, + GHashTable *loaded_lang_ids); +static void parser_state_destroy (ParserState *parser_state); + +static gboolean file_parse (gchar *filename, + GtkSourceLanguage *language, + GtkSourceContextData *ctx_data, + GHashTable *defined_regexes, + GHashTable *styles, + GHashTable *loaded_lang_ids, + GQueue *replacements, + GError **error); + +static GRegexCompileFlags + update_regex_flags (GRegexCompileFlags flags, + const xmlChar *option_name, + const xmlChar *bool_value); + +static gboolean create_definition (ParserState *parser_state, + gchar *id, + gchar *parent_id, + gchar *style, + GError **error); + +static void handle_context_element (ParserState *parser_state); +static void handle_language_element (ParserState *parser_state); +static void handle_define_regex_element (ParserState *parser_state); +static void handle_default_regex_options_element + (ParserState *parser_state); +static void handle_replace_element (ParserState *parser_state); +static void element_start (ParserState *parser_state); +static void element_end (ParserState *parser_state); +static gboolean replace_by_id (const GMatchInfo *match_info, + GString *expanded_regex, + gpointer data); +static gchar *expand_regex (ParserState *parser_state, + gchar *regex, + GRegexCompileFlags flags, + gboolean do_expand_vars, + gboolean insert_parentheses, + GError **error); + +static GQuark +parser_error_quark (void) +{ + static GQuark err_q = 0; + if (err_q == 0) + err_q = g_quark_from_static_string ( + "parser-error-quark"); + + return err_q; +} + +static gboolean +str_to_bool (const xmlChar *string) +{ + g_return_val_if_fail (string != NULL, FALSE); + return g_ascii_strcasecmp ("true", (const gchar *) string) == 0; +} + +static gchar * +generate_new_id (ParserState *parser_state) +{ + gchar *id; + + id = g_strdup_printf ("unnamed-%u", parser_state->id_cookie); + parser_state->id_cookie++; + + DEBUG (g_message ("generated id %s", id)); + + return id; +} + +static gboolean +id_is_decorated (const gchar *id, + gchar **lang_id) +{ + /* This function is quite simple because the XML validator check for + * the correctness of the id with a regex */ + + const gchar *colon; + gboolean is_decorated = FALSE; + + colon = strchr (id, ':'); + + if (colon != NULL && strcmp ("*", colon + 1) != 0) + { + is_decorated = TRUE; + + if (lang_id != NULL) + *lang_id = g_strndup (id, colon - id); + } + + return is_decorated; +} + +static gchar * +decorate_id (ParserState *parser_state, + const gchar *id) +{ + gchar *decorated_id; + + decorated_id = g_strdup_printf ("%s:%s", + parser_state->current_lang_id, id); + + DEBUG (g_message ("decorated '%s' to '%s'", id, decorated_id)); + + return decorated_id; +} + +static gboolean +lang_id_is_already_loaded (ParserState *parser_state, gchar *lang_id) +{ + return g_hash_table_lookup (parser_state->loaded_lang_ids, lang_id) != NULL; +} + +static GRegexCompileFlags +get_regex_flags (xmlNode *node, + GRegexCompileFlags flags) +{ + xmlAttr *attribute; + + for (attribute = node->properties; attribute != NULL; attribute = attribute->next) + { + g_return_val_if_fail (attribute->children != NULL, flags); + + flags = update_regex_flags (flags, attribute->name, + attribute->children->content); + } + + return flags; +} + +static GtkSourceContextFlags +get_context_flags (ParserState *parser_state) +{ + guint i; + xmlChar *value; + GtkSourceContextFlags flags = GTK_SOURCE_CONTEXT_EXTEND_PARENT; + const gchar *names[] = { + "extend-parent", "end-parent", "end-at-line-end", + "first-line-only", "once-only", "style-inside" + }; + GtkSourceContextFlags values[] = { + GTK_SOURCE_CONTEXT_EXTEND_PARENT, + GTK_SOURCE_CONTEXT_END_PARENT, + GTK_SOURCE_CONTEXT_END_AT_LINE_END, + GTK_SOURCE_CONTEXT_FIRST_LINE_ONLY, + GTK_SOURCE_CONTEXT_ONCE_ONLY, + GTK_SOURCE_CONTEXT_STYLE_INSIDE + }; + + g_assert (G_N_ELEMENTS (names) == G_N_ELEMENTS (values)); + + for (i = 0; i < G_N_ELEMENTS (names); ++i) + { + value = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST names[i]); + + if (value != NULL) + { + if (str_to_bool (value)) + flags |= values[i]; + else + flags &= ~values[i]; + } + + xmlFree (value); + } + + return flags; +} + +static gboolean +create_definition (ParserState *parser_state, + gchar *id, + gchar *parent_id, + gchar *style, + GError **error) +{ + gchar *match = NULL, *start = NULL, *end = NULL; + gchar *prefix = NULL, *suffix = NULL; + GtkSourceContextFlags flags; + + xmlNode *context_node, *child; + + GString *all_items = NULL; + + GRegexCompileFlags match_flags = 0, start_flags = 0, end_flags = 0; + + GError *tmp_error = NULL; + + g_assert (parser_state->ctx_data != NULL); + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + flags = get_context_flags (parser_state); + + DEBUG (g_message ("creating context %s, child of %s", id, parent_id ? parent_id : "(null)")); + + /* Fetch the content of the sublements using the tree API on + * the current node */ + context_node = xmlTextReaderExpand (parser_state->reader); + + /* The file should be validated so this should not happen */ + g_assert (context_node != NULL); + + for (child = context_node->children; child != NULL; child = child->next) + { + if (child->type != XML_ELEMENT_NODE) + continue; + + /* FIXME: add PCRE_EXTRA support in EggRegex + * Huh? */ + + g_assert (child->name); + if (xmlStrcmp (BAD_CAST "match", child->name) == 0 + && child->children != NULL) + { + /* */ + match = g_strdup ((gchar *)child->children->content); + match_flags = get_regex_flags (child, parser_state->regex_compile_flags); + } + else if (xmlStrcmp (BAD_CAST "start", child->name) == 0) + { + /* */ + if (child->children != NULL) + { + start = g_strdup ((gchar *)child->children->content); + } + else + { + /* If the element is present but + * has no content use an empty string */ + start = g_strdup (""); + } + start_flags = get_regex_flags (child, parser_state->regex_compile_flags); + } + else if (xmlStrcmp (BAD_CAST "end", child->name) == 0) + { + /* */ + if (child->children != NULL) + end = g_strdup ((gchar *)child->children->content); + else + { + /* If the element is present but + * has no content use an empty string */ + end = g_strdup (""); + } + end_flags = get_regex_flags (child, parser_state->regex_compile_flags); + } + else if (xmlStrcmp (BAD_CAST "prefix", child->name) == 0) + { + /* */ + if (child->children != NULL) + prefix = g_strdup ((gchar*) child->children->content); + else + prefix = g_strdup (""); + } + else if (xmlStrcmp (BAD_CAST "suffix", child->name) == 0) + { + /* */ + if (child->children != NULL) + suffix = g_strdup ((gchar*) child->children->content); + else + suffix = g_strdup (""); + } + else if (xmlStrcmp (BAD_CAST "keyword", child->name) == 0 && + child->children != NULL) + { + /* FIXME: how to specify regex options for keywords? + * They can be specified in prefix, so it's not really + * important, but would be nice (case-sensitive). */ + + /* */ + if (all_items == NULL) + { + all_items = g_string_new (NULL); + + if (prefix != NULL) + g_string_append (all_items, prefix); + else + g_string_append (all_items, + parser_state->opening_delimiter); + + g_string_append (all_items, "("); + g_string_append (all_items, (gchar*) child->children->content); + } + else + { + g_string_append (all_items, "|"); + g_string_append (all_items, (gchar*) child->children->content); + } + } + } + + if (all_items != NULL) + { + g_string_append (all_items, ")"); + + if (suffix != NULL) + g_string_append (all_items, suffix); + else + g_string_append (all_items, + parser_state->closing_delimiter); + + match = g_string_free (all_items, FALSE); + match_flags = parser_state->regex_compile_flags; + } + + DEBUG (g_message ("start: '%s'", start ? start : "(null)")); + DEBUG (g_message ("end: '%s'", end ? end : "(null)")); + DEBUG (g_message ("match: '%s'", match ? match : "(null)")); + + + if (tmp_error == NULL && start != NULL) + { + gchar *tmp = start; + start = expand_regex (parser_state, start, start_flags, + TRUE, FALSE, &tmp_error); + g_free (tmp); + } + if (tmp_error == NULL && end != NULL) + { + gchar *tmp = end; + end = expand_regex (parser_state, end, end_flags, + TRUE, FALSE, &tmp_error); + g_free (tmp); + } + if (tmp_error == NULL && match != NULL) + { + gchar *tmp = match; + match = expand_regex (parser_state, match, match_flags, + TRUE, FALSE, &tmp_error); + g_free (tmp); + } + + if (tmp_error == NULL) + _gtk_source_context_data_define_context (parser_state->ctx_data, + id, + parent_id, + match, + start, + end, + style, + flags, + &tmp_error); + + g_free (match); + g_free (start); + g_free (end); + g_free (prefix); + g_free (suffix); + + if (tmp_error != NULL) + { + g_propagate_error (error, tmp_error); + return FALSE; + } + + return TRUE; +} + +static gboolean +add_ref (ParserState *parser_state, + const gchar *ref, + GtkSourceContextRefOptions options, + const gchar *style, + GError **error) +{ + gboolean all = FALSE; + gchar *ref_id; + gchar *lang_id = NULL; + GError *tmp_error = NULL; + + /* Return if an error is already set */ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (id_is_decorated (ref, &lang_id)) + { + if (!lang_id_is_already_loaded (parser_state, lang_id)) + { + GtkSourceLanguageManager *lm; + GtkSourceLanguage *imported_language; + + lm = _gtk_source_language_get_language_manager (parser_state->language); + imported_language = gtk_source_language_manager_get_language (lm, lang_id); + + if (imported_language == NULL) + { + g_set_error (&tmp_error, + PARSER_ERROR, + PARSER_ERROR_WRONG_ID, + "unable to resolve language '%s' in ref '%s'", + lang_id, ref); + } + else + { + file_parse (imported_language->priv->lang_file_name, + parser_state->language, + parser_state->ctx_data, + parser_state->defined_regexes, + parser_state->styles_mapping, + parser_state->loaded_lang_ids, + parser_state->replacements, + &tmp_error); + + if (tmp_error != NULL) + { + GError *tmp_error2 = NULL; + g_set_error (&tmp_error2, PARSER_ERROR, tmp_error->code, + "In file '%s' referenced from '%s': %s", + imported_language->priv->lang_file_name, + parser_state->language->priv->lang_file_name, + tmp_error->message); + g_clear_error (&tmp_error); + tmp_error = tmp_error2; + } + } + } + ref_id = g_strdup (ref); + } + else + { + ref_id = decorate_id (parser_state, ref); + } + + if (tmp_error == NULL && parser_state->ctx_data != NULL) + { + if (g_str_has_suffix (ref, ":*")) + { + all = TRUE; + ref_id [strlen (ref_id) - 2] = '\0'; + } + + if (all && (options & (GTK_SOURCE_CONTEXT_IGNORE_STYLE | GTK_SOURCE_CONTEXT_OVERRIDE_STYLE))) + { + g_set_error (&tmp_error, PARSER_ERROR, + PARSER_ERROR_WRONG_STYLE, + "style override used with wildcard context reference" + " in language '%s' in ref '%s'", + lang_id != NULL ? lang_id : parser_state->current_lang_id, ref); + } + } + + if (tmp_error == NULL && parser_state->ctx_data != NULL) + { + gchar *container_id; + + container_id = g_queue_peek_head (parser_state->curr_parents); + + /* If the document is validated container_id is never NULL */ + g_assert (container_id); + + _gtk_source_context_data_add_ref (parser_state->ctx_data, + container_id, + ref_id, + options, + style, + all, + &tmp_error); + + DEBUG (g_message ("appended %s in %s", ref_id, container_id)); + } + + g_free (lang_id); + g_free (ref_id); + + if (tmp_error != NULL) + { + g_propagate_error (error, tmp_error); + return FALSE; + } + + return TRUE; +} + +static gboolean +create_sub_pattern (ParserState *parser_state, + gchar *id, + gchar *sub_pattern, + gchar *style, + GError **error) +{ + gchar *container_id; + xmlChar *where; + + GError *tmp_error = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + container_id = g_queue_peek_head (parser_state->curr_parents); + + /* If the document is validated container is never NULL */ + g_assert (container_id); + + where = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "where"); + + _gtk_source_context_data_add_sub_pattern (parser_state->ctx_data, + id, + container_id, + sub_pattern, + (gchar*) where, + style, + &tmp_error); + + xmlFree (where); + + if (tmp_error != NULL) + { + g_propagate_error (error, tmp_error); + return FALSE; + } + + return TRUE; +} + +static void +handle_context_element (ParserState *parser_state) +{ + gchar *id, *parent_id, *style_ref; + xmlChar *ref, *sub_pattern, *tmp; + int is_empty; + gboolean success; + gboolean ignore_style = FALSE; + GtkSourceContextRefOptions options = 0; + + GError *tmp_error = NULL; + + g_return_if_fail (parser_state->error == NULL); + + ref = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "ref"); + sub_pattern = xmlTextReaderGetAttribute (parser_state->reader, + BAD_CAST "sub-pattern"); + + tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "ignore-style"); + if (tmp != NULL && str_to_bool (tmp)) + ignore_style = TRUE; + xmlFree (tmp); + + tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "style-ref"); + if (tmp == NULL || id_is_decorated ((gchar*) tmp, NULL)) + style_ref = g_strdup ((gchar*) tmp); + else + style_ref = decorate_id (parser_state, (gchar*) tmp); + xmlFree (tmp); + + if (ignore_style && ref == NULL) + { + g_set_error (&parser_state->error, + PARSER_ERROR, + PARSER_ERROR_WRONG_STYLE, + "ignore-style used not in a reference to context"); + + xmlFree (ref); + g_free (style_ref); + + return; + } + + if (ignore_style) + { + options |= GTK_SOURCE_CONTEXT_IGNORE_STYLE; + + if (style_ref != NULL) + g_warning ("in file %s: style-ref and ignore-style used simultaneously", + parser_state->filename); + } + + /* XXX */ + if (!ignore_style && style_ref != NULL && + g_hash_table_lookup (parser_state->styles_mapping, style_ref) == NULL) + { + g_warning ("in file %s: style '%s' not defined", parser_state->filename, style_ref); + } + + if (ref != NULL) + { + tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "original"); + if (tmp != NULL && str_to_bool (tmp)) + options |= GTK_SOURCE_CONTEXT_REF_ORIGINAL; + xmlFree (tmp); + + if (style_ref != NULL) + options |= GTK_SOURCE_CONTEXT_OVERRIDE_STYLE; + + add_ref (parser_state, (gchar*) ref, options, style_ref, &tmp_error); + } + else + { + char *freeme = NULL; + + tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "id"); + if (tmp == NULL) + { + freeme = generate_new_id (parser_state); + tmp = xmlStrdup (BAD_CAST freeme); + } + + if (id_is_decorated ((gchar*) tmp, NULL)) + id = g_strdup ((gchar*) tmp); + else + id = decorate_id (parser_state, (gchar*) tmp); + + g_free (freeme); + xmlFree (tmp); + + if (parser_state->ctx_data != NULL) + { + if (sub_pattern != NULL) + { + create_sub_pattern (parser_state, id, + (gchar *)sub_pattern, style_ref, + &tmp_error); + } + else + { + parent_id = g_queue_peek_head ( + parser_state->curr_parents); + + is_empty = xmlTextReaderIsEmptyElement ( + parser_state->reader); + + if (is_empty) + success = _gtk_source_context_data_define_context (parser_state->ctx_data, + id, + parent_id, + "$^", + NULL, + NULL, + NULL, + 0, + &tmp_error); + else + success = create_definition (parser_state, id, parent_id, + style_ref, &tmp_error); + + if (success && !is_empty) + { + /* Push the new context in the curr_parents + * stack only if other contexts can be + * defined inside it */ + g_queue_push_head (parser_state->curr_parents, + g_strdup (id)); + } + } + } + + g_free (id); + } + + g_free (style_ref); + xmlFree (sub_pattern); + xmlFree (ref); + + if (tmp_error != NULL) + g_propagate_error (&parser_state->error, tmp_error); +} + +static void +handle_replace_element (ParserState *parser_state) +{ + xmlChar *id, *ref; + GtkSourceContextReplace *repl; + gchar *replace_with; + + id = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "id"); + ref = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "ref"); + + if (id_is_decorated ((gchar*) ref, NULL)) + replace_with = g_strdup ((gchar *) ref); + else + replace_with = decorate_id (parser_state, (gchar*) ref); + + repl = _gtk_source_context_replace_new ((const gchar *) id, replace_with); + g_queue_push_tail (parser_state->replacements, repl); + + g_free (replace_with); + xmlFree (ref); + xmlFree (id); +} + +static void +handle_language_element (ParserState *parser_state) +{ + /* FIXME: check that the language name, version, etc. are the + * right ones - Paolo */ + xmlChar *lang_id, *lang_version; + xmlChar *expected_version = BAD_CAST "2.0"; + + g_return_if_fail (parser_state->error == NULL); + + lang_version = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "version"); + + if (lang_version == NULL || + xmlStrcmp (expected_version, lang_version) != 0) + { + g_set_error (&parser_state->error, + PARSER_ERROR, + PARSER_ERROR_WRONG_VERSION, + "wrong language version '%s', expected '%s'", + lang_version ? (gchar*) lang_version : "(none)", + (gchar*) expected_version); + } + else + { + lang_id = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "id"); + parser_state->current_lang_id = g_strdup ((gchar *) lang_id); + g_hash_table_insert (parser_state->loaded_lang_ids, lang_id, lang_id); + } + + xmlFree (lang_version); +} + + +struct ReplaceByIdData { + ParserState *parser_state; + GError *error; +}; + +static gboolean +replace_by_id (const GMatchInfo *match_info, + GString *expanded_regex, + gpointer user_data) +{ + gchar *id, *subst, *escapes; + gchar *tmp; + GError *tmp_error = NULL; + + struct ReplaceByIdData *data = user_data; + + escapes = g_match_info_fetch (match_info, 1); + tmp = g_match_info_fetch (match_info, 2); + + g_strstrip (tmp); + + if (id_is_decorated (tmp, NULL)) + id = g_strdup (tmp); + else + id = decorate_id (data->parser_state, tmp); + g_free (tmp); + + subst = g_hash_table_lookup (data->parser_state->defined_regexes, id); + if (subst == NULL) + g_set_error (&tmp_error, + PARSER_ERROR, PARSER_ERROR_WRONG_ID, + _("Unknown id '%s' in regex '%s'"), id, + g_match_info_get_string (match_info)); + + if (tmp_error == NULL) + { + g_string_append (expanded_regex, escapes); + g_string_append (expanded_regex, subst); + } + + g_free (escapes); + g_free (id); + + if (tmp_error != NULL) + { + g_propagate_error (&data->error, tmp_error); + return TRUE; + } + + return FALSE; +} + +static GRegexCompileFlags +update_regex_flags (GRegexCompileFlags flags, + const xmlChar *option_name, + const xmlChar *value) +{ + GRegexCompileFlags single_flag; + gboolean set_flag; + + DEBUG (g_message ("setting the '%s' regex flag to %s", option_name, value)); + + set_flag = str_to_bool (value); + + if (xmlStrcmp (BAD_CAST "case-sensitive", option_name) == 0) + { + single_flag = G_REGEX_CASELESS; + set_flag = !set_flag; + } + else if (xmlStrcmp (BAD_CAST "extended", option_name) == 0) + { + single_flag = G_REGEX_EXTENDED; + } + else if (xmlStrcmp (BAD_CAST "dupnames", option_name) == 0) + { + single_flag = G_REGEX_DUPNAMES; + } + else + { + return flags; + } + + if (set_flag) + flags |= single_flag; + else + flags &= ~single_flag; + + return flags; +} + +static gchar * +expand_regex_vars (ParserState *parser_state, gchar *regex, gint len, GError **error) +{ + /* This is the commented regex without the doubled escape needed + * in a C string: + * + * (?opening_delimiter); + break; + case ']': + g_string_append (expanded_regex, + parser_state->closing_delimiter); + break; + } + + g_free (delim); + g_free (escapes); + + return FALSE; +} + +static gchar * +expand_regex_delimiters (ParserState *parser_state, + gchar *regex, + gint len) +{ + /* This is the commented regex without the doubled escape needed + * in a C string: + * + * (? 0) + { + g_set_error (error, PARSER_ERROR, PARSER_ERROR_MALFORMED_REGEX, + _("in regex '%s': backreferences are not supported"), + regex); + g_regex_unref (compiled); + return NULL; + } + + g_regex_unref (compiled); + } + + if (do_expand_vars) + { + tmp_regex = expand_regex_vars (parser_state, regex, -1, error); + + if (tmp_regex == NULL) + return NULL; + } + else + { + tmp_regex = g_strdup (regex); + } + + regex = tmp_regex; + tmp_regex = expand_regex_delimiters (parser_state, regex, -1); + g_free (regex); + + /* Set the options and add not capturing parentheses if + * insert_parentheses is TRUE (this is needed for included + * regular expressions.) */ + expanded_regex = g_string_new (""); + if (insert_parentheses) + g_string_append (expanded_regex, "(?:"); + g_string_append (expanded_regex, "(?"); + + if (flags != 0) + { + if (flags & G_REGEX_CASELESS) + g_string_append (expanded_regex, "i"); + if (flags & G_REGEX_EXTENDED) + g_string_append (expanded_regex, "x"); + /* J is added here if it's used, but -J isn't added + * below */ + if (flags & G_REGEX_DUPNAMES) + g_string_append (expanded_regex, "J"); + } + + if ((flags & (G_REGEX_CASELESS | G_REGEX_EXTENDED)) != (G_REGEX_CASELESS | G_REGEX_EXTENDED)) + { + g_string_append (expanded_regex, "-"); + if (!(flags & G_REGEX_CASELESS)) + g_string_append (expanded_regex, "i"); + if (!(flags & G_REGEX_EXTENDED)) + g_string_append (expanded_regex, "x"); + } + + g_string_append (expanded_regex, ")"); + g_string_append (expanded_regex, tmp_regex); + if (insert_parentheses) + { + /* The '\n' is needed otherwise, if the regex is "extended" + * and it ends with a comment, the ')' is appended inside the + * comment itself */ + if (flags & G_REGEX_EXTENDED) + g_string_append (expanded_regex, "\n"); + + /* FIXME: if the regex is not extended this doesn't works */ + g_string_append (expanded_regex, ")"); + } + g_free (tmp_regex); + + return g_string_free (expanded_regex, FALSE); +} + +static void +handle_define_regex_element (ParserState *parser_state) +{ + gchar *id; + xmlChar *regex; + xmlChar *tmp; + gchar *expanded_regex; + int i; + const gchar *regex_options[] = {"extended", "case-sensitive", "dupnames", NULL}; + GRegexCompileFlags flags; + GError *tmp_error = NULL; + + int type; + + g_return_if_fail (parser_state->error == NULL); + + if (parser_state->ctx_data == NULL) + return; + + tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "id"); + + /* If the file is validated must have an id + * attribute */ + g_assert (tmp != NULL); + + if (id_is_decorated ((gchar *)tmp, NULL)) + id = g_strdup ((gchar *)tmp); + else + id = decorate_id (parser_state, (gchar *)tmp); + xmlFree (tmp); + + flags = parser_state->regex_compile_flags; + + for (i=0; regex_options[i] != NULL; i++) + { + tmp = xmlTextReaderGetAttribute (parser_state->reader, + BAD_CAST regex_options[i]); + if (tmp != NULL) + { + flags = update_regex_flags (flags, + BAD_CAST regex_options[i], + tmp); + } + xmlFree (tmp); + } + + xmlTextReaderRead (parser_state->reader); + + type = xmlTextReaderNodeType (parser_state->reader); + + if (type == XML_READER_TYPE_TEXT || type == XML_READER_TYPE_CDATA) + regex = xmlTextReaderValue (parser_state->reader); + else + regex = xmlStrdup (BAD_CAST ""); + + expanded_regex = expand_regex (parser_state, (gchar*) regex, flags, + TRUE, TRUE, &tmp_error); + + if (tmp_error == NULL) + { + DEBUG (g_message ("defined regex %s: \"%s\"", id, (gchar *)regex)); + g_hash_table_insert (parser_state->defined_regexes, id, expanded_regex); + } + + xmlFree (regex); + + if (tmp_error != NULL) + g_propagate_error (&parser_state->error, tmp_error); +} + +static void +handle_default_regex_options_element (ParserState *parser_state) +{ + xmlNode *elm; + + g_return_if_fail (parser_state->error == NULL); + + if (parser_state->ctx_data == NULL) + return; + + elm = xmlTextReaderCurrentNode (parser_state->reader); + + parser_state->regex_compile_flags = get_regex_flags (elm, 0); +} + +static void +parse_language_with_id (ParserState *parser_state, + gchar *lang_id) +{ + GtkSourceLanguageManager *lm; + GtkSourceLanguage *imported_language; + + g_return_if_fail (parser_state->error == NULL); + + lm = _gtk_source_language_get_language_manager (parser_state->language); + imported_language = gtk_source_language_manager_get_language (lm, lang_id); + + if (imported_language == NULL) + { + g_set_error (&parser_state->error, + PARSER_ERROR, + PARSER_ERROR_WRONG_ID, + "unable to resolve language '%s'", + lang_id); + } + else + { + file_parse (imported_language->priv->lang_file_name, + parser_state->language, + parser_state->ctx_data, + parser_state->defined_regexes, + parser_state->styles_mapping, + parser_state->loaded_lang_ids, + parser_state->replacements, + &parser_state->error); + } +} + +static void +parse_style (ParserState *parser_state) +{ + gchar *id; + xmlChar *name, *map_to; + xmlChar *tmp; + gchar *lang_id = NULL; + + g_return_if_fail (parser_state->error == NULL); + + tmp = xmlTextReaderGetAttribute (parser_state->reader, + BAD_CAST "id"); + + if (id_is_decorated ((gchar*) tmp, NULL)) + id = g_strdup ((gchar*) tmp); + else + id = decorate_id (parser_state, (gchar*) tmp); + + xmlFree (tmp); + + name = xmlTextReaderGetAttribute (parser_state->reader, + BAD_CAST "_name"); + + if (name != NULL) + { + gchar *tmp2 = _gtk_source_language_translate_string (parser_state->language, + (gchar*) name); + tmp = xmlStrdup (BAD_CAST tmp2); + xmlFree (name); + name = tmp; + g_free (tmp2); + } + else + { + name = xmlTextReaderGetAttribute (parser_state->reader, + BAD_CAST "name"); + } + + map_to = xmlTextReaderGetAttribute (parser_state->reader, + BAD_CAST "map-to"); + + if (map_to != NULL && !id_is_decorated ((gchar*) map_to, &lang_id)) + { + g_set_error (&parser_state->error, + PARSER_ERROR, + PARSER_ERROR_MALFORMED_MAP_TO, + "the map-to attribute '%s' for the style '%s' lacks the prefix", + map_to, id); + } + + if (parser_state->error == NULL && lang_id != NULL && lang_id[0] == 0) + { + g_free (lang_id); + lang_id = NULL; + } + + if (parser_state->error == NULL && lang_id != NULL && + !lang_id_is_already_loaded (parser_state, lang_id)) + { + parse_language_with_id (parser_state, lang_id); + } + + DEBUG (g_message ("style %s (%s) to be mapped to '%s'", + name, id, map_to ? (char*) map_to : "(null)")); + + if (map_to != NULL && + g_hash_table_lookup (parser_state->styles_mapping, map_to) == NULL) + { + g_warning ("in file %s: style '%s' not defined", parser_state->filename, map_to); + } + + if (parser_state->error == NULL) + { + + GtkSourceStyleInfo *info; + + /* Remember the style name only if the style has been defined in + * the lang file we are parsing */ + if (g_str_has_prefix (id, parser_state->language_decoration)) + info = _gtk_source_style_info_new ((gchar *) name, + (gchar *) map_to); + else + info = _gtk_source_style_info_new (NULL, + (gchar *) map_to); + + g_hash_table_insert (parser_state->styles_mapping, g_strdup (id), info); + } + + g_free (lang_id); + g_free (id); + xmlFree (name); + xmlFree (map_to); +} + +static void +handle_keyword_char_class_element (ParserState *parser_state) +{ + xmlChar *char_class; + int ret, type; + + g_return_if_fail (parser_state->error == NULL); + + if (parser_state->ctx_data == NULL) + return; + + do { + ret = xmlTextReaderRead (parser_state->reader); + (void) ret; g_assert (ret == 1); + type = xmlTextReaderNodeType (parser_state->reader); + } + while (type != XML_READER_TYPE_TEXT && type != XML_READER_TYPE_CDATA); + + char_class = xmlTextReaderValue (parser_state->reader); + + g_free (parser_state->opening_delimiter); + g_free (parser_state->closing_delimiter); + + parser_state->opening_delimiter = g_strdup_printf ("(?!<%s)(?=%s)", + char_class, char_class); + parser_state->closing_delimiter = g_strdup_printf ("(?<=%s)(?!%s)", + char_class, char_class); + + xmlFree (char_class); +} + +static void +handle_styles_element (ParserState *parser_state) +{ + int type; + const xmlChar *tag_name; + + g_return_if_fail (parser_state->error == NULL); + + while (parser_state->error == NULL) + { + /* FIXME: is xmlTextReaderIsValid call needed here or + * error func will be called? */ + xmlTextReaderRead (parser_state->reader); + xmlTextReaderIsValid (parser_state->reader); + + if (parser_state->error != NULL) + break; + + tag_name = xmlTextReaderConstName (parser_state->reader); + type = xmlTextReaderNodeType (parser_state->reader); + + /* End at the closing tag */ + if (tag_name && type == XML_READER_TYPE_END_ELEMENT && + !xmlStrcmp (BAD_CAST "styles", tag_name)) + break; + + /* Skip nodes that aren't + + + Hi there! + + +EOFEOF + +cat > $dir/file.tex < $dir/file.m4 < $dir/file.sh < $dir/Makefile < $dir/file.py <> sys.stderr, "Hi there!" + None, True, False +Hello().hello() +EOFEOF + +cat > $dir/file.xml < + + momomomo + +EOFEOF + +cat > $dir/file.y < +#define FOO_BAR(x,y) printf ("x, y") +%} + +%name-prefix="foolala" +%error-verbose +%lex-param {FooLaLa *lala} +%parse-param {FooLaLa *lala} +/* %expect 1 */ + +%union { + int ival; + const char *str; +} + +%token ATOKEN +%token ATOKEN2 + +%type program stmt +%type if_stmt + +%token IF THEN ELSE ELIF FI +%token WHILE DO OD FOR IN +%token CONTINUE BREAK RETURN +%token EQ NEQ LE GE +%token AND OR NOT +%token UMINUS +%token TWODOTS + +%left '-' '+' +%left '*' '/' +%left '%' +%left EQ NEQ '<' '>' GE LE +%left OR +%left AND +%left NOT +%left '#' +%left UMINUS + +%% + +script: program { _ms_parser_set_top_node (parser, \$1); } +; + +program: stmt_or_error { \$\$ = node_list_add (parser, NULL, \$1); } + | program stmt_or_error { \$\$ = node_list_add (parser, MS_NODE_LIST (\$1), \$2); } +; + +stmt_or_error: + error ';' { \$\$ = NULL; } + | stmt ';' { \$\$ = $1; } +; + +variable: IDENTIFIER { \$\$ = node_var (parser, \$1); } +; + +%% +EOFEOF + +cat > $dir/file.desktop < $dir/file.diff < $dir/gtkrc < $dir/file.ini < $dir/file.pl <$title\n\n" if $title; + +$last = {}; # the last indexterm we processed +$first = 1; # this is the first one +$group = ""; # we're not in a group yet +$lastout = ""; # we've not put anything out yet +@seealsos = (); # See also stack. + +# Termcount is > 0 iff some entries were skipped. +$quiet || print STDERR "$termcount entries ignored...\n"; + +&end_entry(); + +print OUT "\n" if $lettergroups; +print OUT "\n"; + +close (OUT); + +$quiet || print STDERR "Done.\n"; + +sub same { + my($a) = shift; + my($b) = shift; + + my($aP) = $a->{'psortas'} || $a->{'primary'}; + my($aS) = $a->{'ssortas'} || $a->{'secondary'}; + my($aT) = $a->{'tsortas'} || $a->{'tertiary'}; + + my($bP) = $b->{'psortas'} || $b->{'primary'}; + my($bS) = $b->{'ssortas'} || $b->{'secondary'}; + my($bT) = $b->{'tsortas'} || $b->{'tertiary'}; + + my($same); + + $aP =~ s/^\s*//; $aP =~ s/\s*$//; $aP = uc($aP); + $aS =~ s/^\s*//; $aS =~ s/\s*$//; $aS = uc($aS); + $aT =~ s/^\s*//; $aT =~ s/\s*$//; $aT = uc($aT); + $bP =~ s/^\s*//; $bP =~ s/\s*$//; $bP = uc($bP); + $bS =~ s/^\s*//; $bS =~ s/\s*$//; $bS = uc($bS); + $bT =~ s/^\s*//; $bT =~ s/\s*$//; $bT = uc($bT); + +# print "[$aP]=[$bP]\n"; +# print "[$aS]=[$bS]\n"; +# print "[$aT]=[$bT]\n"; + + # Two index terms are the same if: + # 1. the primary, secondary, and tertiary entries are the same + # (or have the same SORTAS) + # AND + # 2. They occur in the same titled section + # AND + # 3. They point to the same place + # + # Notes: Scope is used to suppress some entries, but can't be used + # for comparing duplicates. + # Interpretation of "the same place" depends on whether or + # not $linkpoints is true. + + $same = (($aP eq $bP) + && ($aS eq $bS) + && ($aT eq $bT) + && ($a->{'title'} eq $b->{'title'}) + && ($a->{'href'} eq $b->{'href'})); + + # If we're linking to points, they're only the same if they link + # to exactly the same spot. + $same = $same && ($a->{'hrefpoint'} eq $b->{'hrefpoint'}) + if $linkpoints; + + if ($same) { + warn "$me: duplicated index entry found: $aP $aS $aT\n"; + } + + $same; +} + +sub tsame { + # Unlike same(), tsame only compares a single term + my($a) = shift; + my($b) = shift; + my($term) = shift; + my($sterm) = substr($term, 0, 1) . "sortas"; + my($A, $B); + + $A = $a->{$sterm} || $a->{$term}; + $B = $b->{$sterm} || $b->{$term}; + + $A =~ s/^\s*//; $A =~ s/\s*$//; $A = uc($A); + $B =~ s/^\s*//; $B =~ s/\s*$//; $B = uc($B); + + return $A eq $B; +} + +=head1 EXAMPLE +B B<-o> F F +=head1 EXIT STATUS +=over 5 +=item B<0> +Success +=item B<1> +Failure +=back +=head1 AUTHOR +Norm Walsh Endw@nwalsh.comE +Minor updates by Adam Di Carlo Eadam@onshore.comE and Peter Eisentraut Epeter_e@gmx.netE +=cut +EOFEOF + +cat > $dir/file.po <, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-12-17 09:49-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../medit/medit.desktop.in.h:1 +msgid "Text editor" +msgstr "" + +#: ../medit/medit.desktop.in.h:2 +msgid "medit" +msgstr "" +EOFEOF + +cat > $dir/file.texi < $dir/file.dtd < + + + + + +EOFEOF + +cat > $dir/file.la < $dir/file.pc <= 8.0.2 fontconfig libpng12 xrender >= 0.6 x11 +Libs: -L${libdir} -lcairo +Libs.private: -lz -lm +Cflags: -I${includedir}/cairo +EOFEOF + +cat > $dir/file.spec <= 2.3.0 +BuildRequires: libgnome-vfs2-devel >= 2.2.0 +BuildRequires: libgnomeprintui-devel >= 2.7.0 +BuildRequires: perl-XML-Parser +Conflicts: gtksourceview-sharp <= 0.5-3mdk + +%description +GtkSourceview is a library that adds syntax highlighting, +line numbers, and other programming-editor features. +GtkSourceView specializes these features for a code editor. + +%package -n %{lib_name} +Summary: Source code viewing library +Group: Editors +Requires: %{name} >= %{version}-%{release} +Provides: lib%{name} = %{version}-%{release} +Provides: libgtksourceview0 = %{version}-%{release} +Obsoletes: libgtksourceview0 +Provides: libgtksourceview1.0 = %{version}-%{release} +Obsoletes: libgtksourceview1.0 + +%description -n %{lib_name} +GtkSourceview is a library that adds syntax highlighting, +line numbers, and other programming-editor features. +GtkSourceView specializes these features for a code editor. + +%package -n %{lib_name}-devel +Summary: Libraries and include files for GtkSourceView +Group: Development/GNOME and GTK+ +Requires: %{lib_name} = %{version} +Provides: %{name}-devel = %{version}-%{release} +Provides: lib%{name}-devel = %{version}-%{release} +Provides: lib%{name}-%{api_version}-devel = %{version}-%{release} +Provides: libgtksourceview0-devel = %{version}-%{release} +Obsoletes: libgtksourceview0-devel +Provides: libgtksourceview1.0-devel = %{version}-%{release} +Obsoletes: libgtksourceview1.0-devel + +%description -n %{lib_name}-devel +GtkSourceView development files + + +%prep +%setup -q + +%build + +%configure2_5x + +%make + +%install +rm -rf %{buildroot} + +%makeinstall_std + +%{find_lang} %{name}-%{api_version} + +%post -n %{lib_name} -p /sbin/ldconfig + +%postun -n %{lib_name} -p /sbin/ldconfig + +%clean +rm -rf %{buildroot} + +%files -f %{name}-%{api_version}.lang +%defattr(-,root,root) +%doc AUTHORS ChangeLog NEWS README TODO +%{_datadir}/gtksourceview-%{api_version} + +%files -n %{lib_name} +%defattr(-,root,root) +%{_libdir}/*.so.* + +%files -n %{lib_name}-devel +%defattr(-,root,root) +%doc %{_datadir}/gtk-doc/html/gtksourceview +%{_libdir}/*.so +%attr(644,root,root) %{_libdir}/*.la +%{_includedir}/* +%{_libdir}/pkgconfig/* + +%changelog +* Tue Aug 08 2006 Götz Waschk 1.7.2-1mdv2007.0 +- New release 1.7.2 + +* Tue Jul 25 2006 Götz Waschk 1.7.1-1mdk +- New release 1.7.1 +EOFEOF diff --git a/moo/mooedit/langs/testv1.lang b/moo/mooedit/langs/testv1.lang new file mode 100644 index 00000000..475947f0 --- /dev/null +++ b/moo/mooedit/langs/testv1.lang @@ -0,0 +1,57 @@ + + + + + \ + + + + // + + + + + /\* + \*/ + + + + + " + " + + + + + ' + ' + + + + bambom + bombam + + + + bumbam + bambum + + + + kwkw + wkwk + + + + \b([0-9]*\.[0-9]+[eE@][-+]?[0-9]+|[0-9]+[eE@][-+]?[0-9]+|[0-9]*\.[0-9]+)\b + + + + \b(([1-3][0-9]|[1-9])\\[0-9a-zA-Z]+|0x[0-9a-fA-F]+|0[0-7]+)\b + + + + \b([1-9][0-9]*|0)\b + + + diff --git a/moo/mooedit/langs/texinfo.lang b/moo/mooedit/langs/texinfo.lang new file mode 100644 index 00000000..65df286b --- /dev/null +++ b/moo/mooedit/langs/texinfo.lang @@ -0,0 +1,448 @@ + + + + + + text/x-texinfo + *.texi;*.texinfo + @c + + + +