07070100000000000081A40000000000000000000000016659080600000102000000000000000000000000000000000000002300000000gtksourceview-5.12.1/.editorconfigroot = true [*] charset = utf-8 end_of_line = lf [*.[ch]] indent_size = 8 indent_style = tab insert_final_newline = true max_line_length = 100 tab_width = 8 [meson.build] indent_size = 2 indent_style = space [*.lang] indent_size = 2 indent_style = space 07070100000001000081A40000000000000000000000016659080600000027000000000000000000000000000000000000002000000000gtksourceview-5.12.1/.gitignore*~ *.swp build _build .flatpak-builder 07070100000002000041ED0000000000000000000000026659080600000000000000000000000000000000000000000000002000000000gtksourceview-5.12.1/.gitlab-ci07070100000003000081A400000000000000000000000166590806000005A1000000000000000000000000000000000000002400000000gtksourceview-5.12.1/.gitlab-ci.ymlinclude: 'https://gitlab.gnome.org/GNOME/citemplates/raw/master/flatpak/flatpak_ci_initiative.yml' stages: - test - docs - deploy variables: FEDORA_IMAGE: "registry.gitlab.gnome.org/gnome/gtksourceview/fedora:latest" flatpak: variables: BUNDLE: "gtksourceview-test-widget-dev.flatpak" MANIFEST_PATH: "org.gnome.GtkSourceView.TestWidget.json" FLATPAK_MODULE: "gtksourceview" RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo" APP_ID: "org.gnome.GtkSourceView.TestWidget" extends: ".flatpak" reference: image: $FEDORA_IMAGE stage: docs needs: [] variables: MESON_FLAGS: "-Dc_std=c11 -Dcpp_std=c++11 -Dwerror=true --buildtype=release -Dglib:tests=false -Dgtk:media-gstreamer=disabled -Dgtk:broadway-backend=false -Dgtk:demos=false -Dgtk:build-examples=false -Dgtk:build-tests=false -Dgtk:werror=false -Dlibsass:werror=false -Dsassc:werror=false -Dlibadwaita:werror=false" script: - mkdir -p pfx/ - meson ${MESON_FLAGS} --prefix=${PWD}/pfx -Ddocumentation=true _build - ninja -C _build install - mkdir -p _reference/ - mv pfx/share/doc/gtksourceview5/ _reference/ artifacts: paths: - _reference pages: stage: deploy needs: ['reference'] script: - mv _reference public/ artifacts: paths: - public only: - master 07070100000004000081A40000000000000000000000016659080600000102000000000000000000000000000000000000002A00000000gtksourceview-5.12.1/.gitlab-ci/README.md# CI containers In order to build the image used by the CI run the following: ```shell ./run-docker.sh build --base=fedora ``` And to publish the image ```shell ./run-docker.sh push --base=fedora ``` For more details, you can run `./run-docker.sh help` 07070100000005000081A400000000000000000000000166590806000002BB000000000000000000000000000000000000003200000000gtksourceview-5.12.1/.gitlab-ci/fedora.DockerfileFROM fedora:37 RUN dnf update -y RUN dnf -y install --setopt=install_weak_deps=False \ clang \ git \ gi-docgen \ meson \ ninja-build \ pkgconf \ vala \ expat-devel \ gtk4-devel \ gobject-introspection-devel \ libjpeg-turbo-devel \ libpng-devel \ libvala-devel \ sysprof-devel \ vulkan-headers \ wayland-devel \ wayland-protocols-devel RUN dnf clean all # Enable sudo for wheel users RUN sed -i -e 's/# %wheel/%wheel/' -e '0,/%wheel/{s/%wheel/# %wheel/}' /etc/sudoers ARG HOST_USER_ID=5555 ENV HOST_USER_ID ${HOST_USER_ID} RUN useradd -u $HOST_USER_ID -G wheel -ms /bin/bash user USER user WORKDIR /home/user ENV LANG C.UTF-8 07070100000006000081ED0000000000000000000000016659080600000CDD000000000000000000000000000000000000002E00000000gtksourceview-5.12.1/.gitlab-ci/run-docker.sh#!/bin/bash # Copied from https://gitlab.gnome.org/GNOME/gtk/-/blob/main/.gitlab-ci/run-docker.sh read_arg() { # $1 = arg name # $2 = arg value # $3 = arg parameter local rematch='^[^=]*=(.*)$' if [[ $2 =~ $rematch ]]; then read "$1" <<<"${BASH_REMATCH[1]}" else read "$1" <<<"$3" # There is no way to shift our callers args, so # return 1 to indicate they should do it instead. return 1 fi } set -e build=0 run=0 push=0 list=0 print_help=0 no_login=0 while (($# > 0)); do case "${1%%=*}" in build) build=1 ;; run) run=1 ;; push) push=1 ;; list) list=1 ;; help) print_help=1 ;; --base | -b) read_arg base "$@" || shift ;; --version | -v) read_arg base_version "$@" || shift ;; --no-login) no_login=1 ;; *) echo -e "\e[1;31mERROR\e[0m: Unknown option '$1'" exit 1 ;; esac shift done if [ $print_help == 1 ]; then echo "$0 - Build and run Docker images" echo "" echo "Usage: $0 [options] [basename]" echo "" echo "Available commands" echo "" echo " build --base= - Build Docker image .Dockerfile" echo " run --base= - Run Docker image " echo " push --base= - Push Docker image to the registry" echo " list - List available images" echo " help - This help message" echo "" exit 0 fi cd "$(dirname "$0")" if [ $list == 1 ]; then echo "Available Docker images:" for f in *.Dockerfile; do filename=$(basename -- "$f") basename="${filename%.*}" echo -e " \e[1;39m$basename\e[0m" done exit 0 fi # All commands after this require --base to be set if [ -z $base ]; then echo "Usage: $0 " exit 1 fi if [ ! -f "$base.Dockerfile" ]; then echo -e "\e[1;31mERROR\e[0m: Dockerfile for '$base' not found" exit 1 fi if [ -z $base_version ]; then base_version="latest" elif [ $base_version != "latest" ]; then base_version="v$base_version" fi if [ ! -x "$(command -v docker)" ] || [ docker --help |& grep -q podman ]; then # Docker is actually implemented by podman, and its OCI output # is incompatible with some of the dockerd instances on GitLab # CI runners. echo "Using: Podman" format="--format docker" CMD="podman" else echo "Using: Docker" format="" CMD="sudo docker" fi REGISTRY="registry.gitlab.gnome.org" TAG="${REGISTRY}/gnome/gtksourceview/${base}:${base_version}" if [ $build == 1 ]; then echo -e "\e[1;32mBUILDING\e[0m: ${base} as ${TAG}" ${CMD} build \ ${format} \ --build-arg HOST_USER_ID="$UID" \ --tag "${TAG}" \ --file "${base}.Dockerfile" . exit $? fi if [ $push == 1 ]; then echo -e "\e[1;32mPUSHING\e[0m: ${base} as ${TAG}" if [ $no_login == 0 ]; then ${CMD} login ${REGISTRY} fi ${CMD} push ${TAG} exit $? fi if [ $run == 1 ]; then echo -e "\e[1;32mRUNNING\e[0m: ${base} as ${TAG}" ${CMD} run \ --rm \ --volume "$(pwd)/..:/home/user/app" \ --workdir "/home/user/app" \ --tty \ --interactive "${TAG}" \ bash exit $? fi 07070100000007000081A400000000000000000000000166590806000001CE000000000000000000000000000000000000001D00000000gtksourceview-5.12.1/AUTHORSActive authors: Christian Hergert Sébastien Wilmet Old contributors: Chris Phelps Gustavo Giráldez Ignacio Casal Quinteiro Jeroen Zwartepoorte Jesse van den Kieboom Mikael Hermansson Paolo Borelli Paolo Maggi Yevgen Muntyan 07070100000008000081A40000000000000000000000016659080600006744000000000000000000000000000000000000001D00000000gtksourceview-5.12.1/COPYING 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! 07070100000009000081A40000000000000000000000016659080600001D94000000000000000000000000000000000000001D00000000gtksourceview-5.12.1/HACKINGHow to contribute to GtkSourceView ================================== GtkSourceView is hosted on the GNOME GitLab instance, you can fork the repository and then do a merge request: https://gitlab.gnome.org/GNOME/gtksourceview Read the following wiki page to know the conventions for the commit messages: https://wiki.gnome.org/Git/CommitMessages If you have write access to the Git repository, please don't push your commits directly unless you have been given the green light to commit freely to GtkSourceView. When in doubt assume you haven't ;-). People who can commit freely to GtkSourceView: * GtkSourceView maintainers (or sub-maintainers in their area of expertise), with of course discussion before doing important changes. * GNOME "build sheriffs", to fix build issues. C code conventions ------------------ You may encounter old code that doesn't follow all the following code conventions, but for new code it is better to follow them, for consistency. - Avoid trailing whitespace. - Indent the C code with tabulations with a width of eight characters. However, alignment after matching the current scope should be done with spaces. - All blocks should be surrounded by curly braces, even one-line blocks. The curly braces must be placed on their own lines. Like this: if (foo) { call_foo (); } else { call_bar (); } Rationale: it spaces out the code, to have a better readability. And when modifying a block of code, if it switches between one and two lines, we don't need to add/remove the curly braces all the time. - Follow the C99 standard but without "//"-style comments and with all declarations at the top, before statements. Some restrictions apply but relatively should match GTK and GLib. - The files should not have modelines included. A .editorconfig file is provided for configuration indentation and many editors have support for using them. - Do not be cheap about blank lines, spacing the code vertically helps readability. However never use two consecutive blank lines, there is really no need. - As a general rule of thumb, follow the same coding style as the surrounding code. See also: - https://developer.gnome.org/programming-guidelines/stable/ - https://wiki.gnome.org/Projects/GTK%2B/BestPractices - https://wiki.gnome.org/Projects/GLib/CompilerRequirements Programming best-practices -------------------------- GtkSourceView is a sizeable piece of software, developed over the years by different people and GNOME technologies. Some parts of the code may be a little old. So when editing the code, we should try to make it better, not worse. Here are some general advices: - Simplicity: the simpler code the better. Any trick that seem smart when you write it is going to bite you later when reading the code. In fact, the code is read far more often than it is written: for fixing a bug, adding a feature, or simply see how it is implemented. Making the code harder to read is a net loss. - Avoid code duplication, make an effort to refactor common code into utility functions. - Write self-documented code when possible: instead of writing comments, it is often possible to make the code self-documented by choosing good names for the variables, functions and types. Please avoid lots of one-letter variable names, it makes the code hard to understand. Don't be afraid to write long variable names. Also, a variable should be used for only one purpose. A good function name is one that explain clearly all what its code really does. There shouldn't be hidden features. If you can not find easily a good function name, you should probably split the function in smaller pieces. A function should do only one thing, but do it well. - About comments: Do not write comments to state the obvious, for example avoid: i = 0; /* assign 0 to i */ Of course, write GTK-Doc comments to document the public API, especially the class descriptions. The class descriptions gives a nice overview when someone discovers the library. For a private class, it is useful to write a comment at the top describing in a few lines what the class does. Document well the data structures: the invariants (what is or should be always true about certain data fields); for a list, what is the element type; for a hash table, what are the key and value types; etc. It is more important to document the data structures than the functions, because when understanding well the data structures, the functions implementation should be for the most part obvious. When it isn't obvious, it is more important to explain *why* something is implemented in this way, not the *how*. You can deduce the *how* from the code, but not the *why*. If a non-trivial feature was previously implemented in a different way, it's useful to write a comment to describe in a few lines the previous implementation(s), and why it has been changed (for example to fix some problems). It permits to avoid repeating history, otherwise a new developer might wonder why a certain feature is implemented in "this complicated way" and not in "that simpler obvious way". For such things, a comment in the code has more chances to be read than an old commit message (especially if the code has been copied from one repository to another). - Contribute below on the stack. Fix a problem at the right place, instead of writing hacks or heuristics to work around a bug or a lack of feature in an underlying library. - Public API should have precondition guards using g_return_if_fail() or g_return_val_if_fail(). Optionally, you may do this before returning values from the function to help catch bugs earlier in the development cycle. Private functions (such as those with static) should use g_assert() to validate invariants. These are used in debug builds but can be compiled out of production/release builds. - When transfering ownership of an object or struct, use g_steal_pointer() to make it clear when reading that ownership was transfered. Language Specifications ----------------------- The GtkSourceView project attempts to "upstream" a number of language specifications as part of GtkSourceView itself. This is primarily to ease the burden of community maintainership but also so that we can be relatively certain that changes to the internals of GtkSourceView do not break your favoriate language. But there are many (175 at the time of writing) and we cannot possibly maintain correctness for every language specification. Maintenance of these are at a best-effort and rely on community members to contribute improvements to languages they care about. Additionally, there is a user burden to the files being bundled with GtkSourceView as GtkSourceView must load them when loading the GtkSourceLanguageManager. That has memory and CPU overhead parsing them when the process loads. We must balance inclusion of every language against the overhead placed on users. To strike this balance, we ask that there are multiple contributors available for the language specification you would like to add and that you'll be around to help maintain it. Adding a language specification and then not returning to maintain it does nobody any favors and simply transfers burden onto overworked maintainers. It must also match the license of GtkSourceView itself (LGPLv2.1+). 0707010000000A000081A40000000000000000000000016659080600001595000000000000000000000000000000000000001A00000000gtksourceview-5.12.1/NEWSNews in 5.12.1, 2024-05-30 -------------------------- This is a stable release update * Update documentation links * Specify recoloring hex colors for Adwaita-dark * Improve grid drawing positions slightly * Use a weak ref for GtkSourceView backpointer in GtkSourceGutterRenderers which fixes a potential leak of GtkSourceView * Fix a fontconfig check for Windows * Fix section name for elixir News in 5.12.0, 2024-03-15 -------------------------- This is a stable release for GNOME 46 * Do not trigger default indenter for Return when a selection is active * Render overview slider below text rather than above w/ RGBA * Premix certain colors in overview when drawing to avoid RGBA blends on GPU * Avoid round-trip to main loop when map slider needs allocation * Avoid an extra GSK transform for each line number in the gutter * Translation updates News in 5.11.2, 2024-03-03 -------------------------- This is a development release for GNOME 46.rc * Ignore empty globs in language specification for mimetypes * Ensure that partially visible highlight lines are drawn * Fix line style properties after unsetting style scheme * latex.lang: Improvements to command parsing News in 5.11.1, 2024-02-12 -------------------------- This is a development release for GNOME 46.beta * A new -Dbuild-testsuite configuration option * Some build options were renamed to follow more closely what GTK itself uses now. -Dgtk_doc became -Ddocumentation -Dinstall_tests became -Dinstall-tests * Improvements to BuilderBlocks font to support Windows - This supports FontConfig on Windows currently - With Pango 1.52.0 it will support win32 font loader * Fix a rendering issue where the gutter would not highlight the same as the current-line-highlight. * An optimization for line number drawing which avoids using PangoLayout when drawing. * Ensure the gutter is redrawn when focus leaves text view * Improved support for input methods when X11 and ibus are used by making it behave closer to Wayland input methods. * Ignore the current-line highlight when out of view which improves the damage area calculation in GTK. * A new Wren language spec * Updates for the sh language-spec * Translation updates News in 5.11.0, 2024-01-08 -------------------------- This is a development release for GNOME 46.alpha * Fixes for searching invisible text * Sort ordering fixes for guessing languages which fixes loading Python 3 before Python 2. * Various Vim emulation fixes * Updated Languages: Elixir, Python3, C * Updated Style Schemes: Oblivion * Cursor fixes for source gutter * Fix undo after alt+up/down * Fix newline detection fallback on some systems News in 5.10.0, 2023-09-16 -------------------------- 5.10.0 corresponds with GNOME 45.0 and is meant for end-user distribution. Changes since 5.9.0: * Translation updates News in 5.9.0, 2023-09-01 ------------------------- 5.9.0 is a release candidate for GNOME 45. * Documentation improvements * Various introspection improvements * New languages: Blueprint * Updated languages: C, C++, Rust, Python3, Shell, Java, OCaml * Updated style schemes: cobalt * GtkSourceBuffer gained a "loading" property which is toggled when GtkSourceFileLoader is loading the buffer from storage. * More defensiveness improvements in GtkSourceBufferOutputStream. * A leak in Vim emulation has been fixed. * Vim emulation now supports visual replace. * Scheduling of batched workers has been improved with gtk_source_scheduler_add(). * Snippets are now initialized from GtkSourceView.constructed() to allow for applications to hook buffer creation. * Translation updates News in 5.8.0, 2023-03-17 ------------------------- 5.8.0 is meant for end-users which corresponds to the GNOME 44.0 release. * vim: avoid large numbers of small deletions in filter command * adwaita.xml: add missing def:deletion style * todotxt.lang: add support for todo.txt format News in 5.7.2, 2023-03-03 ------------------------- 5.7.2 is a development release of GtkSourceView intended for application developers and unstable distributions alike. * Documentation improvements * Updated languages: java.lang, html.lang * Fix Y offset calculation when GtkTextView:top-margin is set * Completion now requeries providers upon manual activation * CSS updates for completion popover to reduce allocation jitter * Assistants will more aggresively update positioning. It is heavily suggested that you have GNOME/gtk!5564 in whatever GTK you are linking against to reduce the potential for frames rendered with missing allocations/text. News in 5.7.1, 2023-02-15 ------------------------- 5.7.1 is a development release of GtkSourceView intended for application developers and unstable distributions alike. * Updated languages: c.lang, docker.lang, nix.lang * Add missing version functions to GIR generation. * Moving through snippets now retires the completion popover. * Documentation updates. News in 5.7.0, 2023-01-23 ------------------------- 5.7.0 is a development release of GtkSourceView intended for application developers and unstable distributions alike. Changes since 5.6.2 * gutterrenderermarks: avoid potential infinite loop with marks * build: drop unnecessary vapigen check * cpp.lang: add constinit keyword * java.lang: fix escaped characters * lean.lang: strings may contain line breaks * nix.lang: Add Nix syntax highlighting * reasonml.lang: add ReasonML language 0707010000000B000081A400000000000000000000000166590806000004FD000000000000000000000000000000000000001F00000000gtksourceview-5.12.1/README.mdGtkSourceView ============= GtkSourceView is a GNOME library that extends GtkTextView, the standard GTK widget for multiline text editing. GtkSourceView adds support for syntax highlighting, undo/redo, file loading and saving, search and replace, a completion system, printing, displaying line numbers, and other features typical of a source code editor. The GtkSourceView library is free software and is released under the terms of the GNU Lesser General Public License, see the 'COPYING' file for more details. The official web site is https://wiki.gnome.org/Projects/GtkSourceView. Documentation ------------- Nightly documentation can be [found here](https://gnome.pages.gitlab.gnome.org/gtksourceview/gtksourceview5/). Dependencies ------------ * GLib >= 2.70 * GTK >= 4.6 * libxml2 >= 2.6 * fribidi >= 0.19.7 * libpcre2-8 >= 10.21 Installation ------------ Simple install procedure from a tarball: ``` $ mkdir build $ meson build $ cd build [ Become root if necessary ] $ ninja install ``` See the file 'INSTALL' for more detailed information. To build the latest version of GtkSourceView plus its dependencies from Git, [Jhbuild](https://wiki.gnome.org/Projects/Jhbuild) is recommended. How to contribute ----------------- See the 'HACKING' file. 0707010000000C000081A40000000000000000000000016659080600000E84000000000000000000000000000000000000002500000000gtksourceview-5.12.1/README.win32.mdBuilding GtkSourceView for Windows using Visual Studio ========= Meson is now the supported method of building GtkSourceView with Visual Studio, where the process of doing so is described in the following section: Prior to building, you will need the following tools and programs: - Python 3.6.x or later - Meson 0.59.0 (or later) at this time of writing - A version of Visual Studio 2015 to 2022. - Ninja build tool (unless `--backend=vs` is specified in the Meson configure command line for Visual Studio 2015, 2017, 2019, 2022) - The pkg-config utility (or a compatible one), either in your PATH or found by setting PKG_CONFIG to point to such a tool. - CMake, either installed manually or as part of Visual Studio 2017 or later (optional, but recommended, to help locating dependencies) You will also need the following prerequesites installed: - GLib 2.70.0 (or later) at this time of writing - GTK 4.5.0 (or later) at this time of writing - LibXML2 2.6 (or later) at this time of writing - GObject-Introspection 1.70.0 (or later) at this time of writing [optional] For the depedent packages, you will need to ensure that their pkg-config (.pc) files could be found by the `pkg-config` utility, either directly or by passing in `--pkg-config-path=...` to point to the path(s) where the .pc files could be found in the Meson command line. For libxml2, if .pc files are not available, ensure that its headers and import library can be found by ensuring your `%INCLUDE%` and `%LIB%` environment variables include these repective paths, and ensure that `cmake` is installed and in your `%PATH%`. For the introspection to complete successfully, you will also need to ensure that the DLLs can be found in the bindir entries of your .pc files or in your `%PATH%`. ARM64 Windows builds are also supported provided that a valid cross compilation config file is set up and passed into the Meson command line using `--cross-file=...`. #### Building GtkSourceView Please do the following: - Open a Visual Studio command prompt. - In the command prompt, create an empty build directory that is on the same drive as the GtkSourceView sources. - Run the command in the command prompt in your build directory: ``` meson --buildtype=... --prefix=... -Dvapi=false ``` Where: - `--buildtype...` can be `debug`, `release`, `plain` or `debugoptmized` (default), please see the Meson documentation for more details - `--prefix=...` is where the built files are copied to upon 'install' - `Dvapi=false` is for the build to not check for Vala, which is normally not present on Visual Studio builds. You may want to specify the following as well: - `--backend=vs`: Generate Visual Studio projects for building on Visual Studio 2015 2017, 2019 and 2022. This will remove the need for Ninja. Please note that the generated project files are only valid on the machine they are generated, and such builds may not be that well-supported by Meson. Any issues that are found using this method but are not found using Ninja should be reported to the Meson project. If the previous command completed successfully, carry out the build by running `ninja` or by opening and building the projects with the generated Visual Studio projects, if using `--backend=vs`. If desired, run the test using `ninja test` or building the `test` project in the Visual Studio projects. Install the build results using `ninja install` or building the `install` project in the Visual Studio projects. 0707010000000D000041ED0000000000000000000000026659080600000000000000000000000000000000000000000000001A00000000gtksourceview-5.12.1/data0707010000000E000081A4000000000000000000000001665908060000550F000000000000000000000000000000000000003100000000gtksourceview-5.12.1/data/GtkSourceView-logo.pngPNG  IHDRTdٟȬsRGBbKGD pHYs ttIME  IDATxwU)-dR'4 IH EDDQqŲX~vvuuEEL Hd&^oO9?3-d"z^psss>[>W0v>ޥyY ؀Sr|VOq7~%`"P A`I Ā:$P :v"&ZPS'c 5 i qqy5l&0_Nf.و0ALpn1s.zgām|WuO/yrg7y 'Nu|δ22qWPsw !>[4r ,&B9|*lNZ몇{4@8J0ڿF˸&n4ce-/\o{\e\e\ UO^VxշP6maꇤ1q-@F)s _)&DhC6C[]5'&w׷s;,Oqy#0LonQJO\oF' |Q7}o^:}7<s쥧GՒWW\(1[;{?AE(s%vV^ɛ_(q P_u?/\+ű4T'Ԁ ,Oe6 ŗ3lS`*ܞq=T4bɆFh:V;PD,-Ө{|P'hv,. c((ʿ6iAE7t[vўcͯ=}oPc j_2F+6p(lUj^$iu\ abC~8#]BEyb B9O\@okd sz},.| n3ڸlӍwRlc۽?%ָ׼>\IK|A]8Yy Z4X TXHƛPCn H':Lߡ_ @(0mD92ɸW>9M/ 7\o+d_gÿ!Lр]tчΔt*:&Agb.{~mYPe\ހ*ijZ &7, IuH7Dy?󗭺 ${~FuVFs gDK{NKR.'=F٤GȈc3 @b X U%[iOx@>^ kd|皚*U8 &7`]TѝbTznnTۣc:訢?y['f2X7un eʱtAkCPk GM&་eڷ`l*G:fP>}PjE^DCB~q1zlsNV}&N3X /rmp,dbq,y!jG2r< |aG!P I1O[)48 | oN?DU Wn@@Jpp-X tw֧e7 :# }h&h cy)q [v#U0D7릘 VcuH\[d!p2:jwȳM!ɻ$|}>oPLӨ~#yIt4_w&]yBXi}!A ȟsx&a#-If4(3aI<33$M66;zA{nҝHB_dՇ"~KLls}܉:W,K:j s|mႷyo P:$' NaUдJ?(Ei C{e 1qI L?vΉ9_@7$)=0~JTTsxO PcB9Kq-T;~Ken_}=b[ُ2vOLv68>זhF!;IEL珩;Y;y/r 릯G*E&~dOꝤci 0\'[;N;8j)ʄ&(vZN8BS]6F?q;4ﳐ.I/2|vnku p7:\buMgv%g^gaôC6)fO_KbotJı%BC>!Kd"~Aå㌃cuwg8[n3A\ ըcq?t|Զ彳PlO5,oq,<c,^ |uxwls?QC8;c}e0_f'5y&Nn;D{pz[?P\t i\[< *ЀV:plj`6LKa`opϺzoLc ݵ嗰[.CeCPm=-= ǻ~UAP]O{@!/0EG_6⺅׆XBURT} +p(-c:j.|y:ܞ{Ϋ5$uafT_R3uzT6r١EG2[&,0/`t,9| 1CKmOv۲fG.S=fMD\+>X)ɶJT/Q5?Ïd7)eLG,3b j@rksQ/[< ֠zƎfu9}EdBgsAtQr_^S8ah(ǟYFBʜ9\\(o /5 h\wxN+Qܱp;|"] [#w_e tP5c 1b? q8jJf,t-zLJKίiu/2|E0#A3!R ŕ&;wy k\sm$9挋nR@M{_&i؟x>:y$5sM3ĆG۹ڏEL^ۀyyh0+_ ʬNly3N gy>+-tI~dhtP.ֵhYrJJklJCP㴃i;@?|!ƛxs97Z??Ob% 6Pl ACӕ6|tsc3gKITw:;]-QIjfڏ񶝴$v;Oc,duSJDjީ|&?\whyҩsY¹P_OPBɴ1Ҭ" +;#->R(׽e#|J+-6>[6Q6Ϥ|"µ?=+0-. quXSQ-jvts]Y\PL^ B*#(rP4dW:K43jf@Lu&[T@JJ2,!ku-Vm'f$_chuic dsGgP]ϺQ8"1`7 $]F184;\씤C&&[\Mw?DDTf8t!pc@{{/@(4{@stדûpRudY|;E(`smeq݁Bu^ Il[rOz|Lb,ֿ~61TA&ҝɆپۜQQ2s&(J͛n-%E+V" Xֆ"px˒BK\ʎKw3z@,r9V2 }u3 Ib0p{_C Suy-׻QP=ҎSoC]&﷾\: @{uijwXˌ,YR{mF>/W 9lL]VyW"J&BS/̾@Ы +ȎE)Z[/H/< Xf IDAT׽oYs WxeM5}0ԡ;ojC<G|VA?*X o|.xiBT͵C|y%R}}2Z@5"JufPs6x^r36%;~jm!0`?c(%#Rn Hw&C&&O"?~1qA9{O $#]jwdgۨH[˚ ѵ~|7uN;s^qtT[ qdjx{[=U-*t3 ='P4,I3I;b[ECV&Usi@UCT"w=o2}mFNλH⣿(O=9EYXrU_. h%%s J瘔Uꢙ:FJ4V$BQ!6x7 0]G{f3-buNgꏴ[*Q>O*/%~`0`m6tֻ$4=ajTS'r (zXP zSm5AUC#6u{IotzS@иdZ?E=Жjs9"aB@}0KهqHl߳`4}TyE0}> '+N쭨?LniT0L (RŜ0njyc V:5F\A~4sV_д_Zwb+Ŋٜwc/m? λmVB,Xw ҕ~1CїxPާ 2+.:%PWS2dStWk:̺$t~^OS_ )SV}LNc1sWMʊ* ܤee( Q_ qiwR*N3`L^e*_J*ӌ>E}i-˦#ث1d].W&FΚY EtWAN+MU!2ujg/%JIE2J;BxRJ|0HHp.>_(Ƭ̩ꤔ 5#3_6@VNa/~'sV]McgSf62{ ']V Lc_z$;ZB/<|/xK@ â :YI~ kfp~fҥ,^hvVZ}葴Q)C}ޠ:}o j ~tUmr]5,DK~˂sHޥ[< $S >h;4=_7T$n帍7_-#x*_X&UŤ '"]=t,2r+72fotk#>`C^N\ϡ0`&Q҉& )46VV}?N_v5AEOD~ߟעjvS\X0ՠhkMklSW+ Qb3%=/h9np ?U8,+MtJ]IyE3{Td}c`șyQߵZm~q=S5%BgUt}_pR.$e"Jt7dx&D{_ 3'nuȦ82t48#H1?v6=w@8w%*򽐡-ȞDd6>7bǾ.hF^*ןr*_WVOZO<9 L_QIVԁ(DPaE~,Pm.z|!`N'TBեc{>g]*%l|+!i9 a,,i@E|C 3/Y頊V.Ռ&Z/mYÜMH|amWf{z/ZcHjT;'wۨHcwkR{x'^~dgkW1g>`Ӯ~sak 9DK7`N4%:OT^SUY;-?s͹4G}#L?QZ_]Q>{)K72j)Pc$kz5vP{x t*}/ K':Zf>;4j&rK lGp4LGߗhHZl:|''d/d;@iM]^>if@Z[$mlyc3#%z(M"oc`A10hK7$?99ttGJPH! z`TuHToƱkیQ;޴kVUU!2ކHC?{yL>3_HYLSsrVN$o'a[=D6#NxC`CL3R2ub(ZxRJ3u{?r(ZAqlw]MEŵ顛AiVwq]Zi%}1!I4g"7<ePQ4G%tpX*PETqE5(L}܉@$wO IE:1jF@)~;ϸiAp:W*PITc#dg%gT{~+T̢` r&-(#4ǶȦdSqɎfZNZWMk ҉,Ȥ]\<3x<'\k׵U_VW=s߳<ߕCW'={GiI3ǿ?X9 ߨfwD!uB`z%=6) eVv*Awj nDT/g: ц\S`ѤEfw^7Βhv(NEl[󳒒+%P Z49)I>[z Hu^${V~M;VˋDj&j~׮nPJY}Xxrmٯ}8z'gP9+rmqv案aO+28 -oe}l~zǩ1|Tٝ,*T&W70gIUgK-qZ;G%*x7E|\)Y2<,exȦn"h@H7ZoҿT[zf_It&O~P,he32'8P 5)!>,}3E3*qu4Fuu`Zob{)tqL!xEN+?0c3Tˮ˵G^18 U #Ӝr{ RަH{W{"{-N7wbhq[PA"*] >w&Rl*k*[ YqoyHާ0\;Q͘߇uF1FQLʝp/M|[tTbvF3M3(Οuq=mY^v~TYPF@WF^<33U y m'mM||!/xKil+ǟN = i#TآO̺V_u=&$Hxkk(rkhT摮BȲB}S)I:"6?>(ox;{RL+fjh:3kQj=ZfP_yqej#K,pF癲Fp[|Eg߈I'#۝"X鈾 ;'` AQs*7_h8`,h.+Mjd[#.kӢ|#S 4 Ocf\Wn jqsw}V B_mҝҩmd{pA@Ae$[|>\= %U&lnK㿳 mP_rpz~-z4(34BN̙Syqq&^\2Uh.PijwTM5%ݕa!u~Ͱ|Z*@(_Hwnwa'~rҲO`@[id̐4JsYjڌP'ť>wU kD[6D}wXh0c3(t 5EчjҹFŚ 8PQT||YğE3. `w ҙ k5HզuO7FG$cP_2YWڞVz_ԣ d"͌d5λ6Ho &k\zntq>]jl5N'Z.kҗ/ hWN3VR^뼢Wǒٛt2(*wJXx.#/ޙXTθO|ގj8ƌoWV$"&.299ó?do~xz8M&5bh̻*@L|Ӛ3u7U cg$Qc3u/TAy'W]>&簩S~8w>xolc7 +lt.H4++B~k2<u{to -vaF)g^^ %X)eMqz\Ay&󴪋>5_B v[<G7g;# 0^,tmVN]f hyoXӜ,{ZٻnJ\ڪm:N; F\"IgKǻڼk<ý>s50}BFUJI9回Mv:ҌN/W;\Q҇AA~9o=Lem} `1uZNm;iqNx`]ݵvҙY8MU 83/ZS]9a~Mh]~S(Q [gSdP\;εPE2" ܛ xPML(ss x0[A%MgJCjE]a%!T] 0#:,.DT;rw:Sv&&%s'҇Թ0. Vw*ֹZTHTۏNR5S "(S޽όL9/#]g[x׏2;J*V2jDJ-#"/P_ iW~ lT]p49dڝY21j!iOՙGqAciv6Z^P PP/SS_yGhmϱ>M!]hzhh`^!55Ϣ+BgQ^,LqD#\n\GؠiOjg$Xj^pnM);:lGQQѐJ 0ߌL[lA,pUR hWzf:ܽ޸Ȼn~'+ru/S\i3QUvJM8rrkYNˇOÞ"_Xr5r4̐Xj'#qmUF/fbCZvFBUhzx-1 0ݤ, ᜉ )IUZܔc}<1 :ꡜ :}p%fZǶry[Cކ"M :F,{ #oZw x+ZYDP_Xb;-IwJ-.zGfr/^#(XG,M0\r&h+UJ)wW]f)$O2Ģ̀|{ݝ(PQ^Ygka3$^jQbE.8ë.X_3m='bzl׹we=VVПdnjos@Uئ{ Y Jsɹ{|m{b MmPj x}~7 >fP:4. =mj Q,?iͯ]'%3#]sXeɳ݄PN9$TY$l/Em3Sz VU{ar U5w=1 n'Eɕʌp}GSܒsPC[7נmP:=Ept9ux_vjPxYd os;4>ZDS啃v3!Z#&@GHDJD7WNz\Ng4v^j9B쀀CP@ly/D3}$9p =A߃?LBQA*9vޖ MBa%CUk׈`YsP=c5kZ)E  (y'&g#a2 ؊Lơ*p8642kZn l6wC<{Y;~#c&ed(&U3s}p\W&Nx34=o5q\?T~ͼ!ľsx!=1X^og# /<T*ks7*  e_b<ÈdoצxI:jLn}v{K/Nw`E.K(60H|s!A/T}GM^eB%. V&-' > ދ3orz M"1[#jst-hKA˝?Ń77>ߩ<3 98 b|^Ĭ]9Py5Aw~FW(+Ԏo }g*x g?M^=4/!ñLqd,.C(_|j7ȡ3@yxp$3}|38>t@P/ FD2 m,$4=Cz@1 *-I~xڃVAo'Φ!6_욢4!TI0P|$-/MyT'>"5_G_*a#Pa|l7C8D?*ieG, `jԐ`m1+y;pb\a')=-lfA$i.Gr¬`0.EIzdQal6eˁZG]PQ4> ?[E}Q'&}ʹ|v L "| ͯ^ K5,k`MX|LS?J hW(꿆 -ls10;z? o:eJgj!L)#kPFQU8tuBxJV $x^@HTFni(&hV[ g%~bJγ(}"CvPVcQX-hK?BҐ/F;COplb3NY|"֕*G I`^ nDq`v4Uhܵ9ݞQ0thfScC\1jŎZvË9=tL/YE*uGm>C[ Hx Gtֱ3>;Qeane9bg1ATkǎ& 9%*&ՠ~*ŭ֖DZY8UKu QE(!o_ o?ndقxtʝl`L-Ogyf /C+m1Ϻ>bIn** {"E3B`a 08а2__z`\ЗfB@u iW X gh9S ??PDYAI[.|}Pݒya:h C=6,=`} v+BZ89uff|z=\1*E+qx;T^u@|m[C\tȭOLl (O@'12 X o[#)2262-sL+k`0tʷ,[TPscc!ɮMy-fͭӹ>Dײ5M;`nH4M&O8S&@KP$mv\g9<f&P#ux *4?S#1y .%3ZqI{#`$!-t|母_/Du՝c3b0>PBXuQA4ثAғ}䗛ԓ qH q&{l .C]kE8: \do$r*8Ӂ i7){: K&e BQԛ)>i^ǶiĬ;`ϩjLcW:vkGP/}0=&{WT?0p"Uf h\]HCL\\as~H;`|8! :6@#)m|.Ienjc*Q~}rBޓjJ!͢! 7/,f-{2o*`یL4swkizt'ܼPꟍ3B0kp/Gm0u9a^:.JzN l@?#܉xhoOrk|FOrO=N#Ly1 ,L(Q$96*: 􌃌\vY+{>c𭏌P Рvr%1 [jZșd۾hdOXz.5&U ;?!WCBL ir~}# [H³NB ={@g$[EA{a|d%Bp}#߅B轁[kUY*`h-\VgLofsM~d ( uޮ|uCkFХhtM-" ڊnG j79os uu:9J0oϴhfHXfVÍoz2+>\{o<tfs8jdɮj?<ѰhQ/onT@fu}}4ZT9z8F}J"V[D={X c` kCJTʙ z x>Qƀ ?_J^,">S <̶fr|ǰGjܾ.__AQ|`[,m[ urppA/S"Rbܮ' VSM s2AIENDB`0707010000000F000041ED0000000000000000000000026659080600000000000000000000000000000000000000000000002000000000gtksourceview-5.12.1/data/fonts07070100000010000081A400000000000000000000000166590806000001D0000000000000000000000000000000000000003200000000gtksourceview-5.12.1/data/fonts/BuilderBlocks.ttfcmap!XLglyf*DHl0headw6hhea $hmtx loca$ ` maxp name!2w _<w @ !`az{ 1!!1!!  BuilderBlocksRegular07070100000011000081A400000000000000000000000166590806000010A4000000000000000000000000000000000000003200000000gtksourceview-5.12.1/data/fonts/BuilderBlocks.ttx BuilderBlocks Regular 0000 0001 0003 000A 0000000C 000D 0000 00000040 00000000 00000004 00000001 00000020 00000002 00000021 00000060 00000001 00000061 0000007A 00000003 0000007B 0010FFFE 00000001 07070100000012000041ED0000000000000000000000026659080600000000000000000000000000000000000000000000002000000000gtksourceview-5.12.1/data/icons07070100000013000081A400000000000000000000000166590806000000D6000000000000000000000000000000000000002800000000gtksourceview-5.12.1/data/icons/COPYINGThe icons here are licensed under the CC-by-SA 3. You can find the full text of the license at: http://creativecommons.org/licenses/by-sa/3.0/ See git commit's for authors and contributors to individual icons. 07070100000014000041ED0000000000000000000000026659080600000000000000000000000000000000000000000000002800000000gtksourceview-5.12.1/data/icons/hicolor07070100000015000041ED0000000000000000000000026659080600000000000000000000000000000000000000000000003100000000gtksourceview-5.12.1/data/icons/hicolor/scalable07070100000016000041ED0000000000000000000000026659080600000000000000000000000000000000000000000000003900000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions07070100000017000081A40000000000000000000000016659080600000791000000000000000000000000000000000000005900000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/completion-snippet-symbolic.svg 07070100000018000081A400000000000000000000000166590806000003D7000000000000000000000000000000000000005600000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/completion-word-symbolic.svg 07070100000019000081A400000000000000000000000166590806000005B3000000000000000000000000000000000000005100000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-class-symbolic.svg 0707010000001A000081A400000000000000000000000166590806000000FC000000000000000000000000000000000000005200000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-define-symbolic.svg 0707010000001B000081A400000000000000000000000166590806000002AF000000000000000000000000000000000000005000000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-enum-symbolic.svg 0707010000001C000081A400000000000000000000000166590806000003F6000000000000000000000000000000000000005600000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-enum-value-symbolic.svg 0707010000001D000081A40000000000000000000000016659080600000791000000000000000000000000000000000000005400000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-function-symbolic.svg 0707010000001E000081A400000000000000000000000166590806000000D3000000000000000000000000000000000000005300000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-include-symbolic.svg 0707010000001F000081A40000000000000000000000016659080600000725000000000000000000000000000000000000005200000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-method-symbolic.svg 07070100000020000081A4000000000000000000000001665908060000077B000000000000000000000000000000000000005500000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-namespace-symbolic.svg 07070100000021000081A400000000000000000000000166590806000003D8000000000000000000000000000000000000005800000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-struct-field-symbolic.svg 07070100000022000081A4000000000000000000000001665908060000028F000000000000000000000000000000000000005200000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-struct-symbolic.svg 07070100000023000081A40000000000000000000000016659080600000116000000000000000000000000000000000000005300000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-typedef-symbolic.svg 07070100000024000081A40000000000000000000000016659080600000395000000000000000000000000000000000000005100000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-union-symbolic.svg 07070100000025000081A40000000000000000000000016659080600000364000000000000000000000000000000000000005400000000gtksourceview-5.12.1/data/icons/hicolor/scalable/actions/lang-variable-symbolic.svg 07070100000026000081A40000000000000000000000016659080600000068000000000000000000000000000000000000002C00000000gtksourceview-5.12.1/data/icons/meson.buildiconsdir = join_paths(get_option('datadir'), 'icons') install_subdir('hicolor', install_dir: iconsdir) 07070100000027000041ED0000000000000000000000026659080600000000000000000000000000000000000000000000002900000000gtksourceview-5.12.1/data/language-specs07070100000028000081A40000000000000000000000016659080600013624000000000000000000000000000000000000003000000000gtksourceview-5.12.1/data/language-specs/R.lang text/x-R *.R;*.Rout;*.r;*.Rhistory;*.Rt;*.Rout.save;*.Rout.fail # . Name: / .*$/ Name: / / / / Name: / / / / Name: / / / / Name: \b \b Name: / / 0707010000007F000081A4000000000000000000000001665908060000011D000000000000000000000000000000000000003C00000000gtksourceview-5.12.1/data/language-specs/language-specs.its 07070100000080000081A400000000000000000000000166590806000032AE000000000000000000000000000000000000003C00000000gtksourceview-5.12.1/data/language-specs/language-specs.potmsgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2021-09-18 12:17+0200\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=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #. (itstool) path: abnf.lang/language@_section #. (itstool) path: actionscript.lang/language@_section #. (itstool) path: ada.lang/language@_section #. (itstool) path: ansforth94.lang/language@_section #. (itstool) path: asp.lang/language@_section #. (itstool) path: automake.lang/language@_section #. (itstool) path: bennugd.lang/language@_section #. (itstool) path: bluespec.lang/language@_section #. (itstool) path: boo.lang/language@_section #. (itstool) path: cg.lang/language@_section #. (itstool) path: chdr.lang/language@_section #. (itstool) path: c.lang/language@_section #. (itstool) path: cobol.lang/language@_section #. (itstool) path: commonlisp.lang/language@_section #. (itstool) path: cpphdr.lang/language@_section #. (itstool) path: cpp.lang/language@_section #. (itstool) path: csharp.lang/language@_section #. (itstool) path: cuda.lang/language@_section #. (itstool) path: dart.lang/language@_section #. (itstool) path: d.lang/language@_section #. (itstool) path: docker.lang/language@_section #. (itstool) path: eiffel.lang/language@_section #. (itstool) path: erlang.lang/language@_section #. (itstool) path: forth.lang/language@_section #. (itstool) path: fortran.lang/language@_section #. (itstool) path: fsharp.lang/language@_section #. (itstool) path: genie.lang/language@_section #. (itstool) path: glsl.lang/language@_section #. (itstool) path: go.lang/language@_section #. (itstool) path: gradle.lang/language@_section #. (itstool) path: groovy.lang/language@_section #. (itstool) path: haskell.lang/language@_section #. (itstool) path: haskell-literate.lang/language@_section #. (itstool) path: haxe.lang/language@_section #. (itstool) path: idl.lang/language@_section #. (itstool) path: java.lang/language@_section #. (itstool) path: j.lang/language@_section #. (itstool) path: kotlin.lang/language@_section #. (itstool) path: lean.lang/language@_section #. (itstool) path: lex.lang/language@_section #. (itstool) path: llvm.lang/language@_section #. (itstool) path: logtalk.lang/language@_section #. (itstool) path: makefile.lang/language@_section #. (itstool) path: meson.lang/language@_section #. (itstool) path: nemerle.lang/language@_section #. (itstool) path: netrexx.lang/language@_section #. (itstool) path: objc.lang/language@_section #. (itstool) path: objj.lang/language@_section #. (itstool) path: ocaml.lang/language@_section #. (itstool) path: ooc.lang/language@_section #. (itstool) path: opal.lang/language@_section #. (itstool) path: opencl.lang/language@_section #. (itstool) path: pascal.lang/language@_section #. (itstool) path: pig.lang/language@_section #. (itstool) path: powershell.lang/language@_section #. (itstool) path: prolog.lang/language@_section #. (itstool) path: rust.lang/language@_section #. (itstool) path: scala.lang/language@_section #. (itstool) path: scheme.lang/language@_section #. (itstool) path: sml.lang/language@_section #. (itstool) path: solidity.lang/language@_section #. (itstool) path: sparql.lang/language@_section #. (itstool) path: sql.lang/language@_section #. (itstool) path: swift.lang/language@_section #. (itstool) path: systemverilog.lang/language@_section #. (itstool) path: thrift.lang/language@_section #. (itstool) path: vala.lang/language@_section #. (itstool) path: vbnet.lang/language@_section #. (itstool) path: verilog.lang/language@_section #. (itstool) path: vhdl.lang/language@_section #: abnf.lang:30 #: actionscript.lang:24 #: ada.lang:25 #: ansforth94.lang:24 #: asp.lang:23 #: automake.lang:23 #: bennugd.lang:22 #: bluespec.lang:21 #: boo.lang:23 #: cg.lang:23 #: chdr.lang:24 #: c.lang:24 #: cobol.lang:26 #: commonlisp.lang:24 #: cpphdr.lang:24 #: cpp.lang:25 #: csharp.lang:26 #: cuda.lang:22 #: dart.lang:22 #: d.lang:29 #: docker.lang:21 #: eiffel.lang:23 #: erlang.lang:23 #: forth.lang:23 #: fortran.lang:24 #: fsharp.lang:24 #: genie.lang:23 #: glsl.lang:30 #: go.lang:24 #: gradle.lang:21 #: groovy.lang:24 #: haskell.lang:24 #: haskell-literate.lang:23 #: haxe.lang:34 #: idl.lang:23 #: java.lang:24 #: j.lang:23 #: kotlin.lang:24 #: lean.lang:23 #: lex.lang:24 #: llvm.lang:22 #: logtalk.lang:23 #: makefile.lang:22 #: meson.lang:23 #: nemerle.lang:23 #: netrexx.lang:23 #: objc.lang:23 #: objj.lang:26 #: ocaml.lang:26 #: ooc.lang:23 #: opal.lang:23 #: opencl.lang:23 #: pascal.lang:24 #: pig.lang:26 #: powershell.lang:23 #: prolog.lang:23 #: rust.lang:35 #: scala.lang:24 #: scheme.lang:23 #: sml.lang:23 #: solidity.lang:28 #: sparql.lang:23 #: sql.lang:23 #: swift.lang:24 #: systemverilog.lang:21 #: thrift.lang:20 #: vala.lang:27 #: vbnet.lang:23 #: verilog.lang:23 #: vhdl.lang:23 msgid "Source" msgstr "" #. (itstool) path: asciidoc.lang/language@_section #. (itstool) path: bibtex.lang/language@_section #. (itstool) path: docbook.lang/language@_section #. (itstool) path: dtd.lang/language@_section #. (itstool) path: dtl.lang/language@_section #. (itstool) path: erb-html.lang/language@_section #. (itstool) path: erb-js.lang/language@_section #. (itstool) path: erb.lang/language@_section #. (itstool) path: gtk-doc.lang/language@_section #. (itstool) path: haddock.lang/language@_section #. (itstool) path: html.lang/language@_section #. (itstool) path: jade.lang/language@_section #. (itstool) path: jsdoc.lang/language@_section #. (itstool) path: latex.lang/language@_section #. (itstool) path: mallard.lang/language@_section #. (itstool) path: markdown.lang/language@_section #. (itstool) path: mediawiki.lang/language@_section #. (itstool) path: mxml.lang/language@_section #. (itstool) path: rst.lang/language@_section #. (itstool) path: sweave.lang/language@_section #. (itstool) path: t2t.lang/language@_section #. (itstool) path: tera.lang/language@_section #. (itstool) path: texinfo.lang/language@_section #. (itstool) path: xml.lang/language@_section #. (itstool) path: xslt.lang/language@_section #: asciidoc.lang:22 #: bibtex.lang:23 #: docbook.lang:23 #: dtd.lang:23 #: dtl.lang:25 #: erb-html.lang:20 #: erb-js.lang:20 #: erb.lang:20 #: gtk-doc.lang:24 #: haddock.lang:23 #: html.lang:25 #: jade.lang:24 #: jsdoc.lang:23 #: latex.lang:24 #: mallard.lang:22 #: markdown.lang:25 #: mediawiki.lang:22 #: mxml.lang:23 #: rst.lang:22 #: sweave.lang:24 #: t2t.lang:23 #: tera.lang:23 #: texinfo.lang:24 #: xml.lang:26 #: xslt.lang:23 msgid "Markup" msgstr "" #. (itstool) path: awk.lang/language@_section #. (itstool) path: dosbatch.lang/language@_section #. (itstool) path: fish.lang/language@_section #. (itstool) path: gdscript.lang/language@_section #. (itstool) path: javascript-expressions.lang/language@_section #. (itstool) path: javascript-functions-classes.lang/language@_section #. (itstool) path: javascript.lang/language@_section #. (itstool) path: javascript-literals.lang/language@_section #. (itstool) path: javascript-modules.lang/language@_section #. (itstool) path: javascript-statements.lang/language@_section #. (itstool) path: javascript-values.lang/language@_section #. (itstool) path: jsx.lang/language@_section #. (itstool) path: lua.lang/language@_section #. (itstool) path: m4.lang/language@_section #. (itstool) path: perl.lang/language@_section #. (itstool) path: php.lang/language@_section #. (itstool) path: python3.lang/language@_section #. (itstool) path: python.lang/language@_section #. (itstool) path: ruby.lang/language@_section #. (itstool) path: sh.lang/language@_section #. (itstool) path: tcl.lang/language@_section #. (itstool) path: typescript-js-expressions.lang/language@_section #. (itstool) path: typescript-js-functions-classes.lang/language@_section #. (itstool) path: typescript-js-literals.lang/language@_section #. (itstool) path: typescript-js-modules.lang/language@_section #. (itstool) path: typescript-js-statements.lang/language@_section #. (itstool) path: typescript-jsx.lang/language@_section #. (itstool) path: typescript.lang/language@_section #. (itstool) path: typescript-type-expressions.lang/language@_section #. (itstool) path: typescript-type-generics.lang/language@_section #. (itstool) path: typescript-type-literals.lang/language@_section #: awk.lang:23 #: dosbatch.lang:23 #: fish.lang:19 #: gdscript.lang:22 #: javascript-expressions.lang:27 #: javascript-functions-classes.lang:27 #: javascript.lang:27 #: javascript-literals.lang:27 #: javascript-modules.lang:27 #: javascript-statements.lang:27 #: javascript-values.lang:27 #: jsx.lang:23 #: lua.lang:23 #: m4.lang:23 #: perl.lang:25 #: php.lang:28 #: python3.lang:23 #: python.lang:27 #: ruby.lang:27 #: sh.lang:24 #: tcl.lang:23 #: typescript-js-expressions.lang:23 #: typescript-js-functions-classes.lang:23 #: typescript-js-literals.lang:23 #: typescript-js-modules.lang:23 #: typescript-js-statements.lang:23 #: typescript-jsx.lang:23 #: typescript.lang:23 #: typescript-type-expressions.lang:23 #: typescript-type-generics.lang:23 #: typescript-type-literals.lang:23 msgid "Script" msgstr "" #. (itstool) path: cg.lang/language@_name #: cg.lang:23 msgid "CG Shader Language" msgstr "" #. (itstool) path: changelog.lang/language@_section #. (itstool) path: cmake.lang/language@_section #. (itstool) path: css.lang/language@_section #. (itstool) path: csv.lang/language@_section #. (itstool) path: desktop.lang/language@_section #. (itstool) path: diff.lang/language@_section #. (itstool) path: dot.lang/language@_section #. (itstool) path: dpatch.lang/language@_section #. (itstool) path: ftl.lang/language@_section #. (itstool) path: gdb-log.lang/language@_section #. (itstool) path: gtkrc.lang/language@_section #. (itstool) path: ini.lang/language@_section #. (itstool) path: json.lang/language@_section #. (itstool) path: less.lang/language@_section #. (itstool) path: libtool.lang/language@_section #. (itstool) path: logcat.lang/language@_section #. (itstool) path: nsis.lang/language@_section #. (itstool) path: ocl.lang/language@_section #. (itstool) path: pkgconfig.lang/language@_section #. (itstool) path: po.lang/language@_section #. (itstool) path: protobuf.lang/language@_section #. (itstool) path: puppet.lang/language@_section #. (itstool) path: rpmspec.lang/language@_section #. (itstool) path: scss.lang/language@_section #. (itstool) path: terraform.lang/language@_section #. (itstool) path: toml.lang/language@_section #. (itstool) path: yacc.lang/language@_section #. (itstool) path: yaml.lang/language@_section #. (itstool) path: yara.lang/language@_section #: changelog.lang:24 #: cmake.lang:23 #: css.lang:26 #: csv.lang:23 #: desktop.lang:24 #: diff.lang:23 #: dot.lang:23 #: dpatch.lang:23 #: ftl.lang:23 #: gdb-log.lang:18 #: gtkrc.lang:24 #: ini.lang:22 #: json.lang:29 #: less.lang:23 #: libtool.lang:23 #: logcat.lang:23 #: nsis.lang:23 #: ocl.lang:32 #: pkgconfig.lang:23 #: po.lang:23 #: protobuf.lang:21 #: puppet.lang:23 #: rpmspec.lang:24 #: scss.lang:23 #: terraform.lang:23 #: toml.lang:23 #: yacc.lang:23 #: yaml.lang:21 #: yara.lang:26 msgid "Other" msgstr "" #. (itstool) path: chdr.lang/language@_name #: chdr.lang:24 msgid "C/ObjC Header" msgstr "" #. (itstool) path: cpphdr.lang/language@_name #: cpphdr.lang:24 msgid "C++ Header" msgstr "" #. (itstool) path: dosbatch.lang/language@_name #: dosbatch.lang:23 msgid "DOS Batch" msgstr "" #. (itstool) path: dtl.lang/language@_name #: dtl.lang:25 msgid "Django Template" msgstr "" #. (itstool) path: fcl.lang/language@_section #. (itstool) path: gap.lang/language@_section #. (itstool) path: idl-exelis.lang/language@_section #. (itstool) path: imagej.lang/language@_section #. (itstool) path: julia.lang/language@_section #. (itstool) path: matlab.lang/language@_section #. (itstool) path: maxima.lang/language@_section #. (itstool) path: modelica.lang/language@_section #. (itstool) path: octave.lang/language@_section #. (itstool) path: R.lang/language@_section #. (itstool) path: scilab.lang/language@_section #. (itstool) path: star.lang/language@_section #: fcl.lang:23 #: gap.lang:23 #: idl-exelis.lang:20 #: imagej.lang:23 #: julia.lang:23 #: matlab.lang:25 #: maxima.lang:24 #: modelica.lang:25 #: octave.lang:25 #: R.lang:25 #: scilab.lang:23 #: star.lang:28 msgid "Scientific" msgstr "" #. (itstool) path: gdb-log.lang/language@_name #: gdb-log.lang:18 msgid "GDB Log" msgstr "" #. (itstool) path: glsl.lang/language@_name #: glsl.lang:30 msgid "OpenGL Shading Language" msgstr "" #. (itstool) path: llvm.lang/language@_name #: llvm.lang:22 msgid "LLVM IR" msgstr "" #. (itstool) path: po.lang/language@_name #: po.lang:23 msgid "gettext translation" msgstr "" #. (itstool) path: rpmspec.lang/language@_name #: rpmspec.lang:24 msgid "RPM spec" msgstr "" #. (itstool) path: tera.lang/language@_name #: tera.lang:23 msgid "Tera Template" msgstr "" 07070100000081000081A40000000000000000000000016659080600000928000000000000000000000000000000000000003600000000gtksourceview-5.12.1/data/language-specs/language.dtd 07070100000082000081A40000000000000000000000016659080600002B1D000000000000000000000000000000000000003600000000gtksourceview-5.12.1/data/language-specs/language.rng true false TRUE FALSE 0 1 1\.0 07070100000083000081A400000000000000000000000166590806000033B8000000000000000000000000000000000000003700000000gtksourceview-5.12.1/data/language-specs/language2.rng [a-zA-Z0-9_\-]+ ([a-zA-Z0-9_\-]+:)?[a-zA-Z0-9_\-]+ ([a-zA-Z0-9_\-]+:)?[a-zA-Z0-9_\-]+(:\*)? true false [a-zA-Z0-9_\-]+ start end 07070100000084000081A40000000000000000000000016659080600006386000000000000000000000000000000000000003400000000gtksourceview-5.12.1/data/language-specs/latex.lang text/x-tex *.tex;*.ltx;*.sty;*.cls;*.dtx;*.ins;*.bbl % .tex comment true 6 horizontal 6 6 false _Details true end start 0707010000013C000081A40000000000000000000000016659080600000F09000000000000000000000000000000000000004800000000gtksourceview-5.12.1/gtksourceview/gtksourcecompletionlistbox-private.h/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "gtksourcetypes-private.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_COMPLETION_LIST_BOX (gtk_source_completion_list_box_get_type()) G_DECLARE_FINAL_TYPE (GtkSourceCompletionListBox, gtk_source_completion_list_box, GTK_SOURCE, COMPLETION_LIST_BOX, GtkWidget) GtkWidget *_gtk_source_completion_list_box_new (void); GtkSourceCompletionContext *_gtk_source_completion_list_box_get_context (GtkSourceCompletionListBox *self); void _gtk_source_completion_list_box_set_context (GtkSourceCompletionListBox *self, GtkSourceCompletionContext *context); guint _gtk_source_completion_list_box_get_n_rows (GtkSourceCompletionListBox *self); void _gtk_source_completion_list_box_set_n_rows (GtkSourceCompletionListBox *self, guint n_rows); GtkSourceCompletionProposal *_gtk_source_completion_list_box_get_proposal (GtkSourceCompletionListBox *self); gboolean _gtk_source_completion_list_box_get_selected (GtkSourceCompletionListBox *self, GtkSourceCompletionProvider **provider, GtkSourceCompletionProposal **proposal); void _gtk_source_completion_list_box_move_cursor (GtkSourceCompletionListBox *self, GtkMovementStep step, gint direction); void _gtk_source_completion_list_box_set_font_desc (GtkSourceCompletionListBox *self, const PangoFontDescription *font_desc); GtkSourceCompletionListBoxRow *_gtk_source_completion_list_box_get_first (GtkSourceCompletionListBox *self); gboolean _gtk_source_completion_list_box_key_activates (GtkSourceCompletionListBox *self, const GdkKeyEvent *key); int _gtk_source_completion_list_box_get_alternate (GtkSourceCompletionListBox *self); guint _gtk_source_completion_list_box_get_n_alternates (GtkSourceCompletionListBox *self); void _gtk_source_completion_list_box_set_show_icons (GtkSourceCompletionListBox *self, gboolean show_icons); G_END_DECLS 0707010000013D000081A40000000000000000000000016659080600009E85000000000000000000000000000000000000004000000000gtksourceview-5.12.1/gtksourceview/gtksourcecompletionlistbox.c/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourcecompletion-private.h" #include "gtksourcecompletioncontext-private.h" #include "gtksourcecompletionlistbox-private.h" #include "gtksourcecompletionlistboxrow-private.h" #include "gtksourcecompletionproposal.h" #include "gtksourcecompletionprovider.h" #include "gtksourceview-private.h" struct _GtkSourceCompletionListBox { GtkWidget parent_instance; /* The box containing the rows. */ GtkBox *box; /* Font styling for rows */ PangoAttrList *font_attrs; /* The completion context that is being displayed. */ GtkSourceCompletionContext *context; /* The handler for GtkSourceCompletionContext::items-chaged which should * be disconnected when no longer needed. */ gulong items_changed_handler; /* The number of rows we expect to have visible to the user. */ guint n_rows; /* The currently selected index within the result set. Signed to * ensure our math in various places allows going negative to catch * lower edge. */ int selected; /* The alternate proposal for the current selection. This references * something from GtkSourceCompletionProvider.list_alternates(). */ GPtrArray *alternates; int alternate; /* This is set whenever we make a change that requires updating the * row content. We delay the update until the next frame callback so * that we only update once right before we draw the frame. This helps * reduce duplicate work when reacting to ::items-changed in the model. */ guint queued_update; /* These size groups are used to keep each portion of the proposal row * aligned with each other. Since we only have a fixed number of visible * rows, the overhead here is negligible by introducing the size cycle. */ GtkSizeGroup *before_size_group; GtkSizeGroup *typed_text_size_group; GtkSizeGroup *after_size_group; /* The adjustments for scrolling the GtkScrollable. */ GtkAdjustment *hadjustment; GtkAdjustment *vadjustment; /* Gesture to handle button press/touch events. */ GtkGesture *click_gesture; /* If icons are visible */ guint show_icons : 1; }; typedef struct { GtkSourceCompletionListBox *self; GtkSourceCompletionContext *context; guint n_items; guint position; int selected; } UpdateState; enum { PROP_0, PROP_ALTERNATE, PROP_CONTEXT, PROP_PROPOSAL, PROP_N_ROWS, PROP_HADJUSTMENT, PROP_HSCROLL_POLICY, PROP_N_ALTERNATES, PROP_VADJUSTMENT, PROP_VSCROLL_POLICY, N_PROPS }; enum { REPOSITION, N_SIGNALS }; static void gtk_source_completion_list_box_queue_update (GtkSourceCompletionListBox *self); static gboolean gtk_source_completion_list_box_update_cb (GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data); static void gtk_source_completion_list_box_do_update (GtkSourceCompletionListBox *self, gboolean update_selection); G_DEFINE_TYPE_WITH_CODE (GtkSourceCompletionListBox, gtk_source_completion_list_box, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) static GParamSpec *properties [N_PROPS]; static guint signals [N_SIGNALS]; static void gtk_source_completion_list_box_set_selected (GtkSourceCompletionListBox *self, int selected) { GtkSourceCompletionProposal *proposal = NULL; GtkSourceCompletionProvider *provider = NULL; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); if (selected == -1 && self->context != NULL) { GtkSourceCompletion *completion; gboolean select_on_show; completion = gtk_source_completion_context_get_completion (self->context); select_on_show = _gtk_source_completion_get_select_on_show (completion); if (select_on_show) { selected = 0; } } self->selected = selected; self->alternate = -1; g_clear_pointer (&self->alternates, g_ptr_array_unref); if (_gtk_source_completion_list_box_get_selected (self, &provider, &proposal)) { self->alternates = gtk_source_completion_provider_list_alternates (provider, self->context, proposal); if (self->alternates != NULL) { g_ptr_array_set_free_func (self->alternates, g_object_unref); } } gtk_source_completion_list_box_queue_update (self); g_clear_object (&proposal); g_clear_object (&provider); } static gboolean move_next_alternate (GtkWidget *widget, GVariant *param, gpointer user_data) { GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); if (self->alternates == NULL || self->alternates->len == 0) { return FALSE; } if ((guint)(self->alternate + 1) < self->alternates->len) { self->alternate++; } else { self->alternate = -1; } gtk_source_completion_list_box_do_update (self, FALSE); return TRUE; } static void move_next_alternate_action (GtkWidget *widget, const char *action_name, GVariant *param) { g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (widget)); move_next_alternate (widget, param, NULL); } static gboolean move_previous_alternate (GtkWidget *widget, GVariant *param, gpointer user_data) { GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); if (self->alternates == NULL || self->alternates->len == 0) { return FALSE; } if (self->alternate < 0) { self->alternate = self->alternates->len - 1; } else { self->alternate--; } gtk_source_completion_list_box_do_update (self, FALSE); return TRUE; } static void move_previous_alternate_action (GtkWidget *widget, const char *action_name, GVariant *param) { g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (widget)); move_previous_alternate (widget, param, NULL); } static guint gtk_source_completion_list_box_get_offset (GtkSourceCompletionListBox *self) { g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); return gtk_adjustment_get_value (self->vadjustment); } static void gtk_source_completion_list_box_set_offset (GtkSourceCompletionListBox *self, guint offset) { double value = offset; double page_size; double upper; double lower; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); lower = gtk_adjustment_get_lower (self->vadjustment); upper = gtk_adjustment_get_upper (self->vadjustment); page_size = gtk_adjustment_get_page_size (self->vadjustment); if (value > (upper - page_size)) { value = upper - page_size; } if (value < lower) { value = lower; } gtk_adjustment_set_value (self->vadjustment, value); } static void gtk_source_completion_list_box_value_changed (GtkSourceCompletionListBox *self, GtkAdjustment *vadj) { g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_assert (GTK_IS_ADJUSTMENT (vadj)); gtk_source_completion_list_box_queue_update (self); } static void gtk_source_completion_list_box_set_hadjustment (GtkSourceCompletionListBox *self, GtkAdjustment *hadjustment) { g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_assert (!hadjustment || GTK_IS_ADJUSTMENT (hadjustment)); if (g_set_object (&self->hadjustment, hadjustment)) { gtk_source_completion_list_box_queue_update (self); } } static void gtk_source_completion_list_box_set_vadjustment (GtkSourceCompletionListBox *self, GtkAdjustment *vadjustment) { g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_assert (!vadjustment || GTK_IS_ADJUSTMENT (vadjustment)); if (self->vadjustment == vadjustment) return; if (self->vadjustment) { g_signal_handlers_disconnect_by_func (self->vadjustment, G_CALLBACK (gtk_source_completion_list_box_value_changed), self); g_clear_object (&self->vadjustment); } if (vadjustment) { self->vadjustment = g_object_ref (vadjustment); gtk_adjustment_set_lower (self->vadjustment, 0); gtk_adjustment_set_upper (self->vadjustment, 0); gtk_adjustment_set_value (self->vadjustment, 0); gtk_adjustment_set_step_increment (self->vadjustment, 1); gtk_adjustment_set_page_size (self->vadjustment, self->n_rows); gtk_adjustment_set_page_increment (self->vadjustment, self->n_rows); g_signal_connect_object (self->vadjustment, "value-changed", G_CALLBACK (gtk_source_completion_list_box_value_changed), self, G_CONNECT_SWAPPED); } gtk_source_completion_list_box_queue_update (self); } static guint get_row_at_y (GtkSourceCompletionListBox *self, double y) { GtkAllocation alloc; guint offset; guint n_items; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_assert (G_IS_LIST_MODEL (self->context)); gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); offset = gtk_source_completion_list_box_get_offset (self); n_items = g_list_model_get_n_items (G_LIST_MODEL (self->context)); n_items = MAX (1, MIN (self->n_rows, n_items)); return offset + (y / (alloc.height / n_items)); } static void click_gesture_pressed (GtkGestureClick *gesture, guint n_press, double x, double y, GtkSourceCompletionListBox *self) { int selected; g_assert (GTK_IS_GESTURE_CLICK (gesture)); g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); if (self->context == NULL) { return; } selected = get_row_at_y (self, y); if (selected != self->selected) { gtk_source_completion_list_box_set_selected (self, selected); } else { GtkSourceCompletionProvider *provider = NULL; GtkSourceCompletionProposal *proposal = NULL; if (self->selected >= 0 && self->selected < (int)g_list_model_get_n_items (G_LIST_MODEL (self->context)) && _gtk_source_completion_context_get_item_full (self->context, self->selected, &provider, &proposal)) { _gtk_source_completion_activate (gtk_source_completion_context_get_completion (self->context), self->context, provider, proposal); g_clear_object (&provider); g_clear_object (&proposal); } } } static gboolean move_binding_cb (GtkWidget *widget, GVariant *param, gpointer user_data) { GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget; int direction = 0; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_variant_get (param, "(i)", &direction); if (ABS (direction) == 1) { _gtk_source_completion_list_box_move_cursor (self, GTK_MOVEMENT_DISPLAY_LINES, direction); } else { _gtk_source_completion_list_box_move_cursor (self, GTK_MOVEMENT_PAGES, direction > 0 ? 1 : -1); } return TRUE; } static gboolean activate_nth_cb (GtkWidget *widget, GVariant *param, gpointer user_data) { GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget; GtkSourceCompletionProvider *provider = NULL; GtkSourceCompletionProposal *proposal = NULL; int nth = 0; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); if (self->context == NULL) { return FALSE; } g_variant_get (param, "(i)", &nth); if (nth == 0 && self->selected >= 0) { nth = self->selected; } else { nth--; } if (nth < 0 || (guint)nth >= g_list_model_get_n_items (G_LIST_MODEL (self->context))) { return FALSE; } if (!_gtk_source_completion_context_get_item_full (self->context, nth, &provider, &proposal)) { return FALSE; } g_assert (GTK_SOURCE_IS_COMPLETION_PROVIDER (provider)); g_assert (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal)); _gtk_source_completion_activate (gtk_source_completion_context_get_completion (self->context), self->context, provider, proposal); g_clear_object (&proposal); g_clear_object (&provider); return TRUE; } static gboolean activate_nth_tab_cb (GtkWidget *widget, GVariant *param, gpointer user_data) { GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget; GtkSourceView *view; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); if (self->context == NULL) { return FALSE; } /* If Tab was pressed by we have a snippet active, that takes precidence * and we should ignore this completion request. */ view = gtk_source_completion_context_get_view (self->context); if (!view || _gtk_source_view_has_snippet (view)) { return FALSE; } return activate_nth_cb (widget, param, user_data); } static gboolean _gtk_source_completion_list_box_key_pressed_cb (GtkSourceCompletionListBox *self, guint keyval, guint keycode, GdkModifierType state, GtkEventControllerKey *key) { GtkSourceCompletionProvider *provider = NULL; GtkSourceCompletionProposal *proposal = NULL; gboolean ret = FALSE; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_assert (GTK_IS_EVENT_CONTROLLER_KEY (key)); if (self->context == NULL) { return FALSE; } if (_gtk_source_completion_list_box_get_selected (self, &provider, &proposal)) { if (gtk_source_completion_provider_key_activates (provider, self->context, proposal, keyval, state)) { _gtk_source_completion_activate (gtk_source_completion_context_get_completion (self->context), self->context, provider, proposal); ret = TRUE; } } g_clear_object (&proposal); g_clear_object (&provider); return ret; } static void gtk_source_completion_list_box_constructed (GObject *object) { GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)object; G_OBJECT_CLASS (gtk_source_completion_list_box_parent_class)->constructed (object); if (self->hadjustment == NULL) { self->hadjustment = gtk_adjustment_new (0, 0, 0, 0, 0, 0); } if (self->vadjustment == NULL) { self->vadjustment = gtk_adjustment_new (0, 0, 0, 0, 0, 0); } gtk_adjustment_set_lower (self->hadjustment, 0); gtk_adjustment_set_upper (self->hadjustment, 0); gtk_adjustment_set_value (self->hadjustment, 0); gtk_source_completion_list_box_queue_update (self); } static void gtk_source_completion_list_box_dispose (GObject *object) { GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)object; if (self->box != NULL) { gtk_widget_unparent (GTK_WIDGET (self->box)); self->box = NULL; } g_clear_object (&self->before_size_group); g_clear_object (&self->typed_text_size_group); g_clear_object (&self->after_size_group); g_clear_object (&self->hadjustment); g_clear_object (&self->vadjustment); g_clear_pointer (&self->font_attrs, pango_attr_list_unref); G_OBJECT_CLASS (gtk_source_completion_list_box_parent_class)->dispose (object); } static void gtk_source_completion_list_box_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceCompletionListBox *self = GTK_SOURCE_COMPLETION_LIST_BOX (object); switch (prop_id) { case PROP_N_ALTERNATES: g_value_set_int (value, self->alternates ? self->alternates->len : 0); break; case PROP_ALTERNATE: g_value_set_int (value, self->alternate); break; case PROP_CONTEXT: g_value_set_object (value, _gtk_source_completion_list_box_get_context (self)); break; case PROP_PROPOSAL: g_value_take_object (value, _gtk_source_completion_list_box_get_proposal (self)); break; case PROP_N_ROWS: g_value_set_uint (value, _gtk_source_completion_list_box_get_n_rows (self)); break; case PROP_HADJUSTMENT: g_value_set_object (value, self->hadjustment); break; case PROP_VADJUSTMENT: g_value_set_object (value, self->vadjustment); break; case PROP_HSCROLL_POLICY: g_value_set_enum (value, GTK_SCROLL_NATURAL); break; case PROP_VSCROLL_POLICY: g_value_set_enum (value, GTK_SCROLL_NATURAL); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_source_completion_list_box_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceCompletionListBox *self = GTK_SOURCE_COMPLETION_LIST_BOX (object); switch (prop_id) { case PROP_CONTEXT: _gtk_source_completion_list_box_set_context (self, g_value_get_object (value)); break; case PROP_N_ROWS: _gtk_source_completion_list_box_set_n_rows (self, g_value_get_uint (value)); break; case PROP_HADJUSTMENT: gtk_source_completion_list_box_set_hadjustment (self, g_value_get_object (value)); break; case PROP_VADJUSTMENT: gtk_source_completion_list_box_set_vadjustment (self, g_value_get_object (value)); break; case PROP_HSCROLL_POLICY: /* Do nothing */ break; case PROP_VSCROLL_POLICY: /* Do nothing */ break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_source_completion_list_box_class_init (GtkSourceCompletionListBoxClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = gtk_source_completion_list_box_constructed; object_class->dispose = gtk_source_completion_list_box_dispose; object_class->get_property = gtk_source_completion_list_box_get_property; object_class->set_property = gtk_source_completion_list_box_set_property; properties [PROP_ALTERNATE] = g_param_spec_int ("alternate", "Alternate", "The alternate to choose", -1, G_MAXINT, -1, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); properties [PROP_N_ALTERNATES] = g_param_spec_int ("n-alternates", "N Alternates", "The number of alternates", 0, G_MAXINT, 0, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); properties [PROP_CONTEXT] = g_param_spec_object ("context", "Context", "The context being displayed", GTK_SOURCE_TYPE_COMPLETION_CONTEXT, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); properties [PROP_HADJUSTMENT] = g_param_spec_object ("hadjustment", NULL, NULL, GTK_TYPE_ADJUSTMENT, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); properties [PROP_HSCROLL_POLICY] = g_param_spec_enum ("hscroll-policy", NULL, NULL, GTK_TYPE_SCROLLABLE_POLICY, GTK_SCROLL_NATURAL, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); properties [PROP_VADJUSTMENT] = g_param_spec_object ("vadjustment", NULL, NULL, GTK_TYPE_ADJUSTMENT, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); properties [PROP_VSCROLL_POLICY] = g_param_spec_enum ("vscroll-policy", NULL, NULL, GTK_TYPE_SCROLLABLE_POLICY, GTK_SCROLL_NATURAL, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); properties [PROP_PROPOSAL] = g_param_spec_object ("proposal", "Proposal", "The proposal that is currently selected", GTK_SOURCE_TYPE_COMPLETION_PROPOSAL, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); properties [PROP_N_ROWS] = g_param_spec_uint ("n-rows", "N Rows", "The number of visible rows", 1, 32, 5, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); signals [REPOSITION] = g_signal_new_class_handler ("reposition", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, NULL, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_signal_set_va_marshaller (signals [REPOSITION], G_TYPE_FROM_CLASS (klass), g_cclosure_marshal_VOID__VOIDv); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_css_name (widget_class, "list"); gtk_widget_class_install_action (widget_class, "proposal.move-next-alternate", NULL, move_next_alternate_action); gtk_widget_class_install_action (widget_class, "proposal.move-previous-alternate", NULL, move_previous_alternate_action); gtk_widget_class_add_binding (widget_class, GDK_KEY_Down, 0, move_binding_cb, "(i)", 1); gtk_widget_class_add_binding (widget_class, GDK_KEY_Up, 0, move_binding_cb, "(i)", -1); gtk_widget_class_add_binding (widget_class, GDK_KEY_Page_Up, 0, move_binding_cb, "(i)", -2); gtk_widget_class_add_binding (widget_class, GDK_KEY_Page_Down, 0, move_binding_cb, "(i)", 2); gtk_widget_class_add_binding (widget_class, GDK_KEY_1, GDK_ALT_MASK, activate_nth_cb, "(i)", 1); gtk_widget_class_add_binding (widget_class, GDK_KEY_2, GDK_ALT_MASK, activate_nth_cb, "(i)", 2); gtk_widget_class_add_binding (widget_class, GDK_KEY_3, GDK_ALT_MASK, activate_nth_cb, "(i)", 3); gtk_widget_class_add_binding (widget_class, GDK_KEY_4, GDK_ALT_MASK, activate_nth_cb, "(i)", 4); gtk_widget_class_add_binding (widget_class, GDK_KEY_5, GDK_ALT_MASK, activate_nth_cb, "(i)", 5); gtk_widget_class_add_binding (widget_class, GDK_KEY_6, GDK_ALT_MASK, activate_nth_cb, "(i)", 6); gtk_widget_class_add_binding (widget_class, GDK_KEY_7, GDK_ALT_MASK, activate_nth_cb, "(i)", 7); gtk_widget_class_add_binding (widget_class, GDK_KEY_8, GDK_ALT_MASK, activate_nth_cb, "(i)", 8); gtk_widget_class_add_binding (widget_class, GDK_KEY_9, GDK_ALT_MASK, activate_nth_cb, "(i)", 9); gtk_widget_class_add_binding (widget_class, GDK_KEY_Return, 0, activate_nth_cb, "(i)", 0); gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Enter, 0, activate_nth_cb, "(i)", 0); gtk_widget_class_add_binding (widget_class, GDK_KEY_Tab, 0, activate_nth_tab_cb, "(i)", 0); gtk_widget_class_add_binding (widget_class, GDK_KEY_Right, 0, move_next_alternate, NULL); gtk_widget_class_add_binding (widget_class, GDK_KEY_Left, 0, move_previous_alternate, NULL); gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "assistant.hide", NULL); g_type_ensure (GTK_SOURCE_TYPE_COMPLETION_LIST_BOX_ROW); } static void gtk_source_completion_list_box_init (GtkSourceCompletionListBox *self) { GtkEventController *key; key = gtk_event_controller_key_new (); g_signal_connect_swapped (key, "key-pressed", G_CALLBACK (_gtk_source_completion_list_box_key_pressed_cb), self); gtk_widget_add_controller (GTK_WIDGET (self), key); self->box = g_object_new (GTK_TYPE_BOX, "orientation", GTK_ORIENTATION_VERTICAL, "visible", TRUE, NULL); gtk_widget_set_parent (GTK_WIDGET (self->box), GTK_WIDGET (self)); self->selected = -1; self->alternate = -1; self->before_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); self->typed_text_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); self->after_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); self->click_gesture = gtk_gesture_click_new (); gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->click_gesture), GTK_PHASE_BUBBLE); gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (self->click_gesture), FALSE); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->click_gesture), GDK_BUTTON_PRIMARY); g_signal_connect_object (self->click_gesture, "pressed", G_CALLBACK (click_gesture_pressed), self, 0); gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (self->click_gesture)); } static void gtk_source_completion_list_box_do_update (GtkSourceCompletionListBox *self, gboolean update_selection) { GtkSourceCompletionProvider *last_provider = NULL; UpdateState state = {0}; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); state.self = self; state.context = self->context; state.position = gtk_source_completion_list_box_get_offset (self); state.selected = self->selected; if (self->context != NULL) { state.n_items = g_list_model_get_n_items (G_LIST_MODEL (self->context)); } state.position = MIN (state.position, MAX (state.n_items, self->n_rows) - self->n_rows); state.selected = MIN (self->selected, state.n_items ? (int)state.n_items - 1 : 0); if (gtk_adjustment_get_upper (self->vadjustment) != state.n_items) { gtk_adjustment_set_upper (self->vadjustment, state.n_items); } for (GtkWidget *iter = gtk_widget_get_first_child (GTK_WIDGET (self->box)); iter != NULL; iter = gtk_widget_get_next_sibling (iter)) { GtkSourceCompletionProposal *proposal = NULL; GtkSourceCompletionProvider *provider = NULL; gboolean has_alternates = FALSE; if (!GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (iter)) { continue; } if (state.selected >= 0 && state.position == (guint)state.selected) { gtk_widget_set_state_flags (iter, GTK_STATE_FLAG_SELECTED, FALSE); } else { gtk_widget_unset_state_flags (iter, GTK_STATE_FLAG_SELECTED); } if (state.context != NULL && state.position < state.n_items) { _gtk_source_completion_context_get_item_full (state.context, state.position, &provider, &proposal); if (state.selected == (int)state.position) { if (self->alternate >= 0 && self->alternate < (int)self->alternates->len) { g_clear_object (&proposal); proposal = g_object_ref (g_ptr_array_index (self->alternates, self->alternate)); } has_alternates = self->alternates != NULL && self->alternates->len > 0; } _gtk_source_completion_list_box_row_display (GTK_SOURCE_COMPLETION_LIST_BOX_ROW (iter), state.context, provider, proposal, self->show_icons, has_alternates); if (last_provider != NULL && provider != last_provider) { gtk_widget_add_css_class (GTK_WIDGET (iter), "group-leader"); } else { gtk_widget_remove_css_class (GTK_WIDGET (iter), "group-leader"); } gtk_widget_show (iter); } else { gtk_widget_hide (iter); _gtk_source_completion_list_box_row_display (GTK_SOURCE_COMPLETION_LIST_BOX_ROW (iter), NULL, NULL, NULL, self->show_icons, FALSE); } state.position++; last_provider = provider; g_clear_object (&proposal); g_clear_object (&provider); } if (update_selection) { gtk_source_completion_list_box_set_selected (self, state.selected); } g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROPOSAL]); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_ALTERNATES]); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ALTERNATE]); g_signal_emit (self, signals [REPOSITION], 0); } static gboolean gtk_source_completion_list_box_update_cb (GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data) { GtkSourceCompletionListBox *self = (GtkSourceCompletionListBox *)widget; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); self->queued_update = 0; gtk_source_completion_list_box_do_update (self, TRUE); /* There is a chance that the update sequence could cause us to need * to queue another update. But we don't actually need it. Just cancel * any additional request immediately. */ if (self->queued_update != 0) { gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->queued_update); self->queued_update = 0; } return G_SOURCE_REMOVE; } static void gtk_source_completion_list_box_queue_update (GtkSourceCompletionListBox *self) { g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); /* See gtk_source_completion_list_box_set_selected() for why this needs * to be set here to avoid re-entrancy. */ if (self->queued_update == 0) { self->queued_update = gtk_widget_add_tick_callback (GTK_WIDGET (self), gtk_source_completion_list_box_update_cb, NULL, NULL); } } GtkWidget * _gtk_source_completion_list_box_new (void) { return g_object_new (GTK_SOURCE_TYPE_COMPLETION_LIST_BOX, NULL); } guint _gtk_source_completion_list_box_get_n_rows (GtkSourceCompletionListBox *self) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), 0); return self->n_rows; } void _gtk_source_completion_list_box_set_n_rows (GtkSourceCompletionListBox *self, guint n_rows) { g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_return_if_fail (n_rows > 0); g_return_if_fail (n_rows <= 32); if (n_rows != self->n_rows) { GtkWidget *tmp; while ((tmp = gtk_widget_get_first_child (GTK_WIDGET (self->box)))) { gtk_box_remove (self->box, tmp); } self->n_rows = n_rows; if (self->vadjustment != NULL) { gtk_adjustment_set_page_size (self->vadjustment, n_rows); } for (guint i = 0; i < n_rows; i++) { GtkWidget *row; row = _gtk_source_completion_list_box_row_new (); gtk_widget_set_can_focus (GTK_WIDGET (row), FALSE); _gtk_source_completion_list_box_row_attach (GTK_SOURCE_COMPLETION_LIST_BOX_ROW (row), self->before_size_group, self->typed_text_size_group, self->after_size_group); _gtk_source_completion_list_box_row_set_attrs (GTK_SOURCE_COMPLETION_LIST_BOX_ROW (row), self->font_attrs); gtk_box_append (self->box, row); } gtk_source_completion_list_box_queue_update (self); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_ROWS]); } } /** * gtk_source_completion_list_box_get_proposal: * @self: a #GtkSourceCompletionListBox * * Gets the currently selected proposal, or %NULL if no proposal is selected * * Returns: (nullable) (transfer full): a #GtkSourceCompletionProposal or %NULL */ GtkSourceCompletionProposal * _gtk_source_completion_list_box_get_proposal (GtkSourceCompletionListBox *self) { GtkSourceCompletionProposal *ret = NULL; g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), NULL); if (self->context != NULL && self->selected < (int)g_list_model_get_n_items (G_LIST_MODEL (self->context))) ret = g_list_model_get_item (G_LIST_MODEL (self->context), self->selected); g_return_val_if_fail (!ret || GTK_SOURCE_IS_COMPLETION_PROPOSAL (ret), NULL); return ret; } /** * _gtk_source_completion_list_box_get_selected: * @self: an #GtkSourceCompletionListBox * @provider: (out) (transfer full) (optional): a location for the provider * @proposal: (out) (transfer full) (optional): a location for the proposal * * Gets the selected item if there is any. * * If there is a selection, %TRUE is returned and @provider and @proposal * are set to the selected provider/proposal. * * Returns: %TRUE if there is a selection */ gboolean _gtk_source_completion_list_box_get_selected (GtkSourceCompletionListBox *self, GtkSourceCompletionProvider **provider, GtkSourceCompletionProposal **proposal) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), FALSE); if (self->context != NULL) { guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self->context)); if (n_items > 0) { if (self->selected >= 0) { gint selected = MIN (self->selected, (int)n_items - 1); _gtk_source_completion_context_get_item_full (self->context, selected, provider, proposal); return TRUE; } } } return FALSE; } /** * gtk_source_completion_list_box_get_context: * @self: a #GtkSourceCompletionListBox * * Gets the context that is being displayed in the list box. * * Returns: (transfer none) (nullable): an #GtkSourceCompletionContext or %NULL */ GtkSourceCompletionContext * _gtk_source_completion_list_box_get_context (GtkSourceCompletionListBox *self) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), NULL); return self->context; } static void gtk_source_completion_list_box_items_changed_cb (GtkSourceCompletionListBox *self, guint position, guint removed, guint added, GListModel *model) { guint offset; g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_assert (G_IS_LIST_MODEL (model)); offset = gtk_source_completion_list_box_get_offset (self); /* Skip widget resize if results are out of view and wont force our * current results down. */ if ((position >= offset + self->n_rows) || (removed == added && (position + added) < offset)) { return; } gtk_source_completion_list_box_queue_update (self); } /** * gtk_source_completion_list_box_set_context: * @self: a #GtkSourceCompletionListBox * @context: the #GtkSourceCompletionContext * * Sets the context to be displayed. */ void _gtk_source_completion_list_box_set_context (GtkSourceCompletionListBox *self, GtkSourceCompletionContext *context) { g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_return_if_fail (!context || GTK_SOURCE_IS_COMPLETION_CONTEXT (context)); if (self->context == context) return; if (self->context != NULL && self->items_changed_handler != 0) { g_signal_handler_disconnect (self->context, self->items_changed_handler); self->items_changed_handler = 0; } g_set_object (&self->context, context); if (self->context != NULL) { self->items_changed_handler = g_signal_connect_object (self->context, "items-changed", G_CALLBACK (gtk_source_completion_list_box_items_changed_cb), self, G_CONNECT_SWAPPED); } gtk_source_completion_list_box_set_selected (self, -1); gtk_adjustment_set_value (self->vadjustment, 0); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONTEXT]); } GtkSourceCompletionListBoxRow * _gtk_source_completion_list_box_get_first (GtkSourceCompletionListBox *self) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), NULL); for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->box)); child != NULL; child = gtk_widget_get_next_sibling (child)) { if (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (child)) { return GTK_SOURCE_COMPLETION_LIST_BOX_ROW (child); } } return NULL; } void _gtk_source_completion_list_box_move_cursor (GtkSourceCompletionListBox *self, GtkMovementStep step, int direction) { int n_items; int offset; g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); if (self->context == NULL || direction == 0) return; if (!(n_items = g_list_model_get_n_items (G_LIST_MODEL (self->context)))) return; /* n_items is signed so that we can do negative comparison */ if (n_items < 0) return; if (step == GTK_MOVEMENT_BUFFER_ENDS) { if (direction > 0) { gtk_source_completion_list_box_set_offset (self, n_items); gtk_source_completion_list_box_set_selected (self, n_items - 1); } else { gtk_source_completion_list_box_set_offset (self, 0); gtk_source_completion_list_box_set_selected (self, -1); } gtk_source_completion_list_box_queue_update (self); return; } if ((direction < 0 && self->selected == 0) || (direction > 0 && self->selected == n_items - 1)) { return; } if (step == GTK_MOVEMENT_PAGES) { direction *= self->n_rows; } if ((self->selected + direction) > n_items) { gtk_source_completion_list_box_set_selected (self, n_items - 1); } else if ((self->selected + direction) < 0) { gtk_source_completion_list_box_set_selected (self, 0); } else { gtk_source_completion_list_box_set_selected (self, self->selected + direction); } offset = gtk_source_completion_list_box_get_offset (self); if (self->selected < offset) { gtk_source_completion_list_box_set_offset (self, self->selected); } else if (self->selected >= (int)(offset + self->n_rows)) { gtk_source_completion_list_box_set_offset (self, self->selected - self->n_rows + 1); } gtk_source_completion_list_box_queue_update (self); } void _gtk_source_completion_list_box_set_font_desc (GtkSourceCompletionListBox *self, const PangoFontDescription *font_desc) { GtkWidget *iter; g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); g_clear_pointer (&self->font_attrs, pango_attr_list_unref); if (font_desc) { self->font_attrs = pango_attr_list_new (); if (font_desc != NULL) { pango_attr_list_insert (self->font_attrs, pango_attr_font_desc_new (font_desc)); pango_attr_list_insert (self->font_attrs, pango_attr_font_features_new ("tnum")); } } for ((iter = gtk_widget_get_first_child (GTK_WIDGET (self->box))); iter != NULL; iter = gtk_widget_get_next_sibling (iter)) { if (!GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (iter)) { continue; } _gtk_source_completion_list_box_row_set_attrs (GTK_SOURCE_COMPLETION_LIST_BOX_ROW (iter), self->font_attrs); } } int _gtk_source_completion_list_box_get_alternate (GtkSourceCompletionListBox *self) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), 0); return self->alternate + 1; } guint _gtk_source_completion_list_box_get_n_alternates (GtkSourceCompletionListBox *self) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self), 0); return self->alternates ? self->alternates->len : 0; } void _gtk_source_completion_list_box_set_show_icons (GtkSourceCompletionListBox *self, gboolean show_icons) { g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX (self)); self->show_icons = !!show_icons; gtk_source_completion_list_box_queue_update (self); } 0707010000013E000081A40000000000000000000000016659080600000B64000000000000000000000000000000000000004B00000000gtksourceview-5.12.1/gtksourceview/gtksourcecompletionlistboxrow-private.h/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_COMPLETION_LIST_BOX_ROW (gtk_source_completion_list_box_row_get_type()) G_DECLARE_FINAL_TYPE (GtkSourceCompletionListBoxRow, gtk_source_completion_list_box_row, GTK_SOURCE, COMPLETION_LIST_BOX_ROW, GtkListBoxRow) GtkWidget *_gtk_source_completion_list_box_row_new (void); void _gtk_source_completion_list_box_row_display (GtkSourceCompletionListBoxRow *self, GtkSourceCompletionContext *context, GtkSourceCompletionProvider *provider, GtkSourceCompletionProposal *proposal, gboolean show_icons, gboolean has_alternates); gint _gtk_source_completion_list_box_row_get_x_offset (GtkSourceCompletionListBoxRow *self, GtkWidget *toplevel); void _gtk_source_completion_list_box_row_set_attrs (GtkSourceCompletionListBoxRow *self, PangoAttrList *attrs); void _gtk_source_completion_list_box_row_attach (GtkSourceCompletionListBoxRow *self, GtkSizeGroup *before, GtkSizeGroup *typed_text, GtkSizeGroup *after); G_END_DECLS 0707010000013F000081A40000000000000000000000016659080600001DC9000000000000000000000000000000000000004300000000gtksourceview-5.12.1/gtksourceview/gtksourcecompletionlistboxrow.c/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourcecompletioncell-private.h" #include "gtksourcecompletioncontext-private.h" #include "gtksourcecompletionlistboxrow-private.h" #include "gtksourcecompletionproposal.h" #include "gtksourcecompletionprovider.h" struct _GtkSourceCompletionListBoxRow { GtkListBoxRow parent_instance; GtkSourceCompletionProposal *proposal; PangoAttrList *attrs; GtkBox *box; GtkBox *more; GtkSourceCompletionCell *icon; GtkSourceCompletionCell *before; GtkSourceCompletionCell *typed_text; GtkSourceCompletionCell *after; }; G_DEFINE_TYPE (GtkSourceCompletionListBoxRow, gtk_source_completion_list_box_row, GTK_TYPE_LIST_BOX_ROW) static void gtk_source_completion_list_box_row_finalize (GObject *object) { GtkSourceCompletionListBoxRow *self = (GtkSourceCompletionListBoxRow *)object; g_clear_pointer (&self->attrs, pango_attr_list_unref); G_OBJECT_CLASS (gtk_source_completion_list_box_row_parent_class)->finalize (object); } static void gtk_source_completion_list_box_row_class_init (GtkSourceCompletionListBoxRowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = gtk_source_completion_list_box_row_finalize; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gtksourceview/ui/gtksourcecompletionlistboxrow.ui"); gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, box); gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, icon); gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, before); gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, typed_text); gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, after); gtk_widget_class_bind_template_child (widget_class, GtkSourceCompletionListBoxRow, more); g_type_ensure (GTK_SOURCE_TYPE_COMPLETION_CELL); } static void gtk_source_completion_list_box_row_init (GtkSourceCompletionListBoxRow *self) { gtk_widget_init_template (GTK_WIDGET (self)); } GtkWidget * _gtk_source_completion_list_box_row_new (void) { return g_object_new (GTK_SOURCE_TYPE_COMPLETION_LIST_BOX_ROW, NULL); } void _gtk_source_completion_list_box_row_display (GtkSourceCompletionListBoxRow *self, GtkSourceCompletionContext *context, GtkSourceCompletionProvider *provider, GtkSourceCompletionProposal *proposal, gboolean show_icons, gboolean has_alternates) { g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (self)); g_return_if_fail (!context || GTK_SOURCE_IS_COMPLETION_CONTEXT (context)); g_return_if_fail (!provider || GTK_SOURCE_IS_COMPLETION_PROVIDER (provider)); g_return_if_fail (!proposal || GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal)); if (proposal == NULL) { gtk_source_completion_cell_set_widget (self->icon, NULL); gtk_source_completion_cell_set_widget (self->before, NULL); gtk_source_completion_cell_set_widget (self->typed_text, NULL); gtk_source_completion_cell_set_widget (self->after, NULL); } else { gtk_source_completion_provider_display (provider, context, proposal, self->icon); gtk_source_completion_provider_display (provider, context, proposal, self->before); gtk_source_completion_provider_display (provider, context, proposal, self->typed_text); gtk_source_completion_provider_display (provider, context, proposal, self->after); } gtk_widget_set_visible (GTK_WIDGET (self->icon), show_icons); gtk_widget_set_visible (GTK_WIDGET (self->more), has_alternates); } void _gtk_source_completion_list_box_row_attach (GtkSourceCompletionListBoxRow *self, GtkSizeGroup *before, GtkSizeGroup *typed_text, GtkSizeGroup *after) { g_return_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (self)); g_return_if_fail (GTK_IS_SIZE_GROUP (before)); g_return_if_fail (GTK_IS_SIZE_GROUP (typed_text)); g_return_if_fail (GTK_IS_SIZE_GROUP (after)); gtk_size_group_add_widget (before, GTK_WIDGET (self->before)); gtk_size_group_add_widget (typed_text, GTK_WIDGET (self->typed_text)); gtk_size_group_add_widget (after, GTK_WIDGET (self->after)); } static void get_margin_and_border (GtkWidget *widget, GtkBorder *margin, GtkBorder *border) { GtkStyleContext *style_context = gtk_widget_get_style_context (widget); gtk_style_context_get_margin (style_context, margin); gtk_style_context_get_border (style_context, border); } gint _gtk_source_completion_list_box_row_get_x_offset (GtkSourceCompletionListBoxRow *self, GtkWidget *toplevel) { GtkRequisition min; GtkRequisition nat; GtkBorder margin; GtkBorder border; double x = 0; g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (self), 0); g_return_val_if_fail (GTK_IS_WIDGET (toplevel), 0); for (GtkWidget *iter = GTK_WIDGET (self->box); iter != NULL; iter = gtk_widget_get_parent (iter)) { get_margin_and_border (iter, &margin, &border); x += margin.left + border.left; if (iter == toplevel) { break; } } get_margin_and_border (GTK_WIDGET (self->icon), &margin, &border); gtk_widget_get_preferred_size (GTK_WIDGET (self->icon), &min, &nat); x += margin.left + border.left + nat.width + margin.right + border.right; get_margin_and_border (GTK_WIDGET (self->before), &margin, &border); gtk_widget_get_preferred_size (GTK_WIDGET (self->before), &min, &nat); x += margin.left + border.left + nat.width + border.right + margin.right; get_margin_and_border (GTK_WIDGET (self->typed_text), &margin, &border); gtk_widget_get_preferred_size (GTK_WIDGET (self->typed_text), &min, &nat); x += margin.left + border.left; return -x; } void _gtk_source_completion_list_box_row_set_attrs (GtkSourceCompletionListBoxRow *self, PangoAttrList *attrs) { g_assert (GTK_SOURCE_IS_COMPLETION_LIST_BOX_ROW (self)); _gtk_source_completion_cell_set_attrs (self->icon, attrs); _gtk_source_completion_cell_set_attrs (self->before, attrs); _gtk_source_completion_cell_set_attrs (self->typed_text, attrs); _gtk_source_completion_cell_set_attrs (self->after, attrs); } 07070100000140000081A40000000000000000000000016659080600000974000000000000000000000000000000000000004400000000gtksourceview-5.12.1/gtksourceview/gtksourcecompletionlistboxrow.ui 07070100000141000081A40000000000000000000000016659080600000916000000000000000000000000000000000000004100000000gtksourceview-5.12.1/gtksourceview/gtksourcecompletionproposal.c/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourcecompletionproposal.h" /** * GtkSourceCompletionProposal: * * Interface for completion proposals. * * This interface is used to denote that an object is capable of being * a completion proposal for [class@Completion]. * * Currently, no method or functions are required but additional methods * may be added in the future. Proposals created by * #GtkSourceCompletionProvider can use [func@GObject.IMPLEMENT_INTERFACE] to * implement this with %NULL for the interface init function. */ G_DEFINE_INTERFACE (GtkSourceCompletionProposal, gtk_source_completion_proposal, G_TYPE_OBJECT) static void gtk_source_completion_proposal_default_init (GtkSourceCompletionProposalInterface *iface) { } /** * gtk_source_completion_proposal_get_typed_text: * @proposal: a #GtkSourceCompletionProposal * * Gets the typed-text for the proposal, if supported by the implementation. * * Implementing this virtual-function is optional, but can be useful to allow * external tooling to compare results. * * Returns: (transfer full) (nullable): a newly allocated string, or %NULL * * Since: 5.6 */ char * gtk_source_completion_proposal_get_typed_text (GtkSourceCompletionProposal *proposal) { GtkSourceCompletionProposalInterface *iface; g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL); iface = GTK_SOURCE_COMPLETION_PROPOSAL_GET_IFACE (proposal); return iface->get_typed_text ? iface->get_typed_text (proposal) : NULL; } 07070100000142000081A40000000000000000000000016659080600000632000000000000000000000000000000000000004100000000gtksourceview-5.12.1/gtksourceview/gtksourcecompletionproposal.h/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_COMPLETION_PROPOSAL (gtk_source_completion_proposal_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_INTERFACE (GtkSourceCompletionProposal, gtk_source_completion_proposal, GTK_SOURCE, COMPLETION_PROPOSAL, GObject) struct _GtkSourceCompletionProposalInterface { GTypeInterface parent_iface; char *(*get_typed_text) (GtkSourceCompletionProposal *proposal); }; GTK_SOURCE_AVAILABLE_IN_5_6 char *gtk_source_completion_proposal_get_typed_text (GtkSourceCompletionProposal *proposal); G_END_DECLS 07070100000143000081A40000000000000000000000016659080600003ABD000000000000000000000000000000000000004100000000gtksourceview-5.12.1/gtksourceview/gtksourcecompletionprovider.c/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourcecompletioncontext.h" #include "gtksourcecompletioncell.h" #include "gtksourcecompletionproposal.h" #include "gtksourcecompletionprovider.h" /** * GtkSourceCompletionProvider: * * Completion provider interface. * * You must implement this interface to provide proposals to [class@Completion]. * * In most cases, implementations of this interface will want to use * [vfunc@CompletionProvider.populate_async] to asynchronously populate the results * to avoid blocking the main loop. */ G_DEFINE_INTERFACE (GtkSourceCompletionProvider, gtk_source_completion_provider, G_TYPE_OBJECT) static void fallback_populate_async (GtkSourceCompletionProvider *provider, GtkSourceCompletionContext *context, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GListModel *ret; GError *error = NULL; GTask *task; task = g_task_new (provider, cancellable, callback, user_data); g_task_set_source_tag (task, fallback_populate_async); ret = GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (provider)->populate (provider, context, &error); if (ret == NULL) { if (error != NULL) { g_task_return_error (task, g_steal_pointer (&error)); } else { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No results"); } } else { g_task_return_pointer (task, g_steal_pointer (&ret), g_object_unref); } g_clear_object (&task); } static GListModel * fallback_populate_finish (GtkSourceCompletionProvider *provider, GAsyncResult *result, GError **error) { return g_task_propagate_pointer (G_TASK (result), error); } static GListModel * fallback_populate (GtkSourceCompletionProvider *provider, GtkSourceCompletionContext *context, GError **error) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Not supported"); return NULL; } static void fallback_refilter (GtkSourceCompletionProvider *provider, GtkSourceCompletionContext *context, GListModel *model) { } static void fallback_activate (GtkSourceCompletionProvider *provider, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal) { } static void gtk_source_completion_provider_default_init (GtkSourceCompletionProviderInterface *iface) { iface->populate_async = fallback_populate_async; iface->populate_finish = fallback_populate_finish; iface->populate = fallback_populate; iface->refilter = fallback_refilter; iface->activate = fallback_activate; } /** * gtk_source_completion_provider_get_title: * @self: a #GtkSourceCompletionProvider * * Gets the title of the completion provider, if any. * * Currently, titles are not displayed in the completion results, but may be * at some point in the future when non-%NULL. * * Returns: (transfer full) (nullable): a title for the provider or %NULL */ char * gtk_source_completion_provider_get_title (GtkSourceCompletionProvider *self) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), NULL); if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->get_title) return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->get_title (self); return NULL; } /** * gtk_source_completion_provider_get_priority: * @self: a #GtkSourceCompletionProvider * @context: a #GtkSourceCompletionContext * * This function should return the priority of @self in @context. * * The priority is used to sort groups of completion proposals by * provider so that higher priority providers results are shown * above lower priority providers. * * Higher value indicates higher priority. */ int gtk_source_completion_provider_get_priority (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), 0); g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), 0); if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->get_priority) return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->get_priority (self, context); return 0; } /** * gtk_source_completion_provider_is_trigger: * @self: a #GtkSourceCompletionProvider * @iter: a #GtkTextIter * @ch: a #gunichar of the character inserted * * This function is used to determine if a character inserted into the text * editor should cause a new completion request to be triggered. * * An example would be period '.' which might indicate that the user wants * to complete method or field names of an object. * * This method will only trigger when text is inserted into the #GtkTextBuffer * while the completion list is visible and a proposal is selected. Incremental * key-presses (like shift, control, or alt) are not triggerable. */ gboolean gtk_source_completion_provider_is_trigger (GtkSourceCompletionProvider *self, const GtkTextIter *iter, gunichar ch) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), FALSE); g_return_val_if_fail (iter != NULL, FALSE); if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->is_trigger) return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->is_trigger (self, iter, ch); return FALSE; } /** * gtk_source_completion_provider_key_activates: * @self: a #GtkSourceCompletionProvider * @context: a #GtkSourceCompletionContext * @proposal: a #GtkSourceCompletionProposal * @keyval: a keyval such as [const@Gdk.KEY_period] * @state: a #GdkModifierType or 0 * * This function is used to determine if a key typed by the user should * activate @proposal (resulting in committing the text to the editor). * * This is useful when using languages where convention may lead to less * typing by the user. One example may be the use of "." or "-" to expand * a field access in the C programming language. */ gboolean gtk_source_completion_provider_key_activates (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal, guint keyval, GdkModifierType state) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), FALSE); g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), FALSE); g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), FALSE); if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->key_activates) return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->key_activates (self, context, proposal, keyval, state); return FALSE; } /** * gtk_source_completion_provider_populate_async: * @self: a #GtkSourceCompletionProvider * @context: a #GtkSourceCompletionContext * @cancellable: (nullable): a #GCancellable or %NULL * @callback: a callback to execute upon completion * @user_data: closure data for @callback * * Asynchronously requests that the provider populates the completion * results for @context. * * For providers that would like to populate a [iface@Gio.ListModel] while those * results are displayed to the user, * [method@CompletionContext.set_proposals_for_provider] may be used * to reduce latency until the user sees results. */ void gtk_source_completion_provider_populate_async (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self)); g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->populate_async (self, context, cancellable, callback, user_data); } /** * gtk_source_completion_provider_populate_finish: * @self: a #GtkSourceCompletionProvider * @result: a #GAsyncResult provided to callback * @error: a location for a #GError, or %NULL * * Completes an asynchronous operation to populate a completion provider. * * Returns: (transfer full): a #GListModel of #GtkSourceCompletionProposal */ GListModel * gtk_source_completion_provider_populate_finish (GtkSourceCompletionProvider *self, GAsyncResult *result, GError **error) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), NULL); return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->populate_finish (self, result, error); } /** * gtk_source_completion_provider_refilter: * @self: a #GtkSourceCompletionProvider * @context: a #GtkSourceCompletionContext * @model: a #GListModel * * This function can be used to filter results previously provided to * the [class@CompletionContext] by the #GtkSourceCompletionProvider. * * This can happen as the user types additional text onto the word so * that previously matched items may be removed from the list instead of * generating new [iface@Gio.ListModel] of results. */ void gtk_source_completion_provider_refilter (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GListModel *model) { g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self)); g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context)); g_return_if_fail (G_IS_LIST_MODEL (model)); if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->refilter) GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->refilter (self, context, model); } /** * gtk_source_completion_provider_display: * @self: a #GtkSourceCompletionProvider * @context: a #GtkSourceCompletionContext * @proposal: a #GtkSourceCompletionProposal * @cell: a #GtkSourceCompletionCell * * This function requests that the #GtkSourceCompletionProvider prepares * @cell to display the contents of @proposal. * * Based on @cells column type, you may want to display different information. * * This allows for columns of information among completion proposals * resulting in better alignment of similar content (icons, return types, * method names, and parameter lists). */ void gtk_source_completion_provider_display (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal, GtkSourceCompletionCell *cell) { g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self)); g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context)); g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal)); g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CELL (cell)); if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->display) GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->display (self, context, proposal, cell); } /** * gtk_source_completion_provider_activate: * @self: a #GtkSourceCompletionProvider * @context: a #GtkSourceCompletionContext * @proposal: a #GtkSourceCompletionProposal * * This function requests @proposal to be activated by the * #GtkSourceCompletionProvider. * * What the provider does to activate the proposal is specific to that * provider. Many providers may choose to insert a #GtkSourceSnippet with * edit points the user may cycle through. * * See also: [class@Snippet], [class@SnippetChunk], [method@View.push_snippet] */ void gtk_source_completion_provider_activate (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal) { g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self)); g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context)); g_return_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal)); if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->activate) GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->activate (self, context, proposal); } /** * gtk_source_completion_provider_list_alternates: * @self: a #GtkSourceCompletionProvider * @context: a #GtkSourceCompletionContext * @proposal: a #GtkSourceCompletionProposal * * Providers should return a list of alternates to @proposal or %NULL if * there are no alternates available. * * This can be used by the completion view to allow the user to move laterally * through similar proposals, such as overrides of methods by the same name. * * Returns: (nullable) (transfer full) (element-type GtkSourceCompletionProposal): * a #GPtrArray of #GtkSourceCompletionProposal or %NULL. */ GPtrArray * gtk_source_completion_provider_list_alternates (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal) { g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROVIDER (self), NULL); g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), NULL); g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_PROPOSAL (proposal), NULL); if (GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->list_alternates) return GTK_SOURCE_COMPLETION_PROVIDER_GET_IFACE (self)->list_alternates (self, context, proposal); return NULL; } 07070100000144000081A40000000000000000000000016659080600001D51000000000000000000000000000000000000004100000000gtksourceview-5.12.1/gtksourceview/gtksourcecompletionprovider.h/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_COMPLETION_PROVIDER (gtk_source_completion_provider_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_INTERFACE (GtkSourceCompletionProvider, gtk_source_completion_provider, GTK_SOURCE, COMPLETION_PROVIDER, GObject) struct _GtkSourceCompletionProviderInterface { GTypeInterface parent_iface; char *(*get_title) (GtkSourceCompletionProvider *self); int (*get_priority) (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context); gboolean (*is_trigger) (GtkSourceCompletionProvider *self, const GtkTextIter *iter, gunichar ch); gboolean (*key_activates) (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal, guint keyval, GdkModifierType state); GListModel *(*populate) (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GError **error); void (*populate_async) (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); GListModel *(*populate_finish) (GtkSourceCompletionProvider *self, GAsyncResult *result, GError **error); void (*refilter) (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GListModel *model); void (*display) (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal, GtkSourceCompletionCell *cell); void (*activate) (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal); GPtrArray *(*list_alternates) (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal); }; GTK_SOURCE_AVAILABLE_IN_ALL char *gtk_source_completion_provider_get_title (GtkSourceCompletionProvider *self); GTK_SOURCE_AVAILABLE_IN_ALL int gtk_source_completion_provider_get_priority (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_completion_provider_is_trigger (GtkSourceCompletionProvider *self, const GtkTextIter *iter, gunichar ch); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_completion_provider_key_activates (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal, guint keyval, GdkModifierType state); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_completion_provider_populate_async (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); GTK_SOURCE_AVAILABLE_IN_ALL GListModel *gtk_source_completion_provider_populate_finish (GtkSourceCompletionProvider *self, GAsyncResult *result, GError **error); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_completion_provider_refilter (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GListModel *model); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_completion_provider_display (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal, GtkSourceCompletionCell *cell); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_completion_provider_activate (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal); GTK_SOURCE_AVAILABLE_IN_ALL GPtrArray *gtk_source_completion_provider_list_alternates (GtkSourceCompletionProvider *self, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal); G_END_DECLS 07070100000145000081A400000000000000000000000166590806000018E2000000000000000000000000000000000000004400000000gtksourceview-5.12.1/gtksourceview/gtksourcecontextengine-private.h/* * This file is part of GtkSourceView * * Copyright 2003 - Gustavo Giráldez * Copyright 2005 - Marco Barisione, Emanuele Aina * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include "gtksourceengine-private.h" #include "gtksourcetypes.h" #include "gtksourcetypes-private.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_CONTEXT_ENGINE (_gtk_source_context_engine_get_type()) typedef struct _GtkSourceContextData GtkSourceContextData; typedef struct _GtkSourceContextReplace GtkSourceContextReplace; typedef struct _GtkSourceContextClass GtkSourceContextClass; typedef enum _GtkSourceContextFlags { 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 _GtkSourceContextRefOptions { GTK_SOURCE_CONTEXT_IGNORE_STYLE = 1 << 0, GTK_SOURCE_CONTEXT_OVERRIDE_STYLE = 1 << 1, GTK_SOURCE_CONTEXT_REF_ORIGINAL = 1 << 2 } GtkSourceContextRefOptions; G_GNUC_INTERNAL G_DECLARE_FINAL_TYPE (GtkSourceContextEngine, _gtk_source_context_engine, GTK_SOURCE, CONTEXT_ENGINE, GObject) G_GNUC_INTERNAL GtkSourceContextData *_gtk_source_context_data_new (GtkSourceLanguage *lang); G_GNUC_INTERNAL GtkSourceContextData *_gtk_source_context_data_ref (GtkSourceContextData *data); G_GNUC_INTERNAL void _gtk_source_context_data_unref (GtkSourceContextData *data); G_GNUC_INTERNAL GtkSourceContextClass *gtk_source_context_class_new (gchar const *name, gboolean enabled); G_GNUC_INTERNAL void gtk_source_context_class_free (GtkSourceContextClass *cclass); G_GNUC_INTERNAL GtkSourceContextEngine *_gtk_source_context_engine_new (GtkSourceContextData *data); G_GNUC_INTERNAL 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, GSList *context_classes, GtkSourceContextFlags flags, GError **error); G_GNUC_INTERNAL 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, GSList *context_classes, GError **error); G_GNUC_INTERNAL 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); G_GNUC_INTERNAL GtkSourceContextReplace *_gtk_source_context_replace_new (const gchar *to_replace_id, const gchar *replace_with_id); G_GNUC_INTERNAL void _gtk_source_context_replace_free (GtkSourceContextReplace *repl); G_GNUC_INTERNAL gboolean _gtk_source_context_data_finish_parse (GtkSourceContextData *data, GList *overrides, GError **error); /* Only for lang files version 1, do not use it */ G_GNUC_INTERNAL void _gtk_source_context_data_set_escape_char (GtkSourceContextData *data, gunichar esc_char); G_END_DECLS 07070100000146000081A40000000000000000000000016659080600029534000000000000000000000000000000000000003C00000000gtksourceview-5.12.1/gtksourceview/gtksourcecontextengine.c/* -*- mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- * * This file is part of GtkSourceView * * Copyright 2003 - Gustavo Giráldez * Copyright 2005, 2006 - Marco Barisione, Emanuele Aina * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include #include #include #include "gtksourcecontextengine-private.h" #include "gtksourceregion.h" #include "gtksourcelanguage.h" #include "gtksourcelanguage-private.h" #include "gtksourcebuffer.h" #include "gtksourceregex-private.h" #include "gtksourcestyle.h" #include "gtksourcestylescheme.h" #include "gtksourceutils-private.h" #include "gtksourcetrace.h" #undef ENABLE_CHECK_TREE #if defined (GTK_SOURCE_PROFILER_ENABLED) || defined (ENABLE_CHECK_TREE) # define NEED_DEBUG_ID #endif /* Priority of one-time idle which is installed after buffer is modified. */ #define FIRST_UPDATE_PRIORITY G_PRIORITY_HIGH_IDLE /* Maximal amount of time (in milliseconds) allowed to spend in the first idle. * Should be small enough, since in worst case we block ui for this time after * each keypress. */ #define FIRST_UPDATE_TIME_SLICE 10 /* Priority of long running idle which is used to analyze whole buffer, if * the engine wasn't quick enough to analyze it in one shot. */ /* FIXME this priority is low, since we don't want to block other gui stuff. * But, e.g. if we have a big file, and scroll down, we do want the engine * to analyze quickly. Perhaps we want to reinstall first_update in case * of expose events or something. */ #define INCREMENTAL_UPDATE_PRIORITY G_PRIORITY_LOW /* Maximal amount of time (in milliseconds) allowed to spend in one cycle of * background idle. */ #define INCREMENTAL_UPDATE_TIME_SLICE 30 /* Maximal amount of time (in milliseconds) allowed to spend highlihting a * single line. If it is not enough, then highlighting is disabled. */ #define MAX_TIME_FOR_ONE_LINE 2000 #define GTK_SOURCE_CONTEXT_ENGINE_ERROR (gtk_source_context_engine_error_quark ()) #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) 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 struct _ContextClassTag ContextClassTag; typedef enum _GtkSourceContextEngineError { 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_STYLE, GTK_SOURCE_CONTEXT_ENGINE_ERROR_BAD_FILE } GtkSourceContextEngineError; typedef enum _ContextType { CONTEXT_TYPE_SIMPLE = 0, CONTEXT_TYPE_CONTAINER } ContextType; typedef enum _SubPatternWhere { SUB_PATTERN_WHERE_DEFAULT = 0, SUB_PATTERN_WHERE_START, SUB_PATTERN_WHERE_END } SubPatternWhere; struct _ContextDefinition { gchar *id; ContextType type; union { GtkSourceRegex *match; struct { GtkSourceRegex *start; GtkSourceRegex *end; } start_end; } u; /* Name of the style used for contexts of this type. */ gchar *default_style; /* List of DefinitionChild pointers. */ GSList *children; /* Sub-patterns (list of SubPatternDefinition pointers). */ GSList *sub_patterns; guint n_sub_patterns; /* List of class definitions. */ GSList *context_classes; /* Union of every regular expression we can find from this context. */ GtkSourceRegex *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; /* List of class definitions */ GSList *context_classes; /* 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. */ GtkSourceRegex *end; /* The regular expression containing every regular expression that could * be matched in this context. */ GtkSourceRegex *reg_all; /* Either definition->default_style or child_def->style, not copied. */ const gchar *style; GtkTextTag *tag; GtkTextTag **subpattern_tags; /* Cache for generated list of class tags */ GSList *context_classes; /* Cache for generated list of subpattern class tags */ GSList **subpattern_context_classes; 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 end of * a 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 _GtkSourceContextClass { gchar *name; gboolean enabled; }; struct _ContextClassTag { GtkTextTag *tag; gboolean enabled; }; struct _GtkSourceContextData { guint ref_count; GtkSourceLanguage *lang; /* Contains every ContextDefinition indexed by its id. */ GHashTable *definitions; }; struct _GtkSourceContextEngine { GObject parent_instance; 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; /* List of GtkTextTag* for context classes. */ GSList *context_classes; /* Whether or not to actually highlight the buffer. */ gboolean highlight; /* Whether syntax analysis was disabled because of errors. */ gboolean disabled; /* Region covering the unhighlighted text. */ GtkSourceRegion *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; }; #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); static ContextDefinition * gtk_source_context_data_lookup (GtkSourceContextData *ctx_data, const gchar *id) { return g_hash_table_lookup (ctx_data->definitions, id); } static ContextDefinition * gtk_source_context_data_lookup_root (GtkSourceContextData *ctx_data) { const gchar *lang_id; gchar *root_id; ContextDefinition *root_definition; lang_id = gtk_source_language_get_id (ctx_data->lang); root_id = g_strdup_printf ("%s:%s", lang_id, lang_id); root_definition = gtk_source_context_data_lookup (ctx_data, root_id); g_free (root_id); return root_definition; } /* TAGS AND STUFF -------------------------------------------------------------- */ GtkSourceContextClass * gtk_source_context_class_new (gchar const *name, gboolean enabled) { GtkSourceContextClass *def = g_slice_new (GtkSourceContextClass); def->name = g_strdup (name); def->enabled = enabled; return def; } static GtkSourceContextClass * gtk_source_context_class_copy (GtkSourceContextClass *cclass) { return gtk_source_context_class_new (cclass->name, cclass->enabled); } void gtk_source_context_class_free (GtkSourceContextClass *cclass) { g_free (cclass->name); g_slice_free (GtkSourceContextClass, cclass); } static ContextClassTag * context_class_tag_new (GtkTextTag *tag, gboolean enabled) { ContextClassTag *attrtag = g_slice_new (ContextClassTag); attrtag->tag = tag; attrtag->enabled = enabled; return attrtag; } static void context_class_tag_free (ContextClassTag *attrtag) { g_slice_free (ContextClassTag, attrtag); } 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->buffer; data.start = start; data.end = end; if (gtk_text_iter_equal (start, end)) return; g_hash_table_foreach (ce->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; 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->style_scheme == NULL) return; map_to = style_id; style = gtk_source_style_scheme_get_style (ce->style_scheme, style_id); while (style == NULL) { 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. */ map_to = gtk_source_language_get_style_fallback (ce->ctx_data->lang, map_to); if (map_to == NULL) break; style = gtk_source_style_scheme_get_style (ce->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) { GtkTextTag *new_tag; g_assert (style_id != NULL); new_tag = gtk_text_buffer_create_tag (ce->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->n_tags); set_tag_style (ce, new_tag, style_id); ce->n_tags += 1; 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->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); tags = g_slist_prepend (tags, g_object_ref (tag)); g_hash_table_insert (ce->tags, g_strdup (style), tags); #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->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->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 ("%s: oops", G_STRLOC); } 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->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) { gint start = MAX (start_offset, sp->start_at); gint end = MIN (end_offset, sp->end_at); tag = get_subpattern_tag (ce, segment->context, sp->definition); if (tag != NULL) { 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->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); } } 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->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) { GtkSourceRegion *region; GtkSourceRegionIter reg_iter; /* Get the subregions not yet highlighted. */ region = gtk_source_region_intersect_subregion (ce->refresh_region, start, end); if (region == NULL) return; gtk_source_region_get_start_region_iter (region, ®_iter); /* Highlight all subregions from the intersection. * hopefully this will only be one subregion. */ while (!gtk_source_region_iter_is_end (®_iter)) { GtkTextIter s, e; gtk_source_region_iter_get_subregion (®_iter, &s, &e); highlight_region (ce, &s, &e); gtk_source_region_iter_next (®_iter); } g_clear_object (®ion); /* Remove the just highlighted region. */ gtk_source_region_subtract_subregion (ce->refresh_region, start, end); } static GtkTextTag * get_context_class_tag (GtkSourceContextEngine *ce, gchar const *name) { gchar *tag_name; GtkTextTagTable *tag_table; GtkTextTag *tag; tag_name = g_strdup_printf ("gtksourceview:context-classes:%s", name); tag_table = gtk_text_buffer_get_tag_table (ce->buffer); tag = gtk_text_tag_table_lookup (tag_table, tag_name); if (tag == NULL) { tag = gtk_text_buffer_create_tag (ce->buffer, tag_name, NULL); g_return_val_if_fail (tag != NULL, NULL); ce->context_classes = g_slist_prepend (ce->context_classes, g_object_ref (tag)); } g_free (tag_name); return tag; } static GSList * extend_context_classes (GtkSourceContextEngine *ce, GSList *definitions) { GSList *item; GSList *ret = NULL; for (item = definitions; item != NULL; item = g_slist_next (item)) { GtkSourceContextClass *cclass = item->data; ContextClassTag *attrtag = context_class_tag_new (get_context_class_tag (ce, cclass->name), cclass->enabled); ret = g_slist_prepend (ret, attrtag); } return g_slist_reverse (ret); } static GSList * get_subpattern_context_classes (GtkSourceContextEngine *ce, Context *context, SubPatternDefinition *sp_def) { g_assert (sp_def->index < context->definition->n_sub_patterns); if (context->subpattern_context_classes == NULL) context->subpattern_context_classes = g_new0 (GSList *, context->definition->n_sub_patterns); if (context->subpattern_context_classes[sp_def->index] == NULL) { context->subpattern_context_classes[sp_def->index] = extend_context_classes (ce, sp_def->context_classes); } return context->subpattern_context_classes[sp_def->index]; } static GSList * get_context_classes (GtkSourceContextEngine *ce, Context *context) { if (context->context_classes == NULL) { context->context_classes = extend_context_classes (ce, context->definition->context_classes); } return context->context_classes; } static void apply_context_classes (GtkSourceContextEngine *ce, GSList *context_classes, gint start, gint end) { GtkTextIter start_iter; GtkTextIter end_iter; GSList *item; gtk_text_buffer_get_iter_at_offset (ce->buffer, &start_iter, start); end_iter = start_iter; gtk_text_iter_forward_chars (&end_iter, end - start); for (item = context_classes; item != NULL; item = g_slist_next (item)) { ContextClassTag *attrtag = item->data; if (attrtag->enabled) { gtk_text_buffer_apply_tag (ce->buffer, attrtag->tag, &start_iter, &end_iter); } else { gtk_text_buffer_remove_tag (ce->buffer, attrtag->tag, &start_iter, &end_iter); } } } static void add_region_context_classes (GtkSourceContextEngine *ce, Segment *segment, gint start_offset, gint end_offset) { SubPattern *sp; Segment *child; GSList *context_classes; 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); context_classes = get_context_classes (ce, segment->context); if (context_classes != NULL) { apply_context_classes (ce, context_classes, start_offset, end_offset); } for (sp = segment->sub_patterns; sp != NULL; sp = sp->next) { if (sp->start_at >= start_offset && sp->end_at <= end_offset) { gint start = MAX (start_offset, sp->start_at); gint end = MIN (end_offset, sp->end_at); context_classes = get_subpattern_context_classes (ce, segment->context, sp->definition); if (context_classes != NULL) { apply_context_classes (ce, context_classes, start, end); } } } for (child = segment->children; child != NULL && child->start_at < end_offset; child = child->next) { if (child->end_at > start_offset) { add_region_context_classes (ce, child, start_offset, end_offset); } } } static void remove_region_context_classes (GtkSourceContextEngine *ce, const GtkTextIter *start, const GtkTextIter *end) { GSList *l; if (gtk_text_iter_equal (start, end)) { return; } for (l = ce->context_classes; l != NULL; l = l->next) { GtkTextTag *tag = l->data; gtk_text_buffer_remove_tag (ce->buffer, tag, start, end); } } static void refresh_context_classes (GtkSourceContextEngine *ce, const GtkTextIter *start, const GtkTextIter *end) { #ifdef ENABLE_PROFILE GTimer *timer; #endif GtkTextIter realend = *end; if (gtk_text_iter_starts_line (&realend)) { gtk_text_iter_backward_char (&realend); } if (gtk_text_iter_compare (start, &realend) >= 0) { return; } #ifdef ENABLE_PROFILE timer = g_timer_new (); #endif /* First we need to delete tags in the regions. */ remove_region_context_classes (ce, start, &realend); add_region_context_classes (ce, ce->root_segment, gtk_text_iter_get_offset (start), gtk_text_iter_get_offset (&realend)); #ifdef ENABLE_PROFILE g_print ("applied context classes (from %d to %d), %g ms elapsed\n", gtk_text_iter_get_offset (start), gtk_text_iter_get_offset (&realend), g_timer_elapsed (timer, NULL) * 1000); g_timer_destroy (timer); #endif } /* * 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 and notifies view about it. */ static void refresh_range (GtkSourceContextEngine *ce, const GtkTextIter *start, const GtkTextIter *end) { GtkTextIter real_end; if (gtk_text_iter_equal (start, end)) return; /* Refresh the context classes here */ refresh_context_classes (ce, 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->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->invalid, segment)); #endif g_return_if_fail (SEGMENT_IS_INVALID (segment)); ce->invalid = g_slist_insert_sorted (ce->invalid, segment, (GCompareFunc) segment_cmp); GTK_SOURCE_PROFILER_LOG ("%d invalid", g_slist_length (ce->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) { #ifdef ENABLE_CHECK_TREE g_assert (g_slist_find (ce->invalid, segment) != NULL); #endif ce->invalid = g_slist_remove (ce->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: the (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: the (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: the (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->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_new (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->invalid_region; GtkTextBuffer *buffer = ce->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; } #ifdef DEVELOPMENT_BUILD { 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_cmpint (start, <=, end - region->delta); } #endif 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->root_segment, offset, &parent, &prev, &next, ce->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); if (!ce->disabled) { g_return_if_fail (start_offset < end_offset); 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->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->root_segment, start, end - start, ce->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->disabled) { invalidate_region (ce, 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->invalid_region.empty, NULL); return ce->invalid ? ce->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->invalid_region.empty) { gint tmp; gtk_text_buffer_get_iter_at_mark (ce->buffer, &iter, ce->invalid_region.start); tmp = gtk_text_iter_get_offset (&iter); offset = MIN (offset, tmp); } if (ce->invalid) { Segment *segment = ce->invalid->data; offset = MIN (offset, segment->start_at); } if (offset == G_MAXINT) return -1; gtk_text_buffer_get_iter_at_offset (ce->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->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->buffer, &iter, region->start); start = gtk_text_iter_get_offset (&iter); gtk_text_buffer_get_iter_at_mark (ce->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->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 @synchronous * 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->highlight || ce->disabled) return; GTK_SOURCE_PROFILER_BEGIN_MARK; 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) { GtkTextIter valid_end = *start; gtk_text_iter_set_line (&valid_end, invalid_line); ensure_highlighted (ce, start, &valid_end); } install_first_update (ce); } GTK_SOURCE_PROFILER_END_MARK ("ContextEngine::update_highlight", NULL); } /** * 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->highlight) return; ce->highlight = enable != 0; gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (ce->buffer), &start, &end); if (enable) { gtk_source_region_add_subregion (ce->refresh_region, &start, &end); refresh_range (ce, &start, &end); } else { unhighlight_region (ce, &start, &end); } } static void buffer_notify_highlight_syntax_cb (GtkSourceContextEngine *ce) { gboolean highlight; g_object_get (ce->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->invalid == NULL && ce->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 = G_SOURCE_CONTINUE; g_return_val_if_fail (ce->buffer != NULL, G_SOURCE_REMOVE); /* analyze batch of text */ update_syntax (ce, NULL, INCREMENTAL_UPDATE_TIME_SLICE); CHECK_TREE (ce); if (all_analyzed (ce)) { ce->incremental_update = 0; retval = G_SOURCE_REMOVE; } 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->buffer != NULL, G_SOURCE_REMOVE); /* analyze batch of text */ update_syntax (ce, NULL, FIRST_UPDATE_TIME_SLICE); CHECK_TREE (ce); ce->first_update = 0; if (!all_analyzed (ce)) install_idle_worker (ce); return G_SOURCE_REMOVE; } /** * install_idle_worker: * @ce: #GtkSourceContextEngine. * * Schedules reanalyzing buffer in idle. * Always safe to call. */ static void install_idle_worker (GtkSourceContextEngine *ce) { if (ce->first_update == 0 && ce->incremental_update == 0) ce->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->first_update == 0) { if (ce->incremental_update != 0) { g_source_remove (ce->incremental_update); ce->incremental_update = 0; } ce->first_update = g_idle_add_full (FIRST_UPDATE_PRIORITY, (GSourceFunc) first_update_callback, ce, NULL); } } /* GtkSourceContextEngine class ------------------------------------------- */ static void _gtk_source_engine_interface_init (GtkSourceEngineInterface *iface); G_DEFINE_TYPE_WITH_CODE (GtkSourceContextEngine, _gtk_source_context_engine, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_ENGINE, _gtk_source_engine_interface_init)) 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->tags, (GHFunc) remove_tags_hash_cb, gtk_text_buffer_get_tag_table (ce->buffer)); g_hash_table_destroy (ce->tags); ce->tags = NULL; } static void destroy_context_classes_list (GtkSourceContextEngine *ce) { GtkTextTagTable *table; GSList *l; table = gtk_text_buffer_get_tag_table (ce->buffer); for (l = ce->context_classes; l != NULL; l = l->next) { GtkTextTag *tag = l->data; gtk_text_tag_table_remove (table, tag); g_object_unref (tag); } g_slist_free (ce->context_classes); ce->context_classes = 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->buffer == buffer) return; /* Detach previous buffer if there is one. */ if (ce->buffer != NULL) { g_signal_handlers_disconnect_by_func (ce->buffer, (gpointer) buffer_notify_highlight_syntax_cb, ce); if (ce->first_update != 0) g_source_remove (ce->first_update); if (ce->incremental_update != 0) g_source_remove (ce->incremental_update); ce->first_update = 0; ce->incremental_update = 0; if (ce->root_segment != NULL) segment_destroy (ce, ce->root_segment); if (ce->root_context != NULL) context_unref (ce->root_context); g_assert (!ce->invalid); g_slist_free (ce->invalid); ce->root_segment = NULL; ce->root_context = NULL; ce->invalid = NULL; if (ce->invalid_region.start != NULL) gtk_text_buffer_delete_mark (ce->buffer, ce->invalid_region.start); if (ce->invalid_region.end != NULL) gtk_text_buffer_delete_mark (ce->buffer, ce->invalid_region.end); ce->invalid_region.start = NULL; ce->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->n_tags = 0; destroy_context_classes_list (ce); g_clear_object (&ce->refresh_region); } ce->buffer = buffer; if (buffer != NULL) { ContextDefinition *main_definition; GtkTextIter start, end; main_definition = gtk_source_context_data_lookup_root (ce->ctx_data); /* 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->root_context = context_new (NULL, main_definition, NULL, NULL, FALSE); ce->root_segment = create_segment (ce, NULL, ce->root_context, 0, 0, TRUE, NULL); ce->tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); ce->context_classes = NULL; gtk_text_buffer_get_bounds (buffer, &start, &end); ce->invalid_region.start = gtk_text_buffer_create_mark (buffer, NULL, &start, TRUE); ce->invalid_region.end = gtk_text_buffer_create_mark (buffer, NULL, &end, FALSE); if (gtk_text_buffer_get_char_count (buffer) != 0) { ce->invalid_region.empty = FALSE; ce->invalid_region.delta = gtk_text_buffer_get_char_count (buffer); } else { ce->invalid_region.empty = TRUE; ce->invalid_region.delta = 0; } g_object_get (buffer, "highlight-syntax", &ce->highlight, NULL); ce->refresh_region = gtk_source_region_new (buffer); g_signal_connect_swapped (buffer, "notify::highlight-syntax", G_CALLBACK (buffer_notify_highlight_syntax_cb), ce); install_first_update (ce); } } /** * disable_syntax_analysis: * @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_syntax_analysis (GtkSourceContextEngine *ce) { if (!ce->disabled) { ce->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_SOURCE_IS_CONTEXT_ENGINE (engine)); g_return_if_fail (GTK_SOURCE_IS_STYLE_SCHEME (scheme) || scheme == NULL); ce = GTK_SOURCE_CONTEXT_ENGINE (engine); if (g_set_object (&ce->style_scheme, scheme)) { g_hash_table_foreach (ce->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->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); } g_assert (!ce->tags); g_assert (!ce->root_context); g_assert (!ce->root_segment); if (ce->first_update != 0) { g_source_remove (ce->first_update); ce->first_update = 0; } if (ce->incremental_update != 0) { g_source_remove (ce->incremental_update); ce->incremental_update = 0; } _gtk_source_context_data_unref (ce->ctx_data); if (ce->style_scheme != NULL) g_object_unref (ce->style_scheme); G_OBJECT_CLASS (_gtk_source_context_engine_parent_class)->finalize (object); } static void _gtk_source_engine_interface_init (GtkSourceEngineInterface *iface) { iface->attach_buffer = gtk_source_context_engine_attach_buffer; iface->text_inserted = gtk_source_context_engine_text_inserted; iface->text_deleted = gtk_source_context_engine_text_deleted; iface->update_highlight = gtk_source_context_engine_update_highlight; iface->set_style_scheme = gtk_source_context_engine_set_style_scheme; } static void _gtk_source_context_engine_class_init (GtkSourceContextEngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gtk_source_context_engine_finalize; } static void _gtk_source_context_engine_init (GtkSourceContextEngine *ce) { ce = _gtk_source_context_engine_get_instance_private (ce); } 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_SOURCE_TYPE_CONTEXT_ENGINE, NULL); ce->ctx_data = _gtk_source_context_data_ref (ctx_data); return ce; } /** * _gtk_source_context_data_new: * @lang: #GtkSourceLanguage. * * Creates new context definition set. It does not set lang->ctx_data, * that's lang business. */ GtkSourceContextData * _gtk_source_context_data_new (GtkSourceLanguage *lang) { GtkSourceContextData *ctx_data; g_return_val_if_fail (GTK_SOURCE_IS_LANGUAGE (lang), NULL); ctx_data = g_slice_new (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->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) _gtk_source_language_clear_ctx_data (ctx_data->lang, ctx_data); g_hash_table_destroy (ctx_data->definitions); g_slice_free (GtkSourceContextData, ctx_data); } } /* 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, GtkSourceRegex *regex, SubPatternWhere where) { GSList *sub_pattern_list = state->context->definition->sub_patterns; if (SEGMENT_IS_CONTAINER (state)) { gint start_pos; gint end_pos; _gtk_source_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 ("%s: oops", G_STRLOC); else if (line->start_at + end_pos > state->end_at) g_critical ("%s: oops", G_STRLOC); else state->start_len = line->start_at + end_pos - state->start_at; } else { if (line->start_at + start_pos < state->start_at) g_critical ("%s: oops", G_STRLOC); else if (line->start_at + end_pos != state->end_at) g_critical ("%s: oops", G_STRLOC); 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) { _gtk_source_regex_fetch_named_pos (regex, line->text, sp_def->u.name, &start_pos, &end_pos); } else { _gtk_source_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, GtkSourceRegex *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. */ _gtk_source_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 (!_gtk_source_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, GtkSourceRegex *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 GtkSourceRegex * create_reg_all (Context *context, ContextDefinition *definition) { DefinitionsIter iter; DefinitionChild *child_def; GString *all; GtkSourceRegex *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) { GtkSourceRegex *end; if (_gtk_source_regex_is_resolved (definition->u.start_end.end)) { end = definition->u.start_end.end; } else { g_return_val_if_fail (context && context->end, NULL); end = context->end; } g_string_append (all, _gtk_source_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, _gtk_source_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 ("%s: oops", G_STRLOC); */ 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) { GtkSourceRegex *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, _gtk_source_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 = _gtk_source_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_clear_error (&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 = _gtk_source_regex_resolve (definition->u.start_end.end, definition->u.start_end.start, line_text); } /* Create reg_all. If it is possible 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 && !_gtk_source_regex_is_resolved (definition->u.start_end.end))) { context->reg_all = create_reg_all (context, NULL); } else { if (!definition->reg_all) definition->reg_all = create_reg_all (NULL, definition); context->reg_all = _gtk_source_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 (gpointer text, Context *context, gpointer user_data) { 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; } g_assert (ptr != NULL); 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; guint i; if (context == NULL || --context->ref_count != 0) return; GTK_SOURCE_PROFILER_LOG ("destroying context %s", 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); _gtk_source_regex_unref (context->end); _gtk_source_regex_unref (context->reg_all); if (context->subpattern_context_classes != NULL) { for (i = 0; i < context->definition->n_sub_patterns; ++i) { g_slist_free_full (context->subpattern_context_classes[i], (GDestroyNotify)context_class_tag_free); } } g_slist_free_full (context->context_classes, (GDestroyNotify)context_class_tag_free); g_free (context->subpattern_context_classes); g_free (context->subpattern_tags); g_slice_free (Context, context); } static void context_freeze_hash_cb (gpointer text, Context *context, gpointer user_data) { 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); } static void context_thaw_cb (Context *ctx, gpointer user_data) { context_thaw (ctx); } /** * 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_cb, 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 || _gtk_source_regex_is_resolved (definition->u.start_end.end)) { 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 = _gtk_source_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, 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->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 segment. * @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->hint == segment) ce->hint = NULL; if (ce->hint2 == segment) ce->hint2 = NULL; if (SEGMENT_IS_INVALID (segment)) remove_invalid (ce, segment); context_unref (segment->context); #ifdef ENABLE_DEBUG g_assert (!g_slist_find (ce->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 (!_gtk_source_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->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->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 (!_gtk_source_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->hint2); apply_sub_patterns (new_segment, line, definition->u.match, SUB_PATTERN_WHERE_DEFAULT); ce->hint2 = new_segment; } /* Terminate parent if needed */ if (CONTEXT_ENDS_PARENT (new_context)) { do { ce->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 && _gtk_source_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 contexts 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 && _gtk_source_regex_is_resolved (current_context->end) && _gtk_source_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. * @had_bom: if a BOM was found in the buffer * * 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, gboolean had_bom) { gint pos = *line_pos; g_assert (!ce->hint2 || ce->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 (!_gtk_source_regex_match (state->context->reg_all, line->text, line->byte_length, pos)) { return FALSE; } _gtk_source_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 != had_bom) 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->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->hint2 || ce->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->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->hint2 != NULL) { Segment *s2 = ce->hint2; gboolean child = FALSE; while (s2 != NULL) { if (s2 == s) { child = TRUE; break; } s2 = s2->parent; } if (child) ce->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. * @had_bom: if the buffer had a BOM * * 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, gboolean had_bom) { gint line_pos = 0; GList *end_segments = NULL; GTimer *timer; g_assert (SEGMENT_IS_CONTAINER (state)); if (ce->hint2 == NULL || ce->hint2->parent != state) ce->hint2 = state->last_child; g_assert (!ce->hint2 || ce->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, had_bom)) break; if (g_timer_elapsed (timer, NULL) * 1000 > MAX_TIME_FOR_ONE_LINE) { g_critical ("%s", _("Highlighting a single line took too much time, " "syntax highlighting will be disabled")); disable_syntax_analysis (ce); break; } g_assert (new_state != NULL); g_assert (SEGMENT_IS_CONTAINER (new_state)); state = new_state; if (ce->hint2 == NULL || ce->hint2->parent != state) ce->hint2 = state->last_child; g_assert (!ce->hint2 || ce->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->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_end: 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->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->root_segment->end_at) return ce->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->root_segment) { static int c; g_print ("searching from root %d\n", ++c); } #endif result = get_segment_ (hint ? hint : ce->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->hint is being deleted, set it to some * neighbour segment */ if (ce->hint == segment) { if (segment->next != NULL) ce->hint = segment->next; else if (segment->prev != NULL) ce->hint = segment->prev; else ce->hint = segment->parent; } /* if ce->hint2 is being deleted, set it to some * neighbour segment */ if (ce->hint2 == segment) { if (segment->next != NULL) ce->hint2 = segment->next; else if (segment->prev != NULL) ce->hint2 = segment->prev; else ce->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. * * Recursively 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->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->root_segment; Segment *child, *hint_prev; if (root->children == NULL) return; if (hint == NULL) hint = ce->hint; if (hint != NULL) while (hint != NULL && hint->parent != ce->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->hint = next; continue; } if (child->start_at > end) { ce->hint = child; break; } segment_erase_range_ (ce, child, start, end); child = next; } child = hint_prev; while (child != NULL) { Segment *prev = child->prev; if (ce->hint == NULL) ce->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); } #define IS_BOM(c) (c == 0xFEFF) /** * 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). */ /* TODO it must be refactored. */ static void update_syntax (GtkSourceContextEngine *ce, const GtkTextIter *end, gint time) { GtkTextBuffer *buffer; GtkTextIter start_iter, end_iter; GtkTextIter line_start, line_end; Segment *state; Segment *invalid; gint start_offset, end_offset; gint line_start_offset, line_end_offset; gint analyzed_end; gboolean first_line = FALSE; gboolean had_bom = FALSE; GTimer *timer; buffer = ce->buffer; state = ce->root_segment; context_freeze (ce->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); } if (0 == start_offset) { gunichar c; first_line = TRUE; /* If it is the first line and it starts with BOM, skip it * since regexes in lang files do not take it into account */ c = gtk_text_iter_get_char (&start_iter); if (IS_BOM (c)) { had_bom = TRUE; gtk_text_iter_forward_char (&start_iter); start_offset = gtk_text_iter_get_offset (&start_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->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->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 (first_line) { state = ce->root_segment; } else { state = get_segment_at_offset (ce, ce->hint ? ce->hint : state, line_start_offset - 1); } g_assert (state->context != NULL); ce->hint2 = ce->hint; if (ce->hint2 != NULL && ce->hint2->parent != state) ce->hint2 = NULL; state = analyze_line (ce, state, &line, had_bom); /* At this point analyze_line() could have disabled highlighting */ if (ce->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->hint2 != NULL) ce->hint = ce->hint2; else ce->hint = state; line_info_destroy (&line); gtk_source_region_add_subregion (ce->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->hint ? ce->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); } first_line = (start_offset - had_bom) == line_start_offset; } if (analyzed_end == gtk_text_buffer_get_char_count (buffer)) { g_assert (g_slist_length (ce->invalid) <= 1); if (ce->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); GTK_SOURCE_PROFILER_LOG ("analyzed %d chars from %d to %d in %fms", 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->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_new (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 GSList * copy_context_classes (GSList *context_classes) { GSList *ret = NULL; while (context_classes) { ret = g_slist_prepend (ret, gtk_source_context_class_copy (context_classes->data)); context_classes = g_slist_next (context_classes); } return g_slist_reverse (ret); } static ContextDefinition * context_definition_new (const gchar *id, ContextType type, const gchar *match, const gchar *start, const gchar *end, const gchar *style, GSList *context_classes, 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; default: g_assert_not_reached (); } definition = g_slice_new0 (ContextDefinition); if (match != NULL) { definition->u.match = _gtk_source_regex_new (match, G_REGEX_ANCHORED, error); if (definition->u.match == NULL) { regex_error = TRUE; } else if (!_gtk_source_regex_is_resolved (definition->u.match)) { regex_error = TRUE; unresolved_error = TRUE; _gtk_source_regex_unref (definition->u.match); definition->u.match = NULL; } } if (start != NULL) { definition->u.start_end.start = _gtk_source_regex_new (start, G_REGEX_ANCHORED, error); if (definition->u.start_end.start == NULL) { regex_error = TRUE; } else if (!_gtk_source_regex_is_resolved (definition->u.start_end.start)) { regex_error = TRUE; unresolved_error = TRUE; _gtk_source_regex_unref (definition->u.start_end.start); definition->u.start_end.start = NULL; } } if (end != NULL && !regex_error) { definition->u.start_end.end = _gtk_source_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; definition->context_classes = copy_context_classes (context_classes); 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: _gtk_source_regex_unref (definition->u.match); break; case CONTEXT_TYPE_CONTAINER: _gtk_source_regex_unref (definition->u.start_end.start); _gtk_source_regex_unref (definition->u.start_end.end); break; default: g_assert_not_reached (); } 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_slist_free_full (sp_def->context_classes, (GDestroyNotify)gtk_source_context_class_free); 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); _gtk_source_regex_unref (definition->reg_all); g_slist_free_full (definition->context_classes, (GDestroyNotify)gtk_source_context_class_free); g_slist_free_full (definition->children, (GDestroyNotify)definition_child_free); 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, GSList *context_classes, 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 (gtk_source_context_data_lookup (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: g_assert (match_regex == NULL); break; default: g_assert_not_reached (); } 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 = gtk_source_context_data_lookup (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, context_classes, 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, GSList *context_classes, 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 (gtk_source_context_data_lookup (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 = gtk_source_context_data_lookup (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_new (SubPatternDefinition); #ifdef NEED_DEBUG_ID sp_def->id = g_strdup (id); #endif sp_def->style = g_strdup (style); sp_def->where = where_num; number = _gtk_source_utils_string_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++; sp_def->context_classes = copy_context_classes (context_classes); 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 = gtk_source_context_data_lookup (ctx_data, ref_id); parent = gtk_source_context_data_lookup (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”"), gtk_source_language_get_id (ctx_data->lang), 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 = gtk_source_context_data_lookup (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”"), gtk_source_language_get_id (data->ctx_data->lang), 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 = gtk_source_context_data_lookup (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 = gtk_source_context_data_lookup (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", gtk_source_language_get_id (ctx_data->lang), gtk_source_language_get_id (ctx_data->lang)); main_definition = gtk_source_context_data_lookup (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”)."), gtk_source_language_get_id (ctx_data->lang)); 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_clear_error (&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, 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, 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_clear_error (&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->invalid, segment) != NULL); else g_assert (g_slist_find (ce->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_tree (GtkSourceContextEngine *ce) { Segment *root = ce->root_segment; check_regex (); g_assert (root->start_at == 0); if (ce->invalid_region.empty) g_assert (root->end_at == gtk_text_buffer_get_char_count (ce->buffer)); g_assert (!root->parent); check_segment (ce, root); g_assert (!ce->root_context->parent); g_assert (root->context == ce->root_context); check_context (ce->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 */ 07070100000147000081A4000000000000000000000001665908060000061C000000000000000000000000000000000000003F00000000gtksourceview-5.12.1/gtksourceview/gtksourceencoding-private.h/* * This file is part of GtkSourceView * * Copyright 2014 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include #include "gtksourcetypes-private.h" G_BEGIN_DECLS /* * GtkSourceEncodingDuplicates: * @GTK_SOURCE_ENCODING_DUPLICATES_KEEP_FIRST: Keep the first occurrence. * @GTK_SOURCE_ENCODING_DUPLICATES_KEEP_LAST: Keep the last occurrence. * * Specifies which encoding occurrence to keep when removing duplicated * encodings in a list with [method@Encoding.remove_duplicates]. */ typedef enum _GtkSourceEncodingDuplicates { GTK_SOURCE_ENCODING_DUPLICATES_KEEP_FIRST, GTK_SOURCE_ENCODING_DUPLICATES_KEEP_LAST } GtkSourceEncodingDuplicates; GTK_SOURCE_INTERNAL GSList *_gtk_source_encoding_remove_duplicates (GSList *encodings, GtkSourceEncodingDuplicates removal_type); G_END_DECLS 07070100000148000081A40000000000000000000000016659080600004568000000000000000000000000000000000000003700000000gtksourceview-5.12.1/gtksourceview/gtksourceencoding.c/* * This file is part of GtkSourceView * * Copyright 2002-2005 - Paolo Maggi * Copyright 2014, 2015 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourceencoding.h" #include "gtksourceencoding-private.h" #include /** * GtkSourceEncoding: * * Character encoding. * * The #GtkSourceEncoding boxed type represents a character encoding. It is used * for example by #GtkSourceFile. Note that the text in GTK widgets is always * encoded in UTF-8. */ struct _GtkSourceEncoding { gint index; const gchar *charset; const gchar *name; }; G_DEFINE_BOXED_TYPE (GtkSourceEncoding, gtk_source_encoding, gtk_source_encoding_copy, gtk_source_encoding_free) /* * The original versions of the following tables are taken from profterm * * Copyright 2002 Red Hat, Inc. */ typedef enum _GtkSourceEncodingIndex { GTK_SOURCE_ENCODING_ISO_8859_1, GTK_SOURCE_ENCODING_ISO_8859_2, GTK_SOURCE_ENCODING_ISO_8859_3, GTK_SOURCE_ENCODING_ISO_8859_4, GTK_SOURCE_ENCODING_ISO_8859_5, GTK_SOURCE_ENCODING_ISO_8859_6, GTK_SOURCE_ENCODING_ISO_8859_7, GTK_SOURCE_ENCODING_ISO_8859_8, GTK_SOURCE_ENCODING_ISO_8859_9, GTK_SOURCE_ENCODING_ISO_8859_10, GTK_SOURCE_ENCODING_ISO_8859_13, GTK_SOURCE_ENCODING_ISO_8859_14, GTK_SOURCE_ENCODING_ISO_8859_15, GTK_SOURCE_ENCODING_ISO_8859_16, GTK_SOURCE_ENCODING_UTF_7, GTK_SOURCE_ENCODING_UTF_16, GTK_SOURCE_ENCODING_UTF_16_BE, GTK_SOURCE_ENCODING_UTF_16_LE, GTK_SOURCE_ENCODING_UTF_32, GTK_SOURCE_ENCODING_UCS_2, GTK_SOURCE_ENCODING_UCS_4, GTK_SOURCE_ENCODING_ARMSCII_8, GTK_SOURCE_ENCODING_BIG5, GTK_SOURCE_ENCODING_BIG5_HKSCS, GTK_SOURCE_ENCODING_CP_866, GTK_SOURCE_ENCODING_EUC_JP, GTK_SOURCE_ENCODING_EUC_JP_MS, GTK_SOURCE_ENCODING_CP932, GTK_SOURCE_ENCODING_EUC_KR, GTK_SOURCE_ENCODING_EUC_TW, GTK_SOURCE_ENCODING_GB18030, GTK_SOURCE_ENCODING_GB2312, GTK_SOURCE_ENCODING_GBK, GTK_SOURCE_ENCODING_GEOSTD8, GTK_SOURCE_ENCODING_IBM_850, GTK_SOURCE_ENCODING_IBM_852, GTK_SOURCE_ENCODING_IBM_855, GTK_SOURCE_ENCODING_IBM_857, GTK_SOURCE_ENCODING_IBM_862, GTK_SOURCE_ENCODING_IBM_864, GTK_SOURCE_ENCODING_ISO_2022_JP, GTK_SOURCE_ENCODING_ISO_2022_KR, GTK_SOURCE_ENCODING_ISO_IR_111, GTK_SOURCE_ENCODING_JOHAB, GTK_SOURCE_ENCODING_KOI8_R, GTK_SOURCE_ENCODING_KOI8__R, GTK_SOURCE_ENCODING_KOI8_U, GTK_SOURCE_ENCODING_SHIFT_JIS, GTK_SOURCE_ENCODING_TCVN, GTK_SOURCE_ENCODING_TIS_620, GTK_SOURCE_ENCODING_UHC, GTK_SOURCE_ENCODING_VISCII, GTK_SOURCE_ENCODING_WINDOWS_1250, GTK_SOURCE_ENCODING_WINDOWS_1251, GTK_SOURCE_ENCODING_WINDOWS_1252, GTK_SOURCE_ENCODING_WINDOWS_1253, GTK_SOURCE_ENCODING_WINDOWS_1254, GTK_SOURCE_ENCODING_WINDOWS_1255, GTK_SOURCE_ENCODING_WINDOWS_1256, GTK_SOURCE_ENCODING_WINDOWS_1257, GTK_SOURCE_ENCODING_WINDOWS_1258, GTK_SOURCE_ENCODING_LAST, GTK_SOURCE_ENCODING_UTF_8, GTK_SOURCE_ENCODING_UNKNOWN } GtkSourceEncodingIndex; static const GtkSourceEncoding utf8_encoding = { GTK_SOURCE_ENCODING_UTF_8, "UTF-8", N_("Unicode") }; /* Initialized in gtk_source_encoding_lazy_init(). */ static GtkSourceEncoding unknown_encoding = { GTK_SOURCE_ENCODING_UNKNOWN, NULL, NULL }; static const GtkSourceEncoding encodings[] = { { GTK_SOURCE_ENCODING_ISO_8859_1, "ISO-8859-1", N_("Western") }, { GTK_SOURCE_ENCODING_ISO_8859_2, "ISO-8859-2", N_("Central European") }, { GTK_SOURCE_ENCODING_ISO_8859_3, "ISO-8859-3", N_("South European") }, { GTK_SOURCE_ENCODING_ISO_8859_4, "ISO-8859-4", N_("Baltic") }, { GTK_SOURCE_ENCODING_ISO_8859_5, "ISO-8859-5", N_("Cyrillic") }, { GTK_SOURCE_ENCODING_ISO_8859_6, "ISO-8859-6", N_("Arabic") }, { GTK_SOURCE_ENCODING_ISO_8859_7, "ISO-8859-7", N_("Greek") }, { GTK_SOURCE_ENCODING_ISO_8859_8, "ISO-8859-8", N_("Hebrew Visual") }, { GTK_SOURCE_ENCODING_ISO_8859_9, "ISO-8859-9", N_("Turkish") }, { GTK_SOURCE_ENCODING_ISO_8859_10, "ISO-8859-10", N_("Nordic") }, { GTK_SOURCE_ENCODING_ISO_8859_13, "ISO-8859-13", N_("Baltic") }, { GTK_SOURCE_ENCODING_ISO_8859_14, "ISO-8859-14", N_("Celtic") }, { GTK_SOURCE_ENCODING_ISO_8859_15, "ISO-8859-15", N_("Western") }, { GTK_SOURCE_ENCODING_ISO_8859_16, "ISO-8859-16", N_("Romanian") }, { GTK_SOURCE_ENCODING_UTF_7, "UTF-7", N_("Unicode") }, { GTK_SOURCE_ENCODING_UTF_16, "UTF-16", N_("Unicode") }, { GTK_SOURCE_ENCODING_UTF_16_BE, "UTF-16BE", N_("Unicode") }, { GTK_SOURCE_ENCODING_UTF_16_LE, "UTF-16LE", N_("Unicode") }, { GTK_SOURCE_ENCODING_UTF_32, "UTF-32", N_("Unicode") }, { GTK_SOURCE_ENCODING_UCS_2, "UCS-2", N_("Unicode") }, { GTK_SOURCE_ENCODING_UCS_4, "UCS-4", N_("Unicode") }, { GTK_SOURCE_ENCODING_ARMSCII_8, "ARMSCII-8", N_("Armenian") }, { GTK_SOURCE_ENCODING_BIG5, "BIG5", N_("Chinese Traditional") }, { GTK_SOURCE_ENCODING_BIG5_HKSCS, "BIG5-HKSCS", N_("Chinese Traditional") }, { GTK_SOURCE_ENCODING_CP_866, "CP866", N_("Cyrillic/Russian") }, { GTK_SOURCE_ENCODING_EUC_JP, "EUC-JP", N_("Japanese") }, { GTK_SOURCE_ENCODING_EUC_JP_MS, "EUC-JP-MS", N_("Japanese") }, { GTK_SOURCE_ENCODING_CP932, "CP932", N_("Japanese") }, { GTK_SOURCE_ENCODING_EUC_KR, "EUC-KR", N_("Korean") }, { GTK_SOURCE_ENCODING_EUC_TW, "EUC-TW", N_("Chinese Traditional") }, { GTK_SOURCE_ENCODING_GB18030, "GB18030", N_("Chinese Simplified") }, { GTK_SOURCE_ENCODING_GB2312, "GB2312", N_("Chinese Simplified") }, { GTK_SOURCE_ENCODING_GBK, "GBK", N_("Chinese Simplified") }, { GTK_SOURCE_ENCODING_GEOSTD8, "GEORGIAN-ACADEMY", N_("Georgian") }, /* FIXME GEOSTD8 ? */ { GTK_SOURCE_ENCODING_IBM_850, "IBM850", N_("Western") }, { GTK_SOURCE_ENCODING_IBM_852, "IBM852", N_("Central European") }, { GTK_SOURCE_ENCODING_IBM_855, "IBM855", N_("Cyrillic") }, { GTK_SOURCE_ENCODING_IBM_857, "IBM857", N_("Turkish") }, { GTK_SOURCE_ENCODING_IBM_862, "IBM862", N_("Hebrew") }, { GTK_SOURCE_ENCODING_IBM_864, "IBM864", N_("Arabic") }, { GTK_SOURCE_ENCODING_ISO_2022_JP, "ISO-2022-JP", N_("Japanese") }, { GTK_SOURCE_ENCODING_ISO_2022_KR, "ISO-2022-KR", N_("Korean") }, { GTK_SOURCE_ENCODING_ISO_IR_111, "ISO-IR-111", N_("Cyrillic") }, { GTK_SOURCE_ENCODING_JOHAB, "JOHAB", N_("Korean") }, { GTK_SOURCE_ENCODING_KOI8_R, "KOI8R", N_("Cyrillic") }, { GTK_SOURCE_ENCODING_KOI8__R, "KOI8-R", N_("Cyrillic") }, { GTK_SOURCE_ENCODING_KOI8_U, "KOI8U", N_("Cyrillic/Ukrainian") }, { GTK_SOURCE_ENCODING_SHIFT_JIS, "SHIFT_JIS", N_("Japanese") }, { GTK_SOURCE_ENCODING_TCVN, "TCVN", N_("Vietnamese") }, { GTK_SOURCE_ENCODING_TIS_620, "TIS-620", N_("Thai") }, { GTK_SOURCE_ENCODING_UHC, "UHC", N_("Korean") }, { GTK_SOURCE_ENCODING_VISCII, "VISCII", N_("Vietnamese") }, { GTK_SOURCE_ENCODING_WINDOWS_1250, "WINDOWS-1250", N_("Central European") }, { GTK_SOURCE_ENCODING_WINDOWS_1251, "WINDOWS-1251", N_("Cyrillic") }, { GTK_SOURCE_ENCODING_WINDOWS_1252, "WINDOWS-1252", N_("Western") }, { GTK_SOURCE_ENCODING_WINDOWS_1253, "WINDOWS-1253", N_("Greek") }, { GTK_SOURCE_ENCODING_WINDOWS_1254, "WINDOWS-1254", N_("Turkish") }, { GTK_SOURCE_ENCODING_WINDOWS_1255, "WINDOWS-1255", N_("Hebrew") }, { GTK_SOURCE_ENCODING_WINDOWS_1256, "WINDOWS-1256", N_("Arabic") }, { GTK_SOURCE_ENCODING_WINDOWS_1257, "WINDOWS-1257", N_("Baltic") }, { GTK_SOURCE_ENCODING_WINDOWS_1258, "WINDOWS-1258", N_("Vietnamese") } }; static void gtk_source_encoding_lazy_init (void) { static gboolean initialized = FALSE; const gchar *locale_charset; if (G_LIKELY (initialized)) { return; } if (g_get_charset (&locale_charset) == FALSE) { unknown_encoding.charset = g_strdup (locale_charset); } initialized = TRUE; } /** * gtk_source_encoding_get_from_charset: * @charset: a character set. * * Gets a #GtkSourceEncoding from a character set such as "UTF-8" or * "ISO-8859-1". * * Returns: (nullable): the corresponding #GtkSourceEncoding, or %NULL * if not found. */ const GtkSourceEncoding * gtk_source_encoding_get_from_charset (const gchar *charset) { gint i; g_return_val_if_fail (charset != NULL, NULL); if (g_ascii_strcasecmp (charset, "UTF-8") == 0) { return gtk_source_encoding_get_utf8 (); } for (i = 0; i < GTK_SOURCE_ENCODING_LAST; i++) { if (g_ascii_strcasecmp (charset, encodings[i].charset) == 0) { return &encodings[i]; } } gtk_source_encoding_lazy_init (); if (unknown_encoding.charset != NULL && g_ascii_strcasecmp (charset, unknown_encoding.charset) == 0) { return &unknown_encoding; } return NULL; } /** * gtk_source_encoding_get_all: * * Gets all encodings. * * Returns: (transfer container) (element-type GtkSource.Encoding): a list of * all #GtkSourceEncoding's. Free with g_slist_free(). */ GSList * gtk_source_encoding_get_all (void) { GSList *all = NULL; gint i; for (i = GTK_SOURCE_ENCODING_LAST - 1; i >= 0; i--) { all = g_slist_prepend (all, (gpointer) &encodings[i]); } all = g_slist_prepend (all, (gpointer) &utf8_encoding); return all; } /** * gtk_source_encoding_get_utf8: * * Returns: the UTF-8 encoding. */ const GtkSourceEncoding * gtk_source_encoding_get_utf8 (void) { return &utf8_encoding; } /** * gtk_source_encoding_get_current: * * Gets the #GtkSourceEncoding for the current locale. * * See also [func@GLib.get_charset]. * * Returns: the current locale encoding. */ const GtkSourceEncoding * gtk_source_encoding_get_current (void) { static gboolean initialized = FALSE; static const GtkSourceEncoding *locale_encoding = NULL; const gchar *locale_charset; gtk_source_encoding_lazy_init (); if (G_LIKELY (initialized)) { return locale_encoding; } if (g_get_charset (&locale_charset)) { locale_encoding = &utf8_encoding; } else { locale_encoding = gtk_source_encoding_get_from_charset (locale_charset); } if (locale_encoding == NULL) { locale_encoding = &unknown_encoding; } initialized = TRUE; return locale_encoding; } /** * gtk_source_encoding_to_string: * @enc: a #GtkSourceEncoding. * * Returns: a string representation. Free with g_free() when no longer needed. */ gchar * gtk_source_encoding_to_string (const GtkSourceEncoding* enc) { g_return_val_if_fail (enc != NULL, NULL); gtk_source_encoding_lazy_init (); g_return_val_if_fail (enc->charset != NULL, NULL); if (enc->name != NULL) { return g_strdup_printf ("%s (%s)", _(enc->name), enc->charset); } else if (g_ascii_strcasecmp (enc->charset, "ANSI_X3.4-1968") == 0) { return g_strdup_printf ("US-ASCII (%s)", enc->charset); } else { return g_strdup (enc->charset); } } /** * gtk_source_encoding_get_charset: * @enc: a #GtkSourceEncoding. * * Gets the character set of the #GtkSourceEncoding, such as "UTF-8" or * "ISO-8859-1". * * Returns: the character set of the #GtkSourceEncoding. */ const gchar * gtk_source_encoding_get_charset (const GtkSourceEncoding* enc) { g_return_val_if_fail (enc != NULL, NULL); gtk_source_encoding_lazy_init (); g_return_val_if_fail (enc->charset != NULL, NULL); return enc->charset; } /** * gtk_source_encoding_get_name: * @enc: a #GtkSourceEncoding. * * Gets the name of the #GtkSourceEncoding such as "Unicode" or "Western". * * Returns: the name of the #GtkSourceEncoding. */ const gchar * gtk_source_encoding_get_name (const GtkSourceEncoding* enc) { g_return_val_if_fail (enc != NULL, NULL); gtk_source_encoding_lazy_init (); return (enc->name == NULL) ? _("Unknown") : _(enc->name); } static GSList * strv_to_list (const gchar * const *enc_str) { GSList *res = NULL; gchar **p; for (p = (gchar **)enc_str; p != NULL && *p != NULL; p++) { const gchar *charset = *p; const GtkSourceEncoding *enc; if (g_str_equal (charset, "CURRENT")) { g_get_charset (&charset); } g_return_val_if_fail (charset != NULL, NULL); enc = gtk_source_encoding_get_from_charset (charset); if (enc != NULL && g_slist_find (res, enc) == NULL) { res = g_slist_prepend (res, (gpointer)enc); } } return g_slist_reverse (res); } static GSList * remove_duplicates_keep_first (GSList *list) { GSList *new_list = NULL; GSList *l; for (l = list; l != NULL; l = l->next) { gpointer cur_encoding = l->data; if (g_slist_find (new_list, cur_encoding) == NULL) { new_list = g_slist_prepend (new_list, cur_encoding); } } new_list = g_slist_reverse (new_list); g_slist_free (list); return new_list; } static GSList * remove_duplicates_keep_last (GSList *list) { GSList *new_list = NULL; GSList *l; list = g_slist_reverse (list); for (l = list; l != NULL; l = l->next) { gpointer cur_encoding = l->data; if (g_slist_find (new_list, cur_encoding) == NULL) { new_list = g_slist_prepend (new_list, cur_encoding); } } g_slist_free (list); return new_list; } /* * _gtk_source_encoding_remove_duplicates: * @list: (element-type GtkSource.Encoding): a list of #GtkSourceEncoding's. * @removal_type: the #GtkSourceEncodingDuplicates. * * A convenience function to remove duplicated encodings in a list. * * Returns: (transfer container) (element-type GtkSource.Encoding): the new * start of the #GSList. */ GSList * _gtk_source_encoding_remove_duplicates (GSList *list, GtkSourceEncodingDuplicates removal_type) { switch (removal_type) { case GTK_SOURCE_ENCODING_DUPLICATES_KEEP_FIRST: return remove_duplicates_keep_first (list); case GTK_SOURCE_ENCODING_DUPLICATES_KEEP_LAST: return remove_duplicates_keep_last (list); default: break; } g_return_val_if_reached (list); } /** * gtk_source_encoding_get_default_candidates: * * Gets the list of default candidate encodings to try when loading a file. * * See [method@FileLoader.set_candidate_encodings]. * * This function returns a different list depending on the current locale (i.e. * language, country and default encoding). The UTF-8 encoding and the current * locale encoding are guaranteed to be present in the returned list. * * Returns: (transfer container) (element-type GtkSource.Encoding): the list of * default candidate encodings. Free with g_slist_free(). */ GSList * gtk_source_encoding_get_default_candidates (void) { const gchar *encodings_str; const gchar *encodings_str_translated; GVariant *encodings_variant; const gchar **encodings_strv; GSList *encodings_list; GError *error = NULL; /* Translators: This is the sorted list of encodings used by * GtkSourceView for automatic detection of the file encoding. You may * want to customize it adding encodings that are common in your * country, for instance the GB18030 encoding for the Chinese * translation. You may also want to remove the ISO-8859-15 encoding * (covering English and most Western European languages) if you think * people in your country will rarely use it. "CURRENT" is a magic * value used by GtkSourceView and it represents the encoding for the * current locale, so please don't translate the "CURRENT" term. Only * recognized encodings are used. See * https://gitlab.gnome.org/GNOME/gtksourceview/blob/master/gtksourceview/gtksourceencoding.c#L142 * for a list of supported encodings. * Keep the same format: square brackets, single quotes, commas. */ encodings_str = N_("['UTF-8', 'CURRENT', 'ISO-8859-15', 'UTF-16']"); encodings_str_translated = _(encodings_str); encodings_variant = g_variant_parse (G_VARIANT_TYPE_STRING_ARRAY, encodings_str_translated, NULL, NULL, &error); if (error != NULL) { const gchar * const *language_names = g_get_language_names (); g_warning ("Error while parsing encodings list for locale %s:\n" "Translated list: %s\n" "Error message: %s", language_names[0], encodings_str_translated, error->message); g_clear_error (&error); encodings_variant = g_variant_parse (G_VARIANT_TYPE_STRING_ARRAY, encodings_str, NULL, NULL, &error); g_assert_no_error (error); } encodings_strv = g_variant_get_strv (encodings_variant, NULL); encodings_list = strv_to_list (encodings_strv); g_free ((gpointer) encodings_strv); /* Ensure that UTF-8 and CURRENT are present. */ encodings_list = g_slist_prepend (encodings_list, (gpointer) gtk_source_encoding_get_current ()); encodings_list = g_slist_prepend (encodings_list, (gpointer) &utf8_encoding); encodings_list = _gtk_source_encoding_remove_duplicates (encodings_list, GTK_SOURCE_ENCODING_DUPLICATES_KEEP_LAST); g_variant_unref (encodings_variant); return encodings_list; } /** * gtk_source_encoding_copy: * @enc: a #GtkSourceEncoding. * * Used by language bindings. * * Returns: a copy of @enc. */ GtkSourceEncoding * gtk_source_encoding_copy (const GtkSourceEncoding *enc) { g_return_val_if_fail (enc != NULL, NULL); return (GtkSourceEncoding *) enc; } /** * gtk_source_encoding_free: * @enc: a #GtkSourceEncoding. * * Used by language bindings. */ void gtk_source_encoding_free (GtkSourceEncoding *enc) { g_return_if_fail (enc != NULL); } 07070100000149000081A400000000000000000000000166590806000009E8000000000000000000000000000000000000003700000000gtksourceview-5.12.1/gtksourceview/gtksourceencoding.h/* * This file is part of GtkSourceView * * Copyright 2002-2005 - Paolo Maggi * Copyright 2014, 2015 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_ENCODING (gtk_source_encoding_get_type ()) GTK_SOURCE_AVAILABLE_IN_ALL GType gtk_source_encoding_get_type (void) G_GNUC_CONST; GTK_SOURCE_AVAILABLE_IN_ALL const GtkSourceEncoding *gtk_source_encoding_get_from_charset (const gchar *charset); GTK_SOURCE_AVAILABLE_IN_ALL gchar *gtk_source_encoding_to_string (const GtkSourceEncoding *enc); GTK_SOURCE_AVAILABLE_IN_ALL const gchar *gtk_source_encoding_get_name (const GtkSourceEncoding *enc); GTK_SOURCE_AVAILABLE_IN_ALL const gchar *gtk_source_encoding_get_charset (const GtkSourceEncoding *enc); GTK_SOURCE_AVAILABLE_IN_ALL const GtkSourceEncoding *gtk_source_encoding_get_utf8 (void); GTK_SOURCE_AVAILABLE_IN_ALL const GtkSourceEncoding *gtk_source_encoding_get_current (void); GTK_SOURCE_AVAILABLE_IN_ALL GSList *gtk_source_encoding_get_all (void); GTK_SOURCE_AVAILABLE_IN_ALL GSList *gtk_source_encoding_get_default_candidates (void); /* These should not be used, they are just to make python bindings happy */ GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceEncoding *gtk_source_encoding_copy (const GtkSourceEncoding *enc); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_encoding_free (GtkSourceEncoding *enc); G_END_DECLS 0707010000014A000081A40000000000000000000000016659080600000BF6000000000000000000000000000000000000003D00000000gtksourceview-5.12.1/gtksourceview/gtksourceengine-private.h/* * This file is part of GtkSourceView * * Copyright 2003 - Gustavo Giráldez * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include #include "gtksourcetypes.h" #include "gtksourcetypes-private.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_ENGINE (_gtk_source_engine_get_type()) G_GNUC_INTERNAL G_DECLARE_INTERFACE (GtkSourceEngine, _gtk_source_engine, GTK_SOURCE, ENGINE, GObject) struct _GtkSourceEngineInterface { GTypeInterface parent_interface; 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); }; G_GNUC_INTERNAL void _gtk_source_engine_attach_buffer (GtkSourceEngine *engine, GtkTextBuffer *buffer); G_GNUC_INTERNAL void _gtk_source_engine_text_inserted (GtkSourceEngine *engine, gint start_offset, gint end_offset); G_GNUC_INTERNAL void _gtk_source_engine_text_deleted (GtkSourceEngine *engine, gint offset, gint length); G_GNUC_INTERNAL void _gtk_source_engine_update_highlight (GtkSourceEngine *engine, const GtkTextIter *start, const GtkTextIter *end, gboolean synchronous); G_GNUC_INTERNAL void _gtk_source_engine_set_style_scheme (GtkSourceEngine *engine, GtkSourceStyleScheme *scheme); G_END_DECLS 0707010000014B000081A40000000000000000000000016659080600000CB2000000000000000000000000000000000000003500000000gtksourceview-5.12.1/gtksourceview/gtksourceengine.c/* * This file is part of GtkSourceView * * Copyright 2003 - Gustavo Giráldez * * GtkSourceView 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. * * GtkSourceView 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, see . */ /* Interface for syntax highlighting engines. */ #include "config.h" #include "gtksourceengine-private.h" #include "gtksourcestylescheme.h" G_DEFINE_INTERFACE (GtkSourceEngine, _gtk_source_engine, G_TYPE_OBJECT) static void _gtk_source_engine_default_init (GtkSourceEngineInterface *interface) { } void _gtk_source_engine_attach_buffer (GtkSourceEngine *engine, GtkTextBuffer *buffer) { g_return_if_fail (GTK_SOURCE_IS_ENGINE (engine)); g_return_if_fail (GTK_SOURCE_ENGINE_GET_IFACE (engine)->attach_buffer != NULL); GTK_SOURCE_ENGINE_GET_IFACE (engine)->attach_buffer (engine, buffer); } void _gtk_source_engine_text_inserted (GtkSourceEngine *engine, gint start_offset, gint end_offset) { g_return_if_fail (GTK_SOURCE_IS_ENGINE (engine)); g_return_if_fail (GTK_SOURCE_ENGINE_GET_IFACE (engine)->text_inserted != NULL); GTK_SOURCE_ENGINE_GET_IFACE (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_SOURCE_IS_ENGINE (engine)); g_return_if_fail (GTK_SOURCE_ENGINE_GET_IFACE (engine)->text_deleted != NULL); GTK_SOURCE_ENGINE_GET_IFACE (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_SOURCE_IS_ENGINE (engine)); g_return_if_fail (start != NULL && end != NULL); g_return_if_fail (GTK_SOURCE_ENGINE_GET_IFACE (engine)->update_highlight != NULL); GTK_SOURCE_ENGINE_GET_IFACE (engine)->update_highlight (engine, start, end, synchronous); } void _gtk_source_engine_set_style_scheme (GtkSourceEngine *engine, GtkSourceStyleScheme *scheme) { g_return_if_fail (GTK_SOURCE_IS_ENGINE (engine)); g_return_if_fail (GTK_SOURCE_IS_STYLE_SCHEME (scheme) || scheme == NULL); g_return_if_fail (GTK_SOURCE_ENGINE_GET_IFACE (engine)->set_style_scheme != NULL); GTK_SOURCE_ENGINE_GET_IFACE (engine)->set_style_scheme (engine, scheme); } 0707010000014C000081A40000000000000000000000016659080600000A44000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/gtksourceview/gtksourcefile-private.h/* * This file is part of GtkSourceView * * Copyright 2014, 2015 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include "gtksourcefile.h" G_BEGIN_DECLS G_GNUC_INTERNAL void _gtk_source_file_set_encoding (GtkSourceFile *file, const GtkSourceEncoding *encoding); G_GNUC_INTERNAL void _gtk_source_file_set_newline_type (GtkSourceFile *file, GtkSourceNewlineType newline_type); G_GNUC_INTERNAL void _gtk_source_file_set_compression_type (GtkSourceFile *file, GtkSourceCompressionType compression_type); G_GNUC_INTERNAL GMountOperation *_gtk_source_file_create_mount_operation (GtkSourceFile *file); G_GNUC_INTERNAL gboolean _gtk_source_file_get_modification_time (GtkSourceFile *file, gint64 *modification_time); G_GNUC_INTERNAL void _gtk_source_file_set_modification_time (GtkSourceFile *file, gint64 modification_time); G_GNUC_INTERNAL void _gtk_source_file_set_externally_modified (GtkSourceFile *file, gboolean externally_modified); G_GNUC_INTERNAL void _gtk_source_file_set_deleted (GtkSourceFile *file, gboolean deleted); G_GNUC_INTERNAL void _gtk_source_file_set_readonly (GtkSourceFile *file, gboolean readonly); G_END_DECLS 0707010000014D000081A4000000000000000000000001665908060000465E000000000000000000000000000000000000003300000000gtksourceview-5.12.1/gtksourceview/gtksourcefile.c/* * This file is part of GtkSourceView * * Copyright 2014, 2015 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourcefile-private.h" #include "gtksourceencoding.h" #include "gtksource-enumtypes.h" /** * GtkSourceFile: * * On-disk representation of a [class@Buffer]. * * A `GtkSourceFile` object is the on-disk representation of a [class@Buffer]. * With a `GtkSourceFile`, you can create and configure a [class@FileLoader] * and [class@FileSaver] which take by default the values of the * `GtkSourceFile` properties (except for the file loader which auto-detect some * properties). On a successful load or save operation, the `GtkSourceFile` * properties are updated. If an operation fails, the `GtkSourceFile` properties * have still the previous valid values. */ enum { PROP_0, PROP_LOCATION, PROP_ENCODING, PROP_NEWLINE_TYPE, PROP_COMPRESSION_TYPE, PROP_READ_ONLY, N_PROPS }; typedef struct { GFile *location; const GtkSourceEncoding *encoding; GtkSourceNewlineType newline_type; GtkSourceCompressionType compression_type; GtkSourceMountOperationFactory mount_operation_factory; gpointer mount_operation_userdata; GDestroyNotify mount_operation_notify; /* Last known modification time of 'location'. The value is updated on a * file loading or file saving. */ gint64 modification_time; guint modification_time_set : 1; guint externally_modified : 1; guint deleted : 1; guint readonly : 1; } GtkSourceFilePrivate; G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceFile, gtk_source_file, G_TYPE_OBJECT) static GParamSpec *properties[N_PROPS]; static void gtk_source_file_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceFile *file = GTK_SOURCE_FILE (object); GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); switch (prop_id) { case PROP_LOCATION: g_value_set_object (value, priv->location); break; case PROP_ENCODING: g_value_set_boxed (value, priv->encoding); break; case PROP_NEWLINE_TYPE: g_value_set_enum (value, priv->newline_type); break; case PROP_COMPRESSION_TYPE: g_value_set_enum (value, priv->compression_type); break; case PROP_READ_ONLY: g_value_set_boolean (value, priv->readonly); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_file_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceFile *file = GTK_SOURCE_FILE (object); switch (prop_id) { case PROP_LOCATION: gtk_source_file_set_location (file, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_file_dispose (GObject *object) { GtkSourceFile *file = GTK_SOURCE_FILE (object); GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_clear_object (&priv->location); if (priv->mount_operation_notify != NULL) { priv->mount_operation_notify (priv->mount_operation_userdata); priv->mount_operation_notify = NULL; } G_OBJECT_CLASS (gtk_source_file_parent_class)->dispose (object); } static void gtk_source_file_class_init (GtkSourceFileClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = gtk_source_file_get_property; object_class->set_property = gtk_source_file_set_property; object_class->dispose = gtk_source_file_dispose; /** * GtkSourceFile:location: * * The location. */ properties [PROP_LOCATION] = g_param_spec_object ("location", "Location", "", G_TYPE_FILE, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFile:encoding: * * The character encoding, initially %NULL. After a successful file * loading or saving operation, the encoding is non-%NULL. */ properties[PROP_ENCODING] = g_param_spec_boxed ("encoding", "Encoding", "", GTK_SOURCE_TYPE_ENCODING, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFile:newline-type: * * The line ending type. */ properties[PROP_NEWLINE_TYPE] = g_param_spec_enum ("newline-type", "Newline type", "", GTK_SOURCE_TYPE_NEWLINE_TYPE, GTK_SOURCE_NEWLINE_TYPE_LF, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFile:compression-type: * * The compression type. */ properties [PROP_COMPRESSION_TYPE] = g_param_spec_enum ("compression-type", "Compression type", "", GTK_SOURCE_TYPE_COMPRESSION_TYPE, GTK_SOURCE_COMPRESSION_TYPE_NONE, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFile:read-only: * * Whether the file is read-only or not. The value of this property is * not updated automatically (there is no file monitors). */ properties [PROP_READ_ONLY] = g_param_spec_boolean ("read-only", "Read Only", "", FALSE, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); } static void gtk_source_file_init (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); priv->encoding = NULL; priv->newline_type = GTK_SOURCE_NEWLINE_TYPE_LF; priv->compression_type = GTK_SOURCE_COMPRESSION_TYPE_NONE; } /** * gtk_source_file_new: * * Returns: a new #GtkSourceFile object. */ GtkSourceFile * gtk_source_file_new (void) { return g_object_new (GTK_SOURCE_TYPE_FILE, NULL); } /** * gtk_source_file_set_location: * @file: a #GtkSourceFile. * @location: (nullable): the new #GFile, or %NULL. * * Sets the location. */ void gtk_source_file_set_location (GtkSourceFile *file, GFile *location) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_if_fail (GTK_SOURCE_IS_FILE (file)); g_return_if_fail (location == NULL || G_IS_FILE (location)); if (g_set_object (&priv->location, location)) { g_object_notify_by_pspec (G_OBJECT (file), properties[PROP_LOCATION]); /* The modification_time is for the old location. */ priv->modification_time_set = FALSE; priv->externally_modified = FALSE; priv->deleted = FALSE; } } /** * gtk_source_file_get_location: * @file: a #GtkSourceFile. * * Returns: (transfer none): the #GFile. */ GFile * gtk_source_file_get_location (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL); return priv->location; } void _gtk_source_file_set_encoding (GtkSourceFile *file, const GtkSourceEncoding *encoding) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_if_fail (GTK_SOURCE_IS_FILE (file)); if (priv->encoding != encoding) { priv->encoding = encoding; g_object_notify_by_pspec (G_OBJECT (file), properties[PROP_ENCODING]); } } /** * gtk_source_file_get_encoding: * @file: a #GtkSourceFile. * * The encoding is initially %NULL. After a successful file loading or saving * operation, the encoding is non-%NULL. * * Returns: the character encoding. */ const GtkSourceEncoding * gtk_source_file_get_encoding (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL); return priv->encoding; } void _gtk_source_file_set_newline_type (GtkSourceFile *file, GtkSourceNewlineType newline_type) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_if_fail (GTK_SOURCE_IS_FILE (file)); if (priv->newline_type != newline_type) { priv->newline_type = newline_type; g_object_notify_by_pspec (G_OBJECT (file), properties[PROP_NEWLINE_TYPE]); } } /** * gtk_source_file_get_newline_type: * @file: a #GtkSourceFile. * * Returns: the newline type. */ GtkSourceNewlineType gtk_source_file_get_newline_type (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), GTK_SOURCE_NEWLINE_TYPE_DEFAULT); return priv->newline_type; } void _gtk_source_file_set_compression_type (GtkSourceFile *file, GtkSourceCompressionType compression_type) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_if_fail (GTK_SOURCE_IS_FILE (file)); if (priv->compression_type != compression_type) { priv->compression_type = compression_type; g_object_notify_by_pspec (G_OBJECT (file), properties[PROP_COMPRESSION_TYPE]); } } /** * gtk_source_file_get_compression_type: * @file: a #GtkSourceFile. * * Returns: the compression type. */ GtkSourceCompressionType gtk_source_file_get_compression_type (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), GTK_SOURCE_COMPRESSION_TYPE_NONE); return priv->compression_type; } /** * gtk_source_file_set_mount_operation_factory: * @file: a #GtkSourceFile. * @callback: (scope notified): a #GtkSourceMountOperationFactory to call when a * #GMountOperation is needed. * @user_data: (closure): the data to pass to the @callback function. * @notify: (nullable): function to call on @user_data when the @callback is no * longer needed, or %NULL. * * Sets a [callback@MountOperationFactory] function that will be called when a * [class@Gio.MountOperation] must be created. * * This is useful for creating a [class@Gtk.MountOperation] with the parent [class@Gtk.Window]. * * If a mount operation factory isn't set, [ctor@Gio.MountOperation.new] will be * called. */ void gtk_source_file_set_mount_operation_factory (GtkSourceFile *file, GtkSourceMountOperationFactory callback, gpointer user_data, GDestroyNotify notify) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_if_fail (GTK_SOURCE_IS_FILE (file)); if (priv->mount_operation_notify != NULL) { priv->mount_operation_notify (priv->mount_operation_userdata); } priv->mount_operation_factory = callback; priv->mount_operation_userdata = user_data; priv->mount_operation_notify = notify; } GMountOperation * _gtk_source_file_create_mount_operation (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); return (file != NULL && priv->mount_operation_factory != NULL) ? priv->mount_operation_factory (file, priv->mount_operation_userdata) : g_mount_operation_new (); } gboolean _gtk_source_file_get_modification_time (GtkSourceFile *file, gint64 *modification_time) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_assert (modification_time != NULL); if (file == NULL) { return FALSE; } g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), FALSE); if (priv->modification_time_set) { *modification_time = priv->modification_time; } return priv->modification_time_set; } void _gtk_source_file_set_modification_time (GtkSourceFile *file, gint64 modification_time) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); if (file != NULL) { g_return_if_fail (GTK_SOURCE_IS_FILE (file)); priv->modification_time = modification_time; priv->modification_time_set = TRUE; } } /** * gtk_source_file_is_local: * @file: a #GtkSourceFile. * * Returns whether the file is local. If the [property@File:location] is %NULL, * returns %FALSE. * * Returns: whether the file is local. */ gboolean gtk_source_file_is_local (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), FALSE); if (priv->location == NULL) { return FALSE; } return g_file_has_uri_scheme (priv->location, "file"); } /** * gtk_source_file_check_file_on_disk: * @file: a #GtkSourceFile. * * Checks synchronously the file on disk, to know whether the file is externally * modified, or has been deleted, and whether the file is read-only. * * #GtkSourceFile doesn't create a [class@Gio.FileMonitor] to track those properties, so * this function needs to be called instead. Creating lots of [class@Gio.FileMonitor]'s * would take lots of resources. * * Since this function is synchronous, it is advised to call it only on local * files. See [method@File.is_local]. */ void gtk_source_file_check_file_on_disk (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); GFileInfo *info; if (priv->location == NULL) { return; } info = g_file_query_info (priv->location, G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (info == NULL) { priv->deleted = TRUE; return; } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED) && priv->modification_time_set) { GDateTime *dt; gint64 mtime; dt = g_file_info_get_modification_date_time (info); mtime = g_date_time_to_unix (dt); /* Note that the modification time can even go backwards if the * user is copying over an old file. */ if (mtime != priv->modification_time) { priv->externally_modified = TRUE; } g_date_time_unref (dt); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) { gboolean readonly; readonly = !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); _gtk_source_file_set_readonly (file, readonly); } g_object_unref (info); } void _gtk_source_file_set_externally_modified (GtkSourceFile *file, gboolean externally_modified) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_if_fail (GTK_SOURCE_IS_FILE (file)); priv->externally_modified = externally_modified != FALSE; } /** * gtk_source_file_is_externally_modified: * @file: a #GtkSourceFile. * * Returns whether the file is externally modified. If the * [property@File:location] is %NULL, returns %FALSE. * * To have an up-to-date value, you must first call * [method@File.check_file_on_disk]. * * Returns: whether the file is externally modified. */ gboolean gtk_source_file_is_externally_modified (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), FALSE); return priv->externally_modified; } void _gtk_source_file_set_deleted (GtkSourceFile *file, gboolean deleted) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_if_fail (GTK_SOURCE_IS_FILE (file)); priv->deleted = deleted != FALSE; } /** * gtk_source_file_is_deleted: * @file: a #GtkSourceFile. * * Returns whether the file has been deleted. If the * [property@File:location] is %NULL, returns %FALSE. * * To have an up-to-date value, you must first call * [method@File.check_file_on_disk]. * * Returns: whether the file has been deleted. */ gboolean gtk_source_file_is_deleted (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), FALSE); return priv->deleted; } void _gtk_source_file_set_readonly (GtkSourceFile *file, gboolean readonly) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_if_fail (GTK_SOURCE_IS_FILE (file)); readonly = readonly != FALSE; if (priv->readonly != readonly) { priv->readonly = readonly; g_object_notify_by_pspec (G_OBJECT (file), properties[PROP_READ_ONLY]); } } /** * gtk_source_file_is_readonly: * @file: a #GtkSourceFile. * * Returns whether the file is read-only. If the * [property@File:location] is %NULL, returns %FALSE. * * To have an up-to-date value, you must first call * [method@File.check_file_on_disk]. * * Returns: whether the file is read-only. */ gboolean gtk_source_file_is_readonly (GtkSourceFile *file) { GtkSourceFilePrivate *priv = gtk_source_file_get_instance_private (file); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), FALSE); return priv->readonly; } 0707010000014E000081A4000000000000000000000001665908060000128B000000000000000000000000000000000000003300000000gtksourceview-5.12.1/gtksourceview/gtksourcefile.h/* * This file is part of GtkSourceView * * Copyright 2014, 2015 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_FILE (gtk_source_file_get_type()) /** * GtkSourceNewlineType: * @GTK_SOURCE_NEWLINE_TYPE_LF: line feed, used on UNIX. * @GTK_SOURCE_NEWLINE_TYPE_CR: carriage return, used on Mac. * @GTK_SOURCE_NEWLINE_TYPE_CR_LF: carriage return followed by a line feed, used * on Windows. */ typedef enum _GtkSourceNewlineType { GTK_SOURCE_NEWLINE_TYPE_LF, GTK_SOURCE_NEWLINE_TYPE_CR, GTK_SOURCE_NEWLINE_TYPE_CR_LF } GtkSourceNewlineType; /** * GTK_SOURCE_NEWLINE_TYPE_DEFAULT: * * The default newline type on the current OS. */ #ifdef G_OS_WIN32 #define GTK_SOURCE_NEWLINE_TYPE_DEFAULT GTK_SOURCE_NEWLINE_TYPE_CR_LF #else #define GTK_SOURCE_NEWLINE_TYPE_DEFAULT GTK_SOURCE_NEWLINE_TYPE_LF #endif /** * GtkSourceCompressionType: * @GTK_SOURCE_COMPRESSION_TYPE_NONE: plain text. * @GTK_SOURCE_COMPRESSION_TYPE_GZIP: gzip compression. */ typedef enum _GtkSourceCompressionType { GTK_SOURCE_COMPRESSION_TYPE_NONE, GTK_SOURCE_COMPRESSION_TYPE_GZIP } GtkSourceCompressionType; /** * GtkSourceMountOperationFactory: * @file: a #GtkSourceFile. * @userdata: user data * * Type definition for a function that will be called to create a * [class@Gio.MountOperation]. This is useful for creating a [class@Gtk.MountOperation]. */ typedef GMountOperation *(*GtkSourceMountOperationFactory) (GtkSourceFile *file, gpointer userdata); GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_DERIVABLE_TYPE (GtkSourceFile, gtk_source_file, GTK_SOURCE, FILE, GObject) struct _GtkSourceFileClass { GObjectClass parent_class; /*< private >*/ gpointer _reserved[10]; }; GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceFile *gtk_source_file_new (void); GTK_SOURCE_AVAILABLE_IN_ALL GFile *gtk_source_file_get_location (GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_set_location (GtkSourceFile *file, GFile *location); GTK_SOURCE_AVAILABLE_IN_ALL const GtkSourceEncoding *gtk_source_file_get_encoding (GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceNewlineType gtk_source_file_get_newline_type (GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceCompressionType gtk_source_file_get_compression_type (GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_set_mount_operation_factory (GtkSourceFile *file, GtkSourceMountOperationFactory callback, gpointer user_data, GDestroyNotify notify); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_check_file_on_disk (GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_file_is_local (GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_file_is_externally_modified (GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_file_is_deleted (GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_file_is_readonly (GtkSourceFile *file); G_END_DECLS 0707010000014F000081A400000000000000000000000166590806000093EA000000000000000000000000000000000000003900000000gtksourceview-5.12.1/gtksourceview/gtksourcefileloader.c/* * This file is part of GtkSourceView * * Copyright 2005 - Paolo Maggi * Copyright 2007 - Paolo Maggi, Steve Frécinaux * Copyright 2008 - Jesse van den Kieboom * Copyright 2014, 2016 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include #include "gtksourcefileloader.h" #include "gtksourcebuffer-private.h" #include "gtksourcefile-private.h" #include "gtksourcebufferoutputstream-private.h" #include "gtksourceencoding.h" #include "gtksourceencoding-private.h" #include "gtksource-enumtypes.h" #include "gtksourcetrace.h" #include "gtksourceutils-private.h" /** * GtkSourceFileLoader: * * Load a file into a GtkSourceBuffer. * * A `GtkSourceFileLoader` object permits to load the contents of a [iface@Gio.File] or a * [class@Gio.InputStream] into a [class@Buffer]. * * A file loader should be used only for one load operation, including errors * handling. If an error occurs, you can reconfigure the loader and relaunch the * operation with [method@FileLoader.load_async]. * * Running a `GtkSourceFileLoader` is an undoable action for the * [class@Buffer]. * * After a file loading, the buffer is reset to the contents provided by the * [iface@Gio.File] or [class@Gio.InputStream], so the buffer is set as “unmodified”, that is, * [method@Gtk.TextBuffer.set_modified] is called with %FALSE. If the contents isn't * saved somewhere (for example if you load from stdin), then you should * probably call [method@Gtk.TextBuffer.set_modified] with %TRUE after calling * [method@FileLoader.load_finish]. */ enum { PROP_0, PROP_BUFFER, PROP_FILE, PROP_LOCATION, PROP_INPUT_STREAM }; #define READ_N_PAGES 2 #define READ_CHUNK_SIZE (_gtk_source_utils_get_page_size()*READ_N_PAGES) #define LOADER_QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \ G_FILE_ATTRIBUTE_STANDARD_TYPE "," \ G_FILE_ATTRIBUTE_TIME_MODIFIED "," \ G_FILE_ATTRIBUTE_STANDARD_SIZE "," \ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE struct _GtkSourceFileLoader { GObject parent_instance; /* Weak ref to the GtkSourceBuffer. A strong ref could create a * reference cycle in an application. For example a subclass of * GtkSourceBuffer can have a strong ref to the FileLoader. */ GtkSourceBuffer *source_buffer; /* Weak ref to the GtkSourceFile. A strong ref could create a reference * cycle in an application. For example a subclass of GtkSourceFile can * have a strong ref to the FileLoader. */ GtkSourceFile *file; GFile *location; /* The value of the :input-stream property. Do not confuse with the * input_stream field in TaskData. */ GInputStream *input_stream_property; GSList *candidate_encodings; const GtkSourceEncoding *auto_detected_encoding; GtkSourceNewlineType auto_detected_newline_type; GtkSourceCompressionType auto_detected_compression_type; GTask *task; gint64 load_begin_time; }; typedef struct { /* The two streams cannot be spliced directly, because: * (1) We need to call the progress callback. * (2) Sync methods must be used for the output stream, and async * methods for the input stream. */ GInputStream *input_stream; GtkSourceBufferOutputStream *output_stream; GFileInfo *info; GFileProgressCallback progress_cb; gpointer progress_cb_data; GDestroyNotify progress_cb_notify; goffset total_bytes_read; goffset total_size; gssize chunk_bytes_read; gchar *chunk_buffer; guint guess_content_type_from_content : 1; guint tried_mount : 1; } TaskData; G_DEFINE_TYPE (GtkSourceFileLoader, gtk_source_file_loader, G_TYPE_OBJECT) static void open_file (GTask *task); static void read_file_chunk (GTask *task); static TaskData * task_data_new (void) { TaskData *task_data = g_new0 (TaskData, 1); task_data->chunk_buffer = _gtk_source_utils_aligned_alloc (_gtk_source_utils_get_page_size (), READ_N_PAGES, _gtk_source_utils_get_page_size ()); return task_data; } static void task_data_free (gpointer data) { TaskData *task_data = data; if (task_data == NULL) { return; } g_clear_object (&task_data->input_stream); g_clear_object (&task_data->output_stream); g_clear_object (&task_data->info); if (task_data->progress_cb_notify != NULL) { task_data->progress_cb_notify (task_data->progress_cb_data); } _gtk_source_utils_aligned_free (task_data->chunk_buffer); g_free (task_data); } static GtkSourceCompressionType get_compression_type_from_content_type (const gchar *content_type) { if (content_type == NULL) { return GTK_SOURCE_COMPRESSION_TYPE_NONE; } if (g_content_type_is_a (content_type, "application/x-gzip")) { return GTK_SOURCE_COMPRESSION_TYPE_GZIP; } return GTK_SOURCE_COMPRESSION_TYPE_NONE; } static void gtk_source_file_loader_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceFileLoader *loader = GTK_SOURCE_FILE_LOADER (object); switch (prop_id) { case PROP_BUFFER: g_assert (loader->source_buffer == NULL); loader->source_buffer = g_value_get_object (value); g_object_add_weak_pointer (G_OBJECT (loader->source_buffer), (gpointer *)&loader->source_buffer); break; case PROP_FILE: g_assert (loader->file == NULL); loader->file = g_value_get_object (value); g_object_add_weak_pointer (G_OBJECT (loader->file), (gpointer *)&loader->file); break; case PROP_LOCATION: g_assert (loader->location == NULL); loader->location = g_value_dup_object (value); break; case PROP_INPUT_STREAM: g_assert (loader->input_stream_property == NULL); loader->input_stream_property = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_file_loader_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceFileLoader *loader = GTK_SOURCE_FILE_LOADER (object); switch (prop_id) { case PROP_BUFFER: g_value_set_object (value, loader->source_buffer); break; case PROP_FILE: g_value_set_object (value, loader->file); break; case PROP_LOCATION: g_value_set_object (value, loader->location); break; case PROP_INPUT_STREAM: g_value_set_object (value, loader->input_stream_property); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_file_loader_dispose (GObject *object) { GtkSourceFileLoader *loader = GTK_SOURCE_FILE_LOADER (object); if (loader->source_buffer != NULL) { g_object_remove_weak_pointer (G_OBJECT (loader->source_buffer), (gpointer *)&loader->source_buffer); loader->source_buffer = NULL; } if (loader->file != NULL) { g_object_remove_weak_pointer (G_OBJECT (loader->file), (gpointer *)&loader->file); loader->file = NULL; } g_clear_object (&loader->location); g_clear_object (&loader->input_stream_property); g_clear_object (&loader->task); g_slist_free (loader->candidate_encodings); loader->candidate_encodings = NULL; G_OBJECT_CLASS (gtk_source_file_loader_parent_class)->dispose (object); } static void set_default_candidate_encodings (GtkSourceFileLoader *loader) { GSList *list; GSList *l; const GtkSourceEncoding *file_encoding; /* Get first the default candidates from GtkSourceEncoding. If the * GtkSourceFile's encoding has been set by a FileLoader or FileSaver, * put it at the beginning of the list. */ list = gtk_source_encoding_get_default_candidates (); if (loader->file == NULL) { goto end; } file_encoding = gtk_source_file_get_encoding (loader->file); if (file_encoding == NULL) { goto end; } /* Remove file_encoding from the list, if already present, and prepend * it to the list. */ for (l = list; l != NULL; l = l->next) { const GtkSourceEncoding *cur_encoding = l->data; if (cur_encoding == file_encoding) { list = g_slist_delete_link (list, l); /* The list doesn't contain duplicates, normally. */ break; } } list = g_slist_prepend (list, (gpointer) file_encoding); end: g_slist_free (loader->candidate_encodings); loader->candidate_encodings = list; } static void gtk_source_file_loader_constructed (GObject *object) { GtkSourceFileLoader *loader = GTK_SOURCE_FILE_LOADER (object); if (loader->file != NULL) { set_default_candidate_encodings (loader); if (loader->location == NULL && loader->input_stream_property == NULL) { loader->location = gtk_source_file_get_location (loader->file); if (loader->location != NULL) { g_object_ref (loader->location); } else { g_warning ("GtkSourceFileLoader: the GtkSourceFile's location is NULL. " "Call gtk_source_file_set_location() or read from a GInputStream."); } } } G_OBJECT_CLASS (gtk_source_file_loader_parent_class)->constructed (object); } static void gtk_source_file_loader_class_init (GtkSourceFileLoaderClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = gtk_source_file_loader_dispose; object_class->set_property = gtk_source_file_loader_set_property; object_class->get_property = gtk_source_file_loader_get_property; object_class->constructed = gtk_source_file_loader_constructed; /** * GtkSourceFileLoader:buffer: * * The #GtkSourceBuffer to load the contents into. The * #GtkSourceFileLoader object has a weak reference to the buffer. */ g_object_class_install_property (object_class, PROP_BUFFER, g_param_spec_object ("buffer", "GtkSourceBuffer", "", GTK_SOURCE_TYPE_BUFFER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFileLoader:file: * * The #GtkSourceFile. The #GtkSourceFileLoader object has a weak * reference to the file. */ g_object_class_install_property (object_class, PROP_FILE, g_param_spec_object ("file", "GtkSourceFile", "", GTK_SOURCE_TYPE_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFileLoader:location: * * The #GFile to load. If the #GtkSourceFileLoader:input-stream is * %NULL, by default the location is taken from the #GtkSourceFile at * construction time. */ g_object_class_install_property (object_class, PROP_LOCATION, g_param_spec_object ("location", "Location", "", G_TYPE_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFileLoader:input-stream: * * The #GInputStream to load. Useful for reading stdin. If this property * is set, the #GtkSourceFileLoader:location property is ignored. */ g_object_class_install_property (object_class, PROP_INPUT_STREAM, g_param_spec_object ("input-stream", "Input stream", "", G_TYPE_INPUT_STREAM, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } static void gtk_source_file_loader_init (GtkSourceFileLoader *loader) { loader = gtk_source_file_loader_get_instance_private (loader); } static void close_input_stream_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GInputStream *input_stream = G_INPUT_STREAM (source_object); GTask *task = G_TASK (user_data); TaskData *task_data; GError *error = NULL; G_GNUC_UNUSED gint64 begin_time; GTK_SOURCE_PROFILER_BEGIN_MARK; task_data = g_task_get_task_data (task); begin_time = GTK_SOURCE_PROFILER_CURRENT_TIME; g_input_stream_close_finish (input_stream, result, &error); GTK_SOURCE_PROFILER_MARK (GTK_SOURCE_PROFILER_CURRENT_TIME - begin_time, "FileLoader.close-input", ""); if (error != NULL) { GTK_SOURCE_PROFILER_LOG ("Error closing input stream: %s", error->message); g_task_return_error (task, error); goto cleanup; } begin_time = GTK_SOURCE_PROFILER_CURRENT_TIME; g_output_stream_close (G_OUTPUT_STREAM (task_data->output_stream), g_task_get_cancellable (task), &error); GTK_SOURCE_PROFILER_MARK (GTK_SOURCE_PROFILER_CURRENT_TIME - begin_time, "FileLoader.close-output", ""); if (error != NULL) { GTK_SOURCE_PROFILER_LOG ("Error closing output stream: %s", error->message); g_task_return_error (task, error); goto cleanup; } /* Check if we needed some fallback char, if so, check if there was a * previous error and if not set a fallback used error. */ if (gtk_source_buffer_output_stream_get_num_fallbacks (task_data->output_stream) > 0) { g_task_return_new_error (task, GTK_SOURCE_FILE_LOADER_ERROR, GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK, _("There was a character encoding conversion error " "and it was needed to use a fallback character.")); goto cleanup; } /* The task completion here will result in the task's callback * getting called. So it is important that function does relatively * little amount of work initially or we could end up blocking the * main loop for long enough that we drop a frame. * * Therefore, we add a mark here to make it easier to find that * in applications. Often it is things like setting syntax or * other slightly intenstive operations that should be made async. */ begin_time = GTK_SOURCE_PROFILER_CURRENT_TIME; g_task_return_boolean (task, TRUE); GTK_SOURCE_PROFILER_MARK (GTK_SOURCE_PROFILER_CURRENT_TIME - begin_time, "FileLoader.task-complete-cb", ""); cleanup: GTK_SOURCE_PROFILER_END_MARK (G_STRFUNC, ""); } static void write_complete (GTask *task) { TaskData *task_data; task_data = g_task_get_task_data (task); g_input_stream_close_async (task_data->input_stream, g_task_get_priority (task), g_task_get_cancellable (task), close_input_stream_cb, task); } static void write_file_chunk (GTask *task) { TaskData *task_data; gssize chunk_bytes_written = 0; task_data = g_task_get_task_data (task); while (chunk_bytes_written < task_data->chunk_bytes_read) { gssize bytes_written; GError *error = NULL; /* We use sync methods on the buffer stream since it is in memory. Using * async would be racy and we can end up with invalidated iters. */ bytes_written = g_output_stream_write (G_OUTPUT_STREAM (task_data->output_stream), task_data->chunk_buffer + chunk_bytes_written, task_data->chunk_bytes_read - chunk_bytes_written, g_task_get_cancellable (task), &error); GTK_SOURCE_PROFILER_LOG ("Written: %" G_GSSIZE_FORMAT, bytes_written); if (error != NULL) { GTK_SOURCE_PROFILER_LOG ("Write error: %s", error->message); g_task_return_error (task, error); return; } chunk_bytes_written += bytes_written; } /* FIXME: note that calling the progress callback blocks the read... * Check if it isn't a performance problem. */ if (task_data->progress_cb != NULL && task_data->total_size > 0) { task_data->progress_cb (task_data->total_bytes_read, task_data->total_size, task_data->progress_cb_data); } read_file_chunk (task); } static void read_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GInputStream *input_stream = G_INPUT_STREAM (source_object); GTask *task = G_TASK (user_data); GtkSourceFileLoader *loader; TaskData *task_data; GError *error = NULL; GTK_SOURCE_PROFILER_BEGIN_MARK; loader = g_task_get_source_object (task); task_data = g_task_get_task_data (task); task_data->chunk_bytes_read = g_input_stream_read_finish (input_stream, result, &error); if (error != NULL) { g_task_return_error (task, error); goto cleanup; } /* Check for the extremely unlikely case where the file size overflows. */ if (task_data->total_bytes_read + task_data->chunk_bytes_read < task_data->total_bytes_read) { g_task_return_new_error (task, GTK_SOURCE_FILE_LOADER_ERROR, GTK_SOURCE_FILE_LOADER_ERROR_TOO_BIG, _("File too big.")); goto cleanup; } if (task_data->guess_content_type_from_content && task_data->chunk_bytes_read > 0 && task_data->total_bytes_read == 0) { gchar *guessed; guessed = g_content_type_guess (NULL, (guchar *)task_data->chunk_buffer, task_data->chunk_bytes_read, NULL); if (guessed != NULL) { g_file_info_set_attribute_string (task_data->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, guessed); g_free (guessed); } } /* End of the file, we are done! */ if (task_data->chunk_bytes_read == 0) { /* Flush the stream to ensure proper line ending detection. */ g_output_stream_flush (G_OUTPUT_STREAM (task_data->output_stream), NULL, NULL); loader->auto_detected_encoding = gtk_source_buffer_output_stream_get_guessed (task_data->output_stream); loader->auto_detected_newline_type = gtk_source_buffer_output_stream_detect_newline_type (task_data->output_stream); write_complete (task); goto cleanup; } task_data->total_bytes_read += task_data->chunk_bytes_read; write_file_chunk (task); cleanup: GTK_SOURCE_PROFILER_END_MARK (G_STRFUNC, ""); } static void read_file_chunk (GTask *task) { TaskData *task_data; task_data = g_task_get_task_data (task); g_input_stream_read_async (task_data->input_stream, task_data->chunk_buffer, READ_CHUNK_SIZE, g_task_get_priority (task), g_task_get_cancellable (task), read_cb, task); } static void add_gzip_decompressor_stream (GTask *task) { TaskData *task_data; GZlibDecompressor *decompressor; GInputStream *new_input_stream; task_data = g_task_get_task_data (task); decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); new_input_stream = g_converter_input_stream_new (task_data->input_stream, G_CONVERTER (decompressor)); g_object_unref (task_data->input_stream); g_object_unref (decompressor); task_data->input_stream = new_input_stream; } static void create_input_stream (GTask *task) { GtkSourceFileLoader *loader; TaskData *task_data; loader = g_task_get_source_object (task); task_data = g_task_get_task_data (task); loader->auto_detected_compression_type = GTK_SOURCE_COMPRESSION_TYPE_NONE; if (loader->input_stream_property != NULL) { task_data->input_stream = g_object_ref (loader->input_stream_property); } else if (g_file_info_has_attribute (task_data->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE)) { const gchar *content_type = g_file_info_get_content_type (task_data->info); switch (get_compression_type_from_content_type (content_type)) { case GTK_SOURCE_COMPRESSION_TYPE_GZIP: add_gzip_decompressor_stream (task); loader->auto_detected_compression_type = GTK_SOURCE_COMPRESSION_TYPE_GZIP; break; case GTK_SOURCE_COMPRESSION_TYPE_NONE: /* NOOP */ break; default: g_assert_not_reached (); } } g_return_if_fail (task_data->input_stream != NULL); /* start reading */ read_file_chunk (task); } static void query_info_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GFile *location = G_FILE (source_object); GTask *task = G_TASK (user_data); TaskData *task_data; GError *error = NULL; GTK_SOURCE_PROFILER_BEGIN_MARK; task_data = g_task_get_task_data (task); g_clear_object (&task_data->info); task_data->info = g_file_query_info_finish (location, result, &error); if (error != NULL) { g_task_return_error (task, error); goto cleanup; } if (g_file_info_has_attribute (task_data->info, G_FILE_ATTRIBUTE_STANDARD_TYPE) && g_file_info_get_file_type (task_data->info) != G_FILE_TYPE_REGULAR) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, _("Not a regular file.")); goto cleanup; } if (g_file_info_has_attribute (task_data->info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) { task_data->total_size = g_file_info_get_attribute_uint64 (task_data->info, G_FILE_ATTRIBUTE_STANDARD_SIZE); } create_input_stream (task); cleanup: GTK_SOURCE_PROFILER_END_MARK (G_STRFUNC, ""); } static void mount_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GFile *location = G_FILE (source_object); GTask *task = G_TASK (user_data); GError *error = NULL; GTK_SOURCE_PROFILER_BEGIN_MARK; g_file_mount_enclosing_volume_finish (location, result, &error); if (error != NULL) { g_task_return_error (task, error); } else { /* Try again to open the file for reading. */ open_file (task); } GTK_SOURCE_PROFILER_END_MARK (G_STRFUNC, ""); } static void recover_not_mounted (GTask *task) { GtkSourceFileLoader *loader; TaskData *task_data; GMountOperation *mount_operation; GTK_SOURCE_PROFILER_BEGIN_MARK; loader = g_task_get_source_object (task); task_data = g_task_get_task_data (task); mount_operation = _gtk_source_file_create_mount_operation (loader->file); task_data->tried_mount = TRUE; g_file_mount_enclosing_volume (loader->location, G_MOUNT_MOUNT_NONE, mount_operation, g_task_get_cancellable (task), mount_cb, task); g_object_unref (mount_operation); GTK_SOURCE_PROFILER_END_MARK (G_STRFUNC, ""); } static void open_file_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GFile *location = G_FILE (source_object); GTask *task = G_TASK (user_data); TaskData *task_data; GError *error = NULL; GTK_SOURCE_PROFILER_BEGIN_MARK; task_data = g_task_get_task_data (task); g_clear_object (&task_data->input_stream); task_data->input_stream = G_INPUT_STREAM (g_file_read_finish (location, result, &error)); if (error != NULL) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED) && !task_data->tried_mount) { recover_not_mounted (task); g_error_free (error); return; } g_task_return_error (task, error); goto cleanup; } /* Get the file info: note we cannot use * g_file_input_stream_query_info_async since it is not able to get the * content type etc, beside it is not supported by gvfs. * Using the file instead of the stream is slightly racy, but for * loading this is not too bad... */ g_file_query_info_async (location, LOADER_QUERY_ATTRIBUTES, G_FILE_QUERY_INFO_NONE, g_task_get_priority (task), g_task_get_cancellable (task), query_info_cb, task); cleanup: GTK_SOURCE_PROFILER_END_MARK (G_STRFUNC, ""); } static void open_file (GTask *task) { GtkSourceFileLoader *loader; loader = g_task_get_source_object (task); g_file_read_async (loader->location, g_task_get_priority (task), g_task_get_cancellable (task), open_file_cb, task); } GQuark gtk_source_file_loader_error_quark (void) { static GQuark quark = 0; if (G_UNLIKELY (quark == 0)) { quark = g_quark_from_static_string ("gtk-source-file-loader-error"); } return quark; } /** * gtk_source_file_loader_new: * @buffer: the #GtkSourceBuffer to load the contents into. * @file: the #GtkSourceFile. * * Creates a new `GtkSourceFileLoader` object. The contents is read from the * [class@File]'s location. * * If not already done, call [method@File.set_location] before calling this constructor. * The previous location is anyway not needed, because as soon as the file loading begins, * the @buffer is emptied. * * Returns: a new #GtkSourceFileLoader object. */ GtkSourceFileLoader * gtk_source_file_loader_new (GtkSourceBuffer *buffer, GtkSourceFile *file) { g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL); return g_object_new (GTK_SOURCE_TYPE_FILE_LOADER, "buffer", buffer, "file", file, NULL); } /** * gtk_source_file_loader_new_from_stream: * @buffer: the #GtkSourceBuffer to load the contents into. * @file: the #GtkSourceFile. * @stream: the #GInputStream to load, e.g. stdin. * * Creates a new #GtkSourceFileLoader object. The contents is read from @stream. * * Returns: a new #GtkSourceFileLoader object. */ GtkSourceFileLoader * gtk_source_file_loader_new_from_stream (GtkSourceBuffer *buffer, GtkSourceFile *file, GInputStream *stream) { g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL); g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL); return g_object_new (GTK_SOURCE_TYPE_FILE_LOADER, "buffer", buffer, "file", file, "input-stream", stream, NULL); } /** * gtk_source_file_loader_set_candidate_encodings: * @loader: a #GtkSourceFileLoader. * @candidate_encodings: (element-type GtkSourceEncoding): a list of * #GtkSourceEncodings. * * Sets the candidate encodings for the file loading. * * The encodings are tried in the same order as the list. * * For convenience, @candidate_encodings can contain duplicates. Only the first * occurrence of a duplicated encoding is kept in the list. * * By default the candidate encodings are (in that order in the list): * * 1. If set, the [class@File]'s encoding as returned by [method@File.get_encoding]. * 2. The default candidates as returned by [func@Encoding.get_default_candidates]. */ void gtk_source_file_loader_set_candidate_encodings (GtkSourceFileLoader *loader, GSList *candidate_encodings) { GSList *list; g_return_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader)); g_return_if_fail (loader->task == NULL); list = g_slist_copy (candidate_encodings); list = _gtk_source_encoding_remove_duplicates (list, GTK_SOURCE_ENCODING_DUPLICATES_KEEP_FIRST); g_slist_free (loader->candidate_encodings); loader->candidate_encodings = list; } /** * gtk_source_file_loader_get_buffer: * @loader: a #GtkSourceFileLoader. * * Returns: (transfer none): the #GtkSourceBuffer to load the contents into. */ GtkSourceBuffer * gtk_source_file_loader_get_buffer (GtkSourceFileLoader *loader) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL); return loader->source_buffer; } /** * gtk_source_file_loader_get_file: * @loader: a #GtkSourceFileLoader. * * Returns: (transfer none): the #GtkSourceFile. */ GtkSourceFile * gtk_source_file_loader_get_file (GtkSourceFileLoader *loader) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL); return loader->file; } /** * gtk_source_file_loader_get_location: * @loader: a #GtkSourceFileLoader. * * Returns: (nullable) (transfer none): the #GFile to load, or %NULL * if an input stream is used. */ GFile * gtk_source_file_loader_get_location (GtkSourceFileLoader *loader) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL); return loader->location; } /** * gtk_source_file_loader_get_input_stream: * @loader: a #GtkSourceFileLoader. * * Returns: (nullable) (transfer none): the #GInputStream to load, or %NULL * if a #GFile is used. */ GInputStream * gtk_source_file_loader_get_input_stream (GtkSourceFileLoader *loader) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL); return loader->input_stream_property; } /** * gtk_source_file_loader_load_async: * @loader: a #GtkSourceFileLoader. * @io_priority: the I/O priority of the request. E.g. %G_PRIORITY_LOW, * %G_PRIORITY_DEFAULT or %G_PRIORITY_HIGH. * @cancellable: (nullable): optional #GCancellable object, %NULL to ignore. * @progress_callback: (scope notified) (nullable): function to call back with * progress information, or %NULL if progress information is not needed. * @progress_callback_data: (closure): user data to pass to @progress_callback. * @progress_callback_notify: (nullable): function to call on * @progress_callback_data when the @progress_callback is no longer needed, or * %NULL. * @callback: (scope async): a #GAsyncReadyCallback to call when the request is * satisfied. * @user_data: user data to pass to @callback. * * Loads asynchronously the file or input stream contents into the [class@Buffer]. * * See the [iface@Gio.AsyncResult] documentation to know how to use this * function. */ /* The GDestroyNotify is needed, currently the following bug is not fixed: * https://bugzilla.gnome.org/show_bug.cgi?id=616044 */ void gtk_source_file_loader_load_async (GtkSourceFileLoader *loader, gint io_priority, GCancellable *cancellable, GFileProgressCallback progress_callback, gpointer progress_callback_data, GDestroyNotify progress_callback_notify, GAsyncReadyCallback callback, gpointer user_data) { TaskData *task_data; gboolean implicit_trailing_newline; g_return_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (loader->task == NULL); loader->task = g_task_new (loader, cancellable, callback, user_data); g_task_set_priority (loader->task, io_priority); task_data = task_data_new (); g_task_set_task_data (loader->task, task_data, task_data_free); task_data->progress_cb = progress_callback; task_data->progress_cb_data = progress_callback_data; task_data->progress_cb_notify = progress_callback_notify; if (loader->source_buffer == NULL || loader->file == NULL || (loader->location == NULL && loader->input_stream_property == NULL)) { g_task_return_new_error (loader->task, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid argument"); return; } /* Leave an internal mark about the buffer loading during the * progreess of the operation so external tooling can find it. */ _gtk_source_buffer_begin_loading (loader->source_buffer); g_signal_connect_object (loader->task, "notify::completed", G_CALLBACK (_gtk_source_buffer_end_loading), loader->source_buffer, G_CONNECT_SWAPPED); loader->load_begin_time = GTK_SOURCE_PROFILER_CURRENT_TIME; /* Update GtkSourceFile location directly. The other GtkSourceFile * properties are updated when the operation is finished. But since the * file is loaded, the previous contents is lost, so the previous * location is anyway not needed. And for display purposes, the new * location is directly needed (for example to display the filename in a * tab or an info bar with the progress information). */ if (loader->input_stream_property != NULL) { gtk_source_file_set_location (loader->file, NULL); } else { gtk_source_file_set_location (loader->file, loader->location); } implicit_trailing_newline = gtk_source_buffer_get_implicit_trailing_newline (loader->source_buffer); /* The BufferOutputStream has a strong reference to the buffer. * We create the BufferOutputStream here so we are sure that the * buffer will not be destroyed during the file loading. */ task_data->output_stream = gtk_source_buffer_output_stream_new (loader->source_buffer, loader->candidate_encodings, implicit_trailing_newline); if (loader->input_stream_property != NULL) { task_data->guess_content_type_from_content = TRUE; task_data->info = g_file_info_new (); create_input_stream (loader->task); } else { open_file (loader->task); } } /** * gtk_source_file_loader_load_finish: * @loader: a #GtkSourceFileLoader. * @result: a #GAsyncResult. * @error: a #GError, or %NULL. * * Finishes a file loading started with [method@FileLoader.load_async]. * * If the contents has been loaded, the following [class@File] properties will * be updated: the location, the encoding, the newline type and the compression * type. * * Returns: whether the contents has been loaded successfully. */ gboolean gtk_source_file_loader_load_finish (GtkSourceFileLoader *loader, GAsyncResult *result, GError **error) { gboolean ok; gboolean update_file_properties; GError *real_error = NULL; g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (g_task_is_valid (result, loader), FALSE); ok = g_task_propagate_boolean (G_TASK (result), &real_error); if (error != NULL && real_error != NULL) { *error = g_error_copy (real_error); } /* Update the file properties if the contents has been loaded. The * contents can be loaded successfully, or there can be encoding * conversion errors with fallback characters. In the latter case, the * encoding may be wrong, but since the contents has anyway be loaded, * the file properties must be updated. * With the other errors, normally the contents hasn't been loaded into * the buffer, i.e. the buffer is still empty. */ update_file_properties = ok || (real_error != NULL && real_error->domain == GTK_SOURCE_FILE_LOADER_ERROR && real_error->code == GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK); if (update_file_properties && loader->file != NULL) { TaskData *task_data; task_data = g_task_get_task_data (G_TASK (result)); /* The location is already updated at the beginning of the * operation. */ _gtk_source_file_set_encoding (loader->file, loader->auto_detected_encoding); _gtk_source_file_set_newline_type (loader->file, loader->auto_detected_newline_type); _gtk_source_file_set_compression_type (loader->file, loader->auto_detected_compression_type); _gtk_source_file_set_externally_modified (loader->file, FALSE); _gtk_source_file_set_deleted (loader->file, FALSE); if (g_file_info_has_attribute (task_data->info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) { GDateTime *dt; gint64 mtime = 0; dt = g_file_info_get_modification_date_time (task_data->info); if (dt != NULL) { mtime = g_date_time_to_unix (dt); g_date_time_unref (dt); } _gtk_source_file_set_modification_time (loader->file, mtime); } if (g_file_info_has_attribute (task_data->info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) { gboolean readonly; readonly = !g_file_info_get_attribute_boolean (task_data->info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); _gtk_source_file_set_readonly (loader->file, readonly); } else { _gtk_source_file_set_readonly (loader->file, FALSE); } } g_clear_object (&loader->task); if (real_error != NULL) { g_error_free (real_error); } GTK_SOURCE_PROFILER_MARK (GTK_SOURCE_PROFILER_CURRENT_TIME - loader->load_begin_time, "GtkSourceFileLoader.load", g_file_peek_path (loader->location)); return ok; } /** * gtk_source_file_loader_get_encoding: * @loader: a #GtkSourceFileLoader. * * Returns: the detected file encoding. */ const GtkSourceEncoding * gtk_source_file_loader_get_encoding (GtkSourceFileLoader *loader) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), NULL); return loader->auto_detected_encoding; } /** * gtk_source_file_loader_get_newline_type: * @loader: a #GtkSourceFileLoader. * * Returns: the detected newline type. */ GtkSourceNewlineType gtk_source_file_loader_get_newline_type (GtkSourceFileLoader *loader) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), GTK_SOURCE_NEWLINE_TYPE_LF); return loader->auto_detected_newline_type; } /** * gtk_source_file_loader_get_compression_type: * @loader: a #GtkSourceFileLoader. * * Returns: the detected compression type. */ GtkSourceCompressionType gtk_source_file_loader_get_compression_type (GtkSourceFileLoader *loader) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_LOADER (loader), GTK_SOURCE_COMPRESSION_TYPE_NONE); return loader->auto_detected_compression_type; } 07070100000150000081A40000000000000000000000016659080600001494000000000000000000000000000000000000003900000000gtksourceview-5.12.1/gtksourceview/gtksourcefileloader.h/* * This file is part of GtkSourceView * * Copyright 2005 - Paolo Maggi * Copyright 2007 - Paolo Maggi, Steve Frécinaux * Copyright 2008 - Jesse van den Kieboom * Copyright 2014 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include #include "gtksourcetypes.h" #include "gtksourcefile.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_FILE_LOADER (gtk_source_file_loader_get_type()) #define GTK_SOURCE_FILE_LOADER_ERROR (gtk_source_file_loader_error_quark()) /** * GtkSourceFileLoaderError: * @GTK_SOURCE_FILE_LOADER_ERROR_TOO_BIG: The file is too big. * @GTK_SOURCE_FILE_LOADER_ERROR_ENCODING_AUTO_DETECTION_FAILED: It is not * possible to detect the encoding automatically. * @GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK: There was an encoding * conversion error and it was needed to use a fallback character. * * An error code used with the %GTK_SOURCE_FILE_LOADER_ERROR domain. */ typedef enum _GtkSourceFileLoaderError { GTK_SOURCE_FILE_LOADER_ERROR_TOO_BIG, GTK_SOURCE_FILE_LOADER_ERROR_ENCODING_AUTO_DETECTION_FAILED, GTK_SOURCE_FILE_LOADER_ERROR_CONVERSION_FALLBACK } GtkSourceFileLoaderError; GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkSourceFileLoader, gtk_source_file_loader, GTK_SOURCE, FILE_LOADER, GObject) GTK_SOURCE_AVAILABLE_IN_ALL GQuark gtk_source_file_loader_error_quark (void); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceFileLoader *gtk_source_file_loader_new (GtkSourceBuffer *buffer, GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceFileLoader *gtk_source_file_loader_new_from_stream (GtkSourceBuffer *buffer, GtkSourceFile *file, GInputStream *stream); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_loader_set_candidate_encodings (GtkSourceFileLoader *loader, GSList *candidate_encodings); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceBuffer *gtk_source_file_loader_get_buffer (GtkSourceFileLoader *loader); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceFile *gtk_source_file_loader_get_file (GtkSourceFileLoader *loader); GTK_SOURCE_AVAILABLE_IN_ALL GFile *gtk_source_file_loader_get_location (GtkSourceFileLoader *loader); GTK_SOURCE_AVAILABLE_IN_ALL GInputStream *gtk_source_file_loader_get_input_stream (GtkSourceFileLoader *loader); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_loader_load_async (GtkSourceFileLoader *loader, gint io_priority, GCancellable *cancellable, GFileProgressCallback progress_callback, gpointer progress_callback_data, GDestroyNotify progress_callback_notify, GAsyncReadyCallback callback, gpointer user_data); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_file_loader_load_finish (GtkSourceFileLoader *loader, GAsyncResult *result, GError **error); GTK_SOURCE_AVAILABLE_IN_ALL const GtkSourceEncoding *gtk_source_file_loader_get_encoding (GtkSourceFileLoader *loader); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceNewlineType gtk_source_file_loader_get_newline_type (GtkSourceFileLoader *loader); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceCompressionType gtk_source_file_loader_get_compression_type (GtkSourceFileLoader *loader); G_END_DECLS 07070100000151000081A40000000000000000000000016659080600009988000000000000000000000000000000000000003800000000gtksourceview-5.12.1/gtksourceview/gtksourcefilesaver.c/* * This file is part of GtkSourceView * * Copyright 2005-2007 - Paolo Borelli and Paolo Maggi * Copyright 2007 - Steve Frécinaux * Copyright 2008 - Jesse van den Kieboom * Copyright 2014, 2016 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include #include "gtksourcefilesaver.h" #include "gtksourcefile-private.h" #include "gtksourcebufferinputstream-private.h" #include "gtksourceencoding.h" #include "gtksourcebuffer.h" #include "gtksourcebuffer-private.h" #include "gtksource-enumtypes.h" #include "gtksourceutils-private.h" /** * GtkSourceFileSaver: * * Save a [class@Buffer] into a file. * * A `GtkSourceFileSaver` object permits to save a [class@Buffer] into a * [iface@Gio.File]. * * A file saver should be used only for one save operation, including errors * handling. If an error occurs, you can reconfigure the saver and relaunch the * operation with [method@FileSaver.save_async]. */ /* The code has been written initially in gedit (GeditDocumentSaver). * It uses a GtkSourceBufferInputStream as input, create converter(s) if needed * for the encoding and the compression, and write the contents to a * GOutputStream (the file). */ #if 0 #define DEBUG(x) (x) #else #define DEBUG(x) #endif #define WRITE_N_PAGES 2 #define WRITE_CHUNK_SIZE (_gtk_source_utils_get_page_size()*WRITE_N_PAGES) #define QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_TIME_MODIFIED enum { PROP_0, PROP_BUFFER, PROP_FILE, PROP_LOCATION, PROP_ENCODING, PROP_NEWLINE_TYPE, PROP_COMPRESSION_TYPE, PROP_FLAGS, N_PROPS }; struct _GtkSourceFileSaver { GObject parent_instance; /* Weak ref to the GtkSourceBuffer. A strong ref could create a * reference cycle in an application. For example a subclass of * GtkSourceBuffer can have a strong ref to the FileSaver. */ GtkSourceBuffer *source_buffer; /* Weak ref to the GtkSourceFile. A strong ref could create a reference * cycle in an application. For example a subclass of GtkSourceFile can * have a strong ref to the FileSaver. */ GtkSourceFile *file; GFile *location; const GtkSourceEncoding *encoding; GtkSourceNewlineType newline_type; GtkSourceCompressionType compression_type; GtkSourceFileSaverFlags flags; GTask *task; }; typedef struct { /* The output_stream contains the required converter(s) for the encoding * and the compression type. * The two streams cannot be spliced directly, because: * (1) We need to call the progress callback. * (2) Sync methods must be used for the input stream, and async * methods for the output stream. */ GtkSourceBufferInputStream *input_stream; GOutputStream *output_stream; GFileInfo *info; goffset total_size; GFileProgressCallback progress_cb; gpointer progress_cb_data; GDestroyNotify progress_cb_notify; /* This field is used when cancelling the output stream: an error occurs * and is stored in this field, the output stream is cancelled * asynchronously, and then the error is reported to the task. */ GError *error; gssize chunk_bytes_read; gssize chunk_bytes_written; gchar *chunk_buffer; guint tried_mount : 1; } TaskData; G_DEFINE_TYPE (GtkSourceFileSaver, gtk_source_file_saver, G_TYPE_OBJECT) static GParamSpec *properties [N_PROPS]; static void read_file_chunk (GTask *task); static void write_file_chunk (GTask *task); static void recover_not_mounted (GTask *task); static TaskData * task_data_new (void) { TaskData *task_data = g_new0 (TaskData, 1); task_data->chunk_buffer = _gtk_source_utils_aligned_alloc (_gtk_source_utils_get_page_size (), WRITE_N_PAGES, _gtk_source_utils_get_page_size ()); return task_data; } static void task_data_free (gpointer data) { TaskData *task_data = data; if (task_data == NULL) { return; } g_clear_object (&task_data->input_stream); g_clear_object (&task_data->output_stream); g_clear_object (&task_data->info); g_clear_error (&task_data->error); if (task_data->progress_cb_notify != NULL) { task_data->progress_cb_notify (task_data->progress_cb_data); } _gtk_source_utils_aligned_free (task_data->chunk_buffer); g_free (task_data); } static void gtk_source_file_saver_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object); switch (prop_id) { case PROP_BUFFER: g_assert (saver->source_buffer == NULL); saver->source_buffer = g_value_get_object (value); g_object_add_weak_pointer (G_OBJECT (saver->source_buffer), (gpointer *)&saver->source_buffer); break; case PROP_FILE: g_assert (saver->file == NULL); saver->file = g_value_get_object (value); g_object_add_weak_pointer (G_OBJECT (saver->file), (gpointer *)&saver->file); break; case PROP_LOCATION: g_assert (saver->location == NULL); saver->location = g_value_dup_object (value); break; case PROP_ENCODING: gtk_source_file_saver_set_encoding (saver, g_value_get_boxed (value)); break; case PROP_NEWLINE_TYPE: gtk_source_file_saver_set_newline_type (saver, g_value_get_enum (value)); break; case PROP_COMPRESSION_TYPE: gtk_source_file_saver_set_compression_type (saver, g_value_get_enum (value)); break; case PROP_FLAGS: gtk_source_file_saver_set_flags (saver, g_value_get_flags (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_file_saver_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object); switch (prop_id) { case PROP_BUFFER: g_value_set_object (value, saver->source_buffer); break; case PROP_FILE: g_value_set_object (value, saver->file); break; case PROP_LOCATION: g_value_set_object (value, saver->location); break; case PROP_ENCODING: g_value_set_boxed (value, saver->encoding); break; case PROP_NEWLINE_TYPE: g_value_set_enum (value, saver->newline_type); break; case PROP_COMPRESSION_TYPE: g_value_set_enum (value, saver->compression_type); break; case PROP_FLAGS: g_value_set_flags (value, saver->flags); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_file_saver_dispose (GObject *object) { GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object); if (saver->source_buffer != NULL) { g_object_remove_weak_pointer (G_OBJECT (saver->source_buffer), (gpointer *)&saver->source_buffer); saver->source_buffer = NULL; } if (saver->file != NULL) { g_object_remove_weak_pointer (G_OBJECT (saver->file), (gpointer *)&saver->file); saver->file = NULL; } g_clear_object (&saver->location); g_clear_object (&saver->task); G_OBJECT_CLASS (gtk_source_file_saver_parent_class)->dispose (object); } static void gtk_source_file_saver_constructed (GObject *object) { GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object); if (saver->file != NULL) { const GtkSourceEncoding *encoding; GtkSourceNewlineType newline_type; GtkSourceCompressionType compression_type; encoding = gtk_source_file_get_encoding (saver->file); gtk_source_file_saver_set_encoding (saver, encoding); newline_type = gtk_source_file_get_newline_type (saver->file); gtk_source_file_saver_set_newline_type (saver, newline_type); compression_type = gtk_source_file_get_compression_type (saver->file); gtk_source_file_saver_set_compression_type (saver, compression_type); if (saver->location == NULL) { saver->location = gtk_source_file_get_location (saver->file); if (saver->location != NULL) { g_object_ref (saver->location); } else { g_warning ("GtkSourceFileSaver: the GtkSourceFile's location is NULL. " "Use gtk_source_file_saver_new_with_target()."); } } } G_OBJECT_CLASS (gtk_source_file_saver_parent_class)->constructed (object); } static void gtk_source_file_saver_class_init (GtkSourceFileSaverClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = gtk_source_file_saver_dispose; object_class->set_property = gtk_source_file_saver_set_property; object_class->get_property = gtk_source_file_saver_get_property; object_class->constructed = gtk_source_file_saver_constructed; /** * GtkSourceFileSaver:buffer: * * The #GtkSourceBuffer to save. The #GtkSourceFileSaver object has a * weak reference to the buffer. */ properties [PROP_BUFFER] = g_param_spec_object ("buffer", "GtkSourceBuffer", "", GTK_SOURCE_TYPE_BUFFER, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFileSaver:file: * * The #GtkSourceFile. The #GtkSourceFileSaver object has a weak * reference to the file. */ properties [PROP_FILE] = g_param_spec_object ("file", "GtkSourceFile", "", GTK_SOURCE_TYPE_FILE, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFileSaver:location: * * The #GFile where to save the buffer. By default the location is taken * from the #GtkSourceFile at construction time. */ properties [PROP_LOCATION] = g_param_spec_object ("location", "Location", "", G_TYPE_FILE, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFileSaver:encoding: * * The file's encoding. */ properties [PROP_ENCODING] = g_param_spec_boxed ("encoding", "Encoding", "", GTK_SOURCE_TYPE_ENCODING, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFileSaver:newline-type: * * The newline type. */ properties [PROP_NEWLINE_TYPE] = g_param_spec_enum ("newline-type", "Newline type", "", GTK_SOURCE_TYPE_NEWLINE_TYPE, GTK_SOURCE_NEWLINE_TYPE_LF, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFileSaver:compression-type: * * The compression type. */ properties [PROP_COMPRESSION_TYPE] = g_param_spec_enum ("compression-type", "Compression type", "", GTK_SOURCE_TYPE_COMPRESSION_TYPE, GTK_SOURCE_COMPRESSION_TYPE_NONE, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); /** * GtkSourceFileSaver:flags: * * File saving flags. */ properties [PROP_FLAGS] = g_param_spec_flags ("flags", "Flags", "", GTK_SOURCE_TYPE_FILE_SAVER_FLAGS, GTK_SOURCE_FILE_SAVER_FLAGS_NONE, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); } static void gtk_source_file_saver_init (GtkSourceFileSaver *saver) { } /* BEGIN NOTE: * * This fixes an issue in GOutputStream that applies the atomic replace save * strategy. The stream moves the written file to the original file when the * stream is closed. However, there is no way currently to tell the stream that * the save should be aborted (there could be a conversion error). The patch * explicitly closes the output stream in all these cases with a GCancellable in * the cancelled state, causing the output stream to close, but not move the * file. This makes use of an implementation detail in the local file stream * and should be properly fixed by adding the appropriate API in GIO. Until * then, at least we prevent data corruption for now. * * Relevant bug reports: * * Bug 615110 - write file ignore encoding errors (gedit) * https://bugzilla.gnome.org/show_bug.cgi?id=615110 * * Bug 602412 - g_file_replace does not restore original file when there is * errors while writing (glib/gio) * https://bugzilla.gnome.org/show_bug.cgi?id=602412 */ static void cancel_output_stream_ready_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GOutputStream *output_stream = G_OUTPUT_STREAM (source_object); GTask *task = G_TASK (user_data); TaskData *task_data; task_data = g_task_get_task_data (task); g_output_stream_close_finish (output_stream, result, NULL); if (task_data->error != NULL) { GError *error = task_data->error; task_data->error = NULL; g_task_return_error (task, error); } else { g_task_return_boolean (task, FALSE); } } static void cancel_output_stream (GTask *task) { TaskData *task_data; GCancellable *cancellable; DEBUG ({ g_print ("Cancel output stream\n"); }); task_data = g_task_get_task_data (task); cancellable = g_cancellable_new (); g_cancellable_cancel (cancellable); g_output_stream_close_async (task_data->output_stream, g_task_get_priority (task), cancellable, cancel_output_stream_ready_cb, task); g_object_unref (cancellable); } /* * END NOTE */ static void query_info_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GFile *location = G_FILE (source_object); GTask *task = G_TASK (user_data); TaskData *task_data; GError *error = NULL; DEBUG ({ g_print ("Finished query info on file\n"); }); task_data = g_task_get_task_data (task); g_clear_object (&task_data->info); task_data->info = g_file_query_info_finish (location, result, &error); if (error != NULL) { DEBUG ({ g_print ("Query info failed: %s\n", error->message); }); g_task_return_error (task, error); return; } g_task_return_boolean (task, TRUE); } static void close_output_stream_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GOutputStream *output_stream = G_OUTPUT_STREAM (source_object); GTask *task = G_TASK (user_data); GtkSourceFileSaver *saver; GError *error = NULL; DEBUG ({ g_print ("%s\n", G_STRFUNC); }); saver = g_task_get_source_object (task); g_output_stream_close_finish (output_stream, result, &error); if (error != NULL) { DEBUG ({ g_print ("Closing stream error: %s\n", error->message); }); g_task_return_error (task, error); return; } /* Get the file info: note we cannot use * g_file_output_stream_query_info_async() since it is not able to get * the modification time. */ DEBUG ({ g_print ("Query info on file\n"); }); g_file_query_info_async (saver->location, QUERY_ATTRIBUTES, G_FILE_QUERY_INFO_NONE, g_task_get_priority (task), g_task_get_cancellable (task), query_info_cb, task); } static void write_complete (GTask *task) { TaskData *task_data; GError *error = NULL; DEBUG ({ g_print ("Close input stream\n"); }); task_data = g_task_get_task_data (task); g_input_stream_close (G_INPUT_STREAM (task_data->input_stream), g_task_get_cancellable (task), &error); if (error != NULL) { DEBUG ({ g_print ("Closing input stream error: %s\n", error->message); }); g_clear_error (&task_data->error); task_data->error = error; cancel_output_stream (task); return; } DEBUG ({ g_print ("Close output stream\n"); }); g_output_stream_close_async (task_data->output_stream, g_task_get_priority (task), g_task_get_cancellable (task), close_output_stream_cb, task); } static void write_file_chunk_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GOutputStream *output_stream = G_OUTPUT_STREAM (source_object); GTask *task = G_TASK (user_data); TaskData *task_data; gssize bytes_written; GError *error = NULL; DEBUG ({ g_print ("%s\n", G_STRFUNC); }); task_data = g_task_get_task_data (task); bytes_written = g_output_stream_write_finish (output_stream, result, &error); DEBUG ({ g_print ("Written: %" G_GSSIZE_FORMAT "\n", bytes_written); }); if (error != NULL) { DEBUG ({ g_print ("Write error: %s\n", error->message); }); g_clear_error (&task_data->error); task_data->error = error; cancel_output_stream (task); return; } task_data->chunk_bytes_written += bytes_written; /* Write again */ if (task_data->chunk_bytes_written < task_data->chunk_bytes_read) { write_file_chunk (task); return; } if (task_data->progress_cb != NULL) { gsize total_chars_written; total_chars_written = _gtk_source_buffer_input_stream_tell (task_data->input_stream); task_data->progress_cb (total_chars_written, task_data->total_size, task_data->progress_cb_data); } read_file_chunk (task); } static void write_file_chunk (GTask *task) { TaskData *task_data; DEBUG ({ g_print ("%s\n", G_STRFUNC); }); task_data = g_task_get_task_data (task); g_output_stream_write_async (task_data->output_stream, task_data->chunk_buffer + task_data->chunk_bytes_written, task_data->chunk_bytes_read - task_data->chunk_bytes_written, g_task_get_priority (task), g_task_get_cancellable (task), write_file_chunk_cb, task); } static void read_file_chunk (GTask *task) { TaskData *task_data; GError *error = NULL; DEBUG ({ g_print ("%s\n", G_STRFUNC); }); task_data = g_task_get_task_data (task); task_data->chunk_bytes_written = 0; /* We use sync methods on doc stream since it is in memory. Using async * would be racy and we could end up with invalid iters. */ task_data->chunk_bytes_read = g_input_stream_read (G_INPUT_STREAM (task_data->input_stream), task_data->chunk_buffer, WRITE_CHUNK_SIZE, g_task_get_cancellable (task), &error); if (error != NULL) { g_clear_error (&task_data->error); task_data->error = error; cancel_output_stream (task); return; } /* Check if we finished reading and writing. */ if (task_data->chunk_bytes_read == 0) { write_complete (task); return; } write_file_chunk (task); } static void replace_file_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GFile *location = G_FILE (source_object); GTask *task = G_TASK (user_data); GtkSourceFileSaver *saver; TaskData *task_data; GFileOutputStream *file_output_stream; GOutputStream *output_stream; GError *error = NULL; DEBUG ({ g_print ("%s\n", G_STRFUNC); }); saver = g_task_get_source_object (task); task_data = g_task_get_task_data (task); file_output_stream = g_file_replace_finish (location, result, &error); if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED) && !task_data->tried_mount) { recover_not_mounted (task); g_error_free (error); return; } else if (error != NULL) { DEBUG ({ g_print ("Opening file failed: %s\n", error->message); }); g_task_return_error (task, error); return; } if (saver->compression_type == GTK_SOURCE_COMPRESSION_TYPE_GZIP) { GZlibCompressor *compressor; DEBUG ({ g_print ("Use gzip compressor\n"); }); compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1); output_stream = g_converter_output_stream_new (G_OUTPUT_STREAM (file_output_stream), G_CONVERTER (compressor)); g_object_unref (compressor); g_object_unref (file_output_stream); } else { output_stream = G_OUTPUT_STREAM (file_output_stream); } /* FIXME: manage converter error? */ DEBUG ({ g_print ("Encoding charset: %s\n", gtk_source_encoding_get_charset (saver->encoding)); }); if (saver->encoding != gtk_source_encoding_get_utf8 ()) { GCharsetConverter *converter; converter = g_charset_converter_new (gtk_source_encoding_get_charset (saver->encoding), "UTF-8", NULL); g_clear_object (&task_data->output_stream); task_data->output_stream = g_converter_output_stream_new (output_stream, G_CONVERTER (converter)); g_object_unref (converter); g_object_unref (output_stream); } else { g_clear_object (&task_data->output_stream); task_data->output_stream = G_OUTPUT_STREAM (output_stream); } task_data->total_size = _gtk_source_buffer_input_stream_get_total_size (task_data->input_stream); DEBUG ({ g_print ("Total number of characters: %" G_GINT64_FORMAT "\n", task_data->total_size); }); read_file_chunk (task); } static void begin_write (GTask *task) { GtkSourceFileSaver *saver; gboolean create_backup; saver = g_task_get_source_object (task); create_backup = (saver->flags & GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP) != 0; DEBUG ({ g_print ("Start replacing file contents\n"); g_print ("Make backup: %s\n", create_backup ? "yes" : "no"); }); g_file_replace_async (saver->location, NULL, create_backup, G_FILE_CREATE_NONE, g_task_get_priority (task), g_task_get_cancellable (task), replace_file_cb, task); } static void check_externally_modified_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GFile *location = G_FILE (source_object); GTask *task = G_TASK (user_data); GtkSourceFileSaver *saver; TaskData *task_data; GFileInfo *info; gint64 old_mtime; gint64 cur_mtime; GError *error = NULL; DEBUG ({ g_print ("%s\n", G_STRFUNC); }); saver = g_task_get_source_object (task); task_data = g_task_get_task_data (task); info = g_file_query_info_finish (location, result, &error); if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED) && !task_data->tried_mount) { recover_not_mounted (task); g_error_free (error); return; } /* It's perfectly fine if the file doesn't exist yet. */ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&error); } else if (error != NULL) { DEBUG ({ g_print ("Check externally modified failed: %s\n", error->message); }); g_task_return_error (task, error); return; } if (_gtk_source_file_get_modification_time (saver->file, &old_mtime) && info != NULL && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) { GDateTime *dt; dt = g_file_info_get_modification_date_time (info); cur_mtime = g_date_time_to_unix (dt); g_date_time_unref (dt); if (old_mtime != cur_mtime) { DEBUG ({ g_print ("The file is externally modified\n"); }); g_task_return_new_error (task, GTK_SOURCE_FILE_SAVER_ERROR, GTK_SOURCE_FILE_SAVER_ERROR_EXTERNALLY_MODIFIED, _("The file is externally modified.")); g_object_unref (info); return; } } begin_write (task); if (info != NULL) { g_object_unref (info); } } static void check_externally_modified (GTask *task) { GtkSourceFileSaver *saver; gboolean save_as = FALSE; saver = g_task_get_source_object (task); if (saver->file != NULL) { GFile *prev_location; prev_location = gtk_source_file_get_location (saver->file); /* Don't check for externally modified for a "save as" operation, * because the user has normally accepted to overwrite the file if it * already exists. */ save_as = (prev_location == NULL || !g_file_equal (prev_location, saver->location)); } if (saver->flags & GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME || save_as) { begin_write (task); return; } DEBUG ({ g_print ("Check externally modified\n"); }); g_file_query_info_async (saver->location, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, g_task_get_priority (task), g_task_get_cancellable (task), check_externally_modified_cb, task); } static void mount_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GFile *location = G_FILE (source_object); GTask *task = G_TASK (user_data); GError *error = NULL; DEBUG ({ g_print ("%s\n", G_STRFUNC); }); g_file_mount_enclosing_volume_finish (location, result, &error); if (error != NULL) { g_task_return_error (task, error); return; } check_externally_modified (task); } static void recover_not_mounted (GTask *task) { GtkSourceFileSaver *saver; TaskData *task_data; GMountOperation *mount_operation; saver = g_task_get_source_object (task); task_data = g_task_get_task_data (task); mount_operation = _gtk_source_file_create_mount_operation (saver->file); DEBUG ({ g_print ("%s\n", G_STRFUNC); }); task_data->tried_mount = TRUE; g_file_mount_enclosing_volume (saver->location, G_MOUNT_MOUNT_NONE, mount_operation, g_task_get_cancellable (task), mount_cb, task); g_object_unref (mount_operation); } GQuark gtk_source_file_saver_error_quark (void) { static GQuark quark = 0; if (G_UNLIKELY (quark == 0)) { quark = g_quark_from_static_string ("gtk-source-file-saver-error"); } return quark; } /** * gtk_source_file_saver_new: * @buffer: the #GtkSourceBuffer to save. * @file: the #GtkSourceFile. * * Creates a new #GtkSourceFileSaver object. The @buffer will be saved to the * [class@File]'s location. * * This constructor is suitable for a simple "save" operation, when the @file * already contains a non-%NULL [property@File:location]. * * Returns: a new #GtkSourceFileSaver object. */ GtkSourceFileSaver * gtk_source_file_saver_new (GtkSourceBuffer *buffer, GtkSourceFile *file) { g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL); return g_object_new (GTK_SOURCE_TYPE_FILE_SAVER, "buffer", buffer, "file", file, NULL); } /** * gtk_source_file_saver_new_with_target: * @buffer: the #GtkSourceBuffer to save. * @file: the #GtkSourceFile. * @target_location: the #GFile where to save the buffer to. * * Creates a new #GtkSourceFileSaver object with a target location. * * When the file saving is finished successfully, @target_location is set to the @file's * [property@File:location] property. If an error occurs, the previous valid * location is still available in [class@File]. * * This constructor is suitable for a "save as" operation, or for saving a new * buffer for the first time. * * Returns: a new #GtkSourceFileSaver object. */ GtkSourceFileSaver * gtk_source_file_saver_new_with_target (GtkSourceBuffer *buffer, GtkSourceFile *file, GFile *target_location) { g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL); g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL); g_return_val_if_fail (G_IS_FILE (target_location), NULL); return g_object_new (GTK_SOURCE_TYPE_FILE_SAVER, "buffer", buffer, "file", file, "location", target_location, NULL); } /** * gtk_source_file_saver_get_buffer: * @saver: a #GtkSourceFileSaver. * * Returns: (transfer none): the #GtkSourceBuffer to save. */ GtkSourceBuffer * gtk_source_file_saver_get_buffer (GtkSourceFileSaver *saver) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL); return saver->source_buffer; } /** * gtk_source_file_saver_get_file: * @saver: a #GtkSourceFileSaver. * * Returns: (transfer none): the #GtkSourceFile. */ GtkSourceFile * gtk_source_file_saver_get_file (GtkSourceFileSaver *saver) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL); return saver->file; } /** * gtk_source_file_saver_get_location: * @saver: a #GtkSourceFileSaver. * * Returns: (transfer none): the #GFile where to save the buffer to. */ GFile * gtk_source_file_saver_get_location (GtkSourceFileSaver *saver) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL); return saver->location; } /** * gtk_source_file_saver_set_encoding: * @saver: a #GtkSourceFileSaver. * @encoding: (nullable): the new encoding, or %NULL for UTF-8. * * Sets the encoding. If @encoding is %NULL, the UTF-8 encoding will be set. * * By default the encoding is taken from the #GtkSourceFile. */ void gtk_source_file_saver_set_encoding (GtkSourceFileSaver *saver, const GtkSourceEncoding *encoding) { g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver)); g_return_if_fail (saver->task == NULL); if (encoding == NULL) { encoding = gtk_source_encoding_get_utf8 (); } if (saver->encoding != encoding) { saver->encoding = encoding; g_object_notify_by_pspec (G_OBJECT (saver), properties [PROP_ENCODING]); } } /** * gtk_source_file_saver_get_encoding: * @saver: a #GtkSourceFileSaver. * * Returns: the encoding. */ const GtkSourceEncoding * gtk_source_file_saver_get_encoding (GtkSourceFileSaver *saver) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL); return saver->encoding; } /** * gtk_source_file_saver_set_newline_type: * @saver: a #GtkSourceFileSaver. * @newline_type: the new newline type. * * Sets the newline type. By default the newline type is taken from the * #GtkSourceFile. */ void gtk_source_file_saver_set_newline_type (GtkSourceFileSaver *saver, GtkSourceNewlineType newline_type) { g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver)); g_return_if_fail (saver->task == NULL); if (saver->newline_type != newline_type) { saver->newline_type = newline_type; g_object_notify_by_pspec (G_OBJECT (saver), properties [PROP_NEWLINE_TYPE]); } } /** * gtk_source_file_saver_get_newline_type: * @saver: a #GtkSourceFileSaver. * * Returns: the newline type. */ GtkSourceNewlineType gtk_source_file_saver_get_newline_type (GtkSourceFileSaver *saver) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), GTK_SOURCE_NEWLINE_TYPE_DEFAULT); return saver->newline_type; } /** * gtk_source_file_saver_set_compression_type: * @saver: a #GtkSourceFileSaver. * @compression_type: the new compression type. * * Sets the compression type. By default the compression type is taken from the * #GtkSourceFile. */ void gtk_source_file_saver_set_compression_type (GtkSourceFileSaver *saver, GtkSourceCompressionType compression_type) { g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver)); g_return_if_fail (saver->task == NULL); if (saver->compression_type != compression_type) { saver->compression_type = compression_type; g_object_notify_by_pspec (G_OBJECT (saver), properties [PROP_COMPRESSION_TYPE]); } } /** * gtk_source_file_saver_get_compression_type: * @saver: a #GtkSourceFileSaver. * * Returns: the compression type. */ GtkSourceCompressionType gtk_source_file_saver_get_compression_type (GtkSourceFileSaver *saver) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), GTK_SOURCE_COMPRESSION_TYPE_NONE); return saver->compression_type; } /** * gtk_source_file_saver_set_flags: * @saver: a #GtkSourceFileSaver. * @flags: the new flags. */ void gtk_source_file_saver_set_flags (GtkSourceFileSaver *saver, GtkSourceFileSaverFlags flags) { g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver)); g_return_if_fail (saver->task == NULL); if (saver->flags != flags) { saver->flags = flags; g_object_notify_by_pspec (G_OBJECT (saver), properties [PROP_FLAGS]); } } /** * gtk_source_file_saver_get_flags: * @saver: a #GtkSourceFileSaver. * * Returns: the flags. */ GtkSourceFileSaverFlags gtk_source_file_saver_get_flags (GtkSourceFileSaver *saver) { g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), GTK_SOURCE_FILE_SAVER_FLAGS_NONE); return saver->flags; } /** * gtk_source_file_saver_save_async: * @saver: a #GtkSourceFileSaver. * @io_priority: the I/O priority of the request. E.g. %G_PRIORITY_LOW, * %G_PRIORITY_DEFAULT or %G_PRIORITY_HIGH. * @cancellable: (nullable): optional #GCancellable object, %NULL to ignore. * @progress_callback: (scope notified) (nullable): function to call back with * progress information, or %NULL if progress information is not needed. * @progress_callback_data: (closure): user data to pass to @progress_callback. * @progress_callback_notify: (nullable): function to call on * @progress_callback_data when the @progress_callback is no longer needed, or * %NULL. * @callback: (scope async): a #GAsyncReadyCallback to call when the request is * satisfied. * @user_data: user data to pass to @callback. * * Saves asynchronously the buffer into the file. * * See the [iface@Gio.AsyncResult] documentation to know how to use this function. */ /* The GDestroyNotify is needed, currently the following bug is not fixed: * https://bugzilla.gnome.org/show_bug.cgi?id=616044 */ void gtk_source_file_saver_save_async (GtkSourceFileSaver *saver, gint io_priority, GCancellable *cancellable, GFileProgressCallback progress_callback, gpointer progress_callback_data, GDestroyNotify progress_callback_notify, GAsyncReadyCallback callback, gpointer user_data) { TaskData *task_data; gboolean check_invalid_chars; gboolean implicit_trailing_newline; g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (saver->task == NULL); saver->task = g_task_new (saver, cancellable, callback, user_data); g_task_set_priority (saver->task, io_priority); task_data = task_data_new (); g_task_set_task_data (saver->task, task_data, task_data_free); task_data->progress_cb = progress_callback; task_data->progress_cb_data = progress_callback_data; task_data->progress_cb_notify = progress_callback_notify; if (saver->source_buffer == NULL || saver->file == NULL || saver->location == NULL) { g_task_return_boolean (saver->task, FALSE); return; } check_invalid_chars = (saver->flags & GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS) == 0; if (check_invalid_chars && _gtk_source_buffer_has_invalid_chars (saver->source_buffer)) { g_task_return_new_error (saver->task, GTK_SOURCE_FILE_SAVER_ERROR, GTK_SOURCE_FILE_SAVER_ERROR_INVALID_CHARS, _("The buffer contains invalid characters.")); return; } DEBUG ({ g_print ("Start saving\n"); }); implicit_trailing_newline = gtk_source_buffer_get_implicit_trailing_newline (saver->source_buffer); /* The BufferInputStream has a strong reference to the buffer. * We create the BufferInputStream here so we are sure that the * buffer will not be destroyed during the file saving. */ task_data->input_stream = _gtk_source_buffer_input_stream_new (GTK_TEXT_BUFFER (saver->source_buffer), saver->newline_type, implicit_trailing_newline); check_externally_modified (saver->task); } /** * gtk_source_file_saver_save_finish: * @saver: a #GtkSourceFileSaver. * @result: a #GAsyncResult. * @error: a #GError, or %NULL. * * Finishes a file saving started with [method@FileSaver.save_async]. * * If the file has been saved successfully, the following [class@File] * properties will be updated: the location, the encoding, the newline type and * the compression type. * * Since the 3.20 version, [method@Gtk.TextBuffer.set_modified] is called with %FALSE * if the file has been saved successfully. * * Returns: whether the file was saved successfully. */ gboolean gtk_source_file_saver_save_finish (GtkSourceFileSaver *saver, GAsyncResult *result, GError **error) { gboolean ok; g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (g_task_is_valid (result, saver), FALSE); ok = g_task_propagate_boolean (G_TASK (result), error); if (ok && saver->file != NULL) { TaskData *task_data; gtk_source_file_set_location (saver->file, saver->location); _gtk_source_file_set_encoding (saver->file, saver->encoding); _gtk_source_file_set_newline_type (saver->file, saver->newline_type); _gtk_source_file_set_compression_type (saver->file, saver->compression_type); _gtk_source_file_set_externally_modified (saver->file, FALSE); _gtk_source_file_set_deleted (saver->file, FALSE); _gtk_source_file_set_readonly (saver->file, FALSE); task_data = g_task_get_task_data (G_TASK (result)); if (g_file_info_has_attribute (task_data->info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) { GDateTime *dt; gint64 mtime; dt = g_file_info_get_modification_date_time (task_data->info); mtime = g_date_time_to_unix (dt); g_date_time_unref (dt); _gtk_source_file_set_modification_time (saver->file, mtime); } } if (ok && saver->source_buffer != NULL) { gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (saver->source_buffer), FALSE); } g_clear_object (&saver->task); return ok; } 07070100000152000081A4000000000000000000000001665908060000193A000000000000000000000000000000000000003800000000gtksourceview-5.12.1/gtksourceview/gtksourcefilesaver.h/* * This file is part of GtkSourceView * * Copyright 2005, 2007 Paolo Maggi * Copyright 2007 Steve Frécinaux * Copyright 2008 Jesse van den Kieboom * Copyright 2014 Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include #include "gtksourcetypes.h" #include "gtksourcefile.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_FILE_SAVER (gtk_source_file_saver_get_type()) #define GTK_SOURCE_FILE_SAVER_ERROR (gtk_source_file_saver_error_quark()) /** * GtkSourceFileSaverError: * @GTK_SOURCE_FILE_SAVER_ERROR_INVALID_CHARS: The buffer contains invalid * characters. * @GTK_SOURCE_FILE_SAVER_ERROR_EXTERNALLY_MODIFIED: The file is externally * modified. * * An error code used with the %GTK_SOURCE_FILE_SAVER_ERROR domain. */ typedef enum _GtkSourceFileSaverError { GTK_SOURCE_FILE_SAVER_ERROR_INVALID_CHARS, GTK_SOURCE_FILE_SAVER_ERROR_EXTERNALLY_MODIFIED } GtkSourceFileSaverError; /** * GtkSourceFileSaverFlags: * @GTK_SOURCE_FILE_SAVER_FLAGS_NONE: No flags. * @GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS: Ignore invalid characters. * @GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME: Save file despite external modifications. * @GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP: Create a backup before saving the file. * * Flags to define the behavior of a [flags@FileSaverFlags]. */ typedef enum _GtkSourceFileSaverFlags { GTK_SOURCE_FILE_SAVER_FLAGS_NONE = 0, GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS = 1 << 0, GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME = 1 << 1, GTK_SOURCE_FILE_SAVER_FLAGS_CREATE_BACKUP = 1 << 2 } GtkSourceFileSaverFlags; GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkSourceFileSaver, gtk_source_file_saver, GTK_SOURCE, FILE_SAVER, GObject) GTK_SOURCE_AVAILABLE_IN_ALL GQuark gtk_source_file_saver_error_quark (void); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceFileSaver *gtk_source_file_saver_new (GtkSourceBuffer *buffer, GtkSourceFile *file); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceFileSaver *gtk_source_file_saver_new_with_target (GtkSourceBuffer *buffer, GtkSourceFile *file, GFile *target_location); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceBuffer *gtk_source_file_saver_get_buffer (GtkSourceFileSaver *saver); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceFile *gtk_source_file_saver_get_file (GtkSourceFileSaver *saver); GTK_SOURCE_AVAILABLE_IN_ALL GFile *gtk_source_file_saver_get_location (GtkSourceFileSaver *saver); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_saver_set_encoding (GtkSourceFileSaver *saver, const GtkSourceEncoding *encoding); GTK_SOURCE_AVAILABLE_IN_ALL const GtkSourceEncoding *gtk_source_file_saver_get_encoding (GtkSourceFileSaver *saver); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_saver_set_newline_type (GtkSourceFileSaver *saver, GtkSourceNewlineType newline_type); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceNewlineType gtk_source_file_saver_get_newline_type (GtkSourceFileSaver *saver); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_saver_set_compression_type (GtkSourceFileSaver *saver, GtkSourceCompressionType compression_type); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceCompressionType gtk_source_file_saver_get_compression_type (GtkSourceFileSaver *saver); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_saver_set_flags (GtkSourceFileSaver *saver, GtkSourceFileSaverFlags flags); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceFileSaverFlags gtk_source_file_saver_get_flags (GtkSourceFileSaver *saver); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_file_saver_save_async (GtkSourceFileSaver *saver, gint io_priority, GCancellable *cancellable, GFileProgressCallback progress_callback, gpointer progress_callback_data, GDestroyNotify progress_callback_notify, GAsyncReadyCallback callback, gpointer user_data); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_file_saver_save_finish (GtkSourceFileSaver *saver, GAsyncResult *result, GError **error); G_END_DECLS 07070100000153000081A40000000000000000000000016659080600000721000000000000000000000000000000000000003D00000000gtksourceview-5.12.1/gtksourceview/gtksourcegutter-private.h/* * This file is part of GtkSourceView * * Copyright 2009 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include #include "gtksourcetypes.h" G_BEGIN_DECLS G_GNUC_INTERNAL GtkSourceGutter *_gtk_source_gutter_new (GtkTextWindowType type, GtkSourceView *view); G_GNUC_INTERNAL GtkSourceGutterLines *_gtk_source_gutter_get_lines (GtkSourceGutter *gutter); G_GNUC_INTERNAL void _gtk_source_gutter_queue_draw (GtkSourceGutter *gutter); G_GNUC_INTERNAL void _gtk_source_gutter_css_changed (GtkSourceGutter *gutter, GtkCssStyleChange *change); G_GNUC_INTERNAL void _gtk_source_gutter_apply_scheme (GtkSourceGutter *gutter, GtkSourceStyleScheme *scheme); G_GNUC_INTERNAL void _gtk_source_gutter_unapply_scheme (GtkSourceGutter *gutter, GtkSourceStyleScheme *scheme); G_END_DECLS 07070100000154000081A400000000000000000000000166590806000080CD000000000000000000000000000000000000003500000000gtksourceview-5.12.1/gtksourceview/gtksourcegutter.c/* * This file is part of GtkSourceView * * Copyright 2009 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourcebuffer.h" #include "gtksourcegutter.h" #include "gtksourcegutter-private.h" #include "gtksourcegutterlines.h" #include "gtksourcegutterlines-private.h" #include "gtksourcestylescheme-private.h" #include "gtksourceview-private.h" #include "gtksourcegutterrenderer.h" #include "gtksourcegutterrenderer-private.h" /** * GtkSourceGutter: * * Gutter object for [class@View]. * * The `GtkSourceGutter` object represents the left or right gutter of the text * view. It is used by [class@View] to draw the line numbers and * [class@Mark]s that might be present on a line. By packing * additional [class@GutterRenderer] objects in the gutter, you can extend the * gutter with your own custom drawings. * * To get a `GtkSourceGutter`, use the [method@View.get_gutter] function. * * The gutter works very much the same way as cells rendered in a [class@Gtk.TreeView]. * The concept is similar, with the exception that the gutter does not have an * underlying [iface@Gtk.TreeModel]. The builtin line number renderer is at position * %GTK_SOURCE_VIEW_GUTTER_POSITION_LINES (-30) and the marks renderer is at * %GTK_SOURCE_VIEW_GUTTER_POSITION_MARKS (-20). The gutter sorts the renderers * in ascending order, from left to right. So the marks are displayed on the * right of the line numbers. */ enum { PROP_0, PROP_VIEW, PROP_WINDOW_TYPE, }; typedef struct { GtkSourceGutterRenderer *renderer; gint prelit; gint position; } Renderer; struct _GtkSourceGutter { GtkWidget parent_instance; GtkSourceView *view; GList *renderers; GtkSourceGutterLines *lines; GSignalGroup *signals; GBinding *target_binding; GtkTextWindowType window_type; GtkOrientation orientation; double pointer_x; double pointer_y; guint is_drawing : 1; guint pointer_in_gutter : 1; }; G_DEFINE_TYPE (GtkSourceGutter, gtk_source_gutter, GTK_TYPE_WIDGET) static void on_gutter_pressed_cb (GtkSourceGutter *gutter, gint n_presses, gdouble x, gdouble y, GtkGestureClick *click); static void do_redraw (GtkSourceGutter *gutter); static void gtk_source_gutter_snapshot (GtkWidget *widget, GtkSnapshot *snapshot); static void gtk_source_gutter_size_allocate (GtkWidget *widget, gint width, gint height, gint baseline); static Renderer * renderer_new (GtkSourceGutter *gutter, GtkSourceGutterRenderer *renderer, gint position) { Renderer *ret; ret = g_slice_new0 (Renderer); ret->renderer = g_object_ref_sink (renderer); ret->position = position; ret->prelit = -1; _gtk_source_gutter_renderer_set_view (renderer, gutter->view); return ret; } static void renderer_free (Renderer *renderer) { _gtk_source_gutter_renderer_set_view (renderer->renderer, NULL); g_object_unref (renderer->renderer); g_slice_free (Renderer, renderer); } static void get_alignment_modes (GtkSourceGutter *gutter, gboolean *needs_wrap_first, gboolean *needs_wrap_last) { const GList *list; g_assert (GTK_SOURCE_GUTTER (gutter)); g_assert (needs_wrap_first != NULL); g_assert (needs_wrap_last != NULL); *needs_wrap_first = FALSE; *needs_wrap_last = FALSE; for (list = gutter->renderers; list; list = list->next) { Renderer *renderer = list->data; GtkSourceGutterRendererAlignmentMode mode; mode = gtk_source_gutter_renderer_get_alignment_mode (renderer->renderer); switch (mode) { case GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_FIRST: *needs_wrap_first = TRUE; break; case GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_LAST: *needs_wrap_last = TRUE; break; case GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_CELL: default: break; } } } static void gtk_source_gutter_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceGutter *gutter = GTK_SOURCE_GUTTER (object); switch (prop_id) { case PROP_VIEW: g_value_set_object (value, gutter->view); break; case PROP_WINDOW_TYPE: g_value_set_enum (value, gutter->window_type); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void on_adjustment_value_changed (GtkAdjustment *adj, GtkSourceGutter *gutter) { _gtk_source_gutter_queue_draw (gutter); } static void on_adjustment_upper_changed (GtkAdjustment *adj, GParamSpec *pspec, GtkSourceGutter *gutter) { _gtk_source_gutter_queue_draw (gutter); } static void connect_view (GtkSourceGutter *gutter, GtkSourceView *view) { const gchar *property_name; g_assert (GTK_SOURCE_IS_GUTTER (gutter)); g_assert (GTK_SOURCE_IS_VIEW (view)); g_assert (gutter->target_binding == NULL); if (gutter->window_type == GTK_TEXT_WINDOW_LEFT || gutter->window_type == GTK_TEXT_WINDOW_RIGHT) { property_name = "vadjustment"; } else { property_name = "hadjustment"; } g_object_bind_property (view, property_name, gutter->signals, "target", G_BINDING_SYNC_CREATE); } static void disconnect_view (GtkSourceGutter *gutter, GtkSourceView *view) { g_assert (GTK_SOURCE_IS_GUTTER (gutter)); g_assert (GTK_SOURCE_IS_VIEW (view)); g_clear_pointer (&gutter->target_binding, g_binding_unbind); } static void set_view (GtkSourceGutter *gutter, GtkSourceView *view) { g_return_if_fail (GTK_SOURCE_IS_GUTTER (gutter)); g_return_if_fail (!view || GTK_SOURCE_IS_VIEW (view)); if (view == gutter->view) { return; } if (gutter->view != NULL) { disconnect_view (gutter, gutter->view); } gutter->view = view; if (view != NULL) { connect_view (gutter, view); } } static void do_redraw (GtkSourceGutter *gutter) { if (!gutter->is_drawing) { gtk_widget_queue_draw (GTK_WIDGET (gutter)); } } static void gtk_source_gutter_map (GtkWidget *widget) { gtk_widget_set_cursor_from_name (widget, "default"); GTK_WIDGET_CLASS (gtk_source_gutter_parent_class)->map (widget); } static void gtk_source_gutter_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkSourceGutter *gutter = GTK_SOURCE_GUTTER (widget); const GList *item; /* Calculate size */ for (item = gutter->renderers; item; item = item->next) { Renderer *renderer = item->data; int r_minimum; int r_natural; int r_minimum_baseline; int r_natural_baseline; if (!gtk_widget_get_visible (GTK_WIDGET (renderer->renderer))) { continue; } gtk_widget_measure (GTK_WIDGET (renderer->renderer), orientation, for_size, &r_minimum, &r_natural, &r_minimum_baseline, &r_natural_baseline); *minimum += r_minimum; *natural += r_natural; } *minimum_baseline = -1; *natural_baseline = -1; } static void gtk_source_gutter_motion_cb (GtkSourceGutter *gutter, double x, double y, GtkEventControllerMotion *motion) { g_assert (GTK_SOURCE_IS_GUTTER (gutter)); g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); gutter->pointer_x = x; gutter->pointer_y = y; gutter->pointer_in_gutter = TRUE; _gtk_source_gutter_queue_draw (gutter); } static void gtk_source_gutter_leave_cb (GtkSourceGutter *gutter, GtkEventControllerMotion *motion) { g_assert (GTK_SOURCE_IS_GUTTER (gutter)); g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); gutter->pointer_x = -1; gutter->pointer_y = -1; gutter->pointer_in_gutter = FALSE; _gtk_source_gutter_queue_draw (gutter); } static void gtk_source_gutter_root (GtkWidget *widget) { GtkWidget *parent; g_assert (GTK_SOURCE_IS_GUTTER (widget)); GTK_WIDGET_CLASS (gtk_source_gutter_parent_class)->root (widget); parent = gtk_widget_get_parent (widget); /* The GtkTextViewChild has "overflow" set to Hidden and we * want to allow drawing over that. */ if (parent != NULL) { gtk_widget_set_overflow (parent, GTK_OVERFLOW_VISIBLE); } } static void gtk_source_gutter_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceGutter *gutter = GTK_SOURCE_GUTTER (object); switch (prop_id) { case PROP_WINDOW_TYPE: gutter->window_type = g_value_get_enum (value); break; case PROP_VIEW: set_view (gutter, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_gutter_constructed (GObject *object) { GtkSourceGutter *gutter = GTK_SOURCE_GUTTER (object); if (gutter->window_type == GTK_TEXT_WINDOW_LEFT || gutter->window_type == GTK_TEXT_WINDOW_RIGHT) { gutter->orientation = GTK_ORIENTATION_HORIZONTAL; gtk_widget_set_vexpand (GTK_WIDGET (gutter), TRUE); } else { gutter->orientation = GTK_ORIENTATION_VERTICAL; gtk_widget_set_hexpand (GTK_WIDGET (gutter), TRUE); } G_OBJECT_CLASS (gtk_source_gutter_parent_class)->constructed (object); } static void gtk_source_gutter_dispose (GObject *object) { GtkSourceGutter *gutter = (GtkSourceGutter *)object; g_clear_pointer (&gutter->target_binding, g_binding_unbind); if (gutter->signals != NULL) { g_signal_group_set_target (gutter->signals, NULL); g_clear_object (&gutter->signals); } G_OBJECT_CLASS (gtk_source_gutter_parent_class)->dispose (object); } static void gtk_source_gutter_class_init (GtkSourceGutterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = gtk_source_gutter_constructed; object_class->dispose = gtk_source_gutter_dispose; object_class->get_property = gtk_source_gutter_get_property; object_class->set_property = gtk_source_gutter_set_property; widget_class->map = gtk_source_gutter_map; widget_class->measure = gtk_source_gutter_measure; widget_class->size_allocate = gtk_source_gutter_size_allocate; widget_class->snapshot = gtk_source_gutter_snapshot; widget_class->root = gtk_source_gutter_root; /** * GtkSourceGutter:view: * * The #GtkSourceView of the gutter. */ g_object_class_install_property (object_class, PROP_VIEW, g_param_spec_object ("view", "View", "", GTK_SOURCE_TYPE_VIEW, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GtkSourceGutter:window-type: * * The text window type on which the window is placed. */ g_object_class_install_property (object_class, PROP_WINDOW_TYPE, g_param_spec_enum ("window_type", "Window Type", "The gutters' text window type", GTK_TYPE_TEXT_WINDOW_TYPE, GTK_TEXT_WINDOW_LEFT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); gtk_widget_class_set_css_name (widget_class, "gutter"); } static void gtk_source_gutter_init (GtkSourceGutter *gutter) { GtkGesture *click; GtkEventController *motion; gutter->window_type = GTK_TEXT_WINDOW_LEFT; gutter->signals = g_signal_group_new (GTK_TYPE_ADJUSTMENT); g_signal_group_connect_object (gutter->signals, "value-changed", G_CALLBACK (on_adjustment_value_changed), gutter, 0); g_signal_group_connect_object (gutter->signals, "notify::upper", G_CALLBACK (on_adjustment_upper_changed), gutter, 0); /* Setup fallback click handling */ click = gtk_gesture_click_new (); g_signal_connect_swapped (click, "pressed", G_CALLBACK (on_gutter_pressed_cb), gutter); gtk_widget_add_controller (GTK_WIDGET (gutter), GTK_EVENT_CONTROLLER (click)); /* Track motion enter/leave for prelit status */ motion = gtk_event_controller_motion_new (); g_signal_connect_swapped (motion, "enter", G_CALLBACK (gtk_source_gutter_motion_cb), gutter); g_signal_connect_swapped (motion, "leave", G_CALLBACK (gtk_source_gutter_leave_cb), gutter); g_signal_connect_swapped (motion, "motion", G_CALLBACK (gtk_source_gutter_motion_cb), gutter); gtk_widget_add_controller (GTK_WIDGET (gutter), motion); } static gint sort_by_position (Renderer *r1, Renderer *r2, gpointer data) { if (r1->position < r2->position) { return -1; } else if (r1->position > r2->position) { return 1; } else { return 0; } } static void append_renderer (GtkSourceGutter *gutter, Renderer *renderer) { gutter->renderers = g_list_insert_sorted_with_data (gutter->renderers, renderer, (GCompareDataFunc)sort_by_position, NULL); } GtkSourceGutter * _gtk_source_gutter_new (GtkTextWindowType type, GtkSourceView *view) { return g_object_new (GTK_SOURCE_TYPE_GUTTER, "window-type", type, "view", view, NULL); } /** * gtk_source_gutter_get_view: * @gutter: a #GtkSourceGutter. * * Returns: (transfer none): the associated #GtkSourceView. */ GtkSourceView * gtk_source_gutter_get_view (GtkSourceGutter *gutter) { g_return_val_if_fail (GTK_SOURCE_IS_GUTTER (gutter), NULL); return gutter->view; } /** * gtk_source_gutter_insert: * @gutter: a #GtkSourceGutter. * @renderer: a gutter renderer (must inherit from #GtkSourceGutterRenderer). * @position: the renderer position. * * Insert @renderer into the gutter. If @renderer is yet unowned then gutter * claims its ownership. Otherwise just increases renderer's reference count. * @renderer cannot be already inserted to another gutter. * * Returns: %TRUE if operation succeeded. Otherwise %FALSE. **/ gboolean gtk_source_gutter_insert (GtkSourceGutter *gutter, GtkSourceGutterRenderer *renderer, gint position) { Renderer* internal_renderer; g_return_val_if_fail (GTK_SOURCE_IS_GUTTER (gutter), FALSE); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), FALSE); g_return_val_if_fail (gtk_source_gutter_renderer_get_view (renderer) == NULL, FALSE); if (gutter->view != NULL) { GtkTextBuffer *buffer; buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (gutter->view)); if (GTK_SOURCE_IS_BUFFER (buffer)) { GtkSourceStyleScheme *scheme; scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer)); if (scheme != NULL) { _gtk_source_style_scheme_apply (scheme, GTK_WIDGET (renderer)); } } } internal_renderer = renderer_new (gutter, renderer, position); append_renderer (gutter, internal_renderer); gtk_widget_set_parent (GTK_WIDGET (renderer), GTK_WIDGET (gutter)); gtk_widget_queue_resize (GTK_WIDGET (gutter)); return TRUE; } static gboolean renderer_find (GtkSourceGutter *gutter, GtkSourceGutterRenderer *renderer, Renderer **ret, GList **retlist) { GList *list; for (list = gutter->renderers; list; list = list->next) { *ret = list->data; if ((*ret)->renderer == renderer) { if (retlist) { *retlist = list; } return TRUE; } } return FALSE; } void gtk_source_gutter_remove (GtkSourceGutter *gutter, GtkSourceGutterRenderer *renderer) { Renderer *ret; GList *retlist; g_return_if_fail (GTK_SOURCE_IS_GUTTER (gutter)); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); if (renderer_find (gutter, renderer, &ret, &retlist)) { gutter->renderers = g_list_delete_link (gutter->renderers, retlist); gtk_widget_unparent (GTK_WIDGET (renderer)); renderer_free (ret); gtk_widget_queue_resize (GTK_WIDGET (gutter)); } else { g_warning ("Failed to locate %s within %s", G_OBJECT_TYPE_NAME (renderer), G_OBJECT_TYPE_NAME (gutter)); } } /** * gtk_source_gutter_reorder: * @gutter: a #GtkSourceGutterRenderer. * @renderer: a #GtkCellRenderer. * @position: the new renderer position. * * Reorders @renderer in @gutter to new @position. */ void gtk_source_gutter_reorder (GtkSourceGutter *gutter, GtkSourceGutterRenderer *renderer, gint position) { Renderer *ret; GList *retlist; g_return_if_fail (GTK_SOURCE_IS_GUTTER (gutter)); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); if (renderer_find (gutter, renderer, &ret, &retlist)) { gutter->renderers = g_list_delete_link (gutter->renderers, retlist); ret->position = position; append_renderer (gutter, ret); gtk_widget_queue_allocate (GTK_WIDGET (gutter)); } } static void gtk_source_gutter_size_allocate (GtkWidget *widget, gint width, gint height, gint baseline) { GtkSourceGutter *gutter = GTK_SOURCE_GUTTER (widget); const GList *list; gint x = 0; GTK_WIDGET_CLASS (gtk_source_gutter_parent_class)->size_allocate (widget, width, height, baseline); for (list = gutter->renderers; list; list = list->next) { Renderer *renderer = list->data; GtkRequisition child_req; GtkAllocation alloc; gtk_widget_get_preferred_size (GTK_WIDGET (renderer->renderer), &child_req, NULL); alloc.x = x; alloc.y = 0; alloc.width = child_req.width; alloc.height = height; gtk_widget_size_allocate (GTK_WIDGET (renderer->renderer), &alloc, -1); x += alloc.width; } gtk_widget_queue_draw (widget); } static void gtk_source_gutter_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { GtkSourceGutter *gutter = GTK_SOURCE_GUTTER (widget); GtkTextView *text_view = GTK_TEXT_VIEW (gutter->view); GtkTextBuffer *buffer; const GList *list; GdkRectangle visible_rect; GtkTextIter begin, end; GtkTextIter cur, sel; gboolean needs_wrap_first = FALSE; gboolean needs_wrap_last = FALSE; int clip_width = 0; g_clear_object (&gutter->lines); if (gutter->renderers == NULL || text_view == NULL || gtk_widget_get_width (widget) == 0) { return; } buffer = gtk_text_view_get_buffer (text_view); gtk_text_view_get_visible_rect (text_view, &visible_rect); gtk_text_view_get_iter_at_location (text_view, &begin, visible_rect.x, visible_rect.y); gtk_text_view_get_iter_at_location (text_view, &end, visible_rect.x, visible_rect.y + visible_rect.height); /* Try to include an extra line on each edge so that situations * that are dependent on neighboring lines can still include enough * information to draw correctly. This is useful for situations like * git where you might need to draw special delete marks. */ gtk_text_iter_backward_line (&begin); gtk_text_iter_forward_line (&end); /* The first step is to get line information about all the visible * lines. We do this up front so that we can do it once to reduce many * times the renderers need to walk through the buffer contents as that * can be expensive. */ get_alignment_modes (gutter, &needs_wrap_first, &needs_wrap_last); gutter->lines = _gtk_source_gutter_lines_new (text_view, &begin, &end, needs_wrap_first, needs_wrap_last); /* Get the line under the pointer so we can set "prelit" on it */ if (gutter->pointer_in_gutter) { GtkTextIter pointer; GdkRectangle pointer_rect; gtk_text_view_get_iter_at_location (text_view, &pointer, 0, visible_rect.y + gutter->pointer_y); gtk_text_view_get_iter_location (text_view, &pointer, &pointer_rect); pointer_rect.y -= visible_rect.y; if (gutter->pointer_y >= pointer_rect.y && gutter->pointer_y <= pointer_rect.y + pointer_rect.height) { guint line = gtk_text_iter_get_line (&pointer); gtk_source_gutter_lines_add_class (gutter->lines, line, "prelit"); } } /* Draw the current-line highlight if necessary */ if (gtk_source_view_get_highlight_current_line (gutter->view) && !gtk_text_buffer_get_selection_bounds (buffer, &cur, &sel)) { GtkRoot *root; GdkRGBA highlight; guint cursor_line; cursor_line = _gtk_source_gutter_lines_get_cursor_line (gutter->lines); if (cursor_line >= gtk_source_gutter_lines_get_first (gutter->lines) && cursor_line <= gtk_source_gutter_lines_get_last (gutter->lines) && _gtk_source_view_get_current_line_number_background (gutter->view, &highlight) && (root = gtk_widget_get_root (GTK_WIDGET (gutter->view))) && GTK_IS_WINDOW (root) && gtk_window_is_active (GTK_WINDOW (root))) { int width = gtk_widget_get_width (widget); int height; int y; gtk_source_gutter_lines_get_line_yrange (gutter->lines, cursor_line, GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_CELL, &y, &height); gtk_snapshot_append_color (snapshot, &highlight, &GRAPHENE_RECT_INIT (0, y, width, height)); } } gutter->is_drawing = TRUE; /* Now let the renderers populate information about the lines that are * to be rendered. They may need to go through line by line and add * classes (GQuark) to the lines to be used when snapshoting. Since * we've already calculated line information, this is relatively fast. * * We also only emit the ::query-data signal in the case that the * renderer has not override then (*query_data) vfunc which saves quite * a bit of signal overhead. */ for (list = gutter->renderers; list; list = list->next) { Renderer *renderer = list->data; _gtk_source_gutter_renderer_begin (renderer->renderer, gutter->lines); } clip_width = gtk_widget_get_width (widget); /* Allow drawing over the left margin from renderers */ if (gutter->window_type == GTK_TEXT_WINDOW_LEFT) { clip_width += gtk_text_view_get_left_margin (text_view); } gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, clip_width, gtk_widget_get_height (widget))); /* Now let the renderers draw the content for each line. Because * iterating a Linked-List is slower than iterating a series of line * numbers, we make the renderer list the outer loop, and the * snapshotting of lines (within the renderer) the inner loop as part * of snapshot. */ for (list = gutter->renderers; list; list = list->next) { Renderer *renderer = list->data; gtk_widget_snapshot_child (widget, GTK_WIDGET (renderer->renderer), snapshot); } gtk_snapshot_pop (snapshot); /* Allow to call queue_redraw() in end. */ gutter->is_drawing = FALSE; /* Now notify the renderers of completion */ for (list = gutter->renderers; list; list = list->next) { Renderer *renderer = list->data; _gtk_source_gutter_renderer_end (renderer->renderer); } } static Renderer * renderer_at_x (GtkSourceGutter *gutter, gint x, gint *width) { const GList *item; for (item = gutter->renderers; item; item = g_list_next (item)) { Renderer *renderer = item->data; GtkAllocation alloc; gtk_widget_get_allocation (GTK_WIDGET (renderer->renderer), &alloc); if (x >= alloc.x && x <= alloc.x + alloc.width) { return renderer; } } return NULL; } static void get_renderer_rect (GtkSourceGutter *gutter, Renderer *renderer, GtkTextIter *iter, gint line, GdkRectangle *rectangle) { gint y; gint ypad; gtk_widget_get_allocation (GTK_WIDGET (renderer->renderer), rectangle); gtk_text_view_get_line_yrange (GTK_TEXT_VIEW (gutter->view), iter, &y, &rectangle->height); gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (gutter->view), gutter->window_type, 0, y, NULL, &rectangle->y); ypad = gtk_source_gutter_renderer_get_ypad (renderer->renderer); rectangle->y += ypad; rectangle->height -= 2 * ypad; } static gboolean renderer_query_activatable (GtkSourceGutter *gutter, Renderer *renderer, gdouble x, gdouble y, GtkTextIter *line_iter, GdkRectangle *rect) { gint y_buf; gint yline; GtkTextIter iter; GdkRectangle r = {0}; if (renderer == NULL) { return FALSE; } gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (gutter->view), GTK_TEXT_WINDOW_WIDGET, (gint)x, (gint)y, NULL, &y_buf); gtk_text_view_get_line_at_y (GTK_TEXT_VIEW (gutter->view), &iter, y_buf, &yline); if (yline > y_buf) { return FALSE; } get_renderer_rect (gutter, renderer, &iter, yline, &r); if (line_iter) { *line_iter = iter; } if (rect) { *rect = r; } if (y < r.y || y > r.y + r.height) { return FALSE; } return gtk_source_gutter_renderer_query_activatable (renderer->renderer, &iter, &r); } static gboolean get_button (GdkEvent *event, guint *button) { GdkEventType type; g_assert (event != NULL); g_assert (button != NULL); type = gdk_event_get_event_type (event); if (type == GDK_BUTTON_PRESS || type == GDK_BUTTON_RELEASE) { *button = gdk_button_event_get_button (event); return TRUE; } return FALSE; } static gboolean get_modifier_state (GdkEvent *event, GdkModifierType *state) { g_assert (event != NULL); g_assert (state != NULL); *state = gdk_event_get_modifier_state (event); return TRUE; } static void on_gutter_pressed_cb (GtkSourceGutter *gutter, gint n_presses, gdouble x, gdouble y, GtkGestureClick *click) { GdkEvent *last_event; Renderer *renderer; GtkTextIter line_iter; GdkRectangle rect; GdkModifierType state; guint button; g_assert (GTK_SOURCE_IS_GUTTER (gutter)); g_assert (GTK_IS_GESTURE_CLICK (click)); last_event = gtk_gesture_get_last_event (GTK_GESTURE (click), NULL); if (last_event == NULL || !get_modifier_state (last_event, &state) || !get_button (last_event, &button)) { return; } /* Check cell renderer */ renderer = renderer_at_x (gutter, x, NULL); if (renderer_query_activatable (gutter, renderer, x, y, &line_iter, &rect)) { gtk_source_gutter_renderer_activate (renderer->renderer, &line_iter, &rect, button, state, n_presses); do_redraw (gutter); gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); } } void _gtk_source_gutter_css_changed (GtkSourceGutter *gutter, GtkCssStyleChange *change) { g_assert (GTK_SOURCE_IS_GUTTER (gutter)); do_redraw (gutter); } GtkSourceGutterLines * _gtk_source_gutter_get_lines (GtkSourceGutter *gutter) { g_return_val_if_fail (GTK_SOURCE_IS_GUTTER (gutter), NULL); return gutter->lines; } void _gtk_source_gutter_queue_draw (GtkSourceGutter *gutter) { for (const GList *iter = gutter->renderers; iter; iter = iter->next) { Renderer *renderer = iter->data; gtk_widget_queue_draw (GTK_WIDGET (renderer->renderer)); } } void _gtk_source_gutter_apply_scheme (GtkSourceGutter *gutter, GtkSourceStyleScheme *scheme) { if (gutter == NULL) { return; } _gtk_source_style_scheme_apply (scheme, GTK_WIDGET (gutter)); for (const GList *iter = gutter->renderers; iter; iter = iter->next) { Renderer *renderer = iter->data; _gtk_source_style_scheme_apply (scheme, GTK_WIDGET (renderer->renderer)); } } void _gtk_source_gutter_unapply_scheme (GtkSourceGutter *gutter, GtkSourceStyleScheme *scheme) { if (gutter == NULL) { return; } _gtk_source_style_scheme_unapply (scheme, GTK_WIDGET (gutter)); for (const GList *iter = gutter->renderers; iter; iter = iter->next) { Renderer *renderer = iter->data; _gtk_source_style_scheme_unapply (scheme, GTK_WIDGET (renderer->renderer)); } } 07070100000155000081A4000000000000000000000001665908060000082F000000000000000000000000000000000000003500000000gtksourceview-5.12.1/gtksourceview/gtksourcegutter.h/* * This file is part of GtkSourceView * * Copyright 2009 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_GUTTER (gtk_source_gutter_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkSourceGutter, gtk_source_gutter, GTK_SOURCE, GUTTER, GtkWidget) GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceView *gtk_source_gutter_get_view (GtkSourceGutter *gutter); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_gutter_insert (GtkSourceGutter *gutter, GtkSourceGutterRenderer *renderer, gint position); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_reorder (GtkSourceGutter *gutter, GtkSourceGutterRenderer *renderer, gint position); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_remove (GtkSourceGutter *gutter, GtkSourceGutterRenderer *renderer); G_END_DECLS 07070100000156000081A400000000000000000000000166590806000005D8000000000000000000000000000000000000004200000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterlines-private.h/* * This file is part of GtkSourceView * * Copyright 2019 - Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include #include "gtksourcegutterlines.h" G_BEGIN_DECLS G_GNUC_INTERNAL GtkSourceGutterLines *_gtk_source_gutter_lines_new (GtkTextView *text_view, const GtkTextIter *begin, const GtkTextIter *end, gboolean needs_wrap_first, gboolean needs_wrap_last); G_GNUC_INTERNAL guint _gtk_source_gutter_lines_get_cursor_line (GtkSourceGutterLines *lines); G_END_DECLS 07070100000157000081A400000000000000000000000166590806000049E1000000000000000000000000000000000000003A00000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterlines.c/* * This file is part of GtkSourceView * * Copyright 2019 - Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourcegutterlines.h" #include "gtksourcegutterlines-private.h" #include "quarkset-inline.h" /** * GtkSourceGutterLines: * * Collected information about visible lines. * * The `GtkSourceGutterLines` object is used to collect information about * visible lines. * * Use this from your [signal@GutterRenderer::query-data] to collect the * necessary information on visible lines. Doing so reduces the number of * passes through the text btree allowing GtkSourceView to reach more * frames-per-second while performing kinetic scrolling. */ struct _GtkSourceGutterLines { GObject parent_instance; GtkTextView *view; GArray *lines; GdkRectangle visible_rect; guint first; guint last; guint cursor_line; }; typedef struct { QuarkSet classes; gint y; gint height; gint first_height; gint last_height; } LineInfo; G_DEFINE_TYPE (GtkSourceGutterLines, gtk_source_gutter_lines, G_TYPE_OBJECT) static GQuark q_cursor_line; static GQuark q_prelit; static GQuark q_selected; static void gtk_source_gutter_lines_finalize (GObject *object) { GtkSourceGutterLines *lines = (GtkSourceGutterLines *)object; g_clear_pointer (&lines->lines, g_array_unref); g_clear_object (&lines->view); G_OBJECT_CLASS (gtk_source_gutter_lines_parent_class)->finalize (object); } static void gtk_source_gutter_lines_class_init (GtkSourceGutterLinesClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gtk_source_gutter_lines_finalize; q_cursor_line = g_quark_from_static_string ("cursor-line"); q_prelit = g_quark_from_static_string ("prelit"); q_selected = g_quark_from_static_string ("selected"); } static void gtk_source_gutter_lines_init (GtkSourceGutterLines *self) { self->cursor_line = -1; } static void clear_line_info (gpointer data) { LineInfo *info = data; info->y = 0; info->height = 0; quark_set_clear (&info->classes); } GtkSourceGutterLines * _gtk_source_gutter_lines_new (GtkTextView *text_view, const GtkTextIter *begin, const GtkTextIter *end, gboolean needs_wrap_first, gboolean needs_wrap_last) { GtkSourceGutterLines *lines; GtkTextBuffer *buffer; GtkTextMark *mark; GtkTextIter iter; GtkTextIter sel_begin, sel_end; gboolean single_line; guint cursor_line; guint i; int first_selected = -1; int last_selected = -1; g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL); g_return_val_if_fail (begin != NULL, NULL); g_return_val_if_fail (end != NULL, NULL); g_return_val_if_fail (begin != end, NULL); buffer = gtk_text_view_get_buffer (text_view); g_return_val_if_fail (gtk_text_iter_get_buffer (begin) == buffer, NULL); g_return_val_if_fail (gtk_text_iter_get_buffer (end) == buffer, NULL); if (gtk_text_buffer_get_selection_bounds (buffer, &sel_begin, &sel_end)) { gtk_text_iter_order (&sel_begin, &sel_end); first_selected = gtk_text_iter_get_line (&sel_begin); last_selected = gtk_text_iter_get_line (&sel_end); } if (gtk_text_iter_compare (begin, end) > 0) { const GtkTextIter *tmp = begin; begin = end; end = tmp; } g_return_val_if_fail (begin != end, NULL); lines = g_object_new (GTK_SOURCE_TYPE_GUTTER_LINES, NULL); lines->view = g_object_ref (text_view); lines->first = gtk_text_iter_get_line (begin); lines->last = gtk_text_iter_get_line (end); lines->lines = g_array_sized_new (FALSE, FALSE, sizeof (LineInfo), lines->last - lines->first + 1); g_array_set_clear_func (lines->lines, clear_line_info); gtk_text_view_get_visible_rect (text_view, &lines->visible_rect); /* No need to calculate special wrapping if wrap mode is none */ if (gtk_text_view_get_wrap_mode (text_view) == GTK_WRAP_NONE) { needs_wrap_first = FALSE; needs_wrap_last = FALSE; } single_line = !needs_wrap_first && !needs_wrap_last; /* Get the line number containing the cursor to compare while * building the lines to add the "cursor-line" quark. */ mark = gtk_text_buffer_get_insert (buffer); gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark); cursor_line = gtk_text_iter_get_line (&iter); lines->cursor_line = cursor_line; iter = *begin; if (!gtk_text_iter_starts_line (&iter)) { gtk_text_iter_set_line_offset (&iter, 0); } for (i = lines->first; i <= lines->last; i++) { LineInfo info = {0}; if G_LIKELY (single_line) { /* Need to use yrange so that line-height can be taken * into account. */ gtk_text_view_get_line_yrange (text_view, &iter, &info.y, &info.height); info.first_height = info.height; info.last_height = info.height; } else { gtk_text_view_get_line_yrange (text_view, &iter, &info.y, &info.height); if (gtk_text_iter_starts_line (&iter) && gtk_text_iter_ends_line (&iter)) { info.first_height = info.height; info.last_height = info.height; } else { GdkRectangle rect; if (needs_wrap_first) { gtk_text_view_get_iter_location (text_view, &iter, &rect); /* Try to somewhat handle line-height correctly */ info.first_height = ((rect.y - info.y) * 2) + rect.height; } else { info.first_height = info.height; } if (needs_wrap_last) { gtk_text_iter_forward_to_line_end (&iter); /* Prefer the character right before \n to get * more accurate rectangle sizing. */ if (!gtk_text_iter_starts_line (&iter)) { gtk_text_iter_backward_char (&iter); gtk_text_view_get_iter_location (text_view, &iter, &rect); gtk_text_iter_forward_char (&iter); } else { gtk_text_view_get_iter_location (text_view, &iter, &rect); } /* Try to somewhat handle line-height correctly */ info.last_height = ((info.y + info.height) - (rect.y + rect.height)) * 2 + rect.height; } else { info.last_height = info.first_height; } } } if G_UNLIKELY (i == cursor_line) { quark_set_add (&info.classes, q_cursor_line); } if G_UNLIKELY (i >= first_selected && i <= last_selected) { quark_set_add (&info.classes, q_selected); } g_array_append_val (lines->lines, info); if G_UNLIKELY (!gtk_text_iter_forward_line (&iter) && !gtk_text_iter_is_end (&iter)) { break; } } g_return_val_if_fail (lines->lines->len > 0, NULL); g_return_val_if_fail ((lines->last - lines->first) >= (lines->lines->len - 1), NULL); return g_steal_pointer (&lines); } /** * gtk_source_gutter_lines_add_qclass: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * @qname: a class name as a #GQuark * * Adds the class denoted by @qname to @line. * * You may check if a line has @qname by calling * [method@GutterLines.has_qclass]. * * You can remove @qname by calling * [method@GutterLines.remove_qclass]. */ void gtk_source_gutter_lines_add_qclass (GtkSourceGutterLines *lines, guint line, GQuark qname) { LineInfo *info; g_return_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines)); g_return_if_fail (qname != 0); g_return_if_fail (line >= lines->first); g_return_if_fail (line <= lines->last); g_return_if_fail (line - lines->first < lines->lines->len); info = &g_array_index (lines->lines, LineInfo, line - lines->first); quark_set_add (&info->classes, qname); } /** * gtk_source_gutter_lines_add_class: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * @name: a class name * * Adds the class @name to @line. * * @name will be converted to a [alias@GLib.Quark] as part of this process. A * faster version of this function is available via * [method@GutterLines.add_qclass] for situations where the [alias@GLib.Quark] is * known ahead of time. */ void gtk_source_gutter_lines_add_class (GtkSourceGutterLines *lines, guint line, const gchar *name) { g_return_if_fail (name != NULL); gtk_source_gutter_lines_add_qclass (lines, line, g_quark_from_string (name)); } /** * gtk_source_gutter_lines_remove_class: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * @name: a class name * * Removes the class matching @name from @line. * * A faster version of this function is available via * [method@GutterLines.remove_qclass] for situations where the * #GQuark is known ahead of time. */ void gtk_source_gutter_lines_remove_class (GtkSourceGutterLines *lines, guint line, const gchar *name) { GQuark qname; g_return_if_fail (name != NULL); qname = g_quark_try_string (name); if (qname != 0) { gtk_source_gutter_lines_remove_qclass (lines, line, qname); } } /** * gtk_source_gutter_lines_remove_qclass: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * @qname: a #GQuark to remove from @line * * Reverses a call to [method@GutterLines.add_qclass] by removing * the [alias@GLib.Quark] matching @qname. */ void gtk_source_gutter_lines_remove_qclass (GtkSourceGutterLines *lines, guint line, GQuark qname) { LineInfo *info; g_return_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines)); g_return_if_fail (qname != 0); g_return_if_fail (line >= lines->first); g_return_if_fail (line <= lines->last); g_return_if_fail (line - lines->first < lines->lines->len); info = &g_array_index (lines->lines, LineInfo, line - lines->first); quark_set_remove (&info->classes, qname); } /** * gtk_source_gutter_lines_has_class: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * @name: a class name that may be converted, to a #GQuark * * Checks to see if [method@GutterLines.add_class] was called with * the @name for @line. * * A faster version of this function is provided via * [method@GutterLines.has_qclass] for situations where the quark * is known ahead of time. * * Returns: %TRUE if @line contains @name */ gboolean gtk_source_gutter_lines_has_class (GtkSourceGutterLines *lines, guint line, const gchar *name) { GQuark qname; g_return_val_if_fail (name != NULL, FALSE); qname = g_quark_try_string (name); if (qname == 0) { return FALSE; } return gtk_source_gutter_lines_has_qclass (lines, line, qname); } /** * gtk_source_gutter_lines_has_qclass: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * @qname: a #GQuark containing the class name * * Checks to see if [method@GutterLines.add_qclass] was called with * the quark denoted by @qname for @line. * * Returns: %TRUE if @line contains @qname */ gboolean gtk_source_gutter_lines_has_qclass (GtkSourceGutterLines *lines, guint line, GQuark qname) { LineInfo *info; g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines), FALSE); g_return_val_if_fail (qname != 0, FALSE); g_return_val_if_fail (line >= lines->first, FALSE); g_return_val_if_fail (line <= lines->last, FALSE); g_return_val_if_fail (line - lines->first < lines->lines->len, FALSE); info = &g_array_index (lines->lines, LineInfo, line - lines->first); return quark_set_contains (&info->classes, qname); } /** * gtk_source_gutter_lines_is_cursor: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * * Checks to see if @line contains the insertion cursor. * * Returns: %TRUE if the insertion cursor is on @line */ gboolean gtk_source_gutter_lines_is_cursor (GtkSourceGutterLines *lines, guint line) { return line == lines->cursor_line || gtk_source_gutter_lines_has_qclass (lines, line, q_cursor_line); } /** * gtk_source_gutter_lines_is_prelit: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * * Checks to see if @line is marked as prelit. Generally, this means * the mouse pointer is over the line within the gutter. * * Returns: %TRUE if the line is prelit */ gboolean gtk_source_gutter_lines_is_prelit (GtkSourceGutterLines *lines, guint line) { return gtk_source_gutter_lines_has_qclass (lines, line, q_prelit); } /** * gtk_source_gutter_lines_is_selected: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * * Checks to see if the view had a selection and if that selection overlaps * @line in some way. * * Returns: %TRUE if the line contains a selection */ gboolean gtk_source_gutter_lines_is_selected (GtkSourceGutterLines *lines, guint line) { return gtk_source_gutter_lines_has_qclass (lines, line, q_selected); } /** * gtk_source_gutter_lines_get_first: * @lines: a #GtkSourceGutterLines * * Gets the line number (starting from 0) for the first line that is * user visible. * * Returns: a line number starting from 0 */ guint gtk_source_gutter_lines_get_first (GtkSourceGutterLines *lines) { g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines), 0); return lines->first; } /** * gtk_source_gutter_lines_get_last: * @lines: a #GtkSourceGutterLines * * Gets the line number (starting from 0) for the last line that is * user visible. * * Returns: a line number starting from 0 */ guint gtk_source_gutter_lines_get_last (GtkSourceGutterLines *lines) { g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines), 0); return lines->last; } /** * gtk_source_gutter_lines_get_iter_at_line: * @lines: a #GtkSourceGutterLines * @iter: (out): a location for a #GtkTextIter * @line: the line number * * Gets a #GtkTextIter for the current buffer at @line */ void gtk_source_gutter_lines_get_iter_at_line (GtkSourceGutterLines *lines, GtkTextIter *iter, guint line) { GtkTextBuffer *buffer; g_return_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines)); g_return_if_fail (iter != NULL); buffer = gtk_text_view_get_buffer (lines->view); gtk_text_buffer_get_iter_at_line (buffer, iter, line); } /** * gtk_source_gutter_lines_get_view: * @lines: a #GtkSourceGutterLines * * Gets the [class@Gtk.TextView] that the `GtkSourceGutterLines` represents. * * Returns: (transfer none) (not nullable): a #GtkTextView */ GtkTextView * gtk_source_gutter_lines_get_view (GtkSourceGutterLines *lines) { g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines), NULL); return lines->view; } /** * gtk_source_gutter_lines_get_buffer: * @lines: a #GtkSourceGutterLines * * Gets the [class@Gtk.TextBuffer] that the `GtkSourceGutterLines` represents. * * Returns: (transfer none) (not nullable): a #GtkTextBuffer */ GtkTextBuffer * gtk_source_gutter_lines_get_buffer (GtkSourceGutterLines *lines) { g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines), NULL); return gtk_text_view_get_buffer (lines->view); } /** * gtk_source_gutter_lines_get_line_yrange: * @lines: a #GtkSourceGutterLines * @line: a line number starting from zero * @mode: a #GtkSourceGutterRendererAlignmentMode * @y: (out): a location for the Y position in widget coordinates * @height: (out): the line height based on @mode * * Gets the Y range for a line based on @mode. * * The value for @y is relative to the renderers widget coordinates. */ void gtk_source_gutter_lines_get_line_yrange (GtkSourceGutterLines *lines, guint line, GtkSourceGutterRendererAlignmentMode mode, gint *y, gint *height) { LineInfo *info; g_return_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines)); g_return_if_fail (line >= lines->first); g_return_if_fail (line <= lines->last); info = &g_array_index (lines->lines, LineInfo, line - lines->first); if (mode == GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_CELL) { *y = info->y; *height = info->height; } else if (mode == GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_FIRST) { *y = info->y; *height = info->first_height; } else if (mode == GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_LAST) { *y = info->y + info->height - info->last_height; *height = info->last_height; } else { g_return_if_reached (); } *y -= lines->visible_rect.y; } guint _gtk_source_gutter_lines_get_cursor_line (GtkSourceGutterLines *lines) { g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines), 0); return lines->cursor_line; } /** * gtk_source_gutter_lines_has_any_class: * @lines: a #GtkSourceGutterLines * @line: a line contained within @lines * * Checks to see if the line has any GQuark classes set. This can be * used to help renderer implementations avoid work if nothing has * been set on the class. * * Returns: %TRUE if any quark was set for the line * * Since: 5.6 */ gboolean gtk_source_gutter_lines_has_any_class (GtkSourceGutterLines *lines, guint line) { if (lines == NULL || line < lines->first || line > lines->last || line - lines->first >= lines->lines->len) return FALSE; return g_array_index (lines->lines, LineInfo, line - lines->first).classes.len > 0; } 07070100000158000081A400000000000000000000000166590806000015D4000000000000000000000000000000000000003A00000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterlines.h/* * This file is part of GtkSourceView * * Copyright 2019 - Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include #include "gtksourcetypes.h" #include "gtksourcegutterrenderer.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_GUTTER_LINES (gtk_source_gutter_lines_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkSourceGutterLines, gtk_source_gutter_lines, GTK_SOURCE, GUTTER_LINES, GObject) GTK_SOURCE_AVAILABLE_IN_ALL guint gtk_source_gutter_lines_get_first (GtkSourceGutterLines *lines); GTK_SOURCE_AVAILABLE_IN_ALL guint gtk_source_gutter_lines_get_last (GtkSourceGutterLines *lines); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_lines_get_iter_at_line (GtkSourceGutterLines *lines, GtkTextIter *iter, guint line); GTK_SOURCE_AVAILABLE_IN_ALL GtkTextView *gtk_source_gutter_lines_get_view (GtkSourceGutterLines *lines); GTK_SOURCE_AVAILABLE_IN_ALL GtkTextBuffer *gtk_source_gutter_lines_get_buffer (GtkSourceGutterLines *lines); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_lines_add_qclass (GtkSourceGutterLines *lines, guint line, GQuark qname); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_lines_add_class (GtkSourceGutterLines *lines, guint line, const gchar *name); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_lines_remove_class (GtkSourceGutterLines *lines, guint line, const gchar *name); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_lines_remove_qclass (GtkSourceGutterLines *lines, guint line, GQuark qname); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_gutter_lines_has_class (GtkSourceGutterLines *lines, guint line, const gchar *name); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_gutter_lines_has_qclass (GtkSourceGutterLines *lines, guint line, GQuark qname); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_gutter_lines_is_cursor (GtkSourceGutterLines *lines, guint line); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_gutter_lines_is_prelit (GtkSourceGutterLines *lines, guint line); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_gutter_lines_is_selected (GtkSourceGutterLines *lines, guint line); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_lines_get_line_yrange (GtkSourceGutterLines *lines, guint line, GtkSourceGutterRendererAlignmentMode mode, gint *y, gint *height); GTK_SOURCE_AVAILABLE_IN_5_6 gboolean gtk_source_gutter_lines_has_any_class (GtkSourceGutterLines *lines, guint line); G_END_DECLS 07070100000159000081A4000000000000000000000001665908060000060D000000000000000000000000000000000000004500000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrenderer-private.h/* * This file is part of GtkSourceView * * Copyright 2010 - Krzesimir Nowak * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include #include "gtksourcetypes.h" G_BEGIN_DECLS G_GNUC_INTERNAL void _gtk_source_gutter_renderer_set_view (GtkSourceGutterRenderer *renderer, GtkSourceView *view); G_GNUC_INTERNAL void _gtk_source_gutter_renderer_begin (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines); G_GNUC_INTERNAL void _gtk_source_gutter_renderer_snapshot (GtkSourceGutterRenderer *renderer, GtkSnapshot *snapshot, GtkSourceGutterLines *lines); G_GNUC_INTERNAL void _gtk_source_gutter_renderer_end (GtkSourceGutterRenderer *renderer); G_END_DECLS 0707010000015A000081A40000000000000000000000016659080600007C0E000000000000000000000000000000000000003D00000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrenderer.c/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourcebuffer.h" #include "gtksourcegutter.h" #include "gtksourcegutter-private.h" #include "gtksourcegutterrenderer.h" #include "gtksourcegutterrenderer-private.h" #include "gtksourcegutterlines.h" #include "gtksourcestylescheme.h" #include "gtksourceview.h" #include "gtksource-enumtypes.h" #include "gtksource-marshal.h" /** * GtkSourceGutterRenderer: * * Gutter cell renderer. * * A `GtkSourceGutterRenderer` represents a column in a [class@Gutter]. The * column contains one cell for each visible line of the [class@Gtk.TextBuffer]. Due to * text wrapping, a cell can thus span multiple lines of the [class@Gtk.TextView]. In * this case, [enum@GutterRendererAlignmentMode] controls the alignment of * the cell. * * The gutter renderer is a [class@Gtk.Widget] and is measured using the normal widget * measurement facilities. The width of the gutter will be determined by the * measurements of the gutter renderers. * * The width of a gutter renderer generally takes into account the entire text * buffer. For instance, to display the line numbers, if the buffer contains 100 * lines, the gutter renderer will always set its width such as three digits can * be printed, even if only the first 20 lines are shown. Another strategy is to * take into account only the visible lines. In this case, only two digits are * necessary to display the line numbers of the first 20 lines. To take another * example, the gutter renderer for [class@Mark]s doesn't need to take * into account the text buffer to announce its width. It only depends on the * icons size displayed in the gutter column. * * When the available size to render a cell is greater than the required size to * render the cell contents, the cell contents can be aligned horizontally and * vertically with [method@GutterRenderer.set_alignment_mode]. * * The cells rendering occurs using [vfunc@Gtk.Widget.snapshot]. Implementations * should use `gtk_source_gutter_renderer_get_lines()` to retrieve information * about the lines to be rendered. To help with aligning content which takes * into account the padding and alignment of a cell, implementations may call * [method@GutterRenderer.align_cell] for a given line number with the * width and height measurement of the content they width to render. */ typedef struct { GtkSourceGutter *gutter; GtkSourceView *view; GtkSourceBuffer *buffer; GtkSourceGutterLines *lines; gfloat xalign; gfloat yalign; gint xpad; gint ypad; GtkSourceGutterRendererAlignmentMode alignment_mode; guint visible : 1; } GtkSourceGutterRendererPrivate; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkSourceGutterRenderer, gtk_source_gutter_renderer, GTK_TYPE_WIDGET) enum { PROP_0, PROP_ALIGNMENT_MODE, PROP_LINES, PROP_VIEW, PROP_XALIGN, PROP_XPAD, PROP_YALIGN, PROP_YPAD, N_PROPS }; enum { ACTIVATE, QUERY_ACTIVATABLE, QUERY_DATA, N_SIGNALS }; static GParamSpec *properties[N_PROPS]; static guint signals[N_SIGNALS]; static void emit_buffer_changed (GtkSourceView *view, GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); GtkSourceBuffer *buffer; GtkSourceBuffer *old_buffer; old_buffer = priv->buffer; buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); if (buffer == old_buffer) { return; } g_set_weak_pointer (&priv->buffer, buffer); GTK_SOURCE_GUTTER_RENDERER_GET_CLASS (renderer)->change_buffer (renderer, old_buffer); } static void on_buffer_changed (GtkSourceView *view, GParamSpec *spec, GtkSourceGutterRenderer *renderer) { emit_buffer_changed (view, renderer); } static void gtk_source_gutter_renderer_change_buffer (GtkSourceGutterRenderer *renderer, GtkSourceBuffer *buffer) { } static void gtk_source_gutter_renderer_change_view (GtkSourceGutterRenderer *renderer, GtkSourceView *old_view) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); if (old_view != NULL) { g_signal_handlers_disconnect_by_func (old_view, G_CALLBACK (on_buffer_changed), renderer); } if (priv->view != NULL) { emit_buffer_changed (priv->view, renderer); g_signal_connect (priv->view, "notify::buffer", G_CALLBACK (on_buffer_changed), renderer); } } static void gtk_source_gutter_renderer_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { GtkSourceGutterRenderer *renderer = GTK_SOURCE_GUTTER_RENDERER (widget); GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); GtkSourceGutterRendererClass *klass = GTK_SOURCE_GUTTER_RENDERER_GET_CLASS (widget); GtkSourceGutterRendererAlignmentMode mode = priv->alignment_mode; GtkSourceGutterLines *lines = priv->lines; guint first; guint last; guint line; gint y; gint h; if (lines == NULL || klass->snapshot_line == NULL) { return; } first = gtk_source_gutter_lines_get_first (lines); last = gtk_source_gutter_lines_get_last (lines); if (klass->query_data) { for (line = first; line <= last; line++) { gtk_source_gutter_lines_get_line_yrange (lines, line, mode, &y, &h); klass->query_data (renderer, lines, line); klass->snapshot_line (renderer, snapshot, lines, line); } } else { for (line = first; line <= last; line++) { gtk_source_gutter_lines_get_line_yrange (lines, line, mode, &y, &h); klass->snapshot_line (renderer, snapshot, lines, line); } } } static void gtk_source_gutter_renderer_dispose (GObject *object) { GtkSourceGutterRenderer *renderer = GTK_SOURCE_GUTTER_RENDERER (object); GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_clear_weak_pointer (&priv->buffer); if (priv->view != NULL) { _gtk_source_gutter_renderer_set_view (renderer, NULL); } G_OBJECT_CLASS (gtk_source_gutter_renderer_parent_class)->dispose (object); } static void gtk_source_gutter_renderer_root (GtkWidget *widget) { GtkSourceGutterRenderer *renderer = GTK_SOURCE_GUTTER_RENDERER (widget); GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); GtkWidget *gutter; GTK_WIDGET_CLASS (gtk_source_gutter_renderer_parent_class)->root (widget); gutter = gtk_widget_get_ancestor (widget, GTK_SOURCE_TYPE_GUTTER); if (GTK_SOURCE_IS_GUTTER (gutter)) { priv->gutter = GTK_SOURCE_GUTTER (gutter); } } static void gtk_source_gutter_renderer_unroot (GtkWidget *widget) { GtkSourceGutterRenderer *renderer = GTK_SOURCE_GUTTER_RENDERER (widget); GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); priv->gutter = NULL; GTK_WIDGET_CLASS (gtk_source_gutter_renderer_parent_class)->unroot (widget); } static void gtk_source_gutter_renderer_real_begin (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines) { } static void gtk_source_gutter_renderer_real_end (GtkSourceGutterRenderer *renderer) { } static void gtk_source_gutter_renderer_query_data (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines, guint line) { g_signal_emit (renderer, signals[QUERY_DATA], 0, lines, line); } static void gtk_source_gutter_renderer_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceGutterRenderer *self = GTK_SOURCE_GUTTER_RENDERER (object); switch (prop_id) { case PROP_XPAD: gtk_source_gutter_renderer_set_xpad (self, g_value_get_int (value)); break; case PROP_YPAD: gtk_source_gutter_renderer_set_ypad (self, g_value_get_int (value)); break; case PROP_XALIGN: gtk_source_gutter_renderer_set_xalign (self, g_value_get_float (value)); break; case PROP_YALIGN: gtk_source_gutter_renderer_set_yalign (self, g_value_get_float (value)); break; case PROP_ALIGNMENT_MODE: gtk_source_gutter_renderer_set_alignment_mode (self, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_gutter_renderer_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceGutterRenderer *self = GTK_SOURCE_GUTTER_RENDERER (object); GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (self); switch (prop_id) { case PROP_LINES: g_value_set_object (value, priv->lines); break; case PROP_XALIGN: g_value_set_float (value, priv->xalign); break; case PROP_XPAD: g_value_set_int (value, priv->xpad); break; case PROP_YALIGN: g_value_set_float (value, priv->yalign); break; case PROP_YPAD: g_value_set_int (value, priv->ypad); break; case PROP_VIEW: g_value_set_object (value, priv->view); break; case PROP_ALIGNMENT_MODE: g_value_set_enum (value, priv->alignment_mode); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_gutter_renderer_class_init (GtkSourceGutterRendererClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = gtk_source_gutter_renderer_dispose; object_class->get_property = gtk_source_gutter_renderer_get_property; object_class->set_property = gtk_source_gutter_renderer_set_property; widget_class->root = gtk_source_gutter_renderer_root; widget_class->unroot = gtk_source_gutter_renderer_unroot; widget_class->snapshot = gtk_source_gutter_renderer_snapshot; klass->begin = gtk_source_gutter_renderer_real_begin; klass->end = gtk_source_gutter_renderer_real_end; klass->change_buffer = gtk_source_gutter_renderer_change_buffer; klass->change_view = gtk_source_gutter_renderer_change_view; klass->query_data = gtk_source_gutter_renderer_query_data; /** * GtkSourceGutterRenderer:lines: * * Contains information about the lines to be rendered. * * It should be used by #GtkSourceGutterRenderer implementations from [vfunc@Gtk.Widget.snapshot]. */ properties[PROP_LINES] = g_param_spec_object ("lines", "Lines", "Information about the lines to render", GTK_SOURCE_TYPE_GUTTER_LINES, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * GtkSourceGutterRenderer:xpad: * * The left and right padding of the renderer. */ properties[PROP_XPAD] = g_param_spec_int ("xpad", "X Padding", "The x-padding", 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * GtkSourceGutterRenderer:ypad: * * The top and bottom padding of the renderer. */ properties[PROP_YPAD] = g_param_spec_int ("ypad", "Y Padding", "The y-padding", 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * GtkSourceGutterRenderer:xalign: * * The horizontal alignment of the renderer. * * Set to 0 for a left alignment. 1 for a right alignment. And 0.5 for centering the cells. * A value lower than 0 doesn't modify the alignment. */ properties[PROP_XALIGN] = g_param_spec_float ("xalign", "X Alignment", "The x-alignment", 0.0, 1.0, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * GtkSourceGutterRenderer:yalign: * * The vertical alignment of the renderer. * * Set to 0 for a top alignment. 1 for a bottom alignment. And 0.5 for centering the cells. * A value lower than 0 doesn't modify the alignment. */ properties[PROP_YALIGN] = g_param_spec_float ("yalign", "Y Alignment", "The y-alignment", 0.0, 1.0, 0.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * GtkSourceGutterRenderer:view: * * The view on which the renderer is placed. **/ properties[PROP_VIEW] = g_param_spec_object ("view", "The View", "The view", GTK_TYPE_TEXT_VIEW, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * GtkSourceGutterRenderer:alignment-mode: * * The alignment mode of the renderer. * * This can be used to indicate that in the case a cell spans multiple lines (due to text wrapping) * the alignment should work on either the full cell, the first line or the last line. **/ properties[PROP_ALIGNMENT_MODE] = g_param_spec_enum ("alignment-mode", "Alignment Mode", "The alignment mode", GTK_SOURCE_TYPE_GUTTER_RENDERER_ALIGNMENT_MODE, GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_CELL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, properties); /** * GtkSourceGutterRenderer::activate: * @renderer: the #GtkSourceGutterRenderer who emits the signal * @iter: a #GtkTextIter * @area: a #GdkRectangle * @button: the button that was pressed * @state: a #GdkModifierType of state * @n_presses: the number of button presses * * The signal is emitted when the renderer is activated. */ signals[ACTIVATE] = g_signal_new ("activate", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkSourceGutterRendererClass, activate), NULL, NULL, _gtk_source_marshal_VOID__BOXED_BOXED_UINT_FLAGS_INT, G_TYPE_NONE, 5, GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE, GDK_TYPE_RECTANGLE | G_SIGNAL_TYPE_STATIC_SCOPE, G_TYPE_UINT, GDK_TYPE_MODIFIER_TYPE, G_TYPE_INT); g_signal_set_va_marshaller (signals[ACTIVATE], G_TYPE_FROM_CLASS (klass), _gtk_source_marshal_VOID__BOXED_BOXED_UINT_FLAGS_INTv); /** * GtkSourceGutterRenderer::query-activatable: * @renderer: the #GtkSourceGutterRenderer who emits the signal * @iter: a #GtkTextIter * @area: a #GdkRectangle * @event: the #GdkEvent that is causing the activatable query * * The signal is emitted when the renderer can possibly be activated. */ signals[QUERY_ACTIVATABLE] = g_signal_new ("query-activatable", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkSourceGutterRendererClass, query_activatable), g_signal_accumulator_true_handled, NULL, _gtk_source_marshal_BOOLEAN__BOXED_BOXED, G_TYPE_BOOLEAN, 2, GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE, GDK_TYPE_RECTANGLE | G_SIGNAL_TYPE_STATIC_SCOPE); g_signal_set_va_marshaller (signals[QUERY_ACTIVATABLE], G_TYPE_FROM_CLASS (klass), _gtk_source_marshal_BOOLEAN__BOXED_BOXEDv); signals[QUERY_DATA] = g_signal_new ("query-data", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, _gtk_source_marshal_VOID__OBJECT_UINT, G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_UINT); g_signal_set_va_marshaller (signals[QUERY_DATA], G_TYPE_FROM_CLASS (klass), _gtk_source_marshal_VOID__OBJECT_UINTv); gtk_widget_class_set_css_name (widget_class, "gutterrenderer"); } static void gtk_source_gutter_renderer_init (GtkSourceGutterRenderer *self) { } /** * gtk_source_gutter_renderer_query_activatable: * @renderer: a #GtkSourceGutterRenderer * @iter: a #GtkTextIter at the start of the line to be activated * @area: a #GdkRectangle of the cell area to be activated * * Get whether the renderer is activatable at the location provided. This is * called from [class@Gutter] to determine whether a renderer is activatable * using the mouse pointer. * * Returns: %TRUE if the renderer can be activated, %FALSE otherwise * **/ gboolean gtk_source_gutter_renderer_query_activatable (GtkSourceGutterRenderer *renderer, const GtkTextIter *iter, const GdkRectangle *area) { gboolean ret; g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), FALSE); g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (area != NULL, FALSE); ret = FALSE; g_signal_emit (renderer, signals[QUERY_ACTIVATABLE], 0, iter, area, &ret); return ret; } /** * gtk_source_gutter_renderer_activate: * @renderer: a #GtkSourceGutterRenderer * @iter: a #GtkTextIter at the start of the line where the renderer is activated * @area: a #GdkRectangle of the cell area where the renderer is activated * @button: the button that was pressed * @state: a #GdkModifierType * @n_presses: the number of button presses * * Emits the [signal@GutterRenderer::activate] signal of the renderer. This is * called from [class@Gutter] and should never have to be called manually. */ void gtk_source_gutter_renderer_activate (GtkSourceGutterRenderer *renderer, const GtkTextIter *iter, const GdkRectangle *area, guint button, GdkModifierType state, gint n_presses) { g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); g_return_if_fail (iter != NULL); g_return_if_fail (area != NULL); g_signal_emit (renderer, signals[ACTIVATE], 0, iter, area, button, state, n_presses); } /** * gtk_source_gutter_renderer_set_alignment_mode: * @renderer: a #GtkSourceGutterRenderer * @mode: a #GtkSourceGutterRendererAlignmentMode * * Set the alignment mode. The alignment mode describes the manner in which the * renderer is aligned (see [property@GutterRenderer:xalign] and * [property@GutterRenderer:yalign]). **/ void gtk_source_gutter_renderer_set_alignment_mode (GtkSourceGutterRenderer *renderer, GtkSourceGutterRendererAlignmentMode mode) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_if_fail (GTK_SOURCE_GUTTER_RENDERER (renderer)); g_return_if_fail (mode == GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_CELL || mode == GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_FIRST || mode == GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_LAST); if (priv->alignment_mode != mode) { priv->alignment_mode = mode; g_object_notify_by_pspec (G_OBJECT (renderer), properties[PROP_ALIGNMENT_MODE]); gtk_widget_queue_draw (GTK_WIDGET (renderer)); } } /** * gtk_source_gutter_renderer_get_alignment_mode: * @renderer: a #GtkSourceGutterRenderer * * Get the alignment mode. * * The alignment mode describes the manner in which the * renderer is aligned (see [property@GutterRenderer:xalign] and * [property@GutterRenderer:yalign]). * * Returns: a #GtkSourceGutterRendererAlignmentMode **/ GtkSourceGutterRendererAlignmentMode gtk_source_gutter_renderer_get_alignment_mode (GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), 0); return priv->alignment_mode; } /** * gtk_source_gutter_renderer_get_view: * @renderer: a #GtkSourceGutterRenderer * * Get the view associated to the gutter renderer * * Returns: (transfer none): a #GtkSourceView **/ GtkSourceView * gtk_source_gutter_renderer_get_view (GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), NULL); return GTK_SOURCE_VIEW (priv->view); } void _gtk_source_gutter_renderer_set_view (GtkSourceGutterRenderer *renderer, GtkSourceView *view) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); GtkSourceView *old_view; g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); g_return_if_fail (view == NULL || GTK_SOURCE_IS_VIEW (view)); old_view = priv->view; if (g_set_weak_pointer (&priv->view, view)) { GTK_SOURCE_GUTTER_RENDERER_GET_CLASS (renderer)->change_view (renderer, old_view); } g_object_notify_by_pspec (G_OBJECT (renderer), properties[PROP_VIEW]); } static void get_line_rect (GtkSourceGutterRenderer *renderer, guint line, GdkRectangle *rect) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); GtkSourceGutterLines *lines = NULL; if (priv->gutter != NULL) { lines = _gtk_source_gutter_get_lines (priv->gutter); } if (lines != NULL) { gint y; gint height; gtk_source_gutter_lines_get_line_yrange (lines, line, priv->alignment_mode, &y, &height); rect->x = priv->xpad; rect->y = y + priv->ypad; rect->width = gtk_widget_get_width (GTK_WIDGET (renderer)); rect->height = height; rect->width -= 2 * priv->xpad; rect->height -= 2 * priv->ypad; } else { rect->x = 0; rect->y = 0; rect->width = 0; rect->height = 0; } } /** * gtk_source_gutter_renderer_align_cell: * @renderer: the #GtkSourceGutterRenderer * @line: the line number for content * @width: the width of the content to draw * @height: the height of the content to draw * @x: (out): the X position to render the content * @y: (out): the Y position to render the content * * Locates where to render content that is @width x @height based on * the renderers alignment and padding. * * The location will be placed into @x and @y and is relative to the * renderer's coordinates. * * It is encouraged that renderers use this function when snappshotting * to ensure consistent placement of their contents. */ void gtk_source_gutter_renderer_align_cell (GtkSourceGutterRenderer *renderer, guint line, gfloat width, gfloat height, gfloat *x, gfloat *y) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); GdkRectangle rect; g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); get_line_rect (renderer, line, &rect); *x = rect.x + MAX (0, (rect.width - width)) * priv->xalign; *y = rect.y + MAX (0, (rect.height - height)) * priv->yalign; } /** * gtk_source_gutter_renderer_get_xpad: * @renderer: a #GtkSourceGutterRenderer * * Gets the `xpad` property. * * This may be used to adjust the cell rectangle that the renderer will use to draw. */ gint gtk_source_gutter_renderer_get_xpad (GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), 0); return priv->xpad; } /** * gtk_source_gutter_renderer_set_xpad: * @renderer: a #GtkSourceGutterRenderer * @xpad: the Y padding for the drawing cell * * Adjusts the `xpad` property. * * This may be used to adjust the cell rectangle that the renderer will use to draw. */ void gtk_source_gutter_renderer_set_xpad (GtkSourceGutterRenderer *renderer, gint xpad) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); g_return_if_fail (xpad >= 0); if (priv->xpad != xpad) { priv->xpad = xpad; g_object_notify_by_pspec (G_OBJECT (renderer), properties[PROP_XPAD]); gtk_widget_queue_draw (GTK_WIDGET (renderer)); } } /** * gtk_source_gutter_renderer_get_ypad: * @renderer: a #GtkSourceGutterRenderer * * Gets the `ypad` property. * * This may be used to adjust the cell rectangle that the renderer will use to draw. */ gint gtk_source_gutter_renderer_get_ypad (GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), 0); return priv->ypad; } /** * gtk_source_gutter_renderer_set_ypad: * @renderer: a #GtkSourceGutterRenderer * @ypad: the Y padding for the drawing cell * * Adjusts the `ypad` property. * * This may be used to adjust the cell rectangle that the renderer will use to draw. */ void gtk_source_gutter_renderer_set_ypad (GtkSourceGutterRenderer *renderer, gint ypad) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); g_return_if_fail (ypad >= 0); if (priv->ypad != ypad) { priv->ypad = ypad; g_object_notify_by_pspec (G_OBJECT (renderer), properties[PROP_YPAD]); gtk_widget_queue_draw (GTK_WIDGET (renderer)); } } /** * gtk_source_gutter_renderer_get_xalign: * @renderer: a #GtkSourceGutterRenderer * * Gets the `xalign` property. * * This may be used to adjust where within the cell rectangle the renderer will draw. */ gfloat gtk_source_gutter_renderer_get_xalign (GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), 0); return priv->xalign; } /** * gtk_source_gutter_renderer_set_xalign: * @renderer: a #GtkSourceGutterRenderer * @xalign: the Y padding for the drawing cell * * Adjusts the `xalign` property. * * This may be used to adjust where within the cell rectangle the renderer will draw. */ void gtk_source_gutter_renderer_set_xalign (GtkSourceGutterRenderer *renderer, gfloat xalign) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); g_return_if_fail (xalign >= 0); if (priv->xalign != xalign) { priv->xalign = xalign; g_object_notify_by_pspec (G_OBJECT (renderer), properties[PROP_XALIGN]); gtk_widget_queue_draw (GTK_WIDGET (renderer)); } } /** * gtk_source_gutter_renderer_get_yalign: * @renderer: a #GtkSourceGutterRenderer * * Gets the `yalign` property. * * This may be used to adjust where within the cell rectangle the renderer will draw. */ gfloat gtk_source_gutter_renderer_get_yalign (GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), 0); return priv->yalign; } /** * gtk_source_gutter_renderer_set_yalign: * @renderer: a #GtkSourceGutterRenderer * @yalign: the Y padding for the drawing cell * * Adjusts the `yalign` property. * * This may be used to adjust where within the cell rectangle the renderer will draw. */ void gtk_source_gutter_renderer_set_yalign (GtkSourceGutterRenderer *renderer, gfloat yalign) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); g_return_if_fail (yalign >= 0); if (priv->yalign != yalign) { priv->yalign = yalign; g_object_notify_by_pspec (G_OBJECT (renderer), properties[PROP_YALIGN]); gtk_widget_queue_draw (GTK_WIDGET (renderer)); } } /** * gtk_source_gutter_renderer_get_buffer: * @renderer: a #GtkSourceGutterRenderer * * Gets the [class@Buffer] for which the gutter renderer is drawing. * * Returns: (transfer none) (nullable): a #GtkTextBuffer or %NULL */ GtkSourceBuffer * gtk_source_gutter_renderer_get_buffer (GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer), NULL); return priv->buffer; } void _gtk_source_gutter_renderer_begin (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); g_return_if_fail (GTK_SOURCE_IS_GUTTER_LINES (lines)); g_set_object (&priv->lines, lines); GTK_SOURCE_GUTTER_RENDERER_GET_CLASS (renderer)->begin (renderer, lines); } void _gtk_source_gutter_renderer_end (GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererPrivate *priv = gtk_source_gutter_renderer_get_instance_private (renderer); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER (renderer)); GTK_SOURCE_GUTTER_RENDERER_GET_CLASS (renderer)->end (renderer); g_clear_object (&priv->lines); } 0707010000015B000081A40000000000000000000000016659080600002238000000000000000000000000000000000000003D00000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrenderer.h/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_GUTTER_RENDERER (gtk_source_gutter_renderer_get_type()) /** * GtkSourceGutterRendererAlignmentMode: * @GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_CELL: The full cell. * @GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_FIRST: The first line. * @GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_LAST: The last line. * * The alignment mode of the renderer, when a cell spans multiple lines (due to * text wrapping). **/ typedef enum _GtkSourceGutterRendererAlignmentMode { GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_CELL, GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_FIRST, GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_LAST, } GtkSourceGutterRendererAlignmentMode; struct _GtkSourceGutterRendererClass { GtkWidgetClass parent_class; void (*query_data) (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines, guint line); void (*begin) (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines); void (*snapshot_line) (GtkSourceGutterRenderer *renderer, GtkSnapshot *snapshot, GtkSourceGutterLines *lines, guint line); void (*end) (GtkSourceGutterRenderer *renderer); /** * GtkSourceGutterRendererClass::change_view: * @renderer: a #GtkSourceGutterRenderer. * @old_view: (nullable): the old #GtkTextView. * * This is called when the text view changes for @renderer. */ void (*change_view) (GtkSourceGutterRenderer *renderer, GtkSourceView *old_view); /** * GtkSourceGutterRendererClass::change_buffer: * @renderer: a #GtkSourceGutterRenderer. * @old_buffer: (nullable): the old #GtkTextBuffer. * * This is called when the text buffer changes for @renderer. */ void (*change_buffer) (GtkSourceGutterRenderer *renderer, GtkSourceBuffer *old_buffer); /* Signal handlers */ gboolean (*query_activatable) (GtkSourceGutterRenderer *renderer, GtkTextIter *iter, GdkRectangle *area); void (*activate) (GtkSourceGutterRenderer *renderer, GtkTextIter *iter, GdkRectangle *area, guint button, GdkModifierType state, gint n_presses); /*< private >*/ gpointer _reserved[20]; }; GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_DERIVABLE_TYPE (GtkSourceGutterRenderer, gtk_source_gutter_renderer, GTK_SOURCE, GUTTER_RENDERER, GtkWidget) GTK_SOURCE_AVAILABLE_IN_ALL gfloat gtk_source_gutter_renderer_get_xalign (GtkSourceGutterRenderer *renderer); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_set_xalign (GtkSourceGutterRenderer *renderer, gfloat xalign); GTK_SOURCE_AVAILABLE_IN_ALL gfloat gtk_source_gutter_renderer_get_yalign (GtkSourceGutterRenderer *renderer); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_set_yalign (GtkSourceGutterRenderer *renderer, gfloat yalign); GTK_SOURCE_AVAILABLE_IN_ALL gint gtk_source_gutter_renderer_get_xpad (GtkSourceGutterRenderer *renderer); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_set_xpad (GtkSourceGutterRenderer *renderer, gint xpad); GTK_SOURCE_AVAILABLE_IN_ALL gint gtk_source_gutter_renderer_get_ypad (GtkSourceGutterRenderer *renderer); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_set_ypad (GtkSourceGutterRenderer *renderer, gint ypad); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceGutterRendererAlignmentMode gtk_source_gutter_renderer_get_alignment_mode (GtkSourceGutterRenderer *renderer); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_set_alignment_mode (GtkSourceGutterRenderer *renderer, GtkSourceGutterRendererAlignmentMode mode); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceBuffer *gtk_source_gutter_renderer_get_buffer (GtkSourceGutterRenderer *renderer); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceView *gtk_source_gutter_renderer_get_view (GtkSourceGutterRenderer *renderer); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_activate (GtkSourceGutterRenderer *renderer, const GtkTextIter *iter, const GdkRectangle *area, guint button, GdkModifierType state, gint n_presses); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_gutter_renderer_query_activatable (GtkSourceGutterRenderer *renderer, const GtkTextIter *iter, const GdkRectangle *area); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_align_cell (GtkSourceGutterRenderer *renderer, guint line, gfloat width, gfloat height, gfloat *x, gfloat *y); G_END_DECLS 0707010000015C000081A40000000000000000000000016659080600000502000000000000000000000000000000000000004A00000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrendererlines-private.h/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include #include "gtksourcetypes.h" #include "gtksourcetypes-private.h" #include "gtksourcegutterrenderertext.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_GUTTER_RENDERER_LINES (_gtk_source_gutter_renderer_lines_get_type()) G_GNUC_INTERNAL G_DECLARE_FINAL_TYPE (GtkSourceGutterRendererLines, _gtk_source_gutter_renderer_lines, GTK_SOURCE, GUTTER_RENDERER_LINES, GtkSourceGutterRendererText) G_GNUC_INTERNAL GtkSourceGutterRenderer *_gtk_source_gutter_renderer_lines_new (void); G_END_DECLS 0707010000015D000081A4000000000000000000000001665908060000401A000000000000000000000000000000000000004200000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrendererlines.c/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourcegutterrendererlines-private.h" #include "gtksourcegutterrenderertext-private.h" #include "gtksourcegutterlines.h" #include "gtksourceutils-private.h" #include "gtksourceview.h" struct _GtkSourceGutterRendererLines { GtkSourceGutterRendererText parent_instance; PangoFont *cached_font; PangoFont *cached_bold_font; PangoGlyphInfo cached_infos[10]; PangoGlyphInfo cached_bold_infos[10]; GdkRGBA foreground_color; GdkRGBA current_line_color; int cached_baseline; int cached_bold_baseline; int cached_height; int num_line_digits; int prev_line_num; guint highlight_current_line : 1; guint cursor_visible : 1; guint current_line_bold : 1; }; G_DEFINE_FINAL_TYPE (GtkSourceGutterRendererLines, _gtk_source_gutter_renderer_lines, GTK_SOURCE_TYPE_GUTTER_RENDERER_TEXT) static void update_cached_items (GtkSourceGutterRendererLines *self) { PangoLayout *layout; PangoAttrList *attrs; PangoLayoutLine *line; int width, height; g_assert (GTK_SOURCE_IS_GUTTER_RENDERER_LINES (self)); layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), "0123456789"); self->cached_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; if ((line = pango_layout_get_line_readonly (layout, 0))) { if (line->runs != NULL) { const PangoGlyphItem *glyph_item = line->runs->data; const PangoGlyphString *glyphs = glyph_item->glyphs; PangoFont *font = glyph_item->item->analysis.font; guint n_chars = MIN (10, glyph_item->item->num_chars); g_set_object (&self->cached_font, font); for (guint i = 0; i < n_chars; i++) { self->cached_infos[i] = glyphs->glyphs[i]; } } } pango_layout_get_pixel_size (layout, &width, &height); self->cached_height = height; attrs = pango_attr_list_new (); pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD)); pango_layout_set_attributes (layout, attrs); self->cached_bold_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; if ((line = pango_layout_get_line_readonly (layout, 0))) { if (line->runs != NULL) { const PangoGlyphItem *glyph_item = line->runs->data; const PangoGlyphString *glyphs = glyph_item->glyphs; PangoFont *font = glyph_item->item->analysis.font; guint n_chars = MIN (10, glyph_item->item->num_chars); g_set_object (&self->cached_bold_font, font); for (guint i = 0; i < n_chars; i++) { self->cached_bold_infos[i] = glyphs->glyphs[i]; } } } pango_attr_list_unref (attrs); g_object_unref (layout); } static inline gint count_num_digits (gint num_lines) { if (num_lines < 100) { return 2; } else if (num_lines < 1000) { return 3; } else if (num_lines < 10000) { return 4; } else if (num_lines < 100000) { return 5; } else if (num_lines < 1000000) { return 6; } else { return 10; } } static void recalculate_size (GtkSourceGutterRendererLines *renderer) { GtkSourceBuffer *buffer; gint num_lines = 1; gint num_digits; buffer = gtk_source_gutter_renderer_get_buffer (GTK_SOURCE_GUTTER_RENDERER (renderer)); if (buffer != NULL) { num_lines = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (buffer)); } num_digits = count_num_digits (num_lines); if (num_digits < 2) { num_digits = 2; } if (num_digits != renderer->num_line_digits) { renderer->num_line_digits = num_digits; gtk_widget_queue_resize (GTK_WIDGET (renderer)); } } static void on_buffer_changed (GtkSourceBuffer *buffer, GtkSourceGutterRendererLines *renderer) { recalculate_size (renderer); } static void on_buffer_cursor_moved (GtkSourceBuffer *buffer, GtkSourceGutterRendererLines *renderer) { if (renderer->cursor_visible || renderer->highlight_current_line) { /* Redraw if the current-line needs updating */ gtk_widget_queue_draw (GTK_WIDGET (renderer)); } } static void gutter_renderer_change_buffer (GtkSourceGutterRenderer *renderer, GtkSourceBuffer *old_buffer) { GtkSourceGutterRendererLines *lines = GTK_SOURCE_GUTTER_RENDERER_LINES (renderer); GtkSourceBuffer *buffer; if (old_buffer != NULL) { g_signal_handlers_disconnect_by_func (old_buffer, on_buffer_changed, lines); g_signal_handlers_disconnect_by_func (old_buffer, on_buffer_cursor_moved, lines); } buffer = gtk_source_gutter_renderer_get_buffer (renderer); lines->prev_line_num = 0; if (buffer != NULL) { g_signal_connect_object (buffer, "changed", G_CALLBACK (on_buffer_changed), lines, 0); g_signal_connect_object (buffer, "cursor-moved", G_CALLBACK (on_buffer_cursor_moved), lines, 0); recalculate_size (lines); } GTK_SOURCE_GUTTER_RENDERER_CLASS (_gtk_source_gutter_renderer_lines_parent_class)->change_buffer (renderer, old_buffer); } static void gtk_source_gutter_renderer_lines_css_changed (GtkWidget *widget, GtkCssStyleChange *change) { GtkSourceGutterRendererLines *renderer = GTK_SOURCE_GUTTER_RENDERER_LINES (widget); GTK_WIDGET_CLASS (_gtk_source_gutter_renderer_lines_parent_class)->css_changed (widget, change); update_cached_items (renderer); /* Force to recalculate the size. */ renderer->num_line_digits = -1; recalculate_size (renderer); } static void on_view_notify (GtkSourceView *view, GParamSpec *pspec, GtkSourceGutterRendererLines *renderer) { renderer->cursor_visible = gtk_text_view_get_cursor_visible (GTK_TEXT_VIEW (view)); renderer->highlight_current_line = gtk_source_view_get_highlight_current_line (view); } static void gutter_renderer_change_view (GtkSourceGutterRenderer *renderer, GtkSourceView *old_view) { GtkSourceView *new_view; if (old_view != NULL) { g_signal_handlers_disconnect_by_func (old_view, on_view_notify, renderer); } new_view = gtk_source_gutter_renderer_get_view (renderer); if (new_view != NULL) { g_signal_connect_object (new_view, "notify::cursor-visible", G_CALLBACK (on_view_notify), renderer, 0); g_signal_connect_object (new_view, "notify::highlight-current-line", G_CALLBACK (on_view_notify), renderer, 0); on_view_notify (new_view, NULL, GTK_SOURCE_GUTTER_RENDERER_LINES (renderer)); } GTK_SOURCE_GUTTER_RENDERER_CLASS (_gtk_source_gutter_renderer_lines_parent_class)->change_view (renderer, old_view); } static void extend_selection_to_line (GtkSourceGutterRendererLines *renderer, GtkTextIter *line_start) { GtkTextIter start; GtkTextIter end; GtkTextIter line_end; GtkSourceBuffer *buffer; buffer = gtk_source_gutter_renderer_get_buffer (GTK_SOURCE_GUTTER_RENDERER (renderer)); gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), &start, &end); line_end = *line_start; if (!gtk_text_iter_ends_line (&line_end)) { gtk_text_iter_forward_to_line_end (&line_end); } if (gtk_text_iter_compare (&start, line_start) < 0) { gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &start, &line_end); } else if (gtk_text_iter_compare (&end, &line_end) < 0) { /* if the selection is in this line, extend * the selection to the whole line */ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &line_end, line_start); } else { gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &end, line_start); } } static void select_line (GtkSourceGutterRendererLines *renderer, GtkTextIter *line_start) { GtkTextIter iter; GtkSourceBuffer *buffer; buffer = gtk_source_gutter_renderer_get_buffer (GTK_SOURCE_GUTTER_RENDERER (renderer)); iter = *line_start; if (!gtk_text_iter_ends_line (&iter)) { gtk_text_iter_forward_to_line_end (&iter); } /* Select the line, put the cursor at the end of the line */ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &iter, line_start); } static void gutter_renderer_activate (GtkSourceGutterRenderer *renderer, GtkTextIter *iter, GdkRectangle *rect, guint button, GdkModifierType state, gint n_presses) { GtkSourceGutterRendererLines *lines; GtkSourceBuffer *buffer; lines = GTK_SOURCE_GUTTER_RENDERER_LINES (renderer); if (button != 1) { return; } buffer = gtk_source_gutter_renderer_get_buffer (renderer); if (n_presses == 1) { if ((state & GDK_CONTROL_MASK) != 0) { /* Single click + Ctrl -> select the line */ select_line (lines, iter); } else if ((state & GDK_SHIFT_MASK) != 0) { /* Single click + Shift -> extended current selection to include the clicked line */ extend_selection_to_line (lines, iter); } else { gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (buffer), iter); } } else if (n_presses == 2) { select_line (lines, iter); } } static gboolean gutter_renderer_query_activatable (GtkSourceGutterRenderer *renderer, GtkTextIter *iter, GdkRectangle *area) { return gtk_source_gutter_renderer_get_buffer (renderer) != NULL; } static void gtk_source_gutter_renderer_lines_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkSourceGutterRendererLines *renderer = GTK_SOURCE_GUTTER_RENDERER_LINES (widget); if (orientation == GTK_ORIENTATION_VERTICAL) { *minimum = 0; *natural = 0; } else { GtkSourceBuffer *buffer; gchar markup[32]; guint num_lines = 0; gint size; gint xpad; buffer = gtk_source_gutter_renderer_get_buffer (GTK_SOURCE_GUTTER_RENDERER (renderer)); if (buffer != NULL) { num_lines = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (buffer)); } if (num_lines < 99) { num_lines = 99; } g_snprintf (markup, sizeof markup, "%u", num_lines); gtk_source_gutter_renderer_text_measure_markup (GTK_SOURCE_GUTTER_RENDERER_TEXT (renderer), markup, &size, NULL); xpad = gtk_source_gutter_renderer_get_xpad (GTK_SOURCE_GUTTER_RENDERER (renderer)); *natural = *minimum = size + xpad * 2; } *minimum_baseline = -1; *natural_baseline = -1; } static void gtk_source_gutter_renderer_lines_snapshot_line (GtkSourceGutterRenderer *renderer, GtkSnapshot *snapshot, GtkSourceGutterLines *lines, guint line) { GtkSourceGutterRendererLines *self = GTK_SOURCE_GUTTER_RENDERER_LINES (renderer); const PangoGlyphInfo *cached_infos; PangoGlyphString glyph_string = {0}; PangoGlyphInfo glyph_info[12]; GskRenderNode *node; const GdkRGBA *color; const gchar *text; PangoFont *font; float x, y; int height, width; int baseline; int len; if (self->cached_font == NULL) { return; } cached_infos = self->cached_infos; font = self->cached_font; baseline = self->cached_baseline; if (gtk_source_gutter_lines_is_cursor (lines, line)) { color = &self->current_line_color; if (self->current_line_bold) { cached_infos = self->cached_bold_infos; font = self->cached_bold_font; baseline = self->cached_bold_baseline; } } else { color = &self->foreground_color; } len = _gtk_source_utils_int_to_string (line + 1, &text); glyph_string.num_glyphs = len; glyph_string.log_clusters = 0; glyph_string.glyphs = glyph_info; width = 0; height = self->cached_height; for (int i = 0; i < len; i++) { int index = text[i] - '0'; g_assert (index >= 0); g_assert (index < 10); glyph_info[i] = cached_infos[index]; width += glyph_info[i].geometry.width / PANGO_SCALE; } gtk_source_gutter_renderer_align_cell (renderer, line, width, height, &x, &y); node = gsk_text_node_new (font, &glyph_string, color, &GRAPHENE_POINT_INIT (x, y + baseline)); gtk_snapshot_append_node (snapshot, node); gsk_render_node_unref (node); } static void gtk_source_gutter_renderer_lines_begin (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines) { GtkSourceGutterRendererLines *self = GTK_SOURCE_GUTTER_RENDERER_LINES (renderer); gboolean current_line_bold; g_assert (GTK_SOURCE_IS_GUTTER_RENDERER_LINES (self)); g_assert (GTK_SOURCE_IS_GUTTER_LINES (lines)); GTK_SOURCE_GUTTER_RENDERER_CLASS (_gtk_source_gutter_renderer_lines_parent_class)->begin (renderer, lines); _gtk_source_gutter_renderer_text_get_draw (GTK_SOURCE_GUTTER_RENDERER_TEXT (renderer), &self->foreground_color, &self->current_line_color, ¤t_line_bold); self->current_line_bold = !!current_line_bold; } static void gtk_source_gutter_renderer_lines_query_data (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines, guint line) { } static void gtk_source_gutter_renderer_lines_dispose (GObject *object) { GtkSourceGutterRendererLines *self = GTK_SOURCE_GUTTER_RENDERER_LINES (object); g_clear_object (&self->cached_font); g_clear_object (&self->cached_bold_font); G_OBJECT_CLASS (_gtk_source_gutter_renderer_lines_parent_class)->dispose (object); } static void _gtk_source_gutter_renderer_lines_class_init (GtkSourceGutterRendererLinesClass *klass) { GtkSourceGutterRendererClass *renderer_class = GTK_SOURCE_GUTTER_RENDERER_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = gtk_source_gutter_renderer_lines_dispose; widget_class->measure = gtk_source_gutter_renderer_lines_measure; widget_class->css_changed = gtk_source_gutter_renderer_lines_css_changed; renderer_class->activate = gutter_renderer_activate; renderer_class->begin = gtk_source_gutter_renderer_lines_begin; renderer_class->change_buffer = gutter_renderer_change_buffer; renderer_class->change_view = gutter_renderer_change_view; renderer_class->query_activatable = gutter_renderer_query_activatable; renderer_class->query_data = gtk_source_gutter_renderer_lines_query_data; renderer_class->snapshot_line = gtk_source_gutter_renderer_lines_snapshot_line; } static void _gtk_source_gutter_renderer_lines_init (GtkSourceGutterRendererLines *self) { } GtkSourceGutterRenderer * _gtk_source_gutter_renderer_lines_new (void) { return g_object_new (GTK_SOURCE_TYPE_GUTTER_RENDERER_LINES, NULL); } 0707010000015E000081A40000000000000000000000016659080600000504000000000000000000000000000000000000004A00000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrenderermarks-private.h/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include #include "gtksourcetypes.h" #include "gtksourcetypes-private.h" #include "gtksourcegutterrendererpixbuf.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_GUTTER_RENDERER_MARKS (gtk_source_gutter_renderer_marks_get_type()) G_GNUC_INTERNAL G_DECLARE_FINAL_TYPE (GtkSourceGutterRendererMarks, gtk_source_gutter_renderer_marks, GTK_SOURCE, GUTTER_RENDERER_MARKS, GtkSourceGutterRendererPixbuf) G_GNUC_INTERNAL GtkSourceGutterRenderer *gtk_source_gutter_renderer_marks_new (void); G_END_DECLS 0707010000015F000081A400000000000000000000000166590806000027AC000000000000000000000000000000000000004200000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrenderermarks.c/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourcegutterrenderermarks-private.h" #include "gtksourceview.h" #include "gtksourcebuffer.h" #include "gtksourcemarkattributes.h" #include "gtksourcemark.h" #define COMPOSITE_ALPHA 225 struct _GtkSourceGutterRendererMarks { GtkSourceGutterRendererPixbuf parent_instance; }; G_DEFINE_TYPE (GtkSourceGutterRendererMarks, gtk_source_gutter_renderer_marks, GTK_SOURCE_TYPE_GUTTER_RENDERER_PIXBUF) static gint sort_marks_by_priority (gconstpointer m1, gconstpointer m2, gpointer data) { GtkSourceMark *mark1 = (GtkSourceMark *)m1; GtkSourceMark *mark2 = (GtkSourceMark *)m2; GtkSourceView *view = data; GtkTextIter iter1, iter2; gint line1; gint line2; gtk_text_buffer_get_iter_at_mark (gtk_text_mark_get_buffer (GTK_TEXT_MARK (mark1)), &iter1, GTK_TEXT_MARK (mark1)); gtk_text_buffer_get_iter_at_mark (gtk_text_mark_get_buffer (GTK_TEXT_MARK (mark2)), &iter2, GTK_TEXT_MARK (mark2)); line1 = gtk_text_iter_get_line (&iter1); line2 = gtk_text_iter_get_line (&iter2); if (line1 == line2) { gint priority1 = -1; gint priority2 = -1; gtk_source_view_get_mark_attributes (view, gtk_source_mark_get_category (mark1), &priority1); gtk_source_view_get_mark_attributes (view, gtk_source_mark_get_category (mark2), &priority2); return priority1 - priority2; } else { return line2 - line1; } } static int measure_line_height (GtkSourceView *view) { PangoLayout *layout; gint height = 12; layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), "QWERTY"); if (layout) { pango_layout_get_pixel_size (layout, NULL, &height); g_object_unref (layout); } return height - 2; } static void composite_marks (GtkSourceView *view, GtkSourceGutterRendererPixbuf *renderer, GSList *marks, gint size) { /* Draw the mark with higher priority */ marks = g_slist_sort_with_data (marks, sort_marks_by_priority, view); gtk_source_gutter_renderer_pixbuf_set_paintable (renderer, NULL); /* composite all the pixbufs for the marks present at the line */ do { GtkSourceMark *mark; GtkSourceMarkAttributes *attrs; GdkPaintable *paintable; mark = marks->data; attrs = gtk_source_view_get_mark_attributes (view, gtk_source_mark_get_category (mark), NULL); if (attrs != NULL) { paintable = gtk_source_mark_attributes_render_icon (attrs, GTK_WIDGET (view), size); if (paintable != NULL) { gtk_source_gutter_renderer_pixbuf_overlay_paintable (renderer, paintable); } } marks = g_slist_next (marks); } while (marks); } static void gutter_renderer_query_data (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines, guint line) { GtkSourceBuffer *buffer; GtkSourceView *view; GtkTextIter iter; GSList *marks; view = GTK_SOURCE_VIEW (gtk_source_gutter_renderer_get_view (renderer)); buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (buffer), &iter, line); marks = gtk_source_buffer_get_source_marks_at_iter (buffer, &iter, NULL); if (marks != NULL) { gint size = measure_line_height (view); composite_marks (view, GTK_SOURCE_GUTTER_RENDERER_PIXBUF (renderer), marks, size); g_object_set (G_OBJECT (renderer), "xpad", 2, "yalign", 0.5, "xalign", 0.5, "alignment-mode", GTK_SOURCE_GUTTER_RENDERER_ALIGNMENT_MODE_FIRST, NULL); g_slist_free (marks); } else { gtk_source_gutter_renderer_pixbuf_set_paintable (GTK_SOURCE_GUTTER_RENDERER_PIXBUF (renderer), NULL); } } static gboolean set_tooltip_widget_from_marks (GtkSourceView *view, GtkTooltip *tooltip, GSList *marks) { const gint icon_size = 16; GtkGrid *grid = NULL; gint row_num = 0; for (; marks; marks = g_slist_next (marks)) { const gchar *category; GtkSourceMark *mark; GtkSourceMarkAttributes *attrs; gchar *text; gboolean ismarkup = FALSE; GtkWidget *label; GdkPaintable *paintable; mark = marks->data; category = gtk_source_mark_get_category (mark); attrs = gtk_source_view_get_mark_attributes (view, category, NULL); if (attrs == NULL) { continue; } text = gtk_source_mark_attributes_get_tooltip_markup (attrs, mark); if (text == NULL) { text = gtk_source_mark_attributes_get_tooltip_text (attrs, mark); } else { ismarkup = TRUE; } if (text == NULL) { continue; } if (grid == NULL) { grid = GTK_GRID (gtk_grid_new ()); gtk_grid_set_column_spacing (grid, 4); gtk_widget_show (GTK_WIDGET (grid)); } label = gtk_label_new (NULL); if (ismarkup) { gtk_label_set_markup (GTK_LABEL (label), text); } else { gtk_label_set_text (GTK_LABEL (label), text); } gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_widget_set_valign (label, GTK_ALIGN_START); gtk_widget_show (label); paintable = gtk_source_mark_attributes_render_icon (attrs, GTK_WIDGET (view), icon_size); if (paintable == NULL) { gtk_grid_attach (grid, label, 0, row_num, 2, 1); } else { GtkWidget *image; image = gtk_image_new_from_paintable (paintable); gtk_widget_set_halign (image, GTK_ALIGN_START); gtk_widget_set_valign (image, GTK_ALIGN_START); gtk_widget_show (image); gtk_grid_attach (grid, image, 0, row_num, 1, 1); gtk_grid_attach (grid, label, 1, row_num, 1, 1); } row_num++; if (marks->next != NULL) { GtkWidget *separator; separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_widget_show (separator); gtk_grid_attach (grid, separator, 0, row_num, 2, 1); row_num++; } g_free (text); } if (grid == NULL) { return FALSE; } gtk_tooltip_set_custom (tooltip, GTK_WIDGET (grid)); return TRUE; } static gboolean gutter_renderer_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard, GtkTooltip *tooltip) { GtkSourceGutterRenderer *renderer; GSList *marks; GtkSourceView *view; GtkSourceBuffer *buffer; GtkTextIter iter; gboolean ret; renderer = GTK_SOURCE_GUTTER_RENDERER (widget); view = GTK_SOURCE_VIEW (gtk_source_gutter_renderer_get_view (renderer)); buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, 0, y); marks = gtk_source_buffer_get_source_marks_at_iter (buffer, &iter, NULL); if (marks != NULL) { marks = g_slist_sort_with_data (marks, sort_marks_by_priority, view); marks = g_slist_reverse (marks); ret = set_tooltip_widget_from_marks (view, tooltip, marks); g_slist_free (marks); return ret; } return FALSE; } static gboolean gutter_renderer_query_activatable (GtkSourceGutterRenderer *renderer, GtkTextIter *iter, GdkRectangle *area) { return TRUE; } static void gutter_renderer_change_view (GtkSourceGutterRenderer *renderer, GtkSourceView *old_view) { GtkSourceView *view; view = gtk_source_gutter_renderer_get_view (renderer); if (view != NULL) { gtk_widget_set_size_request (GTK_WIDGET (renderer), measure_line_height (view), -1); } if (GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_marks_parent_class)->change_view != NULL) { GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_marks_parent_class)->change_view (renderer, old_view); } } static void gtk_source_gutter_renderer_marks_class_init (GtkSourceGutterRendererMarksClass *klass) { GtkSourceGutterRendererClass *renderer_class = GTK_SOURCE_GUTTER_RENDERER_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); widget_class->query_tooltip = gutter_renderer_query_tooltip; renderer_class->query_data = gutter_renderer_query_data; renderer_class->query_activatable = gutter_renderer_query_activatable; renderer_class->change_view = gutter_renderer_change_view; } static void gtk_source_gutter_renderer_marks_init (GtkSourceGutterRendererMarks *self) { } GtkSourceGutterRenderer * gtk_source_gutter_renderer_marks_new (void) { return g_object_new (GTK_SOURCE_TYPE_GUTTER_RENDERER_MARKS, NULL); } 07070100000160000081A40000000000000000000000016659080600003C30000000000000000000000000000000000000004300000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrendererpixbuf.c/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourcegutterrendererpixbuf.h" #include "gtksourcepixbufhelper-private.h" /** * GtkSourceGutterRendererPixbuf: * * Renders a pixbuf in the gutter. * * A `GtkSourceGutterRendererPixbuf` can be used to render an image in a cell of * [class@Gutter]. */ typedef struct { GtkSourcePixbufHelper *helper; GdkPaintable *paintable; GPtrArray *overlays; } GtkSourceGutterRendererPixbufPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceGutterRendererPixbuf, gtk_source_gutter_renderer_pixbuf, GTK_SOURCE_TYPE_GUTTER_RENDERER) enum { PROP_0, PROP_PIXBUF, PROP_ICON_NAME, PROP_GICON, PROP_PAINTABLE, }; static void clear_overlays (GtkSourceGutterRendererPixbuf *renderer) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); if (priv->overlays != NULL && priv->overlays->len > 0) { g_ptr_array_remove_range (priv->overlays, 0, priv->overlays->len); } } static void gutter_renderer_pixbuf_snapshot_line (GtkSourceGutterRenderer *renderer, GtkSnapshot *snapshot, GtkSourceGutterLines *lines, guint line) { GtkSourceGutterRendererPixbuf *pix = GTK_SOURCE_GUTTER_RENDERER_PIXBUF (renderer); GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (pix); GtkWidget *widget = GTK_WIDGET (renderer); GdkPaintable *paintable; GtkSourceView *view; gint width; gint height; gfloat x = 0; gfloat y = 0; guint i; gint size; view = gtk_source_gutter_renderer_get_view (renderer); width = gtk_widget_get_width (widget); height = gtk_widget_get_height (widget); size = MIN (width, height); paintable = gtk_source_pixbuf_helper_render (priv->helper, GTK_WIDGET (view), size); /* Short-circuit if there is nothing to snapshot */ if (paintable == NULL && (priv->overlays == NULL || priv->overlays->len == 0)) { return; } gtk_source_gutter_renderer_align_cell (renderer, line, size, size, &x, &y); gtk_snapshot_save (snapshot); gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); if (paintable != NULL) { gdk_paintable_snapshot (paintable, snapshot, size, size); } else if (priv->paintable != NULL) { gdk_paintable_snapshot (priv->paintable, snapshot, size, size); } if (priv->overlays != NULL) { for (i = 0; i < priv->overlays->len; i++) { GdkPaintable *ele = g_ptr_array_index (priv->overlays, i); gdk_paintable_snapshot (ele, snapshot, size, size); } } gtk_snapshot_restore (snapshot); } static void gtk_source_gutter_renderer_pixbuf_finalize (GObject *object) { GtkSourceGutterRendererPixbuf *renderer = GTK_SOURCE_GUTTER_RENDERER_PIXBUF (object); GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_clear_pointer (&priv->helper, gtk_source_pixbuf_helper_free); g_clear_pointer (&priv->overlays, g_ptr_array_unref); G_OBJECT_CLASS (gtk_source_gutter_renderer_pixbuf_parent_class)->finalize (object); } static void set_pixbuf (GtkSourceGutterRendererPixbuf *renderer, GdkPixbuf *pixbuf) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_clear_object (&priv->paintable); clear_overlays (renderer); gtk_source_pixbuf_helper_set_pixbuf (priv->helper, pixbuf); } static void set_gicon (GtkSourceGutterRendererPixbuf *renderer, GIcon *icon) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_clear_object (&priv->paintable); clear_overlays (renderer); gtk_source_pixbuf_helper_set_gicon (priv->helper, icon); } static void set_icon_name (GtkSourceGutterRendererPixbuf *renderer, const gchar *icon_name) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_clear_object (&priv->paintable); clear_overlays (renderer); gtk_source_pixbuf_helper_set_icon_name (priv->helper, icon_name); } static void gtk_source_gutter_renderer_pixbuf_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceGutterRendererPixbuf *renderer = GTK_SOURCE_GUTTER_RENDERER_PIXBUF (object); switch (prop_id) { case PROP_PIXBUF: set_pixbuf (renderer, g_value_get_object (value)); break; case PROP_ICON_NAME: set_icon_name (renderer, g_value_get_string (value)); break; case PROP_GICON: set_gicon (renderer, g_value_get_object (value)); break; case PROP_PAINTABLE: gtk_source_gutter_renderer_pixbuf_set_paintable (renderer, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_gutter_renderer_pixbuf_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceGutterRendererPixbuf *renderer = GTK_SOURCE_GUTTER_RENDERER_PIXBUF (object); GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); switch (prop_id) { case PROP_PIXBUF: g_value_set_object (value, gtk_source_pixbuf_helper_get_pixbuf (priv->helper)); break; case PROP_ICON_NAME: g_value_set_string (value, gtk_source_pixbuf_helper_get_icon_name (priv->helper)); break; case PROP_GICON: g_value_set_object (value, gtk_source_pixbuf_helper_get_gicon (priv->helper)); break; case PROP_PAINTABLE: g_value_set_object (value, priv->paintable); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_gutter_renderer_pixbuf_class_init (GtkSourceGutterRendererPixbufClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkSourceGutterRendererClass *renderer_class = GTK_SOURCE_GUTTER_RENDERER_CLASS (klass); object_class->finalize = gtk_source_gutter_renderer_pixbuf_finalize; object_class->get_property = gtk_source_gutter_renderer_pixbuf_get_property; object_class->set_property = gtk_source_gutter_renderer_pixbuf_set_property; renderer_class->snapshot_line = gutter_renderer_pixbuf_snapshot_line; g_object_class_install_property (object_class, PROP_PAINTABLE, g_param_spec_object ("paintable", "Paintable", "The paintable", GDK_TYPE_PAINTABLE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_PIXBUF, g_param_spec_object ("pixbuf", "Pixbuf", "The pixbuf", GDK_TYPE_PIXBUF, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_ICON_NAME, g_param_spec_string ("icon-name", "Icon Name", "The icon name", NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_GICON, g_param_spec_object ("gicon", "GIcon", "The gicon", G_TYPE_ICON, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); } static void gtk_source_gutter_renderer_pixbuf_init (GtkSourceGutterRendererPixbuf *self) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (self); priv->helper = gtk_source_pixbuf_helper_new (); } /** * gtk_source_gutter_renderer_pixbuf_new: * * Create a new #GtkSourceGutterRendererPixbuf. * * Returns: (transfer full): A #GtkSourceGutterRenderer * **/ GtkSourceGutterRenderer * gtk_source_gutter_renderer_pixbuf_new (void) { return g_object_new (GTK_SOURCE_TYPE_GUTTER_RENDERER_PIXBUF, NULL); } /** * gtk_source_gutter_renderer_pixbuf_set_pixbuf: * @renderer: a #GtkSourceGutterRendererPixbuf * @pixbuf: (nullable): the pixbuf, or %NULL. */ void gtk_source_gutter_renderer_pixbuf_set_pixbuf (GtkSourceGutterRendererPixbuf *renderer, GdkPixbuf *pixbuf) { g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_PIXBUF (renderer)); g_return_if_fail (renderer == NULL || GDK_IS_PIXBUF (pixbuf)); set_pixbuf (renderer, pixbuf); } /** * gtk_source_gutter_renderer_pixbuf_get_pixbuf: * @renderer: a #GtkSourceGutterRendererPixbuf * * Get the pixbuf of the renderer. * * Returns: (transfer none): a #GdkPixbuf * **/ GdkPixbuf * gtk_source_gutter_renderer_pixbuf_get_pixbuf (GtkSourceGutterRendererPixbuf *renderer) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_PIXBUF (renderer), NULL); return gtk_source_pixbuf_helper_get_pixbuf (priv->helper); } /** * gtk_source_gutter_renderer_pixbuf_set_gicon: * @renderer: a #GtkSourceGutterRendererPixbuf * @icon: (nullable): the icon, or %NULL. */ void gtk_source_gutter_renderer_pixbuf_set_gicon (GtkSourceGutterRendererPixbuf *renderer, GIcon *icon) { g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_PIXBUF (renderer)); g_return_if_fail (icon == NULL || G_IS_ICON (icon)); set_gicon (renderer, icon); } /** * gtk_source_gutter_renderer_pixbuf_get_gicon: * @renderer: a #GtkSourceGutterRendererPixbuf * * Get the gicon of the renderer * * Returns: (transfer none): a #GIcon * **/ GIcon * gtk_source_gutter_renderer_pixbuf_get_gicon (GtkSourceGutterRendererPixbuf *renderer) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_PIXBUF (renderer), NULL); return gtk_source_pixbuf_helper_get_gicon (priv->helper); } /** * gtk_source_gutter_renderer_pixbuf_set_icon_name: * @renderer: a #GtkSourceGutterRendererPixbuf * @icon_name: (nullable): the icon name, or %NULL. */ void gtk_source_gutter_renderer_pixbuf_set_icon_name (GtkSourceGutterRendererPixbuf *renderer, const gchar *icon_name) { g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_PIXBUF (renderer)); set_icon_name (renderer, icon_name); } const gchar * gtk_source_gutter_renderer_pixbuf_get_icon_name (GtkSourceGutterRendererPixbuf *renderer) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_PIXBUF (renderer), NULL); return gtk_source_pixbuf_helper_get_icon_name (priv->helper); } /** * gtk_source_gutter_renderer_pixbuf_set_paintable: * @renderer: a #GtkSourceGutterRendererPixbuf * @paintable: (nullable): the paintable, or %NULL. */ void gtk_source_gutter_renderer_pixbuf_set_paintable (GtkSourceGutterRendererPixbuf *renderer, GdkPaintable *paintable) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_PIXBUF (renderer)); g_return_if_fail (!paintable || GDK_IS_PAINTABLE (paintable)); clear_overlays (renderer); gtk_source_pixbuf_helper_set_icon_name (priv->helper, NULL); g_set_object (&priv->paintable, paintable); } /** * gtk_source_gutter_renderer_pixbuf_get_paintable: * @renderer: a #GtkSourceGutterRendererPixbuf * * Gets a [iface@Gdk.Paintable] that was set with * [method@GutterRendererPixbuf.set_paintable] * * Returns: (transfer none) (nullable): a #GdkPaintable or %NULL */ GdkPaintable * gtk_source_gutter_renderer_pixbuf_get_paintable (GtkSourceGutterRendererPixbuf *renderer) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_return_val_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_PIXBUF (renderer), NULL); return priv->paintable; } /** * gtk_source_gutter_renderer_pixbuf_overlay_paintable: * @renderer: a #GtkSourceGutterRendererPixbuf * @paintable: a #GdkPaintable * * Allows overlaying a paintable on top of any other image that * has been set for the pixbuf. This will be applied when the * widget is next snapshot. */ void gtk_source_gutter_renderer_pixbuf_overlay_paintable (GtkSourceGutterRendererPixbuf *renderer, GdkPaintable *paintable) { GtkSourceGutterRendererPixbufPrivate *priv = gtk_source_gutter_renderer_pixbuf_get_instance_private (renderer); g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_PIXBUF (renderer)); g_return_if_fail (GDK_IS_PAINTABLE (paintable)); if (priv->overlays == NULL) { priv->overlays = g_ptr_array_new_with_free_func (g_object_unref); } g_ptr_array_add (priv->overlays, g_object_ref (paintable)); } 07070100000161000081A40000000000000000000000016659080600000D8E000000000000000000000000000000000000004300000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrendererpixbuf.h/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include "gtksourcetypes.h" #include "gtksourcegutterrenderer.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_GUTTER_RENDERER_PIXBUF (gtk_source_gutter_renderer_pixbuf_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_DERIVABLE_TYPE (GtkSourceGutterRendererPixbuf, gtk_source_gutter_renderer_pixbuf, GTK_SOURCE, GUTTER_RENDERER_PIXBUF, GtkSourceGutterRenderer) struct _GtkSourceGutterRendererPixbufClass { GtkSourceGutterRendererClass parent_class; /*< private >*/ gpointer _reserved[10]; }; GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceGutterRenderer *gtk_source_gutter_renderer_pixbuf_new (void); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_pixbuf_set_pixbuf (GtkSourceGutterRendererPixbuf *renderer, GdkPixbuf *pixbuf); GTK_SOURCE_AVAILABLE_IN_ALL GdkPixbuf *gtk_source_gutter_renderer_pixbuf_get_pixbuf (GtkSourceGutterRendererPixbuf *renderer); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_pixbuf_set_gicon (GtkSourceGutterRendererPixbuf *renderer, GIcon *icon); GTK_SOURCE_AVAILABLE_IN_ALL GIcon *gtk_source_gutter_renderer_pixbuf_get_gicon (GtkSourceGutterRendererPixbuf *renderer); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_pixbuf_set_icon_name (GtkSourceGutterRendererPixbuf *renderer, const gchar *icon_name); GTK_SOURCE_AVAILABLE_IN_ALL const gchar *gtk_source_gutter_renderer_pixbuf_get_icon_name (GtkSourceGutterRendererPixbuf *renderer); GTK_SOURCE_AVAILABLE_IN_ALL GdkPaintable *gtk_source_gutter_renderer_pixbuf_get_paintable (GtkSourceGutterRendererPixbuf *renderer); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_pixbuf_set_paintable (GtkSourceGutterRendererPixbuf *renderer, GdkPaintable *paintable); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_pixbuf_overlay_paintable (GtkSourceGutterRendererPixbuf *renderer, GdkPaintable *paintable); G_END_DECLS 07070100000162000081A400000000000000000000000166590806000004D4000000000000000000000000000000000000004900000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrenderertext-private.h/* * This file is part of GtkSourceView * * Copyright 2024 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include "gtksourcegutterrenderertext.h" G_BEGIN_DECLS void _gtk_source_gutter_renderer_text_get_draw (GtkSourceGutterRendererText *self, GdkRGBA *foreground_color, GdkRGBA *current_line_color, gboolean *current_line_bold); G_END_DECLS 07070100000163000081A4000000000000000000000001665908060000447F000000000000000000000000000000000000004100000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrenderertext.c/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourcegutterrenderertext-private.h" #include "gtksourcegutterlines.h" #include "gtksourceview-private.h" /** * GtkSourceGutterRendererText: * * Renders text in the gutter. * * A `GtkSourceGutterRendererText` can be used to render text in a cell of * [class@Gutter]. */ typedef struct { int width; int height; } Size; typedef struct { gchar *text; PangoLayout *cached_layout; PangoAttribute *current_line_bold; PangoAttribute *current_line_color; GdkRGBA current_line_color_rgba; GdkRGBA foreground_rgba; gsize text_len; Size cached_sizes[5]; guint is_markup : 1; guint has_selection : 1; } GtkSourceGutterRendererTextPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceGutterRendererText, gtk_source_gutter_renderer_text, GTK_SOURCE_TYPE_GUTTER_RENDERER) enum { PROP_0, PROP_MARKUP, PROP_TEXT, N_PROPS }; static void gtk_source_gutter_renderer_text_clear_cached_sizes (GtkSourceGutterRendererText *text) { GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (text); for (guint i = 0; i < G_N_ELEMENTS (priv->cached_sizes); i++) { priv->cached_sizes[i].width = -1; priv->cached_sizes[i].height = -1; } } static inline void gtk_source_gutter_renderer_text_get_size (GtkSourceGutterRendererTextPrivate *priv, PangoLayout *layout, int text_len, int *width, int *height) { g_assert (text_len > 0); if G_UNLIKELY (text_len > G_N_ELEMENTS (priv->cached_sizes) || priv->cached_sizes[text_len-1].width == -1) { pango_layout_get_pixel_size (layout, width, height); if (text_len <= G_N_ELEMENTS (priv->cached_sizes)) { priv->cached_sizes[text_len-1].width = *width; priv->cached_sizes[text_len-1].height = *height; } } else { *width = priv->cached_sizes[text_len-1].width; *height = priv->cached_sizes[text_len-1].height; } } static void gtk_source_gutter_renderer_text_begin (GtkSourceGutterRenderer *renderer, GtkSourceGutterLines *lines) { GtkSourceGutterRendererText *text = GTK_SOURCE_GUTTER_RENDERER_TEXT (renderer); GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (text); GtkSourceView *view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (renderer)); GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); GdkRGBA current; GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_text_parent_class)->begin (renderer, lines); priv->has_selection = gtk_text_buffer_get_has_selection (buffer); g_clear_object (&priv->cached_layout); priv->cached_layout = gtk_widget_create_pango_layout (GTK_WIDGET (renderer), NULL); G_GNUC_BEGIN_IGNORE_DEPRECATIONS { GtkStyleContext *style_context = gtk_widget_get_style_context (GTK_WIDGET (renderer)); gtk_style_context_get_color (style_context, &priv->foreground_rgba); priv->current_line_color_rgba = priv->foreground_rgba; } G_GNUC_END_IGNORE_DEPRECATIONS if (_gtk_source_view_get_current_line_number_color (view, ¤t)) { priv->current_line_color_rgba = current; priv->current_line_color = pango_attr_foreground_new (current.red * 65535, current.green * 65535, current.blue * 65535); } if (_gtk_source_view_get_current_line_number_bold (view)) { priv->current_line_bold = pango_attr_weight_new (PANGO_WEIGHT_BOLD); } gtk_source_gutter_renderer_text_clear_cached_sizes (text); } static void gtk_source_gutter_renderer_text_snapshot_line (GtkSourceGutterRenderer *renderer, GtkSnapshot *snapshot, GtkSourceGutterLines *lines, guint line) { GtkSourceGutterRendererText *text = GTK_SOURCE_GUTTER_RENDERER_TEXT (renderer); GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (text); PangoLayout *layout; gboolean clear_attributes; float x; float y; int width; int height; if (priv->text == NULL || priv->text_len == 0) { return; } layout = priv->cached_layout; clear_attributes = priv->is_markup; if (priv->is_markup) { pango_layout_set_markup (layout, priv->text, priv->text_len); } else { pango_layout_set_text (layout, priv->text, priv->text_len); } if (G_UNLIKELY (!priv->has_selection && gtk_source_gutter_lines_is_cursor (lines, line))) { PangoAttrList *attrs = pango_layout_get_attributes (layout); if (attrs == NULL) { attrs = pango_attr_list_new (); pango_layout_set_attributes (layout, attrs); } else { pango_attr_list_ref (attrs); } if (priv->current_line_color) { pango_attr_list_insert_before (attrs, pango_attribute_copy (priv->current_line_color)); clear_attributes = TRUE; } if (priv->current_line_bold) { pango_attr_list_insert_before (attrs, pango_attribute_copy (priv->current_line_bold)); clear_attributes = TRUE; } pango_attr_list_unref (attrs); } gtk_source_gutter_renderer_text_get_size (priv, layout, priv->text_len, &width, &height); gtk_source_gutter_renderer_align_cell (renderer, line, width, height, &x, &y); gtk_snapshot_render_layout (snapshot, gtk_widget_get_style_context (GTK_WIDGET (text)), ceilf (x), ceilf (y), layout); if (clear_attributes) { pango_layout_set_attributes (layout, NULL); } } static void gtk_source_gutter_renderer_text_end (GtkSourceGutterRenderer *renderer) { GtkSourceGutterRendererText *text = GTK_SOURCE_GUTTER_RENDERER_TEXT (renderer); GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (text); GTK_SOURCE_GUTTER_RENDERER_CLASS (gtk_source_gutter_renderer_text_parent_class)->end (renderer); g_clear_pointer (&priv->current_line_bold, pango_attribute_destroy); g_clear_pointer (&priv->current_line_color, pango_attribute_destroy); g_clear_object (&priv->cached_layout); } static void measure_text (GtkSourceGutterRendererText *renderer, const gchar *markup, const gchar *text, gint *width, gint *height) { GtkSourceView *view; PangoLayout *layout; if (width != NULL) { *width = 0; } if (height != NULL) { *height = 0; } view = gtk_source_gutter_renderer_get_view (GTK_SOURCE_GUTTER_RENDERER (renderer)); if (view == NULL) { return; } layout = gtk_widget_create_pango_layout (GTK_WIDGET (view), NULL); if (layout == NULL) { return; } if (markup != NULL) { pango_layout_set_markup (layout, markup, -1); } else { pango_layout_set_text (layout, text, -1); } pango_layout_get_pixel_size (layout, width, height); g_object_unref (layout); } /** * gtk_source_gutter_renderer_text_measure: * @renderer: a #GtkSourceGutterRendererText. * @text: the text to measure. * @width: (out) (optional): location to store the width of the text in pixels, * or %NULL. * @height: (out) (optional): location to store the height of the text in * pixels, or %NULL. * * Measures the text provided using the pango layout used by the * #GtkSourceGutterRendererText. */ void gtk_source_gutter_renderer_text_measure (GtkSourceGutterRendererText *renderer, const gchar *text, gint *width, gint *height) { g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_TEXT (renderer)); g_return_if_fail (text != NULL); measure_text (renderer, NULL, text, width, height); } /** * gtk_source_gutter_renderer_text_measure_markup: * @renderer: a #GtkSourceGutterRendererText. * @markup: the pango markup to measure. * @width: (out) (optional): location to store the width of the text in pixels, * or %NULL. * @height: (out) (optional): location to store the height of the text in * pixels, or %NULL. * * Measures the pango markup provided using the pango layout used by the * #GtkSourceGutterRendererText. */ void gtk_source_gutter_renderer_text_measure_markup (GtkSourceGutterRendererText *renderer, const gchar *markup, gint *width, gint *height) { g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_TEXT (renderer)); g_return_if_fail (markup != NULL); measure_text (renderer, markup, NULL, width, height); } static void gtk_source_gutter_renderer_text_real_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkSourceGutterRendererText *renderer = GTK_SOURCE_GUTTER_RENDERER_TEXT (widget); GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (renderer); *minimum = 0; *natural = 0; *minimum_baseline = -1; *natural_baseline = -1; if (orientation == GTK_ORIENTATION_HORIZONTAL) { gint xpad = gtk_source_gutter_renderer_get_xpad (GTK_SOURCE_GUTTER_RENDERER (renderer)); gint width = 0; gint height = 0; if (priv->text != NULL) { if (priv->is_markup) { measure_text (renderer, priv->text, NULL, &width, &height); } else { measure_text (renderer, NULL, priv->text, &width, &height); } } *natural = *minimum = width + 2 * xpad; } } static void gtk_source_gutter_renderer_text_finalize (GObject *object) { GtkSourceGutterRendererText *renderer = GTK_SOURCE_GUTTER_RENDERER_TEXT (object); GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (renderer); g_clear_pointer (&priv->text, g_free); g_clear_object (&priv->cached_layout); G_OBJECT_CLASS (gtk_source_gutter_renderer_text_parent_class)->finalize (object); } static void set_text (GtkSourceGutterRendererText *renderer, const gchar *text, gint length, gboolean is_markup) { GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (renderer); g_free (priv->text); if (text == NULL) { priv->text_len = 0; priv->text = NULL; priv->is_markup = FALSE; } else { priv->text_len = length >= 0 ? length : strlen (text); priv->text = g_strndup (text, priv->text_len); priv->is_markup = !!is_markup; } } static void gtk_source_gutter_renderer_text_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceGutterRendererText *renderer = GTK_SOURCE_GUTTER_RENDERER_TEXT (object); switch (prop_id) { case PROP_MARKUP: set_text (renderer, g_value_get_string (value), -1, TRUE); break; case PROP_TEXT: set_text (renderer, g_value_get_string (value), -1, FALSE); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_gutter_renderer_text_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceGutterRendererText *renderer = GTK_SOURCE_GUTTER_RENDERER_TEXT (object); GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (renderer); switch (prop_id) { case PROP_MARKUP: g_value_set_string (value, priv->is_markup ? priv->text : NULL); break; case PROP_TEXT: g_value_set_string (value, !priv->is_markup ? priv->text : NULL); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_source_gutter_renderer_text_class_init (GtkSourceGutterRendererTextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkSourceGutterRendererClass *renderer_class = GTK_SOURCE_GUTTER_RENDERER_CLASS (klass); object_class->finalize = gtk_source_gutter_renderer_text_finalize; object_class->get_property = gtk_source_gutter_renderer_text_get_property; object_class->set_property = gtk_source_gutter_renderer_text_set_property; widget_class->measure = gtk_source_gutter_renderer_text_real_measure; renderer_class->begin = gtk_source_gutter_renderer_text_begin; renderer_class->end = gtk_source_gutter_renderer_text_end; renderer_class->snapshot_line = gtk_source_gutter_renderer_text_snapshot_line; g_object_class_install_property (object_class, PROP_MARKUP, g_param_spec_string ("markup", "Markup", "The markup", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_TEXT, g_param_spec_string ("text", "Text", "The text", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void gtk_source_gutter_renderer_text_init (GtkSourceGutterRendererText *self) { GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (self); priv->is_markup = TRUE; gtk_source_gutter_renderer_text_clear_cached_sizes (self); } /** * gtk_source_gutter_renderer_text_new: * * Create a new #GtkSourceGutterRendererText. * * Returns: (transfer full): A #GtkSourceGutterRenderer * **/ GtkSourceGutterRenderer * gtk_source_gutter_renderer_text_new (void) { return g_object_new (GTK_SOURCE_TYPE_GUTTER_RENDERER_TEXT, NULL); } void gtk_source_gutter_renderer_text_set_markup (GtkSourceGutterRendererText *renderer, const gchar *markup, gint length) { g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_TEXT (renderer)); set_text (renderer, markup, length, TRUE); } void gtk_source_gutter_renderer_text_set_text (GtkSourceGutterRendererText *renderer, const gchar *text, gint length) { g_return_if_fail (GTK_SOURCE_IS_GUTTER_RENDERER_TEXT (renderer)); set_text (renderer, text, length, FALSE); } void _gtk_source_gutter_renderer_text_get_draw (GtkSourceGutterRendererText *self, GdkRGBA *foreground_color, GdkRGBA *current_line_color, gboolean *current_line_bold) { GtkSourceGutterRendererTextPrivate *priv = gtk_source_gutter_renderer_text_get_instance_private (self); *foreground_color = priv->foreground_rgba; *current_line_color = priv->current_line_color_rgba; *current_line_bold = !priv->has_selection && priv->current_line_bold != NULL; } 07070100000164000081A40000000000000000000000016659080600000C73000000000000000000000000000000000000004100000000gtksourceview-5.12.1/gtksourceview/gtksourcegutterrenderertext.h/* * This file is part of GtkSourceView * * Copyright 2010 - Jesse van den Kieboom * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include "gtksourcetypes.h" #include "gtksourcegutterrenderer.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_GUTTER_RENDERER_TEXT (gtk_source_gutter_renderer_text_get_type()) struct _GtkSourceGutterRendererTextClass { GtkSourceGutterRendererClass parent_class; /*< private >*/ gpointer _reserved[10]; }; GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_DERIVABLE_TYPE (GtkSourceGutterRendererText, gtk_source_gutter_renderer_text, GTK_SOURCE, GUTTER_RENDERER_TEXT, GtkSourceGutterRenderer) GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceGutterRenderer *gtk_source_gutter_renderer_text_new (void); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_text_set_markup (GtkSourceGutterRendererText *renderer, const gchar *markup, gint length); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_text_set_text (GtkSourceGutterRendererText *renderer, const gchar *text, gint length); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_text_measure (GtkSourceGutterRendererText *renderer, const gchar *text, gint *width, gint *height); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_gutter_renderer_text_measure_markup (GtkSourceGutterRendererText *renderer, const gchar *markup, gint *width, gint *height); G_END_DECLS 07070100000165000081A400000000000000000000000166590806000003C1000000000000000000000000000000000000003C00000000gtksourceview-5.12.1/gtksourceview/gtksourcehover-private.h/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "gtksourcehover.h" G_BEGIN_DECLS GtkSourceHover *_gtk_source_hover_new (GtkSourceView *view); G_END_DECLS 07070100000166000081A40000000000000000000000016659080600003C23000000000000000000000000000000000000003400000000gtksourceview-5.12.1/gtksourceview/gtksourcehover.c/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourceassistant-private.h" #include "gtksourcebuffer.h" #include "gtksourcehover-private.h" #include "gtksourcehoverassistant-private.h" #include "gtksourcehovercontext.h" #include "gtksourcehoverprovider.h" #include "gtksourceiter-private.h" #include "gtksourceview-private.h" #define DEFAULT_HOVER_DELAY 500 /** * GtkSourceHover: * * Interactive tooltips. * * `GtkSourceHover` allows a [class@View] to provide contextual information. * When enabled, if the user hovers over a word in the text editor, a series * of registered [iface@HoverProvider] can populate a [class@HoverDisplay] * with useful information. * * To enable call [method@View.get_hover] and add [iface@HoverProvider] * using [method@Hover.add_provider]. To disable, remove all registered * providers with [method@Hover.remove_provider]. * * You can change how long to wait to display the interactive tooltip by * setting the [property@Hover:hover-delay] property in milliseconds. */ struct _GtkSourceHover { GObject parent_instance; GtkSourceView *view; GtkSourceAssistant *assistant; GtkTextBuffer *buffer; GPtrArray *providers; double motion_x; double motion_y; guint hover_delay; GSource *settle_source; guint in_click : 1; }; G_DEFINE_TYPE (GtkSourceHover, gtk_source_hover, G_TYPE_OBJECT) enum { PROP_0, PROP_HOVER_DELAY, N_PROPS }; static GParamSpec *properties [N_PROPS]; static void gtk_source_hover_dismiss (GtkSourceHover *self) { g_assert (GTK_SOURCE_IS_HOVER (self)); g_clear_pointer (&self->settle_source, g_source_destroy); if (self->assistant != NULL) { _gtk_source_hover_assistant_dismiss (GTK_SOURCE_HOVER_ASSISTANT (self->assistant)); } } static void cursor_moved_cb (GtkSourceHover *self, GtkSourceBuffer *buffer) { g_assert (GTK_SOURCE_IS_HOVER (self)); g_assert (GTK_SOURCE_IS_BUFFER (buffer)); if (!self->in_click) { gtk_source_hover_dismiss (self); } } static void gtk_source_hover_notify_buffer (GtkSourceHover *hover, GParamSpec *pspec, GtkSourceView *view) { GtkTextBuffer *buffer; g_assert (GTK_SOURCE_IS_HOVER (hover)); g_assert (GTK_SOURCE_IS_VIEW (view)); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); if (buffer == hover->buffer) { return; } if (hover->buffer != NULL) { g_signal_handlers_disconnect_by_func (hover->buffer, G_CALLBACK (cursor_moved_cb), hover); g_clear_weak_pointer (&hover->buffer); } if (!GTK_SOURCE_IS_BUFFER (buffer)) { return; } if (g_set_weak_pointer (&hover->buffer, buffer)) { g_signal_connect_object (hover->buffer, "cursor-moved", G_CALLBACK (cursor_moved_cb), hover, G_CONNECT_SWAPPED); } } static gboolean gtk_source_hover_get_bounds (GtkSourceHover *self, GtkTextIter *begin, GtkTextIter *end, GtkTextIter *location) { GtkTextIter iter; int x, y; g_assert (GTK_SOURCE_IS_HOVER (self)); g_assert (!self->view || GTK_SOURCE_IS_VIEW (self->view)); g_assert (begin != NULL); g_assert (end != NULL); g_assert (location != NULL); memset (begin, 0, sizeof *begin); memset (end, 0, sizeof *end); memset (location, 0, sizeof *location); if (self->view == NULL) { return FALSE; } g_assert (GTK_SOURCE_IS_VIEW (self->view)); gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (self->view), GTK_TEXT_WINDOW_WIDGET, self->motion_x, self->motion_y, &x, &y); if (!gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (self->view), &iter, x, y)) { return FALSE; } if (g_unichar_isspace (gtk_text_iter_get_char (&iter))) { return FALSE; } *begin = *end = iter; while (!gtk_text_iter_starts_line (begin)) { gtk_text_iter_backward_char (begin); if (g_unichar_isspace (gtk_text_iter_get_char (begin))) { gtk_text_iter_forward_char (begin); break; } } while (!gtk_text_iter_ends_line (end)) { if (g_unichar_isspace (gtk_text_iter_get_char (end))) { break; } if (!gtk_text_iter_forward_char (end)) { break; } } *location = iter; return TRUE; } static gboolean gtk_source_hover_settled_cb (GtkSourceHover *self) { GtkTextIter begin; GtkTextIter end; GtkTextIter location; g_assert (GTK_SOURCE_IS_HOVER (self)); g_clear_pointer (&self->settle_source, g_source_destroy); if (gtk_source_hover_get_bounds (self, &begin, &end, &location)) { _gtk_source_hover_assistant_display (GTK_SOURCE_HOVER_ASSISTANT (self->assistant), (GtkSourceHoverProvider **)self->providers->pdata, self->providers->len, &begin, &end, &location); } return G_SOURCE_REMOVE; } static void gtk_source_hover_queue_settle (GtkSourceHover *self) { g_assert (GTK_SOURCE_IS_HOVER (self)); if G_LIKELY (self->settle_source != NULL) { gint64 ready_time = g_get_monotonic_time () + (1000L * self->hover_delay); g_source_set_ready_time (self->settle_source, ready_time); return; } self->settle_source = g_timeout_source_new (self->hover_delay); g_source_set_callback (self->settle_source, (GSourceFunc)gtk_source_hover_settled_cb, self, NULL); g_source_set_name (self->settle_source, "gtk-source-hover-settle"); g_source_attach (self->settle_source, g_main_context_default ()); g_source_unref (self->settle_source); } static gboolean gtk_source_hover_key_pressed_cb (GtkSourceHover *self, guint keyval, guint keycode, GdkModifierType state, GtkEventControllerKey *controller) { g_assert (GTK_SOURCE_IS_HOVER (self)); g_assert (GTK_IS_EVENT_CONTROLLER_KEY (controller)); gtk_source_hover_dismiss (self); return GDK_EVENT_PROPAGATE; } static void gtk_source_hover_motion_cb (GtkSourceHover *self, double x, double y, GtkEventControllerMotion *controller) { g_assert (GTK_SOURCE_IS_HOVER (self)); g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller)); /* Ignore synthesized motion events */ if (self->motion_x == x && self->motion_y == y) { return; } self->motion_x = x; self->motion_y = y; gtk_source_hover_queue_settle (self); } static void gtk_source_hover_leave_cb (GtkSourceHover *self, GtkEventControllerMotion *controller) { g_assert (GTK_SOURCE_IS_HOVER (self)); g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller)); g_clear_pointer (&self->settle_source, g_source_destroy); } static gboolean gtk_source_hover_scroll_cb (GtkSourceHover *self, double dx, double dy, GtkEventControllerScroll *controller) { g_assert (GTK_SOURCE_IS_HOVER (self)); g_assert (GTK_IS_EVENT_CONTROLLER_SCROLL (controller)); gtk_source_hover_dismiss (self); return GDK_EVENT_PROPAGATE; } static void gtk_source_hover_dispose (GObject *object) { GtkSourceHover *self = (GtkSourceHover *)object; if (self->providers->len > 0) { g_ptr_array_remove_range (self->providers, 0, self->providers->len); } g_clear_pointer (&self->settle_source, g_source_destroy); g_clear_pointer (&self->assistant, _gtk_source_assistant_destroy); g_clear_weak_pointer (&self->view); g_clear_weak_pointer (&self->buffer); G_OBJECT_CLASS (gtk_source_hover_parent_class)->dispose (object); } static void gtk_source_hover_finalize (GObject *object) { GtkSourceHover *self = (GtkSourceHover *)object; g_clear_pointer (&self->providers, g_ptr_array_unref); g_assert (self->assistant == NULL); g_assert (self->settle_source == NULL); G_OBJECT_CLASS (gtk_source_hover_parent_class)->finalize (object); } static void gtk_source_hover_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceHover *self = GTK_SOURCE_HOVER (object); switch (prop_id) { case PROP_HOVER_DELAY: g_value_set_uint (value, self->hover_delay); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_source_hover_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceHover *self = GTK_SOURCE_HOVER (object); switch (prop_id) { case PROP_HOVER_DELAY: self->hover_delay = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_source_hover_class_init (GtkSourceHoverClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = gtk_source_hover_dispose; object_class->finalize = gtk_source_hover_finalize; object_class->get_property = gtk_source_hover_get_property; object_class->set_property = gtk_source_hover_set_property; /** * GtkSourceHover:hover-delay: * * Contains the number of milliseconds to delay before showing the hover assistant. */ properties [PROP_HOVER_DELAY] = g_param_spec_uint ("hover-delay", "Hover Delay", "Number of milliseconds to delay before showing assistant", 1, 5000, DEFAULT_HOVER_DELAY, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); } static void gtk_source_hover_init (GtkSourceHover *self) { self->providers = g_ptr_array_new_with_free_func (g_object_unref); self->hover_delay = DEFAULT_HOVER_DELAY; } static void gtk_source_hover_click_pressed_cb (GtkSourceHover *self, int n_press, double x, double y, GtkGestureClick *click) { g_assert (GTK_SOURCE_IS_HOVER (self)); g_assert (GTK_IS_GESTURE_CLICK (click)); self->in_click = TRUE; } static void gtk_source_hover_click_released_cb (GtkSourceHover *self, int n_press, double x, double y, GtkGestureClick *click) { g_assert (GTK_SOURCE_IS_HOVER (self)); g_assert (GTK_IS_GESTURE_CLICK (click)); self->in_click = FALSE; } GtkSourceHover * _gtk_source_hover_new (GtkSourceView *view) { GtkSourceHover *self; GtkEventController *key; GtkEventController *motion; GtkEventController *scroll; GtkEventController *click; g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL); self = g_object_new (GTK_SOURCE_TYPE_HOVER, NULL); g_set_weak_pointer (&self->view, view); self->assistant = _gtk_source_hover_assistant_new (); _gtk_source_view_add_assistant (view, self->assistant); key = gtk_event_controller_key_new (); g_signal_connect_object (key, "key-pressed", G_CALLBACK (gtk_source_hover_key_pressed_cb), self, G_CONNECT_SWAPPED); gtk_widget_add_controller (GTK_WIDGET (view), key); motion = gtk_event_controller_motion_new (); g_signal_connect_object (motion, "leave", G_CALLBACK (gtk_source_hover_leave_cb), self, G_CONNECT_SWAPPED); g_signal_connect_object (motion, "motion", G_CALLBACK (gtk_source_hover_motion_cb), self, G_CONNECT_SWAPPED); gtk_widget_add_controller (GTK_WIDGET (view), motion); click = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); g_signal_connect_object (click, "pressed", G_CALLBACK (gtk_source_hover_click_pressed_cb), self, G_CONNECT_SWAPPED); g_signal_connect_object (click, "released", G_CALLBACK (gtk_source_hover_click_released_cb), self, G_CONNECT_SWAPPED); gtk_event_controller_set_propagation_phase (click, GTK_PHASE_CAPTURE); gtk_widget_add_controller (GTK_WIDGET (view), click); scroll = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES); g_signal_connect_object (scroll, "scroll", G_CALLBACK (gtk_source_hover_scroll_cb), self, G_CONNECT_SWAPPED); gtk_widget_add_controller (GTK_WIDGET (view), scroll); g_signal_connect_object (view, "notify::buffer", G_CALLBACK (gtk_source_hover_notify_buffer), self, G_CONNECT_SWAPPED); gtk_source_hover_notify_buffer (self, NULL, view); return self; } void gtk_source_hover_add_provider (GtkSourceHover *self, GtkSourceHoverProvider *provider) { g_return_if_fail (GTK_SOURCE_IS_HOVER (self)); g_return_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider)); for (guint i = 0; i < self->providers->len; i++) { if (g_ptr_array_index (self->providers, i) == (gpointer)provider) { return; } } g_ptr_array_add (self->providers, g_object_ref (provider)); } void gtk_source_hover_remove_provider (GtkSourceHover *self, GtkSourceHoverProvider *provider) { g_return_if_fail (GTK_SOURCE_IS_HOVER (self)); g_return_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider)); for (guint i = 0; i < self->providers->len; i++) { if (g_ptr_array_index (self->providers, i) == (gpointer)provider) { g_ptr_array_remove_index (self->providers, i); return; } } } 07070100000167000081A40000000000000000000000016659080600000637000000000000000000000000000000000000003400000000gtksourceview-5.12.1/gtksourceview/gtksourcehover.h/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) # error "Only can be included directly." #endif #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_HOVER (gtk_source_hover_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkSourceHover, gtk_source_hover, GTK_SOURCE, HOVER, GObject) GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_hover_add_provider (GtkSourceHover *self, GtkSourceHoverProvider *provider); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_hover_remove_provider (GtkSourceHover *self, GtkSourceHoverProvider *provider); G_END_DECLS 07070100000168000081A4000000000000000000000001665908060000073C000000000000000000000000000000000000004500000000gtksourceview-5.12.1/gtksourceview/gtksourcehoverassistant-private.h/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "gtksourcetypes.h" #include "gtksourceassistant-private.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_HOVER_ASSISTANT (gtk_source_hover_assistant_get_type()) G_DECLARE_FINAL_TYPE (GtkSourceHoverAssistant, gtk_source_hover_assistant, GTK_SOURCE, HOVER_ASSISTANT, GtkSourceAssistant) GtkSourceAssistant *_gtk_source_hover_assistant_new (void); void _gtk_source_hover_assistant_dismiss (GtkSourceHoverAssistant *self); void _gtk_source_hover_assistant_display (GtkSourceHoverAssistant *self, GtkSourceHoverProvider **providers, guint n_providers, const GtkTextIter *begin, const GtkTextIter *end, const GtkTextIter *location); G_END_DECLS 07070100000169000081A40000000000000000000000016659080600004113000000000000000000000000000000000000003D00000000gtksourceview-5.12.1/gtksourceview/gtksourcehoverassistant.c/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include #include "gtksourceassistant-private.h" #include "gtksourcehoverassistant-private.h" #include "gtksourcehovercontext-private.h" #include "gtksourcehoverdisplay-private.h" #include "gtksourcehover-private.h" #include "gtksourceview.h" #define GTK_SOURCE_HOVER_ASSISTANT_MOTION "GTK_SOURCE_HOVER_ASSISTANT_MOTION" struct _GtkSourceHoverAssistant { GtkSourceAssistant parent_instance; GtkEventController *popover_motion; GtkEventController *root_motion; GtkSourceHoverDisplay *display; GCancellable *cancellable; GdkRectangle hovered_at; double root_x; double root_y; gulong root_motion_handler; gulong root_leave_handler; GSource *dismiss_source; guint disposed : 1; }; G_DEFINE_TYPE (GtkSourceHoverAssistant, gtk_source_hover_assistant, GTK_SOURCE_TYPE_ASSISTANT) static gboolean gtk_source_hover_assistant_should_dismiss (GtkSourceHoverAssistant *self) { GdkSurface *surface; g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); if (gtk_event_controller_motion_contains_pointer (GTK_EVENT_CONTROLLER_MOTION (self->popover_motion))) { return FALSE; } if ((surface = gtk_native_get_surface (GTK_NATIVE (self)))) { GdkRectangle popup_area, root_area; double popup_x, popup_y; double transform_x, transform_y; GtkRoot *root; popup_x = gdk_popup_get_position_x (GDK_POPUP (surface)); popup_y = gdk_popup_get_position_y (GDK_POPUP (surface)); gtk_native_get_surface_transform (GTK_NATIVE (self), &transform_x, &transform_y); popup_area.x = popup_x - transform_x; popup_area.y = popup_y - transform_y; popup_area.width = gdk_surface_get_width (surface); popup_area.height = gdk_surface_get_height (surface); root = gtk_widget_get_root (GTK_WIDGET (self)); root_area.x = 0; root_area.y = 0; root_area.width = gtk_widget_get_width (GTK_WIDGET (root)); root_area.height = gtk_widget_get_height (GTK_WIDGET (root)); if (gdk_rectangle_intersect (&root_area, &popup_area, &popup_area) && gdk_rectangle_contains_point (&popup_area, self->root_x, self->root_y)) { return FALSE; } } return TRUE; } static gboolean gtk_source_hover_assistant_dismiss_cb (GtkSourceHoverAssistant *self) { g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); g_clear_pointer (&self->dismiss_source, g_source_destroy); if (gtk_source_hover_assistant_should_dismiss (self)) { _gtk_source_hover_assistant_dismiss (self); } return G_SOURCE_REMOVE; } static void gtk_source_hover_assistant_queue_dismiss (GtkSourceHoverAssistant *self) { g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); if (self->dismiss_source != NULL) { return; } self->dismiss_source = g_idle_source_new (); g_source_set_name (self->dismiss_source, "gtk-source-hover-assistant-timeout"); g_source_set_callback (self->dismiss_source, (GSourceFunc)gtk_source_hover_assistant_dismiss_cb, self, NULL); g_source_attach (self->dismiss_source, NULL); g_source_unref (self->dismiss_source); } static void gtk_source_hover_assistant_popover_leave_cb (GtkSourceHoverAssistant *self, GtkEventControllerMotion *controller) { g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller)); gtk_source_hover_assistant_queue_dismiss (self); } static void gtk_source_hover_assistant_root_leave_cb (GtkSourceHoverAssistant *self, GtkEventControllerMotion *controller) { g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller)); gtk_source_hover_assistant_queue_dismiss (self); } static void gtk_source_hover_assistant_root_motion_cb (GtkSourceHoverAssistant *self, double x, double y, GtkEventControllerMotion *controller) { g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller)); self->root_x = x; self->root_y = y; gtk_source_hover_assistant_queue_dismiss (self); } static void gtk_source_hover_assistant_root (GtkWidget *widget) { GtkSourceHoverAssistant *self = (GtkSourceHoverAssistant *)widget; GtkRoot *root; GTK_WIDGET_CLASS (gtk_source_hover_assistant_parent_class)->root (widget); if ((root = gtk_widget_get_root (widget))) { GtkEventController *motion; if (!(motion = g_object_get_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION))) { motion = gtk_event_controller_motion_new (); gtk_event_controller_set_name (motion, "gtk-source-hover-assistant-motion"); g_object_set_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION, motion); gtk_widget_add_controller (GTK_WIDGET (root), motion); } self->root_motion = g_object_ref (motion); self->root_motion_handler = g_signal_connect_object (self->root_motion, "motion", G_CALLBACK (gtk_source_hover_assistant_root_motion_cb), self, G_CONNECT_SWAPPED); self->root_leave_handler = g_signal_connect_object (self->root_motion, "leave", G_CALLBACK (gtk_source_hover_assistant_root_leave_cb), self, G_CONNECT_SWAPPED); if (!gtk_widget_get_visible (GTK_WIDGET (self))) { g_signal_handler_block (self->root_motion, self->root_motion_handler); g_signal_handler_block (self->root_motion, self->root_leave_handler); } } } static void gtk_source_hover_assistant_unroot (GtkWidget *widget) { GtkSourceHoverAssistant *self = (GtkSourceHoverAssistant *)widget; GtkRoot *root; if ((root = gtk_widget_get_root (widget))) { GtkEventController *motion; if ((motion = g_object_get_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION))) { g_clear_signal_handler (&self->root_motion_handler, motion); g_clear_signal_handler (&self->root_leave_handler, motion); } else { self->root_motion_handler = 0; self->root_leave_handler = 0; } } GTK_WIDGET_CLASS (gtk_source_hover_assistant_parent_class)->unroot (widget); } static void gtk_source_hover_assistant_show (GtkWidget *widget) { GtkSourceHoverAssistant *self = GTK_SOURCE_HOVER_ASSISTANT (widget); GtkRoot *root; GTK_WIDGET_CLASS (gtk_source_hover_assistant_parent_class)->show (widget); if ((root = gtk_widget_get_root (widget))) { GtkEventController *motion; if ((motion = g_object_get_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION))) { g_signal_handler_unblock (motion, self->root_motion_handler); g_signal_handler_unblock (motion, self->root_leave_handler); } } } static void gtk_source_hover_assistant_hide (GtkWidget *widget) { GtkSourceHoverAssistant *self = GTK_SOURCE_HOVER_ASSISTANT (widget); GtkRoot *root; GTK_WIDGET_CLASS (gtk_source_hover_assistant_parent_class)->hide (widget); gtk_popover_set_pointing_to (GTK_POPOVER (widget), NULL); gtk_popover_set_offset (GTK_POPOVER (widget), 0, 0); if ((root = gtk_widget_get_root (widget))) { GtkEventController *motion; if ((motion = g_object_get_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION))) { g_signal_handler_block (motion, self->root_motion_handler); g_signal_handler_block (motion, self->root_leave_handler); } } } static void gtk_source_hover_assistant_get_target_location (GtkSourceAssistant *assistant, GdkRectangle *rect) { GtkSourceHoverAssistant *self = GTK_SOURCE_HOVER_ASSISTANT (assistant); GtkStyleContext *style_context; GtkBorder padding; *rect = GTK_SOURCE_HOVER_ASSISTANT (assistant)->hovered_at; style_context = gtk_widget_get_style_context (GTK_WIDGET (self->display)); gtk_style_context_get_padding (style_context, &padding); rect->x -= padding.left; } static void gtk_source_hover_assistant_dispose (GObject *object) { GtkSourceHoverAssistant *self = (GtkSourceHoverAssistant *)object; self->disposed = TRUE; self->display = NULL; g_clear_pointer (&self->dismiss_source, g_source_destroy); g_clear_object (&self->root_motion); g_clear_object (&self->popover_motion); g_clear_object (&self->cancellable); G_OBJECT_CLASS (gtk_source_hover_assistant_parent_class)->dispose (object); } static void gtk_source_hover_assistant_click_pressed_cb (GtkSourceHoverAssistant *self, int n_press, double x, double y, GtkGestureClick *click) { GdkEventSequence *sequence; GdkEvent *event; g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); g_assert (GTK_IS_GESTURE_CLICK (click)); sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (click)); event = gtk_gesture_get_last_event (GTK_GESTURE (click), sequence); /* WORKAROUND: See comment below in gtk_source_hover_assistant_init(). * We have to block context menus from here until we can be sure they'll * work with GtkPopover:autohide disabled. */ if (gdk_event_triggers_context_menu (event)) { gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); } } static void gtk_source_hover_assistant_class_init (GtkSourceHoverAssistantClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkSourceAssistantClass *assistant_class = GTK_SOURCE_ASSISTANT_CLASS (klass); object_class->dispose = gtk_source_hover_assistant_dispose; widget_class->root = gtk_source_hover_assistant_root; widget_class->unroot = gtk_source_hover_assistant_unroot; widget_class->show = gtk_source_hover_assistant_show; widget_class->hide = gtk_source_hover_assistant_hide; assistant_class->get_target_location = gtk_source_hover_assistant_get_target_location; } static void gtk_source_hover_assistant_init (GtkSourceHoverAssistant *self) { GtkEventController *click; GtkEventController *scroll; gtk_widget_add_css_class (GTK_WIDGET (self), "hover-assistant"); gtk_popover_set_autohide (GTK_POPOVER (self), FALSE); gtk_popover_set_position (GTK_POPOVER (self), GTK_POS_TOP); scroll = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES); g_signal_connect_object (scroll, "scroll", G_CALLBACK (_gtk_source_hover_assistant_dismiss), self, G_CONNECT_SWAPPED); gtk_widget_add_controller (GTK_WIDGET (self), g_steal_pointer (&scroll)); self->display = g_object_new (GTK_SOURCE_TYPE_HOVER_DISPLAY, NULL); _gtk_source_assistant_set_child (GTK_SOURCE_ASSISTANT (self), GTK_WIDGET (self->display)); self->popover_motion = gtk_event_controller_motion_new (); gtk_event_controller_set_name (self->popover_motion, "gkt-source-hover-assistant-motion"); g_signal_connect_object (self->popover_motion, "leave", G_CALLBACK (gtk_source_hover_assistant_popover_leave_cb), self, G_CONNECT_SWAPPED); gtk_widget_add_controller (GTK_WIDGET (self), g_object_ref (self->popover_motion)); /* WORKAROUND: Until we have a way to ensure that showing context * menus from the popover won't break our popover, we need to prevent * them from potentially breaking input/grabs. */ click = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); g_signal_connect_object (click, "pressed", G_CALLBACK (gtk_source_hover_assistant_click_pressed_cb), self, G_CONNECT_SWAPPED); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click), 0); gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (click), TRUE); gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (click), GTK_PHASE_CAPTURE); gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (click)); } GtkSourceAssistant * _gtk_source_hover_assistant_new (void) { return g_object_new (GTK_SOURCE_TYPE_HOVER_ASSISTANT, NULL); } static void gtk_source_hover_assistant_populate_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GtkSourceHoverContext *context = (GtkSourceHoverContext *)object; GtkSourceHoverAssistant *self = user_data; GError *error = NULL; g_assert (GTK_SOURCE_IS_HOVER_CONTEXT (context)); g_assert (G_IS_ASYNC_RESULT (result)); g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); if (_gtk_source_hover_context_populate_finish (context, result, &error)) { if (!self->disposed) { GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self)); gboolean mapped = parent && gtk_widget_get_mapped (parent); gboolean empty = _gtk_source_hover_display_is_empty (self->display); gtk_widget_set_visible (GTK_WIDGET (self), mapped && !empty); } } g_clear_object (&self); g_clear_error (&error); } void _gtk_source_hover_assistant_display (GtkSourceHoverAssistant *self, GtkSourceHoverProvider **providers, guint n_providers, const GtkTextIter *begin, const GtkTextIter *end, const GtkTextIter *location) { GtkSourceHoverContext *context; GtkSourceView *view; GdkRectangle begin_rect; GdkRectangle end_rect; GdkRectangle location_rect; GdkRectangle visible_rect; g_return_if_fail (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); g_return_if_fail (n_providers == 0 || providers != NULL); g_return_if_fail (begin != NULL); g_return_if_fail (end != NULL); g_return_if_fail (location != NULL); memset (&self->hovered_at, 0, sizeof self->hovered_at); g_cancellable_cancel (self->cancellable); g_clear_object (&self->cancellable); if (n_providers == 0) { gtk_widget_hide (GTK_WIDGET (self)); return; } view = GTK_SOURCE_VIEW (gtk_widget_get_parent (GTK_WIDGET (self))); gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (view), &visible_rect); gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), begin, &begin_rect); gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), end, &end_rect); gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), location, &location_rect); gdk_rectangle_union (&begin_rect, &end_rect, &location_rect); if (!gdk_rectangle_intersect (&location_rect, &visible_rect, &location_rect)) { gtk_widget_hide (GTK_WIDGET (self)); return; } self->hovered_at = location_rect; context = _gtk_source_hover_context_new (view, begin, end, location); for (guint i = 0; i < n_providers; i++) { _gtk_source_hover_context_add_provider (context, providers[i]); } _gtk_source_hover_display_clear (self->display); self->cancellable = g_cancellable_new (); _gtk_source_hover_context_populate_async (context, self->display, self->cancellable, gtk_source_hover_assistant_populate_cb, g_object_ref (self)); g_object_unref (context); } void _gtk_source_hover_assistant_dismiss (GtkSourceHoverAssistant *self) { g_return_if_fail (GTK_SOURCE_IS_HOVER_ASSISTANT (self)); g_cancellable_cancel (self->cancellable); g_clear_object (&self->cancellable); gtk_widget_hide (GTK_WIDGET (self)); _gtk_source_hover_display_clear (self->display); } 0707010000016A000081A400000000000000000000000166590806000008EE000000000000000000000000000000000000004300000000gtksourceview-5.12.1/gtksourceview/gtksourcehovercontext-private.h/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "gtksourcehovercontext.h" G_BEGIN_DECLS GtkSourceHoverContext *_gtk_source_hover_context_new (GtkSourceView *view, const GtkTextIter *begin, const GtkTextIter *end, const GtkTextIter *location); void _gtk_source_hover_context_add_provider (GtkSourceHoverContext *self, GtkSourceHoverProvider *provider); void _gtk_source_hover_context_populate_async (GtkSourceHoverContext *self, GtkSourceHoverDisplay *display, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean _gtk_source_hover_context_populate_finish (GtkSourceHoverContext *self, GAsyncResult *result, GError **error); G_END_DECLS 0707010000016B000081A40000000000000000000000016659080600002AD6000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/gtksourceview/gtksourcehovercontext.c/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourcebuffer.h" #include "gtksourcehovercontext-private.h" #include "gtksourcehoverdisplay.h" #include "gtksourcehoverprovider.h" #include "gtksourceview.h" /** * GtkSourceHoverContext: * * Context for populating [class@HoverDisplay] contents. * * `GtkSourceHoverContext` contains information about the request to populate * contents for a [class@HoverDisplay]. * * It can be used to retrieve the [class@View], [class@Buffer], and * [struct@Gtk.TextIter] for the regions of text which are being displayed. * * Use [method@HoverContext.get_bounds] to get the word that was * requested. [method@HoverContext.get_iter] will get you the location * of the pointer when the request was made. */ struct _GtkSourceHoverContext { GObject parent_instance; GtkSourceView *view; GtkSourceBuffer *buffer; GPtrArray *providers; GtkTextMark *begin; GtkTextMark *end; GtkTextMark *location; }; G_DEFINE_TYPE (GtkSourceHoverContext, gtk_source_hover_context, G_TYPE_OBJECT) typedef struct { guint n_active; guint n_success; } Populate; static void gtk_source_hover_context_dispose (GObject *object) { GtkSourceHoverContext *self = (GtkSourceHoverContext *)object; if (self->buffer != NULL) { if (self->begin != NULL) gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (self->buffer), self->begin); if (self->end != NULL) gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (self->buffer), self->end); if (self->location != NULL) gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (self->buffer), self->location); } g_clear_object (&self->begin); g_clear_object (&self->end); g_clear_object (&self->location); if (self->providers->len > 0) { g_ptr_array_remove_range (self->providers, 0, self->providers->len); } g_clear_weak_pointer (&self->buffer); g_clear_weak_pointer (&self->view); G_OBJECT_CLASS (gtk_source_hover_context_parent_class)->dispose (object); } static void gtk_source_hover_context_finalize (GObject *object) { GtkSourceHoverContext *self = (GtkSourceHoverContext *)object; g_clear_pointer (&self->providers, g_ptr_array_unref); G_OBJECT_CLASS (gtk_source_hover_context_parent_class)->finalize (object); } static void gtk_source_hover_context_class_init (GtkSourceHoverContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = gtk_source_hover_context_dispose; object_class->finalize = gtk_source_hover_context_finalize; } static void gtk_source_hover_context_init (GtkSourceHoverContext *self) { self->providers = g_ptr_array_new_with_free_func (g_object_unref); } void _gtk_source_hover_context_add_provider (GtkSourceHoverContext *self, GtkSourceHoverProvider *provider) { g_return_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self)); g_return_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider)); for (guint i = 0; i < self->providers->len; i++) { if (g_ptr_array_index (self->providers, i) == (gpointer)provider) { return; } } g_ptr_array_add (self->providers, g_object_ref (provider)); } /** * gtk_source_hover_context_get_view: * @self: a #GtkSourceHoverContext * * Returns: (transfer none): the #GtkSourceView that owns the context */ GtkSourceView * gtk_source_hover_context_get_view (GtkSourceHoverContext *self) { g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), NULL); return self->view; } /** * gtk_source_hover_context_get_buffer: * @self: a #GtkSourceHoverContext * * A convenience function to get the buffer. * * Returns: (transfer none): The #GtkSourceBuffer for the view */ GtkSourceBuffer * gtk_source_hover_context_get_buffer (GtkSourceHoverContext *self) { g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), NULL); g_return_val_if_fail (self->view != NULL, NULL); return GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view))); } static GtkTextMark * create_mark (GtkSourceHoverContext *self, const GtkTextIter *iter, gboolean left_gravity) { GtkTextMark *mark; GtkTextBuffer *buffer; g_assert (GTK_SOURCE_IS_HOVER_CONTEXT (self)); buffer = GTK_TEXT_BUFFER (self->buffer); mark = gtk_text_buffer_create_mark (buffer, NULL, iter, left_gravity); return g_object_ref (mark); } GtkSourceHoverContext * _gtk_source_hover_context_new (GtkSourceView *view, const GtkTextIter *begin, const GtkTextIter *end, const GtkTextIter *location) { GtkSourceHoverContext *self; GtkSourceBuffer *buffer; g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL); g_return_val_if_fail (begin != NULL, NULL); g_return_val_if_fail (end != NULL, NULL); g_return_val_if_fail (location != NULL, NULL); buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); self = g_object_new (GTK_SOURCE_TYPE_HOVER_CONTEXT, NULL); g_set_weak_pointer (&self->view, view); g_set_weak_pointer (&self->buffer, buffer); self->begin = create_mark (self, begin, TRUE); self->end = create_mark (self, end, FALSE); self->location = create_mark (self, location, FALSE); return self; } static void gtk_source_hover_context_populate_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GtkSourceHoverProvider *provider = (GtkSourceHoverProvider *)object; GTask *task = user_data; Populate *state; GError *error = NULL; g_assert (GTK_SOURCE_IS_HOVER_PROVIDER (provider)); g_assert (G_IS_ASYNC_RESULT (result)); g_assert (G_IS_TASK (task)); state = g_task_get_task_data (task); if (!gtk_source_hover_provider_populate_finish (provider, result, &error)) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { g_debug ("%s population failed", error->message); } g_clear_error (&error); } else { state->n_success++; } if (--state->n_active == 0) { if (state->n_success > 0) { g_task_return_boolean (task, TRUE); } else { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No hover providers populated the context"); } } g_object_unref (task); } void _gtk_source_hover_context_populate_async (GtkSourceHoverContext *self, GtkSourceHoverDisplay *display, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { Populate *state; GTask *task; g_return_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self)); g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (display)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); state = g_new0 (Populate, 1); state->n_active = self->providers->len; task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, _gtk_source_hover_context_populate_async); g_task_set_task_data (task, state, g_free); if (self->view == NULL || self->buffer == NULL) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cannot populate, view destroyed"); } else if (g_task_return_error_if_cancelled (task)) { /* Do nothing */ } else if (self->providers->len == 0) { g_task_return_boolean (task, TRUE); } else { for (guint i = 0; i < self->providers->len; i++) { GtkSourceHoverProvider *provider = g_ptr_array_index (self->providers, i); g_assert (GTK_SOURCE_IS_HOVER_PROVIDER (provider)); gtk_source_hover_provider_populate_async (provider, self, display, cancellable, gtk_source_hover_context_populate_cb, g_object_ref (task)); } } g_object_unref (task); } gboolean _gtk_source_hover_context_populate_finish (GtkSourceHoverContext *self, GAsyncResult *result, GError **error) { g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), FALSE); g_return_val_if_fail (G_IS_TASK (result), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } gboolean gtk_source_hover_context_get_iter (GtkSourceHoverContext *self, GtkTextIter *iter) { g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), FALSE); g_return_val_if_fail (iter != NULL, FALSE); if (self->buffer == NULL) return FALSE; gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self->buffer), iter, self->location); return TRUE; } /** * gtk_source_hover_context_get_bounds: * @self: an #GtkSourceHoverContext * @begin: (out) (optional): a #GtkTextIter * @end: (out) (optional): a #GtkTextIter * * Gets the current word bounds of the hover. * * If @begin is non-%NULL, it will be set to the start position of the * current word being hovered. * * If @end is non-%NULL, it will be set to the end position for the * current word being hovered. * * Returns: %TRUE if the marks are still valid and @begin or @end was set. */ gboolean gtk_source_hover_context_get_bounds (GtkSourceHoverContext *self, GtkTextIter *begin, GtkTextIter *end) { g_return_val_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (self), FALSE); if (self->buffer == NULL) return FALSE; if (begin != NULL) gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self->buffer), begin, self->begin); if (end != NULL) gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self->buffer), end, self->end); return TRUE; } 0707010000016C000081A400000000000000000000000166590806000007BD000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/gtksourceview/gtksourcehovercontext.h/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) # error "Only can be included directly." #endif #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_HOVER_CONTEXT (gtk_source_hover_context_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkSourceHoverContext, gtk_source_hover_context, GTK_SOURCE, HOVER_CONTEXT, GObject) GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceView *gtk_source_hover_context_get_view (GtkSourceHoverContext *self); GTK_SOURCE_AVAILABLE_IN_ALL GtkSourceBuffer *gtk_source_hover_context_get_buffer (GtkSourceHoverContext *self); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_hover_context_get_iter (GtkSourceHoverContext *self, GtkTextIter *iter); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_hover_context_get_bounds (GtkSourceHoverContext *self, GtkTextIter *begin, GtkTextIter *end); G_END_DECLS 0707010000016D000081A40000000000000000000000016659080600000421000000000000000000000000000000000000004300000000gtksourceview-5.12.1/gtksourceview/gtksourcehoverdisplay-private.h/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "gtksourcehoverdisplay.h" G_BEGIN_DECLS void _gtk_source_hover_display_clear (GtkSourceHoverDisplay *self); gboolean _gtk_source_hover_display_is_empty (GtkSourceHoverDisplay *self); G_END_DECLS 0707010000016E000081A40000000000000000000000016659080600001110000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/gtksourceview/gtksourcehoverdisplay.c/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourcehoverdisplay-private.h" /** * GtkSourceHoverDisplay: * * Display for interactive tooltips. * * `GtkSourceHoverDisplay` is a [class@Gtk.Widget] that may be populated with widgets * to be displayed to the user in interactive tooltips. The children widgets * are packed vertically using a [class@Gtk.Box]. * * Implement the [iface@HoverProvider] interface to be notified of when * to populate a `GtkSourceHoverDisplay` on behalf of the user. */ struct _GtkSourceHoverDisplay { GtkWidget parent_instance; GtkBox *vbox; }; G_DEFINE_TYPE (GtkSourceHoverDisplay, gtk_source_hover_display, GTK_TYPE_WIDGET) static void gtk_source_hover_display_dispose (GObject *object) { GtkSourceHoverDisplay *self = (GtkSourceHoverDisplay *)object; g_clear_pointer ((GtkWidget **)&self->vbox, gtk_widget_unparent); G_OBJECT_CLASS (gtk_source_hover_display_parent_class)->dispose (object); } static void gtk_source_hover_display_class_init (GtkSourceHoverDisplayClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = gtk_source_hover_display_dispose; gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_css_name (widget_class, "GtkSourceHoverDisplay"); } static void gtk_source_hover_display_init (GtkSourceHoverDisplay *self) { self->vbox = g_object_new (GTK_TYPE_BOX, "orientation", GTK_ORIENTATION_VERTICAL, NULL); gtk_widget_set_parent (GTK_WIDGET (self->vbox), GTK_WIDGET (self)); } void gtk_source_hover_display_append (GtkSourceHoverDisplay *self, GtkWidget *child) { g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self)); g_return_if_fail (GTK_IS_WIDGET (child)); gtk_box_append (self->vbox, child); } void gtk_source_hover_display_prepend (GtkSourceHoverDisplay *self, GtkWidget *child) { g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self)); g_return_if_fail (GTK_IS_WIDGET (child)); gtk_box_prepend (self->vbox, child); } void gtk_source_hover_display_insert_after (GtkSourceHoverDisplay *self, GtkWidget *child, GtkWidget *sibling) { g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self)); g_return_if_fail (GTK_IS_WIDGET (child)); g_return_if_fail (!sibling || GTK_IS_WIDGET (sibling)); if (sibling == NULL) { gtk_source_hover_display_append (self, child); } else { gtk_box_insert_child_after (self->vbox, child, sibling); } } void gtk_source_hover_display_remove (GtkSourceHoverDisplay *self, GtkWidget *child) { g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self)); g_return_if_fail (GTK_IS_WIDGET (child)); g_return_if_fail (gtk_widget_get_parent (child) == (GtkWidget *)self->vbox); gtk_box_remove (self->vbox, child); } void _gtk_source_hover_display_clear (GtkSourceHoverDisplay *self) { GtkWidget *child; g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self)); while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->vbox)))) { gtk_box_remove (self->vbox, child); } } gboolean _gtk_source_hover_display_is_empty (GtkSourceHoverDisplay *self) { g_return_val_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (self), FALSE); return gtk_widget_get_first_child (GTK_WIDGET (self->vbox)) == NULL; } 0707010000016F000081A40000000000000000000000016659080600000812000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/gtksourceview/gtksourcehoverdisplay.h/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) # error "Only can be included directly." #endif #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_HOVER_DISPLAY (gtk_source_hover_display_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_FINAL_TYPE (GtkSourceHoverDisplay, gtk_source_hover_display, GTK_SOURCE, HOVER_DISPLAY, GtkWidget) GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_hover_display_append (GtkSourceHoverDisplay *self, GtkWidget *child); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_hover_display_prepend (GtkSourceHoverDisplay *self, GtkWidget *child); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_hover_display_insert_after (GtkSourceHoverDisplay *self, GtkWidget *child, GtkWidget *sibling); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_hover_display_remove (GtkSourceHoverDisplay *self, GtkWidget *child); G_END_DECLS 07070100000170000081A400000000000000000000000166590806000012CF000000000000000000000000000000000000003C00000000gtksourceview-5.12.1/gtksourceview/gtksourcehoverprovider.c/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourcehovercontext.h" #include "gtksourcehoverdisplay.h" #include "gtksourcehoverprovider.h" /** * GtkSourceHoverProvider: * * Interface to populate interactive tooltips. * * `GtkSourceHoverProvider` is an interface that should be implemented to extend * the contents of a [class@HoverDisplay]. This is typical in editors that * interact external tooling such as those utilizing Language Server Protocol. * * If you can populate the [class@HoverDisplay] synchronously, use * [vfunc@HoverProvider.populate]. Otherwise, interface implementations that * may take additional time should use [vfunc@HoverProvider.populate_async] * to avoid blocking the main loop. */ G_DEFINE_INTERFACE (GtkSourceHoverProvider, gtk_source_hover_provider, G_TYPE_OBJECT) static gboolean gtk_source_hover_provider_real_populate (GtkSourceHoverProvider *provider, GtkSourceHoverContext *context, GtkSourceHoverDisplay *display, GError **error) { return TRUE; } static void gtk_source_hover_provider_real_populate_async (GtkSourceHoverProvider *provider, GtkSourceHoverContext *context, GtkSourceHoverDisplay *display, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GError *error = NULL; GTask *task; task = g_task_new (provider, cancellable, callback, user_data); g_task_set_source_tag (task, gtk_source_hover_provider_real_populate_async); if (!GTK_SOURCE_HOVER_PROVIDER_GET_IFACE (provider)->populate (provider, context, display, &error)) g_task_return_error (task, g_steal_pointer (&error)); else g_task_return_boolean (task, TRUE); g_object_unref (task); } static gboolean gtk_source_hover_provider_real_populate_finish (GtkSourceHoverProvider *self, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } static void gtk_source_hover_provider_default_init (GtkSourceHoverProviderInterface *iface) { iface->populate_async = gtk_source_hover_provider_real_populate_async; iface->populate_finish = gtk_source_hover_provider_real_populate_finish; iface->populate = gtk_source_hover_provider_real_populate; } void gtk_source_hover_provider_populate_async (GtkSourceHoverProvider *provider, GtkSourceHoverContext *context, GtkSourceHoverDisplay *display, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider)); g_return_if_fail (GTK_SOURCE_IS_HOVER_CONTEXT (context)); g_return_if_fail (GTK_SOURCE_IS_HOVER_DISPLAY (display)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); GTK_SOURCE_HOVER_PROVIDER_GET_IFACE (provider)->populate_async (provider, context, display, cancellable, callback, user_data); } gboolean gtk_source_hover_provider_populate_finish (GtkSourceHoverProvider *provider, GAsyncResult *result, GError **error) { g_return_val_if_fail (GTK_SOURCE_IS_HOVER_PROVIDER (provider), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); return GTK_SOURCE_HOVER_PROVIDER_GET_IFACE (provider)->populate_finish (provider, result, error); } 07070100000171000081A40000000000000000000000016659080600000BD9000000000000000000000000000000000000003C00000000gtksourceview-5.12.1/gtksourceview/gtksourcehoverprovider.h/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) # error "Only can be included directly." #endif #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_HOVER_PROVIDER (gtk_source_hover_provider_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_INTERFACE (GtkSourceHoverProvider, gtk_source_hover_provider, GTK_SOURCE, HOVER_PROVIDER, GObject) struct _GtkSourceHoverProviderInterface { GTypeInterface parent_iface; gboolean (*populate) (GtkSourceHoverProvider *self, GtkSourceHoverContext *context, GtkSourceHoverDisplay *display, GError **error); void (*populate_async) (GtkSourceHoverProvider *self, GtkSourceHoverContext *context, GtkSourceHoverDisplay *display, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean (*populate_finish) (GtkSourceHoverProvider *self, GAsyncResult *result, GError **error); }; GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_hover_provider_populate_async (GtkSourceHoverProvider *self, GtkSourceHoverContext *context, GtkSourceHoverDisplay *display, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_hover_provider_populate_finish (GtkSourceHoverProvider *self, GAsyncResult *result, GError **error); G_END_DECLS 07070100000172000081A400000000000000000000000166590806000003C4000000000000000000000000000000000000003F00000000gtksourceview-5.12.1/gtksourceview/gtksourceindenter-private.h/* * This file is part of GtkSourceView * * Copyright 2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "gtksourceindenter.h" G_BEGIN_DECLS GtkSourceIndenter *_gtk_source_indenter_internal_new (void); G_END_DECLS 07070100000173000081A4000000000000000000000001665908060000215A000000000000000000000000000000000000003700000000gtksourceview-5.12.1/gtksourceview/gtksourceindenter.c/* * This file is part of GtkSourceView * * Copyright 2015-2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourceindenter-private.h" #include "gtksourceview.h" /** * GtkSourceIndenter: * * Auto-indentation interface. * * By default, [class@View] can auto-indent as you type when * [property@View:auto-indent] is enabled. The indentation simply copies the * previous lines indentation. * * This can be changed by implementing `GtkSourceIndenter` and setting the * [property@View:indenter] property. * * Implementors of this interface should implement both * [vfunc@Indenter.is_trigger] and [vfunc@Indenter.indent]. * * [vfunc@Indenter.is_trigger] is called upon key-press to * determine of the key press should trigger an indentation. The default * implementation of the interface checks to see if the key was * [const@Gdk.KEY_Return] or [const@Gdk.KEY_KP_Enter] without %GDK_SHIFT_MASK set. * * [vfunc@Indenter.indent] is called after text has been * inserted into [class@Buffer] when * [vfunc@Indenter.is_trigger] returned %TRUE. The [struct@Gtk.TextIter] * is placed directly after the inserted character or characters. * * It may be beneficial to move the insertion mark using * [method@Gtk.TextBuffer.select_range] depending on how the indenter changes * the indentation. * * All changes are encapsulated within a single user action so that the * user may undo them using standard undo/redo accelerators. */ /** * GtkSourceIndenterInterface: * @is_trigger: the virtual function pointer for gtk_source_indenter_is_trigger() * @indent: the virtual function pointer for gtk_source_indenter_indent() * * The virtual function table for #GtkSourceIndenter. */ static inline gboolean char_is_space (gunichar ch) { return ch != '\n' && ch != '\r' && g_unichar_isspace (ch); } static gchar * copy_prefix_for_line (GtkTextBuffer *buffer, guint line) { GtkTextIter begin; GtkTextIter end; g_assert (GTK_IS_TEXT_BUFFER (buffer)); gtk_text_buffer_get_iter_at_line_offset (buffer, &begin, line, 0); end = begin; while (!gtk_text_iter_ends_line (&end) && char_is_space (gtk_text_iter_get_char (&end)) && gtk_text_iter_forward_char (&end)) { /* Do Nothing */ } return gtk_text_iter_get_slice (&begin, &end); } static void indent_by_copying_previous_line (GtkSourceIndenter *self, GtkSourceView *view, GtkTextIter *location) { GtkTextBuffer *buffer; GtkTextIter begin; GtkTextIter end; guint line; g_assert (GTK_SOURCE_IS_INDENTER (self)); g_assert (GTK_SOURCE_IS_VIEW (view)); g_assert (location != NULL); buffer = gtk_text_iter_get_buffer (location); line = gtk_text_iter_get_line (location); begin = *location; if (!gtk_text_iter_starts_line (&begin)) { gtk_text_iter_set_line_offset (&begin, 0); } end = *location; while (!gtk_text_iter_ends_line (&end) && char_is_space (gtk_text_iter_get_char (&end)) && gtk_text_iter_forward_char (&end)) { /* Do Nothing */ } if (!gtk_text_iter_equal (&begin, &end)) { gtk_text_buffer_delete (buffer, &begin, &end); } if (line > 0) { gchar *text = copy_prefix_for_line (buffer, line - 1); gtk_text_buffer_insert (buffer, &begin, text, -1); g_free (text); } *location = begin; } static gboolean trigger_on_newline (GtkSourceIndenter *self, GtkSourceView *view, const GtkTextIter *location, GdkModifierType state, guint keyval) { if ((state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_SUPER_MASK)) != 0) return FALSE; if (keyval == GDK_KEY_Return || keyval == GDK_KEY_KP_Enter) { GtkTextBuffer *buffer = gtk_text_iter_get_buffer (location); return !gtk_text_buffer_get_has_selection (buffer); } return FALSE; } G_DEFINE_INTERFACE (GtkSourceIndenter, gtk_source_indenter, G_TYPE_OBJECT) static void gtk_source_indenter_default_init (GtkSourceIndenterInterface *iface) { iface->is_trigger = trigger_on_newline; iface->indent = indent_by_copying_previous_line; } /** * gtk_source_indenter_is_trigger: * @self: a #GtkSourceIndenter * @view: a #GtkSourceView * @location: the location where @ch is to be inserted * @state: modifier state for the insertion * @keyval: the keyval pressed such as [const@Gdk.KEY_Return] * * This function is used to determine if a key pressed should cause the * indenter to automatically indent. * * The default implementation of this virtual method will check to see * if @keyval is [const@Gdk.KEY_Return] or [const@Gdk.KEY_KP_Enter] and @state does * not have %GDK_SHIFT_MASK set. This is to allow the user to avoid * indentation when Shift+Return is pressed. Other indenters may want * to copy this behavior to provide a consistent experience to users. * * Returns: %TRUE if indentation should be automatically triggered; * otherwise %FALSE and no indentation will be performed. */ gboolean gtk_source_indenter_is_trigger (GtkSourceIndenter *self, GtkSourceView *view, const GtkTextIter *location, GdkModifierType state, guint keyval) { g_return_val_if_fail (GTK_SOURCE_IS_INDENTER (self), FALSE); g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), FALSE); g_return_val_if_fail (location != NULL, FALSE); return GTK_SOURCE_INDENTER_GET_IFACE (self)->is_trigger (self, view, location, state, keyval); } /** * gtk_source_indenter_indent: * @self: a #GtkSourceIndenter * @view: a #GtkSourceView * @iter: (inout): the location of the indentation request * * This function should be implemented to alter the indentation of text * within the view. * * @view is provided so that the indenter may retrieve settings such as indentation and tab widths. * * @iter is the location where the indentation was requested. This typically * is after having just inserted a newline (\n) character but can be other * situations such as a manually requested indentation or reformatting. * * See [iface@Indenter.is_trigger] for how to trigger indentation on * various characters inserted into the buffer. * * The implementor of this function is expected to keep @iter valid across * calls to the function and should contain the location of the insert mark * after calling this function. * * The default implementation for this virtual function will copy the * indentation of the previous line. */ void gtk_source_indenter_indent (GtkSourceIndenter *self, GtkSourceView *view, GtkTextIter *iter) { g_return_if_fail (GTK_SOURCE_IS_INDENTER (self)); g_return_if_fail (GTK_SOURCE_IS_VIEW (view)); g_return_if_fail (iter != NULL); GTK_SOURCE_INDENTER_GET_IFACE (self)->indent (self, view, iter); } struct _GtkSourceIndenterInternal { GObject parent_instance; }; #define GTK_SOURCE_TYPE_INDENTER_INTERNAL (gtk_source_indenter_internal_get_type()) G_DECLARE_FINAL_TYPE (GtkSourceIndenterInternal, gtk_source_indenter_internal, GTK_SOURCE, INDENTER_INTERNAL, GObject) G_DEFINE_TYPE_WITH_CODE (GtkSourceIndenterInternal, gtk_source_indenter_internal, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_INDENTER, NULL)) static void gtk_source_indenter_internal_class_init (GtkSourceIndenterInternalClass *klass) { } static void gtk_source_indenter_internal_init (GtkSourceIndenterInternal *self) { } GtkSourceIndenter * _gtk_source_indenter_internal_new (void) { return g_object_new (GTK_SOURCE_TYPE_INDENTER_INTERNAL, NULL); } 07070100000174000081A40000000000000000000000016659080600000923000000000000000000000000000000000000003700000000gtksourceview-5.12.1/gtksourceview/gtksourceindenter.h/* * This file is part of GtkSourceView * * Copyright 2015-2021 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) # error "Only can be included directly." #endif #include #include "gtksourcetypes.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_INDENTER (gtk_source_indenter_get_type()) GTK_SOURCE_AVAILABLE_IN_ALL G_DECLARE_INTERFACE (GtkSourceIndenter, gtk_source_indenter, GTK_SOURCE, INDENTER, GObject) struct _GtkSourceIndenterInterface { GTypeInterface parent_iface; gboolean (*is_trigger) (GtkSourceIndenter *self, GtkSourceView *view, const GtkTextIter *location, GdkModifierType state, guint keyval); void (*indent) (GtkSourceIndenter *self, GtkSourceView *view, GtkTextIter *iter); }; GTK_SOURCE_AVAILABLE_IN_ALL gboolean gtk_source_indenter_is_trigger (GtkSourceIndenter *self, GtkSourceView *view, const GtkTextIter *location, GdkModifierType state, guint keyval); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_indenter_indent (GtkSourceIndenter *self, GtkSourceView *view, GtkTextIter *iter); G_END_DECLS 07070100000175000081A400000000000000000000000166590806000007B1000000000000000000000000000000000000004200000000gtksourceview-5.12.1/gtksourceview/gtksourceinformative-private.h/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "gtksourceassistant-private.h" G_BEGIN_DECLS #define GTK_SOURCE_TYPE_INFORMATIVE (gtk_source_informative_get_type()) G_DECLARE_DERIVABLE_TYPE (GtkSourceInformative, gtk_source_informative, GTK_SOURCE, INFORMATIVE, GtkSourceAssistant) struct _GtkSourceInformativeClass { GtkSourceAssistantClass parent_class; }; const char *gtk_source_informative_get_message (GtkSourceInformative *self); void gtk_source_informative_set_message (GtkSourceInformative *self, const char *message); void gtk_source_informative_set_message_type (GtkSourceInformative *self, GtkMessageType message_type); GtkMessageType gtk_source_informative_get_message_type (GtkSourceInformative *self); const char *gtk_source_informative_get_icon_name (GtkSourceInformative *self); void gtk_source_informative_set_icon_name (GtkSourceInformative *self, const char *icon_name); G_END_DECLS 07070100000176000081A40000000000000000000000016659080600002282000000000000000000000000000000000000003A00000000gtksourceview-5.12.1/gtksourceview/gtksourceinformative.c/* * This file is part of GtkSourceView * * Copyright 2020 Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "gtksourceinformative-private.h" #include "gtksourceview.h" typedef struct { GtkImage *icon; GtkLabel *message; GtkMessageType message_type; } GtkSourceInformativePrivate; enum { PROP_0, PROP_ICON_NAME, PROP_MESSAGE, PROP_MESSAGE_TYPE, N_PROPS }; G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceInformative, gtk_source_informative, GTK_SOURCE_TYPE_ASSISTANT) static GParamSpec *properties [N_PROPS]; const char * gtk_source_informative_get_message (GtkSourceInformative *self) { GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self); g_return_val_if_fail (GTK_SOURCE_IS_INFORMATIVE (self), NULL); return gtk_label_get_label (priv->message); } void gtk_source_informative_set_message (GtkSourceInformative *self, const char *message) { GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self); g_return_if_fail (GTK_SOURCE_IS_INFORMATIVE (self)); gtk_label_set_label (priv->message, message); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]); } GtkMessageType gtk_source_informative_get_message_type (GtkSourceInformative *self) { GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self); g_return_val_if_fail (GTK_SOURCE_IS_INFORMATIVE (self), GTK_MESSAGE_OTHER); return priv->message_type; } void gtk_source_informative_set_message_type (GtkSourceInformative *self, GtkMessageType message_type) { GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self); g_assert (GTK_SOURCE_IS_INFORMATIVE (self)); priv->message_type = message_type; gtk_widget_remove_css_class (GTK_WIDGET (self), "error"); gtk_widget_remove_css_class (GTK_WIDGET (self), "info"); gtk_widget_remove_css_class (GTK_WIDGET (self), "question"); gtk_widget_remove_css_class (GTK_WIDGET (self), "warning"); gtk_widget_remove_css_class (GTK_WIDGET (self), "other"); switch (priv->message_type) { case GTK_MESSAGE_INFO: gtk_widget_add_css_class (GTK_WIDGET (self), "info"); break; case GTK_MESSAGE_WARNING: gtk_widget_add_css_class (GTK_WIDGET (self), "warning"); break; case GTK_MESSAGE_QUESTION: gtk_widget_add_css_class (GTK_WIDGET (self), "question"); break; case GTK_MESSAGE_ERROR: gtk_widget_add_css_class (GTK_WIDGET (self), "error"); break; case GTK_MESSAGE_OTHER: gtk_widget_add_css_class (GTK_WIDGET (self), "other"); break; default: break; } g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE_TYPE]); } const char * gtk_source_informative_get_icon_name (GtkSourceInformative *self) { GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self); g_assert (GTK_SOURCE_IS_INFORMATIVE (self)); return gtk_image_get_icon_name (priv->icon); } void gtk_source_informative_set_icon_name (GtkSourceInformative *self, const char *icon_name) { GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self); g_assert (GTK_SOURCE_IS_INFORMATIVE (self)); gtk_image_set_from_icon_name (priv->icon, icon_name); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ICON_NAME]); } static void gtk_source_informative_get_offset (GtkSourceAssistant *assistant, int *x_offset, int *y_offset) { GtkSourceInformative *self = GTK_SOURCE_INFORMATIVE (assistant); GtkSourceInformativePrivate *priv = gtk_source_informative_get_instance_private (self); GtkStyleContext *style_context; GtkBorder margin; int min_width, min_baseline; GTK_SOURCE_ASSISTANT_CLASS (gtk_source_informative_parent_class)->get_offset (assistant, x_offset, y_offset); gtk_widget_measure (GTK_WIDGET (priv->icon), GTK_ORIENTATION_HORIZONTAL, -1, &min_width, NULL, &min_baseline, NULL); style_context = gtk_widget_get_style_context (GTK_WIDGET (priv->icon)); gtk_style_context_get_margin (style_context, &margin); *x_offset -= min_width; *x_offset += margin.right; } static void gtk_source_informative_get_target_location (GtkSourceAssistant *assistant, GdkRectangle *rect) { g_assert (GTK_SOURCE_IS_ASSISTANT (assistant)); g_assert (rect != NULL); GTK_SOURCE_ASSISTANT_CLASS (gtk_source_informative_parent_class)->get_target_location (assistant, rect); /* Align to beginning of the character */ rect->width = 0; } static void gtk_source_informative_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkSourceInformative *self = GTK_SOURCE_INFORMATIVE (object); switch (prop_id) { case PROP_ICON_NAME: g_value_set_string (value, gtk_source_informative_get_icon_name (self)); break; case PROP_MESSAGE: g_value_set_string (value, gtk_source_informative_get_message (self)); break; case PROP_MESSAGE_TYPE: g_value_set_enum (value, gtk_source_informative_get_message_type (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_source_informative_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkSourceInformative *self = GTK_SOURCE_INFORMATIVE (object); switch (prop_id) { case PROP_ICON_NAME: gtk_source_informative_set_icon_name (self, g_value_get_string (value)); break; case PROP_MESSAGE: gtk_source_informative_set_message (self, g_value_get_string (value)); break; case PROP_MESSAGE_TYPE: gtk_source_informative_set_message_type (self, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_source_informative_class_init (GtkSourceInformativeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkSourceAssistantClass *assistant_class = GTK_SOURCE_ASSISTANT_CLASS (klass); object_class->get_property = gtk_source_informative_get_property; object_class->set_property = gtk_source_informative_set_property; assistant_class->get_offset = gtk_source_informative_get_offset; assistant_class->get_target_location = gtk_source_informative_get_target_location; properties [PROP_ICON_NAME] = g_param_spec_string ("icon-name", "Icon Name", "Icon Name", NULL, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); properties [PROP_MESSAGE] = g_param_spec_string ("message", "Message", "The message for the popover", NULL, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); properties [PROP_MESSAGE_TYPE] = g_param_spec_enum ("message-type", "Message Type", "The message type for the popover", GTK_TYPE_MESSAGE_TYPE, GTK_MESSAGE_INFO, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gtksourceview/ui/gtksourceinformative.ui"); gtk_widget_class_bind_template_child_private (widget_class, GtkSourceInformative, icon); gtk_widget_class_bind_template_child_private (widget_class, GtkSourceInformative, message); } static void gtk_source_informative_init (GtkSourceInformative *self) { gtk_widget_init_template (GTK_WIDGET (self)); gtk_popover_set_autohide (GTK_POPOVER (self), FALSE); } 07070100000177000081A400000000000000000000000166590806000002E4000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/gtksourceview/gtksourceinformative.ui 07070100000178000081A400000000000000000000000166590806000022AA000000000000000000000000000000000000003300000000gtksourceview-5.12.1/gtksourceview/gtksourceinit.c/* * This file is part of GtkSourceView * * Copyright 2016, 2017 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourceview-gresources.h" #include #include "gtksourcebuffer.h" #include "gtksourcebufferinputstream-private.h" #include "gtksourcebufferoutputstream-private.h" #include "gtksourcecompletion.h" #include "gtksourcecompletioncontext.h" #include "gtksourcecompletionproposal.h" #include "gtksourcecompletionprovider.h" #include "gtksourcefileloader.h" #include "gtksourcefilesaver.h" #include "gtksourcegutterrenderer.h" #include "gtksourcegutterrendererpixbuf.h" #include "gtksourcegutterrenderertext.h" #include "gtksourceinit.h" #include "gtksourcelanguagemanager-private.h" #include "gtksourcemap.h" #include "gtksourcesnippetmanager-private.h" #include "gtksourcestyleschemechooser.h" #include "gtksourcestyleschemechooserbutton.h" #include "gtksourcestyleschemechooserwidget.h" #include "gtksourcestyleschememanager-private.h" #include "gtksourcestyleschememanager-private.h" #include "gtksourcestyleschemepreview.h" #include "gtksourceview.h" #include "gtksourcevimimcontext.h" #ifdef G_OS_WIN32 #define WIN32_LEAN_AND_MEAN #include static HMODULE gtk_source_dll; BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: gtk_source_dll = hinstDLL; break; default: /* do nothing */ break; } return TRUE; } #endif /* G_OS_WIN32 */ #ifdef OS_OSX #include static gchar * dirs_os_x_get_bundle_resource_dir (void) { NSAutoreleasePool *pool; gchar *str = NULL; NSString *path; pool = [[NSAutoreleasePool alloc] init]; if ([[NSBundle mainBundle] bundleIdentifier] == nil) { [pool release]; return NULL; } path = [[NSBundle mainBundle] resourcePath]; if (!path) { [pool release]; return NULL; } str = g_strdup ([path UTF8String]); [pool release]; return str; } static gchar * dirs_os_x_get_locale_dir (void) { gchar *res_dir; gchar *ret; res_dir = dirs_os_x_get_bundle_resource_dir (); if (res_dir == NULL) { ret = g_build_filename (DATADIR, "locale", NULL); } else { ret = g_build_filename (res_dir, "share", "locale", NULL); g_free (res_dir); } return ret; } #endif /* OS_OSX */ static gchar * get_locale_dir (void) { gchar *locale_dir; #if defined (G_OS_WIN32) gchar *win32_dir; win32_dir = g_win32_get_package_installation_directory_of_module (gtk_source_dll); locale_dir = g_build_filename (win32_dir, "share", "locale", NULL); g_free (win32_dir); #elif defined (OS_OSX) locale_dir = dirs_os_x_get_locale_dir (); #else locale_dir = g_build_filename (DATADIR, "locale", NULL); #endif return locale_dir; } /** * gtk_source_init: * * Initializes the GtkSourceView library (e.g. for the internationalization). * * This function can be called several times, but is meant to be called at the * beginning of main(), before any other GtkSourceView function call. */ void gtk_source_init (void) { static gboolean done = FALSE; if (!done) { GdkDisplay *display; gchar *locale_dir; locale_dir = get_locale_dir (); bindtextdomain (GETTEXT_PACKAGE, locale_dir); g_free (locale_dir); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); /* Due to potential deadlocks when registering types, we need to * ensure the dependent private class GtkSourceBufferOutputStream * and GtkSourceBufferInputStream have been registered up front. * * See https://bugzilla.gnome.org/show_bug.cgi?id=780216 */ g_type_ensure (GTK_SOURCE_TYPE_BUFFER); g_type_ensure (GTK_SOURCE_TYPE_BUFFER_INPUT_STREAM); g_type_ensure (GTK_SOURCE_TYPE_BUFFER_OUTPUT_STREAM); g_type_ensure (GTK_SOURCE_TYPE_COMPLETION); g_type_ensure (GTK_SOURCE_TYPE_COMPLETION_CONTEXT); g_type_ensure (GTK_SOURCE_TYPE_COMPLETION_PROVIDER); g_type_ensure (GTK_SOURCE_TYPE_COMPLETION_PROPOSAL); g_type_ensure (GTK_SOURCE_TYPE_FILE_LOADER); g_type_ensure (GTK_SOURCE_TYPE_FILE_SAVER); g_type_ensure (GTK_SOURCE_TYPE_GUTTER_RENDERER); g_type_ensure (GTK_SOURCE_TYPE_GUTTER_RENDERER_TEXT); g_type_ensure (GTK_SOURCE_TYPE_GUTTER_RENDERER_PIXBUF); g_type_ensure (GTK_SOURCE_TYPE_MAP); g_type_ensure (GTK_SOURCE_TYPE_STYLE_SCHEME_CHOOSER); g_type_ensure (GTK_SOURCE_TYPE_STYLE_SCHEME_CHOOSER_BUTTON); g_type_ensure (GTK_SOURCE_TYPE_STYLE_SCHEME_CHOOSER_WIDGET); g_type_ensure (GTK_SOURCE_TYPE_STYLE_SCHEME_PREVIEW); g_type_ensure (GTK_SOURCE_TYPE_VIEW); g_type_ensure (GTK_SOURCE_TYPE_VIM_IM_CONTEXT); display = gdk_display_get_default (); if (display != NULL) { GtkCssProvider *css_provider; GtkIconTheme *icon_theme; /* Setup default CSS styling for widgetry */ css_provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (css_provider, "/org/gnome/gtksourceview/css/GtkSourceView.css"); gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION-1); g_clear_object (&css_provider); /* We need an additional provider that ensures that the application cannot set * a background for "textview text" which would end up drawing the background * twice for textview, drawing over our right-margin. See gtksourceview.c for * details on why we draw the right-margin from gtk_source_view_snapshot(). */ css_provider = gtk_css_provider_new (); gtk_css_provider_load_from_data (css_provider, "textview.GtkSourceView text {background: transparent;}\n" "textview.GtkSourceMap text {background: transparent;}\n", -1); gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (css_provider), G_MAXINT); g_clear_object (&css_provider); /* Add path to internal scalable icons */ icon_theme = gtk_icon_theme_get_for_display (display); gtk_icon_theme_add_search_path (icon_theme, HICOLORDIR); } done = TRUE; } } /** * gtk_source_finalize: * * Free the resources allocated by GtkSourceView. For example it unrefs the * singleton objects. * * It is not mandatory to call this function, it's just to be friendlier to * memory debugging tools. This function is meant to be called at the end of * main(). It can be called several times. */ /* Another way is to use a DSO destructor, see gconstructor.h in GLib. * * The advantage of calling gtk_source_finalize() at the end of main() is that * gobject-list [1] correctly reports that all GtkSource* objects have been * finalized when quitting the application. On the other hand a DSO destructor * runs after the gobject-list's last output, so it's much less convenient, see: * commit e761de9c2bee90c232875bbc41e6e73e1f63e145 * * [1] A tool for debugging the lifetime of GObjects: * https://github.com/danni/gobject-list */ void gtk_source_finalize (void) { static gboolean done = FALSE; /* Unref the singletons only once, even if this function is called * multiple times, to see if a reference is not released correctly. * Normally the singleton have a ref count of 1. If for some reason the * ref count is increased somewhere, it needs to be decreased * accordingly, at the right place. */ if (!done) { GtkSourceLanguageManager *language_manager; GtkSourceStyleSchemeManager *style_scheme_manager; GtkSourceSnippetManager *snippet_manager; g_resources_register (gtksourceview_get_resource ()); language_manager = _gtk_source_language_manager_peek_default (); g_clear_object (&language_manager); style_scheme_manager = _gtk_source_style_scheme_manager_peek_default (); g_clear_object (&style_scheme_manager); snippet_manager = _gtk_source_snippet_manager_peek_default (); g_clear_object (&snippet_manager); done = TRUE; } } 07070100000179000081A40000000000000000000000016659080600000476000000000000000000000000000000000000003300000000gtksourceview-5.12.1/gtksourceview/gtksourceinit.h/* * This file is part of GtkSourceView * * Copyright 2017 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION) #error "Only can be included directly." #endif #include #include "gtksourceversion.h" G_BEGIN_DECLS GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_init (void); GTK_SOURCE_AVAILABLE_IN_ALL void gtk_source_finalize (void); G_END_DECLS 0707010000017A000081A40000000000000000000000016659080600000E02000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/gtksourceview/gtksourceiter-private.h/* * This file is part of GtkSourceView * * Copyright 2014, 2016 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #pragma once #include #include "gtksourcetypes-private.h" G_BEGIN_DECLS /* Semi-public functions. */ GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_forward_visible_word_end (GtkTextIter *iter); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_forward_visible_word_ends (GtkTextIter *iter, gint count); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_backward_visible_word_start (GtkTextIter *iter); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_backward_visible_word_starts (GtkTextIter *iter, gint count); GTK_SOURCE_INTERNAL void _gtk_source_iter_extend_selection_word (const GtkTextIter *location, GtkTextIter *start, GtkTextIter *end); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_starts_extra_natural_word (const GtkTextIter *iter, gboolean visible); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_ends_extra_natural_word (const GtkTextIter *iter, gboolean visible); GTK_SOURCE_INTERNAL void _gtk_source_iter_get_leading_spaces_end_boundary (const GtkTextIter *iter, GtkTextIter *leading_end); GTK_SOURCE_INTERNAL void _gtk_source_iter_get_trailing_spaces_start_boundary (const GtkTextIter *iter, GtkTextIter *trailing_start); GTK_SOURCE_INTERNAL void _gtk_source_iter_forward_full_word_end (GtkTextIter *iter); GTK_SOURCE_INTERNAL void _gtk_source_iter_backward_full_word_start (GtkTextIter *iter); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_starts_full_word (const GtkTextIter *iter); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_ends_full_word (const GtkTextIter *iter); GTK_SOURCE_INTERNAL void _gtk_source_iter_forward_extra_natural_word_end (GtkTextIter *iter); GTK_SOURCE_INTERNAL void _gtk_source_iter_backward_extra_natural_word_start (GtkTextIter *iter); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_starts_word (const GtkTextIter *iter); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_ends_word (const GtkTextIter *iter); GTK_SOURCE_INTERNAL gboolean _gtk_source_iter_inside_word (const GtkTextIter *iter); G_END_DECLS 0707010000017B000081A40000000000000000000000016659080600004111000000000000000000000000000000000000003300000000gtksourceview-5.12.1/gtksourceview/gtksourceiter.c/* * This file is part of GtkSourceView * * Copyright 2014, 2016 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include "config.h" #include "gtksourceiter-private.h" /* GtkTextIter functions. Contains forward/backward functions for word * movements, with custom word boundaries that are used for word selection * (double-click) and cursor movements (Ctrl+left, Ctrl+right, etc). The * initial idea was to use those word boundaries directly in GTK, for all text * widgets. But in the end only the GtkTextView::extend-selection signal has * been added to be able to customize the boundaries for double- and * triple-click (the ::move-cursor and ::delete-from-cursor signals were already * present to customize boundaries for cursor movements). The GTK developers * didn't want to change the word boundaries for text widgets. More information: * https://mail.gnome.org/archives/gtk-devel-list/2014-September/msg00019.html * https://bugzilla.gnome.org/show_bug.cgi?id=111503 */ /* TODO: the following Pango bug is now mostly done, see if the code here can be * simplified. * https://bugzilla.gnome.org/show_bug.cgi?id=97545 * "Make pango_default_break follow Unicode TR #29" */ /* Go to the end of the next or current "full word". A full word is a group of * non-blank chars. * In other words, this function is the same as the 'E' Vim command. * * Examples ('|' is the iter position): * "|---- abcd" -> "----| abcd" * "| ---- abcd" -> " ----| abcd" * "--|-- abcd" -> "----| abcd" * "---- a|bcd" -> "---- abcd|" */ void _gtk_source_iter_forward_full_word_end (GtkTextIter *iter) { GtkTextIter pos; gboolean non_blank_found = FALSE; /* It would be better to use gtk_text_iter_forward_visible_char(), but * it doesn't exist. So move by cursor position instead, it should be * equivalent here. */ pos = *iter; while (g_unichar_isspace (gtk_text_iter_get_char (&pos))) { if (!gtk_text_iter_forward_visible_cursor_position (&pos)) { break; } } while (!gtk_text_iter_is_end (&pos) && !g_unichar_isspace (gtk_text_iter_get_char (&pos))) { non_blank_found = TRUE; if (!gtk_text_iter_forward_visible_cursor_position (&pos)) { break; } } if (non_blank_found) { *iter = pos; } } /* Symmetric of iter_forward_full_word_end(). */ void _gtk_source_iter_backward_full_word_start (GtkTextIter *iter) { GtkTextIter pos; GtkTextIter prev; gboolean non_blank_found = FALSE; pos = *iter; while (!gtk_text_iter_is_start (&pos)) { prev = pos; if (!gtk_text_iter_backward_visible_cursor_position (&prev)) { break; } if (!g_unichar_isspace (gtk_text_iter_get_char (&prev))) { break; } pos = prev; } while (!gtk_text_iter_is_start (&pos)) { prev = pos; if (!gtk_text_iter_backward_visible_cursor_position (&prev)) { break; } if (g_unichar_isspace (gtk_text_iter_get_char (&prev))) { break; } non_blank_found = TRUE; pos = prev; } if (non_blank_found) { *iter = pos; } } gboolean _gtk_source_iter_starts_full_word (const GtkTextIter *iter) { GtkTextIter prev = *iter; if (gtk_text_iter_is_end (iter)) { return FALSE; } if (!gtk_text_iter_backward_visible_cursor_position (&prev)) { return !g_unichar_isspace (gtk_text_iter_get_char (iter)); } return (g_unichar_isspace (gtk_text_iter_get_char (&prev)) && !g_unichar_isspace (gtk_text_iter_get_char (iter))); } gboolean _gtk_source_iter_ends_full_word (const GtkTextIter *iter) { GtkTextIter prev = *iter; if (!gtk_text_iter_backward_visible_cursor_position (&prev)) { return FALSE; } return (!g_unichar_isspace (gtk_text_iter_get_char (&prev)) && (gtk_text_iter_is_end (iter) || g_unichar_isspace (gtk_text_iter_get_char (iter)))); } /* Extends the definition of a natural-language word used by Pango. The * underscore is added to the possible characters of a natural-language word. */ void _gtk_source_iter_forward_extra_natural_word_end (GtkTextIter *iter) { GtkTextIter next_word_end = *iter; GtkTextIter next_underscore_end = *iter; GtkTextIter *limit = NULL; gboolean found; if (gtk_text_iter_forward_visible_word_end (&next_word_end)) { limit = &next_word_end; } found = gtk_text_iter_forward_search (iter, "_", GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY, NULL, &next_underscore_end, limit); if (found) { *iter = next_underscore_end; } else { *iter = next_word_end; } while (TRUE) { if (gtk_text_iter_get_char (iter) == '_') { if (!gtk_text_iter_forward_visible_cursor_position (iter)) { break; } } else if (gtk_text_iter_starts_word (iter)) { if (!gtk_text_iter_forward_visible_word_end (iter)) { break; } } else { break; } } } /* Symmetric of iter_forward_extra_natural_word_end(). */ void _gtk_source_iter_backward_extra_natural_word_start (GtkTextIter *iter) { GtkTextIter prev_word_start = *iter; GtkTextIter prev_underscore_start = *iter; GtkTextIter *limit = NULL; gboolean found; if (gtk_text_iter_backward_visible_word_start (&prev_word_start)) { limit = &prev_word_start; } found = gtk_text_iter_backward_search (iter, "_", GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY, &prev_underscore_start, NULL, limit); if (found) { *iter = prev_underscore_start; } else { *iter = prev_word_start; } while (!gtk_text_iter_is_start (iter)) { GtkTextIter prev = *iter; if (!gtk_text_iter_backward_visible_cursor_position (&prev)) { break; } if (gtk_text_iter_get_char (&prev) == '_') { *iter = prev; } else if (gtk_text_iter_ends_word (iter)) { if (!gtk_text_iter_backward_visible_word_start (iter)) { break; } } else { break; } } } static gboolean backward_cursor_position (GtkTextIter *iter, gboolean visible) { if (visible) { return gtk_text_iter_backward_visible_cursor_position (iter); } return gtk_text_iter_backward_cursor_position (iter); } gboolean _gtk_source_iter_starts_extra_natural_word (const GtkTextIter *iter, gboolean visible) { gboolean starts_word; GtkTextIter prev; starts_word = gtk_text_iter_starts_word (iter); prev = *iter; if (!backward_cursor_position (&prev, visible)) { return starts_word || gtk_text_iter_get_char (iter) == '_'; } if (starts_word) { return gtk_text_iter_get_char (&prev) != '_'; } return (gtk_text_iter_get_char (iter) == '_' && gtk_text_iter_get_char (&prev) != '_' && !gtk_text_iter_ends_word (iter)); } gboolean _gtk_source_iter_ends_extra_natural_word (const GtkTextIter *iter, gboolean visible) { GtkTextIter prev; gboolean ends_word; prev = *iter; if (!backward_cursor_position (&prev, visible)) { return FALSE; } ends_word = gtk_text_iter_ends_word (iter); if (gtk_text_iter_is_end (iter)) { return ends_word || gtk_text_iter_get_char (&prev) == '_'; } if (ends_word) { return gtk_text_iter_get_char (iter) != '_'; } return (gtk_text_iter_get_char (&prev) == '_' && gtk_text_iter_get_char (iter) != '_' && !gtk_text_iter_starts_word (iter)); } /* Similar to gtk_text_iter_forward_visible_word_end, but with a custom * definition of "word". * * It is normally the same word boundaries as in Vim. This function is the same * as the 'e' command. * * With the custom word definition, a word can be: * - a natural-language word as defined by Pango, plus the underscore. The * underscore is added because it is often used in programming languages. * - a group of contiguous non-blank characters. */ gboolean _gtk_source_iter_forward_visible_word_end (GtkTextIter *iter) { GtkTextIter orig = *iter; GtkTextIter farthest = *iter; GtkTextIter next_word_end = *iter; GtkTextIter word_start; /* 'farthest' is the farthest position that this function can return. Example: * "|---- aaaa" -> "----| aaaa" */ _gtk_source_iter_forward_full_word_end (&farthest); /* Go to the next extra-natural word end. It can be farther than * 'farthest': * "|---- aaaa" -> "---- aaaa|" * * Or it can remain at the same place: * "aaaa| ----" -> "aaaa| ----" */ _gtk_source_iter_forward_extra_natural_word_end (&next_word_end); if (gtk_text_iter_compare (&farthest, &next_word_end) < 0 || gtk_text_iter_equal (iter, &next_word_end)) { *iter = farthest; goto end; } /* From 'next_word_end', go to the previous extra-natural word start. * * Example 1: * iter: "ab|cd" * next_word_end: "abcd|" -> the good one * word_start: "|abcd" * * Example 2: * iter: "| abcd()" * next_word_end: " abcd|()" -> the good one * word_start: " |abcd()" * * Example 3: * iter: "abcd|()efgh" * next_word_end: "abcd()efgh|" * word_start: "abcd()|efgh" -> the good one, at the end of the word "()". */ word_start = next_word_end; _gtk_source_iter_backward_extra_natural_word_start (&word_start); /* Example 1 */ if (gtk_text_iter_compare (&word_start, iter) <= 0) { *iter = next_word_end; } /* Example 2 */ else if (_gtk_source_iter_starts_full_word (&word_start)) { *iter = next_word_end; } /* Example 3 */ else { *iter = word_start; } end: return !gtk_text_iter_equal (&orig, iter) && !gtk_text_iter_is_end (iter); } /* Symmetric of _gtk_source_iter_forward_visible_word_end(). */ gboolean _gtk_source_iter_backward_visible_word_start (GtkTextIter *iter) { GtkTextIter orig = *iter; GtkTextIter farthest = *iter; GtkTextIter prev_word_start = *iter; GtkTextIter word_end; /* 'farthest' is the farthest position that this function can return. Example: * "aaaa ----|" -> "aaaa |----" */ _gtk_source_iter_backward_full_word_start (&farthest); /* Go to the previous extra-natural word start. It can be farther than * 'farthest': * "aaaa ----|" -> "|aaaa ----" * * Or it can remain at the same place: * "---- |aaaa" -> "---- |aaaa" */ _gtk_source_iter_backward_extra_natural_word_start (&prev_word_start); if (gtk_text_iter_compare (&prev_word_start, &farthest) < 0 || gtk_text_iter_equal (iter, &prev_word_start)) { *iter = farthest; goto end; } /* From 'prev_word_start', go to the next extra-natural word end. * * Example 1: * iter: "ab|cd" * prev_word_start: "|abcd" -> the good one * word_end: "abcd|" * * Example 2: * iter: "()abcd |" * prev_word_start: "()|abcd " -> the good one * word_end: "()abcd| " * * Example 3: * iter: "abcd()|" * prev_word_start: "|abcd()" * word_end: "abcd|()" -> the good one, at the start of the word "()". */ word_end = prev_word_start; _gtk_source_iter_forward_extra_natural_word_end (&word_end); /* Example 1 */ if (gtk_text_iter_compare (iter, &word_end) <= 0) { *iter = prev_word_start; } /* Example 2 */ else if (_gtk_source_iter_ends_full_word (&word_end)) { *iter = prev_word_start; } /* Example 3 */ else { *iter = word_end; } end: return !gtk_text_iter_equal (&orig, iter) && !gtk_text_iter_is_end (iter); } /* Similar to gtk_text_iter_forward_visible_word_ends(). */ gboolean _gtk_source_iter_forward_visible_word_ends (GtkTextIter *iter, gint count) { GtkTextIter orig = *iter; gint i; if (count < 0) { return _gtk_source_iter_backward_visible_word_starts (iter, -count); } for (i = 0; i < count; i++) { if (!_gtk_source_iter_forward_visible_word_end (iter)) { break; } } return !gtk_text_iter_equal (&orig, iter) && !gtk_text_iter_is_end (iter); } /* Similar to gtk_text_iter_backward_visible_word_starts(). */ gboolean _gtk_source_iter_backward_visible_word_starts (GtkTextIter *iter, gint count) { GtkTextIter orig = *iter; gint i; if (count < 0) { return _gtk_source_iter_forward_visible_word_ends (iter, -count); } for (i = 0; i < count; i++) { if (!_gtk_source_iter_backward_visible_word_start (iter)) { break; } } return !gtk_text_iter_equal (&orig, iter) && !gtk_text_iter_is_end (iter); } gboolean _gtk_source_iter_starts_word (const GtkTextIter *iter) { if (_gtk_source_iter_starts_full_word (iter) || _gtk_source_iter_starts_extra_natural_word (iter, TRUE)) { return TRUE; } /* Example: "abcd|()", at the start of the word "()". */ return (!_gtk_source_iter_ends_full_word (iter) && _gtk_source_iter_ends_extra_natural_word (iter, TRUE)); } gboolean _gtk_source_iter_ends_word (const GtkTextIter *iter) { if (_gtk_source_iter_ends_full_word (iter) || _gtk_source_iter_ends_extra_natural_word (iter, TRUE)) { return TRUE; } /* Example: "abcd()|efgh", at the end of the word "()". */ return (!_gtk_source_iter_starts_full_word (iter) && _gtk_source_iter_starts_extra_natural_word (iter, TRUE)); } gboolean _gtk_source_iter_inside_word (const GtkTextIter *iter) { GtkTextIter prev_word_start; GtkTextIter word_end; if (_gtk_source_iter_starts_word (iter)) { return TRUE; } prev_word_start = *iter; if (!_gtk_source_iter_backward_visible_word_start (&prev_word_start)) { return FALSE; } word_end = prev_word_start; _gtk_source_iter_forward_visible_word_end (&word_end); return (gtk_text_iter_compare (&prev_word_start, iter) <= 0 && gtk_text_iter_compare (iter, &word_end) < 0); } /* Used for the GtkTextView::extend-selection signal. */ void _gtk_source_iter_extend_selection_word (const GtkTextIter *location, GtkTextIter *start, GtkTextIter *end) { /* Exactly the same algorithm as in GTK, but with our custom word * boundaries. */ *start = *location; *end = *location; if (_gtk_source_iter_inside_word (start)) { if (!_gtk_source_iter_starts_word (start)) { _gtk_source_iter_backward_visible_word_start (start); } if (!_gtk_source_iter_ends_word (end)) { _gtk_source_iter_forward_visible_word_end (end); } } else { GtkTextIter tmp; tmp = *start; if (_gtk_source_iter_backward_visible_word_start (&tmp)) { _gtk_source_iter_forward_visible_word_end (&tmp); } if (gtk_text_iter_get_line (&tmp) == gtk_text_iter_get_line (start)) { *start = tmp; } else { gtk_text_iter_set_line_offset (start, 0); } tmp = *end; if (!_gtk_source_iter_forward_visible_word_end (&tmp)) { gtk_text_iter_forward_to_end (&tmp); } if (_gtk_source_iter_ends_word (&tmp)) { _gtk_source_iter_backward_visible_word_start (&tmp); } if (gtk_text_iter_get_line (&tmp) == gtk_text_iter_get_line (end)) { *end = tmp; } else { gtk_text_iter_forward_to_line_end (end); } } } /* Get the boundary, on @iter's line, between leading spaces (indentation) and * the text. */ void _gtk_source_iter_get_leading_spaces_end_boundary (const GtkTextIter *iter, GtkTextIter *leading_end) { g_return_if_fail (iter != NULL); g_return_if_fail (leading_end != NULL); *leading_end = *iter; gtk_text_iter_set_line_offset (leading_end, 0); while (!gtk_text_iter_ends_line (leading_end)) { gunichar ch = gtk_text_iter_get_char (leading_end); if (!g_unichar_isspace (ch)) { break; } gtk_text_iter_forward_char (leading_end); } } /* Get the boundary, on @iter's line, between the end of the text and trailing * spaces. */ void _gtk_source_iter_get_trailing_spaces_start_boundary (const GtkTextIter *iter, GtkTextIter *trailing_start) { g_return_if_fail (iter != NULL); g_return_if_fail (trailing_start != NULL); *trailing_start = *iter; if (!gtk_text_iter_ends_line (trailing_start)) { gtk_text_iter_forward_to_line_end (trailing_start); } while (!gtk_text_iter_starts_line (trailing_start)) { GtkTextIter prev; gunichar ch; prev = *trailing_start; gtk_text_iter_backward_char (&prev); ch = gtk_text_iter_get_char (&prev); if (!g_unichar_isspace (ch)) { break; } *trailing_start = prev; } } 0707010000017C000081A4000000000000000000000001665908060000BD7D000000000000000000000000000000000000004000000000gtksourceview-5.12.1/gtksourceview/gtksourcelanguage-parser-2.c/* * Language specification parser for 2.0 version .lang files * This file is part of GtkSourceView * * Copyright 2003 - Gustavo Giráldez * Copyright 2005, 2006 - Emanuele Aina, Marco Barisione * * GtkSourceView 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. * * GtkSourceView 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, see . */ #undef ENABLE_DEBUG #ifdef ENABLE_DEBUG #define DEBUG(x) x #else #define DEBUG(x) #endif #include "config.h" #include "gtksourcebuffer.h" #include "gtksourcelanguage.h" #include "gtksourcelanguage-private.h" #include "gtksourcecontextengine-private.h" #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef G_OS_WIN32 #include #endif #include #define PARSER_ERROR (parser_error_quark ()) #define ATTR_NO_STYLE "" typedef enum _ParserError { 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 (const 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, GSList *context_classes, 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 *attr; for (attr = node->properties; attr != NULL; attr = attr->next) { g_return_val_if_fail (attr->children != NULL, flags); flags = update_regex_flags (flags, attr->name, attr->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 GSList * add_classes (GSList *list, gchar const *classes, gboolean enabled) { gchar **parts; gchar **ptr; GSList *newlist = NULL; if (classes == NULL) { return list; } parts = ptr = g_strsplit (classes, " ", -1); while (*ptr) { GtkSourceContextClass *ctx = gtk_source_context_class_new (*ptr, enabled); newlist = g_slist_prepend (newlist, ctx); ++ptr; } g_strfreev (parts); return g_slist_concat (list, g_slist_reverse (newlist)); } static GSList * parse_classes (ParserState *parser_state) { GSList *ret = NULL; xmlChar *en = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "class"); xmlChar *dis = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "class-disabled"); ret = add_classes (ret, (gchar const *)en, TRUE); ret = add_classes (ret, (gchar const *)dis, FALSE); xmlFree (en); xmlFree (dis); return ret; } static gboolean create_definition (ParserState *parser_state, gchar *id, gchar *parent_id, gchar *style, GSList *context_classes, 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, context_classes, 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 (_gtk_source_language_get_file_name (imported_language), 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", _gtk_source_language_get_file_name (imported_language), _gtk_source_language_get_file_name (parser_state->language), 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, GSList *context_classes, 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, context_classes, &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; GSList *context_classes; 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); } context_classes = parse_classes (parser_state); 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, context_classes, &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, NULL, 0, &tmp_error); else success = create_definition (parser_state, id, parent_id, style_ref, context_classes, &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_slist_free_full (context_classes, (GDestroyNotify)gtk_source_context_class_free); 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; default: 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); } else { g_propagate_error (&parser_state->error, tmp_error); g_free (id); } xmlFree (regex); } 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 (_gtk_source_language_get_file_name (imported_language), 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 type; g_return_if_fail (parser_state->error == NULL); if (parser_state->ctx_data == NULL) return; do { #ifdef G_DISABLE_ASSERT G_GNUC_UNUSED #endif int ret; ret = xmlTextReaderRead (parser_state->reader); 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 ("(?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!

Hi there! ... 07070100000285000081A4000000000000000000000001665908060000020F000000000000000000000000000000000000003D00000000gtksourceview-5.12.1/tests/syntax-highlighting/file.html.erb <%= stylesheet_link_tag 'application.css' %> <% if @current_user %>

Welcome back <%= @current_user.name %>

<% else %> <%# User not logged in so we can't show their name %>

Welcome!

<% end %> 07070100000286000081A40000000000000000000000016659080600000143000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.hx/* Multi line comment * ================== * line 1 * line 2 * */ package; import flash.display.Sprite; class Main extends Sprite { var foo:Int = 400; // single line comment public static function main():Void { @metadata("argument") @:buildMacro var boolean:Bool = false; } } 07070100000287000081A400000000000000000000000166590806000000D5000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.ijm// line comment var variable = "string\n with \t escaped\"characters"; macro "new macro" { NotGlobalVar = 5 +6; result = getPixel(0, 0); run("8-bit"); } function NewFunction() { /* multiline comment*/ } 07070100000288000081A4000000000000000000000001665908060000010D000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.impl-- Opal test IMPLEMENTATION LeapYear IMPORT Nat COMPLETELY DEF leapYear(year) == IF (year % ("400"!) = 0) THEN true IF (year % ("400"!) |= 0) and (year % 100 = 0) THEN false IF (year % 100 |= 0) and (year % 4 = 0) THEN true IF (year % 4 |= 0) THEN false FI 07070100000289000081A400000000000000000000000166590806000000B7000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.ini[module] type=Python file=simple.py version=1.0 [plugin] id=APlugin _name=A Plugin _description=A plugin author=Some Guy ; this is a plugin version, can be anything version=3.1415926 0707010000028A000081A400000000000000000000000166590806000058CE000000000000000000000000000000000000003600000000gtksourceview-5.12.1/tests/syntax-highlighting/file.j/** * Built-in types */ @implementation SomeClass { id i; BOOL i; SEL i; byte i; char i; short i; int i; long i; float i; double i; signed i; unsigned i; signed char i; unsigned long i; unsigned long long i; } + (void)fn { } - (@action)fn { } - (IBAction)fn { } @end /** * Built-in values / functions */ // keywords self; super; _cmd; // values YES; NO; nil; Nil; NULL; // math constants E; LOG10E; LOG2E; LN10; LN2; PI; PI2; PI_2; SQRT1_2; SQRT2; // math functions ABS(); ACOS(); ASIN(); ATAN(); ATAN2(); CEIL(); COS(); EXP(); FLOOR(); LOG(); MAX(); MIN(); POW(); RAND(); ROUND(); SIN(); SQRT(); TAN(); // reference / dereference var a, aRef = @ref(a); @deref(aRef) = 5; console.log(@deref(aRef)); // method reference var a = @selector(setJobTitle:company:); /** * Literals */ // string var a = @"foo"; var a = @'bar'; // array var a = @[1, 2, 3]; // dictionary var a = @{ @"border-color": "#000", @"border-width": 1.0, @"content-margin": CGSizeMakeZero(), }; /** * Messages */ [self length]; [self characterAtIndex:index]; [myPerson setJobTitle:"Founder" company:"Cappuccino Foundation"]; [[self alloc:42] initWithName:aName]; // no semicolon between message calls [self length] [obj arg:self] [[obj arg:self] arg:@selector(foo:)]; /** * Declarations */ /* Forward class declaration */ @class CPString // Forward global variable declaration */ @global CPInvalidArgumentException /* Class declaration */ @implementation Person : CPObject { CPString name; // accessors CPString name @accessors; CPString _nickName @accessors(property=nickName); BOOL _cool @accessors(getter=isCool, setter=setCool:); id foo @accessors(readonly); id bar @accessors(copy); id baz @accessors(readwrite); // outlet @outlet CPButton button; IBOutlet CPCheckBox checkbox; } // static method + (id)personWithName:(CPString)aName { return self; } // instance method - (id)initWithName:(CPString)aName { self = [super init]; name = aName; return self; } - (void)setName:(CPString)aName { name = aName; } - (CPString)name { return name; } @end // categories @implementation CPString (Reversing) - (CPString)reverse { return reversedString; } @end // adopting protocol @implementation Person : CPObject { CPString name; } @end /* Import */ @import @import "MyClass.j" /* Protocol definition */ @protocol Move @required + (void)walk; @optional - (void)run; - (void)jump; @end /* Type definition */ @typedef CPButtonType CPMomentaryLightButton = 0; CPPushOnPushOffButton = 1; CPToggleButton = 2; /** * Preprocessor directives */ #define foo 1 #undef foo { #ifdef foo } function () { #ifndef foo } o = { #if foo == 1 }; @implementation SomeClass #elif defined(foo) { #else id i; } #endif + (void)fn #pragma \ mark foo { #include "foo.js" } @end @protocol Move #warning \ Warning \ message @required + (void)walk; @optional - (void)run; #error Error message - (void)jump; @end // commented out /* #define foo 1 */ // #define foo 1 // from file.js /* * Identifiers */ var example; function example() {} var príklad; function príklad() {} var 例子; function 例子() {} var $jquery; function _lodash() {} var \u0075nicod\u{65}; function \u0075nicod\u{65}() {} /* * Expressions (in expression statements) */ /* * Literals */ /* Keyword values */ var NULL = null; var TRUE = true; var FALSE = false; /* Number */ var decimal1 = 0; var decimal2 = 123.45; var decimal3 = .66667; var decimal4 = 10e20; var decimal5 = 0.2e+1; var decimal6 = .5E-20; var hex1 = 0xDEADBEEF; var hex2 = 0Xcafebabe; // ES2015 binary and octal numbers let binary1 = 0b1010; let binary2 = 0B00001111; let octal1 = 0o0123; let octal2 = 0O4567; // Legacy octal numbers var legacy_octal1 = 01; var legacy_octal2 = 007; // BigInt (ES2020) var decimal1 = 0n; var decimal2 = 123n; var hex1 = 0xDEADBEEFn; var hex2 = 0Xcafebaben; var binary1 = 0b1010n; var binary2 = 0B00001111n; var octal1 = 0o0123n; var octal2 = 0O4567n; /* String */ // Escape sequences '\b\f\n\r\t\v\0\'\"\\'; // Single character escape "\1\01\001"; // Octal escape (Annex B) '\xA9'; // Hexadecimal escape "\u00a9"; // Unicode escape '\u{1D306}'; // Unicode code point escape /* Array literal */ []; [1]; [1.0, 'two', 0x03]; // Trailing comma [ [1,2,3], [4,5,6], ]; // Spread syntax [1, ...a, 2]; /* Object literal */ a = {}; a = { prop: 'value' }; a = { 'prop': 'value', 1: true, .2: 2 }; // Trailing comma a = { prop: 'value', "extends": 1, }; // Shorthand property names a = { b, c, d }; // Getter / setter a = { _hidden: null, get property() { return _hidden; }, set property(value) { this._hidden = value; }, get: 'get', set() { return 'set'; } }; // Incorrectly highlighted as keyword a = { get : 'get', set () { return 'set'; } }; // Shorthand function notation a = { method() {}, *generator() {}, // Async function (ES2017) async method() {}, async /* comment */ method() {}, async get() {}, async() {},// method called "async" async: false, // property called "async" // Async generator (ES2018) async *generator() {} }; // Computed property names a = { ['prop']: 1, ['method']() {} }; // Spread properties (ES2018) a = { ...b, ...getObj('string') }; // Syntax errors a = { prop: 'val': 'val' }; a = { method() {}: 'val' }; a = { get property() {}: 'val' }; a = { *generator: 'val' }; a = { async prop: 'val' }; a = { ...b: 'val' }; a = { ...b() { return 'b'; } }; /* Regular expression literal */ /abc/; x = /abc/gi; function_with_regex_arg(/abc/); [ /abc/m, /def/u ]; a = { regex: /abc/s }; // s (dotAll): ES2018 (1 === 0) ? /abc/ : /def/; /abc/; /* Comment */ /abc/; // Comment var matches = /abc/.exec('Alphabet ... that should contain abc, right?'); // No regex here a = [thing / thing, thing / thing]; x = a /b/ c / d; // Character groups with backslashes /[ab\\]/; // a, b or backslash /[ab\]]/; // a, b or ] /\\[ab]/; // a or b preceded by backslash /\[ab]/; // Literally "[ab]" // Control escape /\cJ/; // Unicode property escape (ES2018) /\p{General_Category=Letter}/u; /\p{Letter}/u; // Named capture groups (ES2018) /(?\d{4})-(?\d{2})-(?\d{2})/u; /(?foo|bar)/u; /^(?.*).\k$/u; // backreference /* Template literal */ console.log(`The sum of 2 and 2 is ${2 + 2}`); let y = 8; let my_string = `This is a multiline string that also contains a template ${y + (4.1 - 2.2)}`; /* * Built-in values */ // global object values Infinity; NaN; undefined; // global object functions decodeURIComponent(); decodeURI(); encodeURIComponent(); encodeURI(); eval(); isFinite(); isNaN(); parseFloat(); parseInt(); // constructors (subset) Array(); BigInt(); // ES2020 Boolean(); Date(); Error(); Function(); Map(); Object(); Promise(); RegExp(); Set(); String(); Symbol(); // objects JSON.parse(); Math.random(); // object keywords arguments; globalThis; // ES2020 super; this; // dynamic import (ES2020) import("module").then(); import /* comment */ ("module").then(); import // comment ("module").then(); a = await import("module"); a = await import /* comment */ ("module"); a = await import // comment ("module"); // import.meta (ES2020) import.meta; import . /* comment */ meta; import // comment .meta; a = import.meta; a = import . /* comment */ meta; a = import // comment .meta; // new.target new.target; new . /* comment */ target; new // comment .target; // properties (subset) array.length; Math.PI; Number.NaN; object.constructor; Class.prototype; Symbol.asyncIterator; // ES2018 Symbol('desc').description; // ES2019 // methods (subset) array.keys(); date.toString(); object.valueOf(); re.test(); array.includes(); // ES2016 Object.values(); // ES2017 Object.entries(); // ES2017 string.padStart(); // ES2017 string.padEnd(); // ES2017 Object.getOwnPropertyDescriptors(); // ES2017 promise.finally(); // ES2018 Object.fromEntries(); // ES2019 string.trimStart(); // ES2019 string.trimEnd(); // ES2019 array.flat(); // ES2019 array.flatMap(); // ES2019 string.matchAll(); // ES2020 Promise.allSettled(); // ES2020 BigInt.asUintN(); // ES2020 string.replaceAll(); // ES2021 /* * Function expression, arrow function */ a = function () { return 1 }; a = function fn() { return; }; a = function fn(x) {}; a = function fn(x, y) {}; // Arrow function x => -x; () => {}; (x, y) => x + y; (x, y) => { return x + y; }; (x, y) => /* comment */ { return x + y; } /* comment */ ; (x) => ({ a: x }); // return object // Default parameters a = function fn(x, y = 1) {}; (x, y = 1) => x + y; // Parameter without default after default parameters a = function fn(x = 1, y) {}; (x = 1, y) => x + y; // Array destructuring a = function fn([x]) {}; a = function fn([x = 5, y = 7]) {}; // default values a = function fn([x, , y]) {}; // ignoring some returned values (elision) a = function fn([x, ...y]) {}; // rest syntax ([x]) => x; ([x = 5, y = 7]) => x + y; // default values ([x, , y]) => x + y; // ignoring some returned values (elision) ([x, ...y]) => y; // rest syntax // Object destructuring a = function fn({ x }) {}; a = function fn({ a: x, b: y }) {}; // assigning to new variable names a = function fn({ x = 5, y = 7 }) {}; // default values a = function fn({ a: x = 5, b: y = 7 }) {}; // assigning to new variable names and default values a = function fn({ ['a']: x, ['b']: y }) {}; // computed property names a = function fn({ x, y, ...rest }) {}; // rest properties (ES2018) ({ x }) => x; ({ a: x, b: y }) => x + y; // assigning to new variable names ({ x = 5, y = 7 }) => x + y; // default values ({ a: x = 5, b: y = 7 }) => x + y; // assigning to new variable names and default values ({ ['a']: x, ['b']: y }) => x + y; // computed property names ({ x, y, ...rest }) => x; // rest properties (ES2018) // Destructuring and default parameters a = function f([x, y] = [1, 2], {c: z} = {c: 3}) {}; ([x, y] = [1, 2], {c: z} = {c: x + y}) => x + y + z; // Generator function a = function*fn() {}; a = function * fn() {}; // Rest parameters a = function fn(...rest) {}; a = function fn(x, y, ...rest) {}; (...rest) => rest; (x, y, ...rest) => rest; // Async function (ES2017) a = async function fn() {}; a = async /* comment */ function fn() {}; a = async /* comment */ function fn() {}; // correctly highlighted, because async cannot be followed by a line terminator async x => x; async () => {}; async /* comment */ () => {}; async /* comment */ () => {}; // correctly highlighted, because async cannot be followed by a line terminator async(); // incorrectly highlighted // Async generator (ES2018) a = async function * fn() {}; // Trailing comma (ES2017) a = function fn(x, y,) {}; (x, y,) => x + y; // Trailing comma after rest parameters (syntax error) a = function fn(x, y, ...rest,) {}; (x, y, ...rest,) => rest; /* * Class expression */ a = class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} }; a = class extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } }; // Incorrectly highlighted as keyword a = class { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } }; // Properties/methods called "constructor" a = class { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } }; // Incorrectly highlighted as built-in method a = class { static constructor() { this._value = null; } }; /* * Operators * use groupings to test, as there can only be one expression (in the * first grouping item) */ // Grouping ( 1 + 2 ); // Increment / decrement ( ++a ); ( --a ); ( a++ ); ( a-- ); // Keyword unary ( await promise() ); // ES2017 ( delete obj.prop ); ( new Array() ); ( void 1 ); ( typeof 'str' ); ( yield 1 ); ( yield* fn() ); // Arithmetic ( 1 + 2 ); ( 1 - 2 ); ( 1 * 2 ); ( 1 / 2 ); ( 1 % 2 ); ( 1 ** 2 ); // ES2016 ( +1 ); ( -1 ); // Keyword relational ( prop in obj ); ( obj instanceof constructor ); // Comparison ( 1 == 2 ); ( 1 != 2 ); ( 1 === 2 ); ( 1 !== 2 ); ( 1 < 2 ); ( 1 > 2 ); ( 1 <= 2 ); ( 1 >= 2 ); // Bitwise ( 1 & 2 ); ( 1 | 2 ); ( 1 ^ 2 ); ( ~1 ); ( 1 << 2 ); ( 1 >> 2 ); ( 1 >>> 2 ); // Logical ( 1 && 2 ); ( 1 || 2 ); ( !1 ); // Nullish coalescing (ES2020) ( a ?? 1 ); // Assignment ( a = 1 ); ( a += 1 ); ( a -= 1 ); ( a *= 1 ); ( a /= 1 ); ( a %= 1 ); ( a **= 1 ); // ES2016 ( a <<= 1 ); ( a >>= 1 ); ( a >>>= 1 ); ( a &= 1 ); ( a |= 1 ); ( a ^= 1 ); // Array destructuring ( [a, b] = [1, 2] ); ( [a = 5, b = 7] = [1] ); // default values ( [a, , b] = f() ); // ignoring some returned values (elision) ( [a, ...b] = [1, 2, 3] ); // rest syntax // Object destructuring ( {a, b} = { a: 1, b: 2} ); ( { a: foo, b: bar } = { a: 1, b: 2 } ); // assigning to new variable names ( { a = 5, b = 7 } = { a: 1 } ); // default values ( { a: foo = 5, b: bar = 7 } = { a: 1 } ); // assigning to new variable names and default values ( { ['a']: foo, ['b']: bar } = { a: 1, b: 2 } ); // computed property names ( { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 } ); // rest properties (ES2018) // Comma 1, 2 ; // Conditional / ternary ( true ? 1 : 2 ); ( true ? : 2 ); // missing true value (syntax error) obj[ true ? 1, 2 : 3 ]; // comma operator inside true expression (syntax error) /* * Property accessors */ // Dot notation arr.length; ( obj . /* comment */ prototype /* comment */ . /* comment */ constructor /* comment */ ); const pi = Math.PI const num = 0 // Bracket notation arr['length']; ( obj /* comment */ [ /* comment */ 'prototype' /* comment */ ] /* comment */ /* comment */ [ /* comment */ 'constructor' /* comment */ ] /* comment */ ); // Mixed obj ['prototype'] . constructor; obj . prototype ['constructor']; /* * Function call */ fn(); obj.fn(1); obj['fn'](1, 2); // Spread syntax fn(x, y, ...args); // Trailing comma (ES2017) fn(x, y,); /* Tagged template */ myTag`That ${ person } is ${ age }`; /* * Optional chaining (ES2020) */ obj?.prototype; obj?.['constructor']; func?.(1, 2, ...args); foo?.3:0; // ternary operator, not optional chaining /* * Statements and declarations */ /* Use strict directive */ "use strict"; function () { 'use strict'; } // invalid directives " use strict"; 'use strict '; "use strict"; 'use strict'; "hello 'use strict' world"; fn("use strict"); { 'use strict'; } /* Block statement */ { hello(); world(); } { hello(); world() } /* Break statement */ break; break label; break // end statement label; // separate statement { break } /* * Class declaration */ class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} } class Foo extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } } // Incorrectly highlighted as keyword class Foo { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } } // Properties/methods called "constructor" class Foo { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } } // Incorrectly highlighted as built-in method class Foo { static constructor() { this._value = null; } } /* Continue statement */ continue; continue label; continue // end statement label; // separate statement { continue } /* Debugger statement */ debugger; debugger ; /* Export / import statement */ export { a }; export { a, b, }; export { x as a }; export { x as a, y as b, }; export var a; export let a, b; export const a = 1; export var a = 1, b = 2; export function fn() {} export function* fn() {} export class Class {} export default 1; export default function () {} export default function *fn() {} export default class {} export { a as default, b }; export * from 'module'; export * as ns from 'module'; export { a, b } from 'module'; export { x as a, y as b, } from 'module'; export { default } from 'module'; import a from "module"; import * as ns from "module"; import { a } from "module"; import { a, b } from "module"; import { x as a } from "module"; import { x as a, y as b } from "module"; import { default as a } from "module"; import a, { b } from "module"; import a, * as ns from "module"; import "module"; /* For statement */ for (i = 0; i < 10; i++) something(); for (var i = 10; i >= 0; i--) { something(); } for (i = 0, j = 0; i < 10; i++, j++) something(); for (let i = 10, j = 0; i >= 0; i--, j += 1) { something(); } for (prop in obj) {} // matches "in" binary operator instead for (const prop in obj) {} for (val of generator()) {} for (var val of array) {} for await (let x of asyncIterable) {} // ES2018 for /* comment */ await /* comment */ (let x of asyncIterable) {} // ES2018 /* Function declaration statement */ function fn() { return; } async function fn() {} // ES2017 async /* comment */ function fn() {} // ES2017 async /* comment */ function fn() {} // correctly highlighted, because async cannot be followed by a line terminator /* If..else statement */ if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) { lessThan(); } else if (a > 0) { greaterThan(); } else { equal(); } /* Label statement */ outer: for (var i = 0; i < 10; i++) { inner /* comment */ : for (var j = 0; j < 2; j++) {} } loop /* comment */ : for (var i in obj) {} // incorrectly highlighted (though it may appear correct) /* Return statement */ return; return 1; return // end statement 1; // separate statement return ( 1 ); { return a } /* Switch statement */ switch (foo) { case 1: doIt(); break; case '2': doSomethingElse(); break; default: oops(); } /* Throw statement */ throw e; throw new Error(); throw // end statement (syntax error) e; // separate statement throw ( new Error() ); { throw new Error() } /* Try...catch statement */ try { somethingDangerous(); } catch (e) { didntWork(e); } catch { // ES2019 return false; } finally { cleanup(); } /* Variable declaration */ // Declaration only const a; let a, b, c; var a , b , c ; // With assignment const a = 1; let a = 1, b = [2, 3], c = 4; var a = 1 , b = [ 2 , 3 ] , c = 4 ; // Array destructuring var [a, b] = [1, 2]; var [ a , b ] = [ 1 , 2 ] ; var [a = 5, b = 7] = [1]; // default values var [a, , b] = f(); // ignoring some returned values (elision) var [a, ...b] = [1, 2, 3]; // rest syntax // Object destructuring var { a, b } = { a: 1, b: 2 }; var { a , b } = { a : 1 , b : 2 } ; var { a: foo, b: bar } = { a: 1, b: 2 }; // assigning to new variable names var { a = 5, b = 7 } = { a: 1 }; // default values var { a: foo = 5, b: bar = 7 } = { a: 1 }; // assigning to new variable names and default values var { ['a']: foo, ['b']: bar } = { a: 1, b: 2 }; // computed property names var { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 }; // rest properties (ES2018) /* While / do...while statement */ while (true) something(); while (1) { something(); } do something(); while (false); do { something(); } while (0); /* With statement */ with (Math) { a = PI * r * r; x = r * cos(PI); y = r * sin(PI / 2); } /* * JSDoc */ /* Inline tag */ /** {@link String} */ /** * {@link http://example.com | Ex\{am\}ple} */ /** {@link*/ /* Type */ /** {String} */ /** * {Foo.B\{a\}r} */ /** {number*/ /* Block tag */ // No arguments /** @constructor */ /** * @deprecated since 1.1.0 */ /** @public*/ // Generic argument /** @default 3.14159 */ /** * @tutorial tutorial-1 */ /** @variation 2*/ // Event name argument /** @fires earthquakeEvent */ /** * @listens touchstart */ /** @event newEvent*/ // Keyword argument /** @access protected */ /** * @kind module */ /** @access private*/ // Name/namepath argument /** @alias foo */ /** * @extends bar */ /** @typeParam T*/ // Type and name arguments /** @param {String} name - A \{chosen\} \@name */ /** * @member {Object} child */ /** @property {number} num*/ // Borrows block tag /** @borrows foo as bar */ /** * @borrows foo as */ /** @borrows foo*/ // Todo block tag /** @todo write more/less test cases */ 0707010000028B000081A4000000000000000000000001665908060000023B000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.jade// Comment - // This is JavaScript - var title = 'jade test' doctype html head title #{title} body h1.title #{title} // This comment will be output //- This comment wont be output //- p This element is in a comment p This element is not in a comment p | The pipe always goes at the beginning of its own line, | not counting indentation. p. Using regular tags can help keep your lines short, but interpolated tags may be easier to #[em visualize] whether the tags and text are whitespace-separated. 0707010000028C000081A400000000000000000000000166590806000005BB000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.java// This file should compile and execute import static java.lang.System.out; interface I { void foo(); String[] bar(boolean okay); } abstract class C implements I { public static void println(String str) { out.println(str); } @Override public void foo() {} } enum E { YEAH } /** Do you document your API? * @param fake Oh no! * @return What? */ record R(int i, double d, char c, Object o, T special) { private static final byte thing = 0; } public final class /* the same as the file name */ file extends C { @Override public String[] bar(boolean okay) { return new String[] { "Float: " + 1f + " or " + 1.e+0f, "Double: " + 1d + " or " + 1.0e-0d, "Long: " + 1L + " or " + 0x1l, "Unsigned: don’t exist in Java", "Escaped chars: \\ \" \101 " + '\141', """ Multiline string: - One - Two - Three""", }; } public static void main(String[] args) { println(E.YEAH.toString()); var me = new file(); me.foo(); for (var wysiwyg: me.bar(false)) { println(wysiwyg); } while (me != null) { me = null; } var what = switch ("hey") { case "" -> ""; default -> { yield "Have fun\u0021"; } }; println(what.toUpperCase()); } } 0707010000028D000081A40000000000000000000000016659080600004BF4000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.js/* * Identifiers */ var example; function example() {} var príklad; function príklad() {} var 例子; function 例子() {} var $jquery; function _lodash() {} var \u0075nicod\u{65}; function \u0075nicod\u{65}() {} /* * Expressions (in expression statements) */ /* * Literals */ /* Keyword values */ var NULL = null; var TRUE = true; var FALSE = false; /* Number */ var decimal1 = 0; var decimal2 = 123.45; var decimal3 = .66667; var decimal4 = 10e20; var decimal5 = 0.2e+1; var decimal6 = .5E-20; var hex1 = 0xDEADBEEF; var hex2 = 0Xcafebabe; // ES2015 binary and octal numbers let binary1 = 0b1010; let binary2 = 0B00001111; let octal1 = 0o0123; let octal2 = 0O4567; // Legacy octal numbers var legacy_octal1 = 01; var legacy_octal2 = 007; // BigInt (ES2020) var decimal1 = 0n; var decimal2 = 123n; var hex1 = 0xDEADBEEFn; var hex2 = 0Xcafebaben; var binary1 = 0b1010n; var binary2 = 0B00001111n; var octal1 = 0o0123n; var octal2 = 0O4567n; /* String */ // Escape sequences '\b\f\n\r\t\v\0\'\"\\'; // Single character escape "\1\01\001"; // Octal escape (Annex B) '\xA9'; // Hexadecimal escape "\u00a9"; // Unicode escape '\u{1D306}'; // Unicode code point escape /* Array literal */ []; [1]; [1.0, 'two', 0x03]; // Trailing comma [ [1,2,3], [4,5,6], ]; // Spread syntax [1, ...a, 2]; /* Object literal */ a = {}; a = { prop: 'value' }; a = { 'prop': 'value', 1: true, .2: 2 }; // Trailing comma a = { prop: 'value', "extends": 1, }; // Shorthand property names a = { b, c, d }; // Getter / setter a = { _hidden: null, get property() { return _hidden; }, set property(value) { this._hidden = value; }, get: 'get', set() { return 'set'; } }; // Incorrectly highlighted as keyword a = { get : 'get', set () { return 'set'; } }; // Shorthand function notation a = { method() {}, *generator() {}, // Async function (ES2017) async method() {}, async /* comment */ method() {}, async get() {}, async() {},// method called "async" async: false, // property called "async" // Async generator (ES2018) async *generator() {} }; // Computed property names a = { ['prop']: 1, ['method']() {} }; // Spread properties (ES2018) a = { ...b, ...getObj('string') }; // Syntax errors a = { prop: 'val': 'val' }; a = { method() {}: 'val' }; a = { get property() {}: 'val' }; a = { *generator: 'val' }; a = { async prop: 'val' }; a = { ...b: 'val' }; a = { ...b() { return 'b'; } }; /* Regular expression literal */ /abc/; x = /abc/gi; function_with_regex_arg(/abc/); [ /abc/m, /def/u ]; a = { regex: /abc/s }; // s (dotAll): ES2018 (1 === 0) ? /abc/ : /def/; /abc/; /* Comment */ /abc/; // Comment var matches = /abc/.exec('Alphabet ... that should contain abc, right?'); // No regex here a = [thing / thing, thing / thing]; x = a /b/ c / d; // Character groups with backslashes /[ab\\]/; // a, b or backslash /[ab\]]/; // a, b or ] /\\[ab]/; // a or b preceded by backslash /\[ab]/; // Literally "[ab]" // Control escape /\cJ/; // Unicode property escape (ES2018) /\p{General_Category=Letter}/u; /\p{Letter}/u; // Named capture groups (ES2018) /(?\d{4})-(?\d{2})-(?\d{2})/u; /(?foo|bar)/u; /^(?.*).\k$/u; // backreference /* Template literal */ console.log(`The sum of 2 and 2 is ${2 + 2}`); let y = 8; let my_string = `This is a multiline string that also contains a template ${y + (4.1 - 2.2)}`; /* * Built-in values */ // global object values Infinity; NaN; undefined; // global object functions decodeURIComponent(); decodeURI(); encodeURIComponent(); encodeURI(); eval(); isFinite(); isNaN(); parseFloat(); parseInt(); // constructors (subset) Array(); BigInt(); // ES2020 Boolean(); Date(); Error(); Function(); Map(); Object(); Promise(); RegExp(); Set(); String(); Symbol(); // objects JSON.parse(); Math.random(); // object keywords arguments; globalThis; // ES2020 super; this; // dynamic import (ES2020) import("module").then(); import /* comment */ ("module").then(); import // comment ("module").then(); a = await import("module"); a = await import /* comment */ ("module"); a = await import // comment ("module"); // import.meta (ES2020) import.meta; import . /* comment */ meta; import // comment .meta; a = import.meta; a = import . /* comment */ meta; a = import // comment .meta; // new.target new.target; new . /* comment */ target; new // comment .target; // properties (subset) array.length; Math.PI; Number.NaN; object.constructor; Class.prototype; Symbol.asyncIterator; // ES2018 Symbol('desc').description; // ES2019 // methods (subset) array.keys(); date.toString(); object.valueOf(); re.test(); array.includes(); // ES2016 Object.values(); // ES2017 Object.entries(); // ES2017 string.padStart(); // ES2017 string.padEnd(); // ES2017 Object.getOwnPropertyDescriptors(); // ES2017 promise.finally(); // ES2018 Object.fromEntries(); // ES2019 string.trimStart(); // ES2019 string.trimEnd(); // ES2019 array.flat(); // ES2019 array.flatMap(); // ES2019 string.matchAll(); // ES2020 Promise.allSettled(); // ES2020 BigInt.asUintN(); // ES2020 string.replaceAll(); // ES2021 /* * Function expression, arrow function */ a = function () { return 1 }; a = function fn() { return; }; a = function fn(x) {}; a = function fn(x, y) {}; // Arrow function x => -x; () => {}; (x, y) => x + y; (x, y) => { return x + y; }; (x, y) => /* comment */ { return x + y; } /* comment */ ; (x) => ({ a: x }); // return object // Default parameters a = function fn(x, y = 1) {}; (x, y = 1) => x + y; // Parameter without default after default parameters a = function fn(x = 1, y) {}; (x = 1, y) => x + y; // Array destructuring a = function fn([x]) {}; a = function fn([x = 5, y = 7]) {}; // default values a = function fn([x, , y]) {}; // ignoring some returned values (elision) a = function fn([x, ...y]) {}; // rest syntax ([x]) => x; ([x = 5, y = 7]) => x + y; // default values ([x, , y]) => x + y; // ignoring some returned values (elision) ([x, ...y]) => y; // rest syntax // Object destructuring a = function fn({ x }) {}; a = function fn({ a: x, b: y }) {}; // assigning to new variable names a = function fn({ x = 5, y = 7 }) {}; // default values a = function fn({ a: x = 5, b: y = 7 }) {}; // assigning to new variable names and default values a = function fn({ ['a']: x, ['b']: y }) {}; // computed property names a = function fn({ x, y, ...rest }) {}; // rest properties (ES2018) ({ x }) => x; ({ a: x, b: y }) => x + y; // assigning to new variable names ({ x = 5, y = 7 }) => x + y; // default values ({ a: x = 5, b: y = 7 }) => x + y; // assigning to new variable names and default values ({ ['a']: x, ['b']: y }) => x + y; // computed property names ({ x, y, ...rest }) => x; // rest properties (ES2018) // Destructuring and default parameters a = function f([x, y] = [1, 2], {c: z} = {c: 3}) {}; ([x, y] = [1, 2], {c: z} = {c: x + y}) => x + y + z; // Generator function a = function*fn() {}; a = function * fn() {}; // Rest parameters a = function fn(...rest) {}; a = function fn(x, y, ...rest) {}; (...rest) => rest; (x, y, ...rest) => rest; // Async function (ES2017) a = async function fn() {}; a = async /* comment */ function fn() {}; a = async /* comment */ function fn() {}; // correctly highlighted, because async cannot be followed by a line terminator async x => x; async () => {}; async /* comment */ () => {}; async /* comment */ () => {}; // correctly highlighted, because async cannot be followed by a line terminator async(); // incorrectly highlighted // Async generator (ES2018) a = async function * fn() {}; // Trailing comma (ES2017) a = function fn(x, y,) {}; (x, y,) => x + y; // Trailing comma after rest parameters (syntax error) a = function fn(x, y, ...rest,) {}; (x, y, ...rest,) => rest; /* * Class expression */ a = class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} }; a = class extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } }; // Incorrectly highlighted as keyword a = class { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } }; // Properties/methods called "constructor" a = class { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } }; // Incorrectly highlighted as built-in method a = class { static constructor() { this._value = null; } }; /* * Operators * use groupings to test, as there can only be one expression (in the * first grouping item) */ // Grouping ( 1 + 2 ); // Increment / decrement ( ++a ); ( --a ); ( a++ ); ( a-- ); // Keyword unary ( await promise() ); // ES2017 ( delete obj.prop ); ( new Array() ); ( void 1 ); ( typeof 'str' ); ( yield 1 ); ( yield* fn() ); // Arithmetic ( 1 + 2 ); ( 1 - 2 ); ( 1 * 2 ); ( 1 / 2 ); ( 1 % 2 ); ( 1 ** 2 ); // ES2016 ( +1 ); ( -1 ); // Keyword relational ( prop in obj ); ( obj instanceof constructor ); // Comparison ( 1 == 2 ); ( 1 != 2 ); ( 1 === 2 ); ( 1 !== 2 ); ( 1 < 2 ); ( 1 > 2 ); ( 1 <= 2 ); ( 1 >= 2 ); // Bitwise ( 1 & 2 ); ( 1 | 2 ); ( 1 ^ 2 ); ( ~1 ); ( 1 << 2 ); ( 1 >> 2 ); ( 1 >>> 2 ); // Logical ( 1 && 2 ); ( 1 || 2 ); ( !1 ); // Nullish coalescing (ES2020) ( a ?? 1 ); // Assignment ( a = 1 ); ( a += 1 ); ( a -= 1 ); ( a *= 1 ); ( a /= 1 ); ( a %= 1 ); ( a **= 1 ); // ES2016 ( a <<= 1 ); ( a >>= 1 ); ( a >>>= 1 ); ( a &= 1 ); ( a |= 1 ); ( a ^= 1 ); // Array destructuring ( [a, b] = [1, 2] ); ( [a = 5, b = 7] = [1] ); // default values ( [a, , b] = f() ); // ignoring some returned values (elision) ( [a, ...b] = [1, 2, 3] ); // rest syntax // Object destructuring ( {a, b} = { a: 1, b: 2} ); ( { a: foo, b: bar } = { a: 1, b: 2 } ); // assigning to new variable names ( { a = 5, b = 7 } = { a: 1 } ); // default values ( { a: foo = 5, b: bar = 7 } = { a: 1 } ); // assigning to new variable names and default values ( { ['a']: foo, ['b']: bar } = { a: 1, b: 2 } ); // computed property names ( { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 } ); // rest properties (ES2018) // Comma 1, 2 ; // Conditional / ternary ( true ? 1 : 2 ); ( true ? : 2 ); // missing true value (syntax error) obj[ true ? 1, 2 : 3 ]; // comma operator inside true expression (syntax error) /* * Property accessors */ // Dot notation arr.length; ( obj . /* comment */ prototype /* comment */ . /* comment */ constructor /* comment */ ); const pi = Math.PI const num = 0 // Bracket notation arr['length']; ( obj /* comment */ [ /* comment */ 'prototype' /* comment */ ] /* comment */ /* comment */ [ /* comment */ 'constructor' /* comment */ ] /* comment */ ); // Mixed obj ['prototype'] . constructor; obj . prototype ['constructor']; /* * Function call */ fn(); obj.fn(1); obj['fn'](1, 2); // Spread syntax fn(x, y, ...args); // Trailing comma (ES2017) fn(x, y,); /* Tagged template */ myTag`That ${ person } is ${ age }`; /* * Optional chaining (ES2020) */ obj?.prototype; obj?.['constructor']; func?.(1, 2, ...args); foo?.3:0; // ternary operator, not optional chaining /* * Statements and declarations */ /* Use strict directive */ "use strict"; function () { 'use strict'; } // invalid directives " use strict"; 'use strict '; "use strict"; 'use strict'; "hello 'use strict' world"; fn("use strict"); { 'use strict'; } /* Block statement */ { hello(); world(); } { hello(); world() } /* Break statement */ break; break label; break // end statement label; // separate statement { break } /* * Class declaration */ class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} } class Foo extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } } // Incorrectly highlighted as keyword class Foo { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } } // Properties/methods called "constructor" class Foo { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } } // Incorrectly highlighted as built-in method class Foo { static constructor() { this._value = null; } } /* Continue statement */ continue; continue label; continue // end statement label; // separate statement { continue } /* Debugger statement */ debugger; debugger ; /* Export / import statement */ export { a }; export { a, b, }; export { x as a }; export { x as a, y as b, }; export var a; export let a, b; export const a = 1; export var a = 1, b = 2; export function fn() {} export function* fn() {} export class Class {} export default 1; export default function () {} export default function *fn() {} export default class {} export { a as default, b }; export * from 'module'; export * as ns from 'module'; export { a, b } from 'module'; export { x as a, y as b, } from 'module'; export { default } from 'module'; import a from "module"; import * as ns from "module"; import { a } from "module"; import { a, b } from "module"; import { x as a } from "module"; import { x as a, y as b } from "module"; import { default as a } from "module"; import a, { b } from "module"; import a, * as ns from "module"; import "module"; /* For statement */ for (i = 0; i < 10; i++) something(); for (var i = 10; i >= 0; i--) { something(); } for (i = 0, j = 0; i < 10; i++, j++) something(); for (let i = 10, j = 0; i >= 0; i--, j += 1) { something(); } for (prop in obj) {} // matches "in" binary operator instead for (const prop in obj) {} for (val of generator()) {} for (var val of array) {} for await (let x of asyncIterable) {} // ES2018 for /* comment */ await /* comment */ (let x of asyncIterable) {} // ES2018 /* Function declaration statement */ function fn() { return; } async function fn() {} // ES2017 async /* comment */ function fn() {} // ES2017 async /* comment */ function fn() {} // correctly highlighted, because async cannot be followed by a line terminator /* If..else statement */ if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) { lessThan(); } else if (a > 0) { greaterThan(); } else { equal(); } /* Label statement */ outer: for (var i = 0; i < 10; i++) { inner /* comment */ : for (var j = 0; j < 2; j++) {} } loop /* comment */ : for (var i in obj) {} // incorrectly highlighted (though it may appear correct) /* Return statement */ return; return 1; return // end statement 1; // separate statement return ( 1 ); { return a } /* Switch statement */ switch (foo) { case 1: doIt(); break; case '2': doSomethingElse(); break; default: oops(); } /* Throw statement */ throw e; throw new Error(); throw // end statement (syntax error) e; // separate statement throw ( new Error() ); { throw new Error() } /* Try...catch statement */ try { somethingDangerous(); } catch (e) { didntWork(e); } catch { // ES2019 return false; } finally { cleanup(); } /* Variable declaration */ // Declaration only const a; let a, b, c; var a , b , c ; // With assignment const a = 1; let a = 1, b = [2, 3], c = 4; var a = 1 , b = [ 2 , 3 ] , c = 4 ; // Array destructuring var [a, b] = [1, 2]; var [ a , b ] = [ 1 , 2 ] ; var [a = 5, b = 7] = [1]; // default values var [a, , b] = f(); // ignoring some returned values (elision) var [a, ...b] = [1, 2, 3]; // rest syntax // Object destructuring var { a, b } = { a: 1, b: 2 }; var { a , b } = { a : 1 , b : 2 } ; var { a: foo, b: bar } = { a: 1, b: 2 }; // assigning to new variable names var { a = 5, b = 7 } = { a: 1 }; // default values var { a: foo = 5, b: bar = 7 } = { a: 1 }; // assigning to new variable names and default values var { ['a']: foo, ['b']: bar } = { a: 1, b: 2 }; // computed property names var { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 }; // rest properties (ES2018) /* While / do...while statement */ while (true) something(); while (1) { something(); } do something(); while (false); do { something(); } while (0); /* With statement */ with (Math) { a = PI * r * r; x = r * cos(PI); y = r * sin(PI / 2); } /* * JSDoc */ /* Inline tag */ /** {@link String} */ /** * {@link http://example.com | Ex\{am\}ple} */ /** {@link*/ /* Type */ /** {String} */ /** * {Foo.B\{a\}r} */ /** {number*/ /* Block tag */ // No arguments /** @constructor */ /** * @deprecated since 1.1.0 */ /** @public*/ // Generic argument /** @default 3.14159 */ /** * @tutorial tutorial-1 */ /** @variation 2*/ // Event name argument /** @fires earthquakeEvent */ /** * @listens touchstart */ /** @event newEvent*/ // Keyword argument /** @access protected */ /** * @kind module */ /** @access private*/ // Name/namepath argument /** @alias foo */ /** * @extends bar */ /** @typeParam T*/ // Type and name arguments /** @param {String} name - A \{chosen\} \@name */ /** * @member {Object} child */ /** @property {number} num*/ // Borrows block tag /** @borrows foo as bar */ /** * @borrows foo as */ /** @borrows foo*/ // Todo block tag /** @todo write more/less test cases */ 0707010000028E000081A40000000000000000000000016659080600000093000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/tests/syntax-highlighting/file.js.erb<% if Time.now.hour < 12 %> window.alert("The hour is <%= Time.now.hour %>") ; <% else %> window.alert("It is now the afternoon") ; <% end %> 0707010000028F000081A40000000000000000000000016659080600000085000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.json{ "hi": -1.1e1, "b": 2, "abc": "hi", "dce": [ -100.0, 299.79e6, 1.2, 2e10, -3, 1, 2, "string" ], "fgh": { "a": 1 } } 07070100000290000081A40000000000000000000000016659080600005051000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.jsx/* * JSX Elements */ // Element name (
); ( ); ( <例子> ); ( <$jquery> ); ( <\u0075nicod\u{65}> ); ( ); ( ); ( ); // Attributes (
); // spread attributes (
); ( ); (
); // Empty element ( ); // Nested elements (
); // Child expression (
{["1", 2, three].join('+')} {...obj}
); // XML character entity / numeric character references (
>/
); // Invalid characters (
greater than > right brace }
); (
valid alternatives: > {">"} } {'}'}
); // Fragment ( <>
); // from file.js /* * Identifiers */ var example; function example() {} var príklad; function príklad() {} var 例子; function 例子() {} var $jquery; function _lodash() {} var \u0075nicod\u{65}; function \u0075nicod\u{65}() {} /* * Expressions (in expression statements) */ /* * Literals */ /* Keyword values */ var NULL = null; var TRUE = true; var FALSE = false; /* Number */ var decimal1 = 0; var decimal2 = 123.45; var decimal3 = .66667; var decimal4 = 10e20; var decimal5 = 0.2e+1; var decimal6 = .5E-20; var hex1 = 0xDEADBEEF; var hex2 = 0Xcafebabe; // ES2015 binary and octal numbers let binary1 = 0b1010; let binary2 = 0B00001111; let octal1 = 0o0123; let octal2 = 0O4567; // Legacy octal numbers var legacy_octal1 = 01; var legacy_octal2 = 007; // BigInt (ES2020) var decimal1 = 0n; var decimal2 = 123n; var hex1 = 0xDEADBEEFn; var hex2 = 0Xcafebaben; var binary1 = 0b1010n; var binary2 = 0B00001111n; var octal1 = 0o0123n; var octal2 = 0O4567n; /* String */ // Escape sequences '\b\f\n\r\t\v\0\'\"\\'; // Single character escape "\1\01\001"; // Octal escape (Annex B) '\xA9'; // Hexadecimal escape "\u00a9"; // Unicode escape '\u{1D306}'; // Unicode code point escape /* Array literal */ []; [1]; [1.0, 'two', 0x03]; // Trailing comma [ [1,2,3], [4,5,6], ]; // Spread syntax [1, ...a, 2]; /* Object literal */ a = {}; a = { prop: 'value' }; a = { 'prop': 'value', 1: true, .2: 2 }; // Trailing comma a = { prop: 'value', "extends": 1, }; // Shorthand property names a = { b, c, d }; // Getter / setter a = { _hidden: null, get property() { return _hidden; }, set property(value) { this._hidden = value; }, get: 'get', set() { return 'set'; } }; // Incorrectly highlighted as keyword a = { get : 'get', set () { return 'set'; } }; // Shorthand function notation a = { method() {}, *generator() {}, // Async function (ES2017) async method() {}, async /* comment */ method() {}, async get() {}, async() {},// method called "async" async: false, // property called "async" // Async generator (ES2018) async *generator() {} }; // Computed property names a = { ['prop']: 1, ['method']() {} }; // Spread properties (ES2018) a = { ...b, ...getObj('string') }; // Syntax errors a = { prop: 'val': 'val' }; a = { method() {}: 'val' }; a = { get property() {}: 'val' }; a = { *generator: 'val' }; a = { async prop: 'val' }; a = { ...b: 'val' }; a = { ...b() { return 'b'; } }; /* Regular expression literal */ /abc/; x = /abc/gi; function_with_regex_arg(/abc/); [ /abc/m, /def/u ]; a = { regex: /abc/s }; // s (dotAll): ES2018 (1 === 0) ? /abc/ : /def/; /abc/; /* Comment */ /abc/; // Comment var matches = /abc/.exec('Alphabet ... that should contain abc, right?'); // No regex here a = [thing / thing, thing / thing]; x = a /b/ c / d; // Character groups with backslashes /[ab\\]/; // a, b or backslash /[ab\]]/; // a, b or ] /\\[ab]/; // a or b preceded by backslash /\[ab]/; // Literally "[ab]" // Control escape /\cJ/; // Unicode property escape (ES2018) /\p{General_Category=Letter}/u; /\p{Letter}/u; // Named capture groups (ES2018) /(?\d{4})-(?\d{2})-(?\d{2})/u; /(?foo|bar)/u; /^(?.*).\k$/u; // backreference /* Template literal */ console.log(`The sum of 2 and 2 is ${2 + 2}`); let y = 8; let my_string = `This is a multiline string that also contains a template ${y + (4.1 - 2.2)}`; /* * Built-in values */ // global object values Infinity; NaN; undefined; // global object functions decodeURIComponent(); decodeURI(); encodeURIComponent(); encodeURI(); eval(); isFinite(); isNaN(); parseFloat(); parseInt(); // constructors (subset) Array(); BigInt(); // ES2020 Boolean(); Date(); Error(); Function(); Map(); Object(); Promise(); RegExp(); Set(); String(); Symbol(); // objects JSON.parse(); Math.random(); // object keywords arguments; globalThis; // ES2020 super; this; // dynamic import (ES2020) import("module").then(); import /* comment */ ("module").then(); import // comment ("module").then(); a = await import("module"); a = await import /* comment */ ("module"); a = await import // comment ("module"); // import.meta (ES2020) import.meta; import . /* comment */ meta; import // comment .meta; a = import.meta; a = import . /* comment */ meta; a = import // comment .meta; // new.target new.target; new . /* comment */ target; new // comment .target; // properties (subset) array.length; Math.PI; Number.NaN; object.constructor; Class.prototype; Symbol.asyncIterator; // ES2018 Symbol('desc').description; // ES2019 // methods (subset) array.keys(); date.toString(); object.valueOf(); re.test(); array.includes(); // ES2016 Object.values(); // ES2017 Object.entries(); // ES2017 string.padStart(); // ES2017 string.padEnd(); // ES2017 Object.getOwnPropertyDescriptors(); // ES2017 promise.finally(); // ES2018 Object.fromEntries(); // ES2019 string.trimStart(); // ES2019 string.trimEnd(); // ES2019 array.flat(); // ES2019 array.flatMap(); // ES2019 string.matchAll(); // ES2020 Promise.allSettled(); // ES2020 BigInt.asUintN(); // ES2020 string.replaceAll(); // ES2021 /* * Function expression, arrow function */ a = function () { return 1 }; a = function fn() { return; }; a = function fn(x) {}; a = function fn(x, y) {}; // Arrow function x => -x; () => {}; (x, y) => x + y; (x, y) => { return x + y; }; (x, y) => /* comment */ { return x + y; } /* comment */ ; (x) => ({ a: x }); // return object // Default parameters a = function fn(x, y = 1) {}; (x, y = 1) => x + y; // Parameter without default after default parameters a = function fn(x = 1, y) {}; (x = 1, y) => x + y; // Array destructuring a = function fn([x]) {}; a = function fn([x = 5, y = 7]) {}; // default values a = function fn([x, , y]) {}; // ignoring some returned values (elision) a = function fn([x, ...y]) {}; // rest syntax ([x]) => x; ([x = 5, y = 7]) => x + y; // default values ([x, , y]) => x + y; // ignoring some returned values (elision) ([x, ...y]) => y; // rest syntax // Object destructuring a = function fn({ x }) {}; a = function fn({ a: x, b: y }) {}; // assigning to new variable names a = function fn({ x = 5, y = 7 }) {}; // default values a = function fn({ a: x = 5, b: y = 7 }) {}; // assigning to new variable names and default values a = function fn({ ['a']: x, ['b']: y }) {}; // computed property names a = function fn({ x, y, ...rest }) {}; // rest properties (ES2018) ({ x }) => x; ({ a: x, b: y }) => x + y; // assigning to new variable names ({ x = 5, y = 7 }) => x + y; // default values ({ a: x = 5, b: y = 7 }) => x + y; // assigning to new variable names and default values ({ ['a']: x, ['b']: y }) => x + y; // computed property names ({ x, y, ...rest }) => x; // rest properties (ES2018) // Destructuring and default parameters a = function f([x, y] = [1, 2], {c: z} = {c: 3}) {}; ([x, y] = [1, 2], {c: z} = {c: x + y}) => x + y + z; // Generator function a = function*fn() {}; a = function * fn() {}; // Rest parameters a = function fn(...rest) {}; a = function fn(x, y, ...rest) {}; (...rest) => rest; (x, y, ...rest) => rest; // Async function (ES2017) a = async function fn() {}; a = async /* comment */ function fn() {}; a = async /* comment */ function fn() {}; // correctly highlighted, because async cannot be followed by a line terminator async x => x; async () => {}; async /* comment */ () => {}; async /* comment */ () => {}; // correctly highlighted, because async cannot be followed by a line terminator async(); // incorrectly highlighted // Async generator (ES2018) a = async function * fn() {}; // Trailing comma (ES2017) a = function fn(x, y,) {}; (x, y,) => x + y; // Trailing comma after rest parameters (syntax error) a = function fn(x, y, ...rest,) {}; (x, y, ...rest,) => rest; /* * Class expression */ a = class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} }; a = class extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } }; // Incorrectly highlighted as keyword a = class { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } }; // Properties/methods called "constructor" a = class { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } }; // Incorrectly highlighted as built-in method a = class { static constructor() { this._value = null; } }; /* * Operators * use groupings to test, as there can only be one expression (in the * first grouping item) */ // Grouping ( 1 + 2 ); // Increment / decrement ( ++a ); ( --a ); ( a++ ); ( a-- ); // Keyword unary ( await promise() ); // ES2017 ( delete obj.prop ); ( new Array() ); ( void 1 ); ( typeof 'str' ); ( yield 1 ); ( yield* fn() ); // Arithmetic ( 1 + 2 ); ( 1 - 2 ); ( 1 * 2 ); ( 1 / 2 ); ( 1 % 2 ); ( 1 ** 2 ); // ES2016 ( +1 ); ( -1 ); // Keyword relational ( prop in obj ); ( obj instanceof constructor ); // Comparison ( 1 == 2 ); ( 1 != 2 ); ( 1 === 2 ); ( 1 !== 2 ); ( 1 < 2 ); ( 1 > 2 ); ( 1 <= 2 ); ( 1 >= 2 ); // Bitwise ( 1 & 2 ); ( 1 | 2 ); ( 1 ^ 2 ); ( ~1 ); ( 1 << 2 ); ( 1 >> 2 ); ( 1 >>> 2 ); // Logical ( 1 && 2 ); ( 1 || 2 ); ( !1 ); // Nullish coalescing (ES2020) ( a ?? 1 ); // Assignment ( a = 1 ); ( a += 1 ); ( a -= 1 ); ( a *= 1 ); ( a /= 1 ); ( a %= 1 ); ( a **= 1 ); // ES2016 ( a <<= 1 ); ( a >>= 1 ); ( a >>>= 1 ); ( a &= 1 ); ( a |= 1 ); ( a ^= 1 ); // Array destructuring ( [a, b] = [1, 2] ); ( [a = 5, b = 7] = [1] ); // default values ( [a, , b] = f() ); // ignoring some returned values (elision) ( [a, ...b] = [1, 2, 3] ); // rest syntax // Object destructuring ( {a, b} = { a: 1, b: 2} ); ( { a: foo, b: bar } = { a: 1, b: 2 } ); // assigning to new variable names ( { a = 5, b = 7 } = { a: 1 } ); // default values ( { a: foo = 5, b: bar = 7 } = { a: 1 } ); // assigning to new variable names and default values ( { ['a']: foo, ['b']: bar } = { a: 1, b: 2 } ); // computed property names ( { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 } ); // rest properties (ES2018) // Comma 1, 2 ; // Conditional / ternary ( true ? 1 : 2 ); ( true ? : 2 ); // missing true value (syntax error) obj[ true ? 1, 2 : 3 ]; // comma operator inside true expression (syntax error) /* * Property accessors */ // Dot notation arr.length; ( obj . /* comment */ prototype /* comment */ . /* comment */ constructor /* comment */ ); const pi = Math.PI const num = 0 // Bracket notation arr['length']; ( obj /* comment */ [ /* comment */ 'prototype' /* comment */ ] /* comment */ /* comment */ [ /* comment */ 'constructor' /* comment */ ] /* comment */ ); // Mixed obj ['prototype'] . constructor; obj . prototype ['constructor']; /* * Function call */ fn(); obj.fn(1); obj['fn'](1, 2); // Spread syntax fn(x, y, ...args); // Trailing comma (ES2017) fn(x, y,); /* Tagged template */ myTag`That ${ person } is ${ age }`; /* * Optional chaining (ES2020) */ obj?.prototype; obj?.['constructor']; func?.(1, 2, ...args); foo?.3:0; // ternary operator, not optional chaining /* * Statements and declarations */ /* Use strict directive */ "use strict"; function () { 'use strict'; } // invalid directives " use strict"; 'use strict '; "use strict"; 'use strict'; "hello 'use strict' world"; fn("use strict"); { 'use strict'; } /* Block statement */ { hello(); world(); } { hello(); world() } /* Break statement */ break; break label; break // end statement label; // separate statement { break } /* * Class declaration */ class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} } class Foo extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } } // Incorrectly highlighted as keyword class Foo { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } } // Properties/methods called "constructor" class Foo { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } } // Incorrectly highlighted as built-in method class Foo { static constructor() { this._value = null; } } /* Continue statement */ continue; continue label; continue // end statement label; // separate statement { continue } /* Debugger statement */ debugger; debugger ; /* Export / import statement */ export { a }; export { a, b, }; export { x as a }; export { x as a, y as b, }; export var a; export let a, b; export const a = 1; export var a = 1, b = 2; export function fn() {} export function* fn() {} export class Class {} export default 1; export default function () {} export default function *fn() {} export default class {} export { a as default, b }; export * from 'module'; export * as ns from 'module'; export { a, b } from 'module'; export { x as a, y as b, } from 'module'; export { default } from 'module'; import a from "module"; import * as ns from "module"; import { a } from "module"; import { a, b } from "module"; import { x as a } from "module"; import { x as a, y as b } from "module"; import { default as a } from "module"; import a, { b } from "module"; import a, * as ns from "module"; import "module"; /* For statement */ for (i = 0; i < 10; i++) something(); for (var i = 10; i >= 0; i--) { something(); } for (i = 0, j = 0; i < 10; i++, j++) something(); for (let i = 10, j = 0; i >= 0; i--, j += 1) { something(); } for (prop in obj) {} // matches "in" binary operator instead for (const prop in obj) {} for (val of generator()) {} for (var val of array) {} for await (let x of asyncIterable) {} // ES2018 for /* comment */ await /* comment */ (let x of asyncIterable) {} // ES2018 /* Function declaration statement */ function fn() { return; } async function fn() {} // ES2017 async /* comment */ function fn() {} // ES2017 async /* comment */ function fn() {} // correctly highlighted, because async cannot be followed by a line terminator /* If..else statement */ if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) { lessThan(); } else if (a > 0) { greaterThan(); } else { equal(); } /* Label statement */ outer: for (var i = 0; i < 10; i++) { inner /* comment */ : for (var j = 0; j < 2; j++) {} } loop /* comment */ : for (var i in obj) {} // incorrectly highlighted (though it may appear correct) /* Return statement */ return; return 1; return // end statement 1; // separate statement return ( 1 ); { return a } /* Switch statement */ switch (foo) { case 1: doIt(); break; case '2': doSomethingElse(); break; default: oops(); } /* Throw statement */ throw e; throw new Error(); throw // end statement (syntax error) e; // separate statement throw ( new Error() ); { throw new Error() } /* Try...catch statement */ try { somethingDangerous(); } catch (e) { didntWork(e); } catch { // ES2019 return false; } finally { cleanup(); } /* Variable declaration */ // Declaration only const a; let a, b, c; var a , b , c ; // With assignment const a = 1; let a = 1, b = [2, 3], c = 4; var a = 1 , b = [ 2 , 3 ] , c = 4 ; // Array destructuring var [a, b] = [1, 2]; var [ a , b ] = [ 1 , 2 ] ; var [a = 5, b = 7] = [1]; // default values var [a, , b] = f(); // ignoring some returned values (elision) var [a, ...b] = [1, 2, 3]; // rest syntax // Object destructuring var { a, b } = { a: 1, b: 2 }; var { a , b } = { a : 1 , b : 2 } ; var { a: foo, b: bar } = { a: 1, b: 2 }; // assigning to new variable names var { a = 5, b = 7 } = { a: 1 }; // default values var { a: foo = 5, b: bar = 7 } = { a: 1 }; // assigning to new variable names and default values var { ['a']: foo, ['b']: bar } = { a: 1, b: 2 }; // computed property names var { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 }; // rest properties (ES2018) /* While / do...while statement */ while (true) something(); while (1) { something(); } do something(); while (false); do { something(); } while (0); /* With statement */ with (Math) { a = PI * r * r; x = r * cos(PI); y = r * sin(PI / 2); } /* * JSDoc */ /* Inline tag */ /** {@link String} */ /** * {@link http://example.com | Ex\{am\}ple} */ /** {@link*/ /* Type */ /** {String} */ /** * {Foo.B\{a\}r} */ /** {number*/ /* Block tag */ // No arguments /** @constructor */ /** * @deprecated since 1.1.0 */ /** @public*/ // Generic argument /** @default 3.14159 */ /** * @tutorial tutorial-1 */ /** @variation 2*/ // Event name argument /** @fires earthquakeEvent */ /** * @listens touchstart */ /** @event newEvent*/ // Keyword argument /** @access protected */ /** * @kind module */ /** @access private*/ // Name/namepath argument /** @alias foo */ /** * @extends bar */ /** @typeParam T*/ // Type and name arguments /** @param {String} name - A \{chosen\} \@name */ /** * @member {Object} child */ /** @property {number} num*/ // Borrows block tag /** @borrows foo as bar */ /** * @borrows foo as */ /** @borrows foo*/ // Todo block tag /** @todo write more/less test cases */ 07070100000291000081A4000000000000000000000001665908060000086C000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.la# moo.la - a libtool library file # Generated by ltmain.sh - GNU libtool 1.5.22 Debian 1.5.22-4 (1.1220.2.365 2005/12/18 22:14:06) # # Please DO NOT delete this file! # It is necessary for linking the library. # The name that we can dlopen(3). dlname='moo.so' # Names of this library. library_names='moo.so moo.so moo.so' # The name of the static archive. old_library='' # Libraries that this one depends upon. dependency_libs=' -L/usr/local/gtk/lib /home/muntyan/projects/gtk/build/moo/moo/libmoo.la -lutil /usr/local/gtk/lib/libgtk-x11-2.0.la -lXext -lXinerama -lXrandr -lXcursor -lXfixes /usr/local/gtk/lib/libgdk-x11-2.0.la -latk-1.0 /usr/local/gtk/lib/libgdk_pixbuf-2.0.la /usr/local/gtk/lib/libpangocairo-1.0.la /usr/local/gtk/lib/libpangoft2-1.0.la /usr/local/gtk/lib/libpango-1.0.la /usr/local/gtk/lib/libcairo.la -lfreetype -lz -lfontconfig -lpng12 -lXrender -lX11 -lm /usr/local/gtk/lib/libgobject-2.0.la /usr/local/gtk/lib/libgmodule-2.0.la -ldl /usr/local/gtk/lib/libgthread-2.0.la -lpthread /usr/local/gtk/lib/libglib-2.0.la -lrt -lpcre /usr/lib/gcc/i486-linux-gnu/4.1.2/../../..//libfam.la -lrpcsvc /usr/lib/gcc/i486-linux-gnu/4.1.2/../../..//libxml2.la -L/usr/lib/python2.4 -lpython2.4 ' # Version information for moo. current=0 age=0 revision=0 # Is this an already installed library? installed=no # Should we warn about portability when linking against -modules? shouldnotlink=yes # Files to dlopen/dlpreopen dlopen='' dlpreopen='' # Directory that this library needs to be installed in: libdir='/usr/local/gtk/lib/python2.4/site-packages' relink_command="(cd /home/muntyan/projects/gtk/build/moo/moo; /bin/sh ../libtool --tag=CC --mode=relink gcc -g -L/usr/local/gtk/lib -o moo.la -rpath /usr/local/gtk/lib/python2.4/site-packages -no-undefined -module -avoid-version -export-symbols-regex initmoo moopython/libmoomod.la libmoo.la -lutil -L/usr/local/gtk/lib -lgtk-x11-2.0 -lgdk-x11-2.0 -latk-1.0 -lgdk_pixbuf-2.0 -lm -lpangocairo-1.0 -lpango-1.0 -lcairo -lgobject-2.0 -lgmodule-2.0 -ldl -lglib-2.0 -pthread -L/usr/local/gtk/lib -lgthread-2.0 -lglib-2.0 -lpcre -lfam -lxml2 -L/usr/lib/python2.4 -lpython2.4 @inst_prefix_dir@)" 07070100000292000081A400000000000000000000000166590806000000D9000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.lean-- line comment /- block comment /- nested block comment -/ -/ @[noinline] def helloWorld : IO Unit := let a := 1 let a1 := 0b1 let a₂ := 0x1 let a' := '1' IO.println "Hello World" #eval helloWorld 07070100000293000081A40000000000000000000000016659080600003B50000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.less/* * nesting */ #header { width: 300px; .navigation { font-size: 12px; } @media (min-width: 768px) { width: 600px; @media (min-resolution: 192dpi) { background-image: url(/img/retina2x.png); } } } /* * parent selector */ .component { &:hover { text-decoration: underline; } // compound selector &__element, &--modifier { border: 1px; } // multiple & & + & { color: red; } // changing selector order body.firefox & { font-weight: normal; } } /* * variables, data types */ // assignment @number: 5em; @double-quoted-string: "foo"; @single-quoted-string: 'bar'; @double-quoted-escape-string: ~"foo"; @single-quoted-escape-string: ~'bar'; @not-quoted-string: baz; @spaces-list: 1px 2px 3px 4px; @commas-list: arial, some-other-arial, sans-serif; @color: #fe57a1; @detached-ruleset: { color: red; div { color: blue; } }; #main { // property value width: @width; // variable reference color: @@color-variable-name; // property variable background-color: $color; // use detached ruleset @detached-ruleset(); // parentheses required } // media type, feature test @import "foo" @type and @feature-test, (@feature-test-content); @media @type and @feature-test, (@feature-test-content) { width: 700px; } // font feature value @font-feature-values Font One { @styleset { nice-style: @value; } } // @keyframes name @keyframes @myanim { from { width: 0; } to { width: 100%; } } /* * cases where Less doesn't currently accept variables (as of Less 3.0.0) * but may be still be incidentally highlighted */ // font feature values font name @font-feature-values @fontname { @styleset { nice-style: 12; } } // @namespace name @namespace @name url('http://www.w3.org/2000/svg'); // @page custom name, pseudo-page @page @name { margin: 0; @left-top { content: ''; } } /* * variable interpolation */ // selector @{hash-id}, #@{id}, #i@{d}, @{whole-class}, .part-@{class}-fragment, @{div}, d@{i}v, [@{attr}=@{value}], [@{attr-selector-content}], @{attr-selector}, @{selection}, ::se@{lect}ion, @{hover}, :ho@{v}er, @{lang-fr}, :la@{ng}(fr), :lang(@{fr}), :nth-child(@{expr}), :not(@{div}) { // property name @{font}: serif; background-@{image}: none; border-@{bottom}-width: 0; // inside strings content: "@{var}"; content: '@{var}'; // nested interpolations content: "@{@{nested}-variable}"; } // font feature type, custom name @font-feature-values Font One { @{type} { @{name}: 12; } } // inside @import url() @import url("http://fonts.googleapis.com/css?family=@{family}"); // inside @import string @import "http://fonts.googleapis.com/css?family=@{family}"; // @keyframes selectors @keyframes myanim { @{from} { width: 0; } @{to} { width: 100%; } } // @page margin box type @page toc, :first { margin: 0; @{margin-box-type} { content: ''; } } /* * operations */ body { width: (1px + (2em - 3rem)) * (4 / 5vh); // unary negation z-index: -@z-index; margin-bottom: -(@base * 2); } /* * functions */ body { // Less string format function content: %("1 plus 2 is %d", 1 + 2); // single line comments not parsed inside url() background: url(http://example.com/styles.css); // semicolons separators color: hsl(90; 100%; 50%); } /* * extend */ // attached to selector #main:extend(.card-style), .sidebar :extend(div pre), :not(:extend(#footer)) { /* not allowed as argument to :not() */ color: blue; } #main { // inside ruleset &:extend(#board:nth-child(2n+3)); // all keyword &:extend(div ~ .inline[name="foo"] all); } /* * property merge */ code { // with comma box-shadow+: 0 0 20px black; // with space transform+_: scale(2); } /* * mixins */ // mixin definition .black() { color: black; } // with parameters .box(@margin, @padding) { // comma separated margin: @margin; padding: @padding; } .box(@margin; @padding) { // semicolon separated margin: @margin; padding: @padding; } .box(@margin: 10px; @padding: 10px) { // default parameters margin: @margin; padding: @padding; } // @arguments .box-shadow(@x: 0; @y: 0; @blur: 1px; @color: #000) { box-shadow: @arguments; } // variable arguments .mixin(...) {} // 0 or more arguments .mixin(@a; ...) {} // at least 1 argument .mixin(@a; @rest...) {} // remaining arguments bound to variable // with pattern .mixin(dark; 5px) {} .mixin(light; #000) {} // mixin call #main { // existing styles #header; .card-style; // existing styles with parentheses (optional), // or mixins with parentheses and no required parameters #header(); .card-style(); // namespaced styles #outer > .inner; #outer .inner; #outer.inner; // important modifier #header !important; .card-style() !important; #outer > .inner !important; // with parameters .box(20px, 10px); // comma separated .box(20px; 10px); // semicolon separated .box(@padding: 10px; @margin: 20px); // named parameters .box(@list...); // expands values in @list as arguments .mixin({ // detached ruleset color: red; div { color: blue; } }); // with pattern .mixin(dark; 5px); .mixin(light; #000); } /* * guards */ // regular styles button when (@my-option = true) { color: white; } & when (@my-option = true) { button { color: white; } a { color: blue; } } // namespace #namespace when (@mode = huge) { .mixin() {} } #namespace { .mixin() when (@mode = huge) {} } // mixin .mixin (@a) when (lightness(@a) >= 50%) { background-color: black; } .mixin (@a) when (lightness(@a) < 50%) { background-color: white; } // comparison operators, logical operators .mixin (@a) when (@a >= 0) and (@a =< 0) {} .mixin (@a) when (@a < 0) or (@a > 0) {} .mixin (@a) when (@a < 0), (@a > 0) {} .mixin (@a) when not (@a = 0) {} // true keyword .mixin (@a) when (@a) {} .mixin (@a) when (@a = true) {} /* * at-rules */ // @import @import "@{themes}/tidal-wave.less"; @import (optional, reference) "foo.less" screen and (orientation: landscape), print; // import options // @plugin @plugin 'my-plugin'; @plugin (options) 'my-plugin'; // plugin options // @plugin function call my-plugin(); div { my-plugin(red; 10px); } /* * test cases */ .declarations-or-selectors { // declarations display:block; font-family:arial; @{property}:block; background-image:url( \( \) \{ { ); background-image:url( url( { ) ); background-image:url( @var ); background-image:url( /* ) { */ ); background-image:url( " ) { " ); background-image:url( ' ) { ' ); margin:@var 10px; display:/* { */block; font-family:" \" \{ { ", serif; font-family:" @{var} ", serif; font-family:' \' \{ { ', serif; font-family:' @{var} ', serif; // incorrectly highlighted declarations display:block ; display:block // { ; // selectors input:focus { opacity: 0.5; } div:nth-child(2n+1) { background-color: gray; } div:-moz-full-screen { display: block; } @{selector}:focus { color: blue; } a:@{state} { color: blue; } a:focus[id=\]\; ] { color: blue; } a:focus[id=@{var} ] { color: blue; } a:focus[id=/* ] ; */] { color: blue; } a:focus[id=" ] ; " ] { color: blue; } a:focus[id=' ] ; ' ] { color: blue; } a:@{var} { color: blue; } a:/* ; */focus { color: blue; } a:focus[id=" ] \" \; ; "] { color: blue; } a:focus[id=" @{var} "] { color: blue; } a:focus[id=' ] \' \; ; '] { color: blue; } a:focus[id=' @{var} '] { color: blue; } a:focus // ; { color: blue; } } // from file.css @charset "UTF-8"; // Less will move the first @charset rule to the top // and ignore any other @charset rules /* * general */ /* whitespace */ #main { color:aqua; float: left!important; margin : 0 ; width : 100% ! important ; } /* case insensitivity */ Body { FONT: 12Px/16pX iTaLiC sans-SERIF; } /* * selectors */ /* simple selectors */ #testID, /* id */ .someclass, /* class */ div, /* type */ *, /* universal */ [lang|="zh"], /* attribute */ [ /* comment */ lang /* comment */ |= /* comment */ "zh" /* comment */ ] { color: black; } /* combinators */ header + main, /* adjacent sibling */ li ~ li, /* general sibling */ ul > li, /* child */ ul ul { /* descendant */ color: blue; } /* pseudo-elements */ :after, ::/* comment */after, ::placeholder, ::/* comment */selection { color: green; } /* pseudo-classes */ :hover, :/* comment */required, :lang(fr), :/* comment */not(div#sidebar.fancy), :nth-child(n+1), :/* comment */nth-last-child(-2n /* comment */ - /* comment */ 30), :nth-of-type(5), :/* comment */nth-last-of-type(even) { color: yellow; } /* pseudo-classes with invalid arguments */ :not(div:before), /* pseudo-element */ :not(input::placeholder), /* pseudo-element */ :not(p:not(:lang(en))), /* nested :not */ :nth-child(1.2n), /* non-integer */ :nth-child(.5), /* non-integer */ :nth-child(n+-1) { /* number sign */ color: red; } /* namespace qualified */ a, /* type in default namespace */ svg|a, /* type in specified namespace */ |a, /* type in no namespace */ *|a, /* type in all namespaces (including no namespace) */ svg|*, /* universal */ svg|[fill] { /* attribute */ color: white; } /* * basic data types */ #main { /* angle */ transform: rotate(+33.333e+3deg); /* color */ color: #f00; color: #f00f; /* #rgba */ color: #ff0000; color: #ff0000ff; /* #rrggbbaa */ color: red; color: lightgoldenrodyellow; color: rebeccapurple; color: currentColor; /* frequency (not currently used for any property) */ content: 44.1kHz; /* integer */ z-index: +255; z-index: -1; /* length */ width: 10px; width: 10.5rem; width: -10e-2vw; /* number */ opacity: .5; opacity: 0.3333; opacity: 1; opacity: 2e-34; /* percentage */ width: 100%; /* string */ content: "double quoted"; content: 'single quoted'; /* time */ transition-duration: .4s; transition-duration: 400ms; /* unicode range */ unicode-range: U+0025-00FF; unicode-range: U+4??; /* wildcard range */ } /* ratio */ @media (min-aspect-ratio: 16/9) {} @media (min-aspect-ratio: 16 /* comment */ / /* comment */ 9) {} /* resolution */ @media (min-resolution: +2.54dpcm) {} /* * identifiers */ /* leading hyphens */ #-here.-there, #-- .--everywhere { /* two hyphens: https://stackoverflow.com/a/30822662 */ color: blue; } /* non-ASCII */ #español, #你好, .❤♫ { color: green; } /* invalid identifiers */ #1id, /* starts with digit */ .-2class { /* starts with hyphen digit */ color: maroon; } /* * escaping */ /* selectors */ #\..\+\ space\@\>, /* special character escape */ #\E9 dition .\0000E9dition .motion_\e9motion, /* Unicode character escape */ .\e33 div, /* trailing space terminates Unicode character escape */ .\e33 div, /* extra space to denote separate tokens */ .\31 23 { /* escape leading digit of identifier */ /* property value */ content: "\E9 dition \ \"\0000E9dition\" \ \e9motion"; font-family: \E9 dition, \"\0000E9dition\", \e9motion; /* function name */ background: \u\72\l(image.png); } /* * functions */ #main { /* url */ background: url("image.svg"); /* function argument keywords */ background-image: linear-gradient(to left top, #fff, blue); grid-template-columns: repeat(2, minmax(max-content, 300px) 1fr) 100px; } /* * style properties */ #main { /* svg */ fill: url(#pattern); text-rendering: optimizeLegibility; /* css3 */ font-variant-east-asian: jis04; size: letter; transition-timing-function: ease-in; /* animatable */ transition-property: height, font-size, visibility; /* custom properties */ --my-custom-color: #fff; color: var(--my-custom-color); background-color: var(--my-custom-color, white); } /* * modifiers */ body { background: pink !important; } /* * media queries */ @media screen, (orientation: portrait) {} @media not (print and (min-monochrome: 16) and (color)) {} @media only screen {} @media not print {} /* * at-rules */ /* @font-face */ @font-face { font-family: MyHelvetica; src: local("Helvetica Neue"), local("HelveticaNeue"), url(MgOpenModerna.ttf); } /* @font-feature-values */ @font-feature-values Font One { @styleset { nice-style: 12; } } .nice-look { font-variant-alternates: styleset(nice-style); } /* @import */ @import URL("fineprint.css"); @import 'custom.css'; @import url('landscape.css') screen and (orientation: landscape), print; /* @keyframes */ @keyframes myanim { from { opacity: 0.0; } 50% { opacity: 0.5; } to { opacity: 1.0; } } /* @media */ @media all { body { background: gray; } @media screen, (orientation: portrait) { body { background: grey; } } } /* @namespace */ @namespace "http://www.w3.org/1999/xhtml"; @namespace svg url(http://www.w3.org/2000/svg); /* @page */ @page { bleed: 1cm; } @page toc, :blank { margin: 2cm; marks: crop cross; } @page index:/*comment*/left { size: A4; @top-right { content: "Page " counter(page); } } /* @supports */ @supports (animation-name: test) { @keyframes 'my-complicated-animation' { 0% { width: 0; } 100% { width: 100%; } } } /* * vendor-specific extensions */ /* pseudo-elements */ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::/* comment */-webkit-inner-spin-button { -webkit-appearance: none; } input[type="number"] { -moz-appearance: textfield; } /* pseudo-classes */ #page:-moz-full-screen, #page:/* comment */-ms-fullscreen, #page:-webkit-full-screen { background: silver; } /* functions */ .footer { background-image: -webkit-linear-gradient(to left top, #fff, blue); } /* style properties */ #sidebar { -ms-overflow-style: -ms-autohiding-scrollbar; } @supports not ((text-align-last: justify) or (-moz-text-align-last: justify)) { body { text-align: justify; } } /* at-rules */ @-webkit-keyframes myanim { from { opacity: 0.0; } 50% { opacity: 0.5; } to { opacity: 1.0; } } 07070100000294000081A400000000000000000000000166590806000001EB000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.lisp; Define a simple package (defpackage #:fu (:use #:alexandria #:cl) (:export #:bar)) (in-package :foo) #| Block comment more text #| embedded block comment |# |# (defmacro bar (quux &rest wibble) "String with some an escape \"" `(angle ,quux ,@wibble)) (bar 123 234. 745e7 126f4 #3r12 #xaa0 #O23 #b0101/11 #< ; invalid sharpsign #\; #\Spac\e ; Some characters #10*01010101 ; bit vector 'baz #'gronk) (format t "~{~a~#[~;, and ~:;, ~]~}" '(a b c)) 07070100000295000081A40000000000000000000000016659080600000222000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.ll; LLVM intermediate representation ; Run with: llc file.ll && gcc file.s && ./a.out @str = internal constant [4 x i8] c"%d\0A\00" declare i32 @printf(i8*, ...) define i32 @main() { ; Print a secret number on the screen %1 = select i1 true, float 0x402ABD70A0000000, float 0xC0FFEE0000000000 %2 = fpext float %1 to double %3 = fmul double %2, 1.000000e+02 %4 = fptoui double %3 to i32 %5 = add i32 %4, 1 ; Call printf call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @str, i32 0, i32 0), i32 %5) ret i32 0 } 07070100000296000081A400000000000000000000000166590806000007B1000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/tests/syntax-highlighting/file.logcat--------- beginning of /dev/log/system V/test ( 145): The quick brown fox jumps over the lazy dog D/test ( 141): The quick brown fox jumps over the lazy dog I/test ( 141): The quick brown fox jumps over the lazy dog W/test ( 141): The quick brown fox jumps over the lazy dog E/test ( 141): The quick brown fox jumps over the lazy dog F/test ( 141): The quick brown fox jumps over the lazy dog --------- beginning of /dev/log/system V(test ( 145): The quick brown fox jumps over the lazy dog D(test ( 141): The quick brown fox jumps over the lazy dog I(test ( 141): The quick brown fox jumps over the lazy dog W(test ( 141): The quick brown fox jumps over the lazy dog E(test ( 141): The quick brown fox jumps over the lazy dog F(test ( 141): The quick brown fox jumps over the lazy dog --------- beginning of /dev/log/main 01-28 12:36:22.826 8 8 V TEST : "The quick brown fox jumps over the lazy dog" 01-28 12:36:22.826 98 98 D TEST : "The quick brown fox jumps over the lazy dog" 01-28 12:36:22.826 398 398 I TEST : "The quick brown fox jumps over the lazy dog" 01-28 12:36:22.826 3398 3398 W TEST : "The quick brown fox jumps over the lazy dog" 01-28 12:36:22.827 3398 3398 E TEST : "The quick brown fox jumps over the lazy dog" 01-28 12:36:22.827 3398 3398 F TEST : "The quick brown fox jumps over the lazy dog" 2017-01-28 12:36:22.826 8 8 V TEST : "The quick brown fox jumps over the lazy dog" 2017-01-28 12:36:22.826 98 98 D TEST : "The quick brown fox jumps over the lazy dog" 2017-01-28 12:36:22.826 398 398 I TEST : "The quick brown fox jumps over the lazy dog" 2017-01-28 12:36:22.826 3398 3398 W TEST : "The quick brown fox jumps over the lazy dog" 2017-01-28 12:36:22.827 3398 3398 E TEST : "The quick brown fox jumps over the lazy dog" 2017-01-28 12:36:22.827 3398 3398 F TEST : "The quick brown fox jumps over the lazy dog" 07070100000297000081A40000000000000000000000016659080600000127000000000000000000000000000000000000003600000000gtksourceview-5.12.1/tests/syntax-highlighting/file.m#import #import @interface Lalala : Object - (BOOL) sayHello; @end @implementation Lalala : Object - (BOOL) sayHello { printf ("Hello there!\n"); return YES; } @end int main (void) { Lalala *obj = [[Lalala alloc] init]; [obj sayHello]; [obj free]; return 0; } 07070100000298000081A40000000000000000000000016659080600000061000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.m4dnl an m4 file AC_DEFINE([foo],[echo "Hi there!"]) AC_CHECK_FUNC([foo],[yes=yes],[yes=no]) foo() 07070100000299000081A40000000000000000000000016659080600000264000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.mdHeading ======= ## Sub-heading Paragraphs are separated by a blank line. Two spaces at the end of a line produces a line break. Text attributes _italic_, **bold**, `monospace`. Horizontal rule: --- Bullet list: * apples * oranges * pears Numbered list: 1. lather 2. rinse 3. repeat An [example](http://example.com). ![Image](Icon-pictures.png "icon") > Markdown uses email-style > characters for blockquoting. Inline HTML is supported.

Formatting inside block-level elements _should_ be **ignored**.

0707010000029A000081A400000000000000000000000166590806000021C2000000000000000000000000000000000000003E00000000gtksourceview-5.12.1/tests/syntax-highlighting/file.mediawiki= Help:Wikitext examples = https://meta.wikimedia.org/wiki/Help:Wikitext_examples == Basic text formatting == You can ''italicize'' text by putting 2 apostrophes on ''each'' side. 3 apostrophes will '''bold''' the text. 5 apostrophes will '''''bold and italicize''''' the text. (Using 4 apostrophes doesn't do anything special --
3 of them '''bold''' the text as usual; the others are ''''just'''' apostrophes around the text.) ---- A single newline generally has no effect on the layout. These can be used to separate sentences within a paragraph. Some editors find that this aids editing and improves the ''diff'' function (used internally to compare different versions of a page). But an empty line starts a new paragraph. When used in a list, a newline ''does'' affect the layout ([[#lists|see below]]). ---- You can break lines
without a new paragraph.
Please use this sparingly. Please do not start a link or ''italics'' or '''bold''' text on one line and end on the next. ---- You should "sign" your comments on talk pages: * Three tildes gives your signature: ~~~ * Four tildes give your signature plus date/time: ~~~~ * Five tildes gives the date/time alone: ~~~~~ == HTML tags == Put text in a monospace ('typewriter') font. The same font is generally used for computer code. ---- Strike out or underline text, or write it in small caps. ---- Superscripts and subscripts: X2, H2O ----
Centered text
* Please note the American spelling of "center". ---- * This is how to {{Font color||yellow|highlight part of a sentence}}. ----
The '''blockquote''' command ''formats'' block quotations, typically by surrounding them with whitespace and a slightly different font.
---- Invisible comments to editors () appear only while editing the page. == Organizing your writing == == Section headings == ''Headings'' organize your writing into sections. The ''Wiki'' software can automatically generate a [[help:Section|table of contents]] from them. === Subsection === Using more "equals" (=) signs creates a subsection. ==== A smaller subsection ==== Don't skip levels, like from two to four equals signs. Start with 2 equals signs, not 1. If you use only 1 on each side, it will be the equivalent of h1 tags, which should be reserved for page titles. ---- * ''Unordered lists'' are easy to do: ** Start every line with a asterisk. *** More asterisks indicate a deeper level. *: Previous item continues. ** A newline * in a list marks the end of the list. *Of course you can start again. ---- # ''Numbered lists'' are: ## Very organized ## Easy to follow A newline marks the end of the list. # New numbering starts with 1. ---- Here's a ''definition list'': ; Word : Definition of the word ; A longer phrase needing definition : Phrase defined ; A word : Which has a definition : Also a second definition : And even a third Begin with a semicolon. One item per line; a newline can appear before the colon, but using a space before the colon improves parsing. ---- * You can even do mixed lists *# and nest them *# inside each other *#* or break lines
in lists. *#; definition lists *#: can be *#:; nested : too ---- : A colon (:) indents a line or paragraph. A newline starts a new paragraph. Should only be used on talk pages. For articles, you probably want the blockquote tag. : We use 1 colon to indent once. :: We use 2 colons to indent twice. ::: 3 colons to indent 3 times, and so on. ---- You can make ''horizontal dividing lines'' (----) to separate text. ---- But you should usually use sections instead, so that they go in the table of contents. ---- You can add footnotes to sentences using the ''ref'' tag -- this is especially good for citing a source. :There are over six billion people in the world.CIA World Factbook, 2006. References: For details, see [[Wikipedia:Footnotes]] and [[Help:Footnotes]]. == Links == Here's a link to a page named [[Official positions|Official position]]. You can even say [[official positions]] and the link will show up correctly. ---- You can put formatting around a link. Example: ''[[Wikipedia]]''. ---- The ''first letter'' of articles is automatically capitalized, so [[wikipedia]] goes to the same place as [[Wikipedia]]. Capitalization matters after the first letter. ---- [[Intentionally permanent red link]] is a page that doesn't exist yet. You could create it by clicking on the link. ---- You can link to a page section by its title: * [[Doxygen#Doxygen Examples]]. If multiple sections have the same title, add a number. [[#Example section 3]] goes to the third section named "Example section". ---- You can make a link point to a different place with a [[Help:Piped link|piped link]]. Put the link target first, then the pipe character "|", then the link text. * [[Help:Link|About Links]] * [[List of cities by country#Morocco|Cities in Morocco]] Or you can use the "pipe trick" so that a title that contains disambiguation text will appear with more concise link text. * [[Spinning (textiles)|]] * [[Boston, Massachusetts|]] ---- You can make an external link just by typing a URL: http://www.nupedia.com You can give it a title: [http://www.nupedia.com Nupedia] Or leave the title blank: [http://www.nupedia.com] External link can be used to link to a wiki page that cannot be linked to with [[page]]: http://meta.wikimedia.org/w/index.php?title=Fotonotes&oldid=482030#Installation ---- Linking to an e-mail address works the same way: mailto:someone@example.com or [mailto:someone@example.com someone] ---- #REDIRECT [[Official positions|Official position]] ---- [[Help:Category|Category links]] do not show up in line but instead at page bottom ''and cause the page to be listed in the category.'' [[Category:English documentation]] Add an extra colon to ''link'' to a category in line without causing the page to be listed in the category: [[:Category:English documentation]] ---- The Wiki reformats linked dates to match the reader's date preferences. These three dates will show up the same if you choose a format in your [[Special:Preferences|]]: * [[1969-07-20]] * [[July 20]], [[1969]] * [[20 July]] [[1969]] == Just show what I typed == The nowiki tag ignores [[Wiki]] ''markup''. It reformats text by removing newlines and multiple spaces. It still interprets special characters: → ----
The pre tag ignores [[Wiki]]
 ''markup''.
It also doesn't     reformat
 text.
It still interprets special
characters: →
---- Leading spaces are another way to preserve formatting. Putting a space at the beginning of each line stops the text from being reformatted. It still interprets [[Wiki]] ''markup'' and special characters: → === Source code === // Hello World in Microsoft C# ("C-Sharp"). using System; class HelloWorld { public static int Main(String[] args) { Console.WriteLine("Hello, World!"); return 0; } } == Images, tables, video, and sounds == A picture, including alternate text: [[Image:Wiki.png|This is Wiki's logo]] You can put the image in a frame with a caption: [[Image:Wiki.png|frame|This is Wiki's logo]] ---- A link to Wikipedia's page for the image: [[:Image:Wiki.png]] Or a link directly to the image itself: [[Media:Wiki.png]] ---- Use '''media:''' links to link directly to sounds or videos: [[media:Classical guitar scale.ogg|A sound file]] ---- Provide a spoken rendition of some text in a template: {{listen |title = Flow, my tears |filename = Flow, my tears.ogg |filesize = 1.41 MB }} ---- '''Text In a Box''' ---- {| border="10" cellspacing="5" cellpadding="10" align="center" |- ! This ! is |- | a | table |} === Galleries === Image:Wiki.png Image:Wiki.png|Captioned Image:Wiki.png Image:Wiki.png|[[Wikipedia|Links]] can be put in captions. == Mathematical formulae == \sum_{n=0}^\infty \frac{x^n}{n!} == Templates == {{Transclusion demo}} ---- {{Help:Transclusion Demo}} ---- This template takes two parameters, and creates underlined text with a hover box for many modern browsers supporting CSS: {{H:title|This is the hover text|Hover your mouse over this text}} Go to this page to see the H:title template itself: {{tl|H:title}} 0707010000029B000081A4000000000000000000000001665908060000055B000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.mlopen Printf (*comment (** comment in comment *) "string *)\nin comment" *) let id = fun x -> x let whatelse _ ?_a ?(b = 1.) ~c () = (Option.value _a ~default:0.) +. b +. c (**) (*<- empty comment*) (**ocamldoc (*and comment*) *) type thing = | Thing1 | Thing2 of string option | Thing3 of { first: string; second: string } let _ = print_string "Type a number: "; let nb1 = read_float () and nb2 = Random.float 10. in printf "%g × 2 is %g.\n" (id nb1) (nb1 *. 2.); printf "%g + %g is %g.\n" nb1 nb2 (whatelse () ~b:nb1 ~c:nb2 ()); print_endline "Now some multiplication table:"; for i1 = 1 to 10 do for i2 = 1 to 10 do printf "%3d " (i1 * i2); done; print_newline (); done; Array.iter (fun thing -> print_endline ( match thing with | Thing1 -> "And 1." | Thing2 None -> "Or 2…" | Thing2 (Some str) -> "Or 2 with " ^ str ^ "!" | Thing3 pair -> "3 says:\n" ^ pair.first ^ "\nand:\n" ^ pair.second ); ) [| Thing1; Thing2 None; Thing2 (Some "this"); Thing3 { first = "this is \097 multiline string"; second = {|I can easily write backslashes because there is a syntax in which \escapes \do \not \\\work|} ^ {x| (this syntax is {|string|})|x}; } |]; 0707010000029C000081A4000000000000000000000001665908060000041F000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.nix{ pkgs, lib, config, inputs, ... }: let /* Modify the package for GNU Hello. Update to latest version and apply a custom patch. */ hello = pkgs.hello.overrideAttrs (attrs: rec { version = "2.12.1"; src = fetchurl { url = "mirror://gnu/hello/hello-${version}.tar.gz"; hash = "sha256-jZkUKv2SV28wsM18tCqNxoCZmLxdYH2Idh9RLibH2yA="; }; patches = attrs.patches or [] ++ [ # Cherry pick a crash fix. ../pkgs/hello/fix-crash.patch ]; }); in { environment.systemPackages = with pkgs; [ firefox hello ]; home-manager.users.jtojnar = { lib, ... }: { dconf.settings = { "org/gnome/desktop/input-sources" = { sources = [ (lib.hm.gvariant.mkTuple [ "xkb" "${config.services.xserver.layout}${lib.optionalString (config.services.xserver.xkbVariant != "") "+" + config.services.xserver.xkbVariant}" ]) ]; }; }; home.file.".config/npm/npmrc".text = '' prefix=''${XDG_DATA_HOME}/npm ''; }; } 0707010000029D000081A40000000000000000000000016659080600000042000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/tests/syntax-highlighting/file.octave% gtk-source-lang: octave % -*- octave -*- No idea what syntax is 0707010000029E000081A40000000000000000000000016659080600000104000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.oocimport structs/[ArrayList, LinkedList], io/FileReader include stdarg, memory, string use sdl, gtk pi := const 3.14 Int: cover from int { toString: func -> String { "%d" format() } } Dog: class extends Animal { barf: func { ("woof! " * 2) println() } } 0707010000029F000081A40000000000000000000000016659080600000117000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.pc# A comment prefix=/usr exec_prefix= libdir=/lib includedir=/include Name: cairo Description: Multi-platform 2D graphics library Version: 1.4.10 Requires.private: freetype2 >= 8.0.2 fontconfig libpng12 xrender >= 0.6 x11 Libs: -L -lcairo Libs.private: -lz -lm Cflags: -I/cairo 070701000002A0000081A400000000000000000000000166590806000003F6000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.php ' /> " /> 070701000002A1000081A4000000000000000000000001665908060000012B000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.pig/* * This file tests Apache Pig 0.12 Syntax Highlighting */ fake = LOAD 'fakeData' USING PigStorage() AS (name:chararray, age:int); fake = limit fake 5; -- operations can be case insensitive fake = FOREACH fake GENERATE TRIM(name) AS name, null as nullField, false AS booleanConst; DUMP fake; 070701000002A2000081A40000000000000000000000016659080600000BBE000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.pl#!/usr/bin/perl -- # -*- Perl -*- # # : collateindex.pl,v 1.10 2004/10/24 17:05:41 petere78 Exp $ print OUT "\n\n" if ; = {}; # the last indexterm we processed = 1; # this is the first one = ""; # we're not in a group yet = ""; # we've not put anything out yet @seealsos = (); # See also stack. # Termcount is > 0 iff some entries were skipped. || print STDERR " entries ignored...\n"; &end_entry(); print OUT "\n" if ; print OUT "\n"; close (OUT); || print STDERR "Done.\n"; sub same { my() = shift; my() = shift; my() = ->{'psortas'} || ->{'primary'}; my() = ->{'ssortas'} || ->{'secondary'}; my() = ->{'tsortas'} || ->{'tertiary'}; my() = ->{'psortas'} || ->{'primary'}; my() = ->{'ssortas'} || ->{'secondary'}; my() = ->{'tsortas'} || ->{'tertiary'}; my(); =~ s/^\s*//; =~ s/\s*$//; = uc(); =~ s/^\s*//; =~ s/\s*$//; = uc(); =~ s/^\s*//; =~ s/\s*$//; = uc(); =~ s/^\s*//; =~ s/\s*$//; = uc(); =~ s/^\s*//; =~ s/\s*$//; = uc(); =~ s/^\s*//; =~ s/\s*$//; = uc(); # print "[]=[]\n"; # print "[]=[]\n"; # print "[]=[]\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 is true. = (( eq ) && ( eq ) && ( eq ) && (->{'title'} eq ->{'title'}) && (->{'href'} eq ->{'href'})); # If we're linking to points, they're only the same if they link # to exactly the same spot. = && (->{'hrefpoint'} eq ->{'hrefpoint'}) if ; if () { warn ": duplicated index entry found: \n"; } ; } sub tsame { # Unlike same(), tsame only compares a single term my() = shift; my() = shift; my() = shift; my() = substr(, 0, 1) . "sortas"; my(, ); = ->{} || ->{}; = ->{} || ->{}; =~ s/^\s*//; =~ s/\s*$//; = uc(); =~ s/^\s*//; =~ s/\s*$//; = uc(); return eq ; } sub login { my @words = split /:/, $str; do { $_ = shift @members; } until /^\s+$/; } =pod =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 =begin html =end html Still POD. =cut sub end { =pod Here's another snippet of valid C. =cut my $foo = { bar => \*STDOUT }; } __END__ if present, this data isn't supposed to be processed as Perl. 070701000002A3000081A400000000000000000000000166590806000002A8000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.po# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , 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 "" 070701000002A4000081A400000000000000000000000166590806000001EE000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.prgimport "mod_say" import "mod_rand" import "mod_screen" import "mod_video" import "mod_map" import "mod_key" // Bouncing box example process main() private int vx=5, vy=5; begin set_mode(640, 480, 16, MODE_WINDOW); graph = map_new(20, 20, 16); map_clear(0, graph, rgb(255, 0, 0)); x = 320; y = 240; while(! key(_esc)) if(x > 630 || x < 10) vx = -vx; end; if(y > 470 || y < 10) vy = -vy; end; x += vx; y += vy; // Move the box frame; end; end; 070701000002A5000081A4000000000000000000000001665908060000004F000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/tests/syntax-highlighting/file.prologconc([],X,X). conc([Car|Cdr], X, [Car|ConcatCdr]):- conc(Cdr, X, ConcatCdr). 070701000002A6000081A40000000000000000000000016659080600000119000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.pyimport sys from sys import * class Hello(object): def __init__(self): object.__init__(self) def hello(self): print >> sys.stderr, "Hi there!" None, True, False r'raw \' \ string' r"""raw multiline \""" string""" Hello().hello() 070701000002A7000081A40000000000000000000000016659080600000360000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.py3#!/usr/bin/env python3 import gi gi.require_version('Gtk', '4.0') from gi.repository import GLib, GObject, Gtk my_fstring = f'this is a fstring {something("here")}' my_fstring = f"this is a fstring {other('stuff'}" class MyClass(GObject.Object): def __init__(self, *args, **kwargs): super().__init__(*args, **kwarg) # Valid numbers, should be highlighted valid_numbers = (1000 + 1_000 + 1_000_000 + 0x1000 + 0x_10 + 0x10_00 + 0b1000 + 0b_10 + 0b1_0 + 0o1000 + 0o_10 + 0o10_00) # Invalid numbers (raise SyntaxError), must not be highlighted invalid_numbers = (1000l + 1000L + 0x10L + 0b10L + 0o10L + 0100 + 1__000 + _100 + 100_ + 0x10_ + 0b10_ + + 0o10_) # Soft keywords match 1: case 1: case = match() case _: re.match(r"*", case) 070701000002A8000081A400000000000000000000000166590806000006A0000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.rb#!/usr/bin/env ruby # puts "\141" # Shows 'a' str = 'lorem' puts str[1..3] # 'ore' # Escape sequences "\a \b \t \n \v \f \r \e \s" # control characters "\\" # backslash "\1 \11 \111" # octal "\xa \xA1" # hexadecimal "\u9aBC \u{1} \u{10FFFF} \u{123 abc DEF}" # unicode "\ca \c" \c\1 \c\11 \c\111 \c\xa \c\xA1" # control characters "\C-a \C-" \C-\1 \C-\11 \C-\111 \C-\xa \C-\xA1" # control characters "\M-a \M-" \M-\1 \M-\11 \M-\111 \M-\xa \M-\xA1" # meta characters "\M-\ca \M-\c" \M-\c\1 \M-\c\11 \M-\c\111 \M-\c\xa \M-\c\xA1" # meta control characters "\c\M-a \c\M-" \c\M-\1 \c\M-\11 \c\M-\111 \c\M-\xa \c\M-\xA1" # meta control characters "\M-\C-a \M-\C-" \M-\C-\1 \M-\C-\11 \M-\C-\111 \M-\C-\xa \M-\C-\xA1" # meta control characters "\C-\M-a \C-\M-" \C-\M-\1 \C-\M-\11 \C-\M-\111 \C-\M-\xa \C-\M-\xA1" # meta control characters # Invalid escape sequences "\xg" "\ug \u{1234567}" "\C \c\C-a \C-\ca \c\M-\ca \c\M-\C-a \C-\M-\ca" "\M \M-\M-a \M-\c\M-a \M-\C-\M-a" # Number methods puts -11.to_s + ' ' + 0x11.to_s + ' ' + 1.1.to_s + ' ' + ?a.to_s + ' ' + 1.x 11.1aa # not a valid method name # Ranges 11..11, 0x11..0x11, 01..07, ?a..?f, 1.1..2.2 # range incl. the last value 11...11, 0x11...0x11, 01...07, ?\x40...?\101, 1.1...2.2 # range excl. the last value 11....111, 0x11......0x11, 1.1.....1.2 # 4 (and more) dots are not a valid range # Character literals str = ?\x41 + ?\101 # == 'AA' puts ?\M-\C-x # xml == '\tlorem\n\tipsum' xml = <<-XML lorem ipsum XML # xml == 'lorem\nipsum' xml = < PREFIX rdf: PREFIX xsd: ASK WHERE { t:double1 rdf:value ?l . t:float1 rdf:value ?r . FILTER ( datatype(?l + ?r) = xsd:double ) } 070701000002AA000081A400000000000000000000000166590806000003E3000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.rs// Comment enum Animal { Dog, Cat } struct Point { x: i32, y: i32 } union MyUnion { f1: u32, f2: f32, } macro_rules! hello_world_macro { () => ( println!("Hello World!"); ) } fn main() { let _i8: i8 = 1i8; let _i16: i16 = 1i16; let _i32: i32 = 1i32; let _i64: i64 = 1i64; let _isize: isize = 1isize; let _u8: u8 = 1u8; let _u16: u16 = 1u16; let _u32: u32 = 1u32; let _u64: u64 = 1u64; let _usize: usize = 1usize; let _f32: f32 = 3.14f32; let _f64: f64 = 3.14f64; let valid_binary = 0b0_10; let invalid_binary= 0b0_13; let valid_octal = 0o3_45; let invalid_octal = 0o3_49; let valid_hexadecimal = 0x9_AF; let invalid_hexadecimal = 0x9_AZ; let valid_char: char = 'a'; let invalid_char = 'ab'; let valid_byte: u8 = b'a'; let invalid_byte = b'ab'; hello_world_macro!(); } #[ /* a comment that contains ] */ derive(Clone) ] struct Foo; fn main() {} 070701000002AB000081A4000000000000000000000001665908060000035A000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.rstHeading ======= These are some addresses: http://www.gnome.org/ and https://www.gnome.org/ and git://git.gnome.org/gtksourceview and ssh://USERNAME@git.gnome.org/git/gtksourceview and gnome-list@gnome.org is the last one. Subheading ---------- - First ``literal`` Item - Second **bold** Item - Subitem `interpreted` - Third *emphasis* Item This is a sentence with a `http-link `_, a `https-link `_, and a footnote [#]_. .. image:: images/example.png :height: 100px :width: 100 px | A line block > A quote block .. This is a block that will be shown. .. This is a comment and will not be shown. .. math:: \lambda = \lambda_{0} + \arctan \left[ \frac{x}{-y} \right] `This is the first item` `This is the second item`_ `This is the third item`_ `This is the fourth item` 070701000002AC000081A40000000000000000000000016659080600000123000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.sci// A comment with whites and tabulations // Scilab editor: http://www.scilab.org/ function [a, b] = myfunction(d, e, f) a = 2.71828 + %pi + f($, :); b = cos(a) + cosh(a); if d == e then b = 10 - e.field; else b = " test " + home return end myvar = 1.23e-45; endfunction 070701000002AD000081A40000000000000000000000016659080600004325000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.scss/* * css extensions */ div { color: #00ff00; // nested rules #inner.element { color: #000000; } // parent selector &:hover { text-decoration: underline; } body.firefox & { font-weight: normal; } &__element, // compound selector &--modifier { border: 1px; } // nested properties font: { family: fantasy; size: 30em; weight: bold; } font: 20px/24px fantasy { weight: bold; } } /* * variables, data types */ // assignment $number: 5em; $double-quoted-string: "foo"; $single-quoted-string: 'bar'; $not-quoted-string: baz; $true: true; $false: false; $null: null; $parens-spaces-list: (left top); $parens-commas-list: (1px, 2px, 3px, 4px); $no-parens-spaces-list: 1px 2px 3px 4px; $no-parens-commas-list: arial, some-other-arial, sans-serif; $trailing-comma: (1 2 3,); // a comma-separated list containing a space-separated list $bracketed-list: [5rem 6rem 7rem]; // comma or space separated $map: (medium: 640, 'large': 960, "x-large": 1280, (xx-large,): 1600); // must have parentheses, be comma separated $color: #fe57a1; $function-reference: get-function($function-name); // assign if not already assigned to $var: 1 !default; #main { // global variable defined in block $width: 5em !global; // list of parent selectors $selectors: &; // property value width: $width; } // media feature test @import "foo" ($orientation: $landscape); @media ($orientation-landscape) { width: 700px; } // font feature value @font-feature-values Font One { @styleset { nice-style: $value; } } // @supports test @supports ($animation-name: $test) { body { animation-name: test; } } @supports ($animation-name-test) { body { animation-name: test; } } // @at-root query @at-root ($type: $value) { .top-level { background: pink; } } @at-root ($query) { .top-level { background: pink; } } /* * operations */ body { // arithmetic operators width: (1px + (2em - 3rem)) * (4 / 5vh) % 6cm; // plain css font: 10px/8px; font: (italic bold 10px/8px); font: #{$font-size}/#{$line-height}; // division width: $width/2; width: round(1.5)/2; width: (500px/2); width: 5px + 8px/2px; // minus sign animation-name: a-1; // identifier margin: (5px - 3px) 5px-3px 3-2 (1 -$var); // subtraction margin: 1 -3em; // negative number margin: -$var -(1); // unary negation // string concatenation content: "Foo " + Bar; // "Foo Bar" font-family: sans- + "serif"; // sans-serif } // string concatenation in string-only context @keyframes ('foo' + bar) {} @keyframes (foo + "bar") {} // comparison operators, logical operators $a: (1 < 2 and 1 > 2) or (1 <= 2) and 1 >= 2; $b: not (1 == 2) and 1 != 2; /* * interpolation */ // selector #{'#id'}, #i#{'d'}, #{'.whole-class'}, .part-#{'class'}-fragment, #{'div'}, d#{'i'}v, [#{'attr'}=#{'value'}], [#{'attr="value"'}], #{'[attr="value"]'}, #{'::selection'}, ::se#{'lect'}ion, #{':hover'}, :ho#{'v'}er, #{':lang(fr)'}, :la#{'ng(f'}r), :lang(#{'fr'}), :nth-child(#{'2n+1'}), :not(#{'div'}), #{'%placeholder'}, %pla#{'ceho'}lder { // property name #{'font'}: serif; background-#{'image'}: none; border-#{'bottom'}-width: 0; // property value font-size: #{$font-size}; font-family: #{'arial, sans'}-serif; width: #{5 * (3 - 1)}px; // function name background: #{'url'}('image.png'); background: u#{'r'}l('image.png'); // !important (exclamation mark needs to be inside) width: 0 #{'!important'}; width: 0 #{'!import'}ant; // inside strings content: "#{$var}"; content: '#{$var}'; // inside comments /* multi-line: #{$yes} */ // single line: #{$no} } // media type, feature test @import "foo" #{'screen'} and (#{'orientation'}: #{'landscape'}); @media #{'screen'} and (#{'orientation: landscape'}) { width: 700px; } // font name, font feature custom name, font feature value @font-feature-values #{'Font One'} { @styleset { // interpolation not accepted here? #{'nice-style'}: #{12}; } } // inside @import url() @import url("http://fonts.googleapis.com/css?family=#{$family}"); // @keyframes name, selector @keyframes #{'myanim'} { #{'from'} { width: 0; } #{'to'} { width: 100%; } } // @namespace name @namespace #{'svg'} url('http://www.w3.org/2000/svg'); // @page custom name, pseudo-page @page #{'toc'}, #{':first'} { margin: 0; @left-top { // interpolation not accepted here? content: ''; } } // @supports test @supports (#{'animation-name'}: #{'test'}) { body { animation-name: test; } } @supports (#{'animation-name: test'}) { body { animation-name: test; } } // @at-root query @at-root (#{'without'}: #{'media'}) { .top-level { background: pink; } } @at-root (#{'without: media'}) { .top-level { background: pink; } } /* * functions */ body { // single line comments not parsed inside url() background: url(http://example.com/styles.css); // keyword arguments color: hsl($hue: 0, $saturation: 100%, $lightness: 50%); } /* * at-rules */ // @import @import "rounded-corners", "text-shadow"; // multiple files #main { // nested @import @import "example"; // but not inside mixins or control directives } // nested @media @media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } } // @extend #main { @extend #hello; // no error if not found @extend .from[the="other-side"] !optional; // placeholder selector @extend div%placeholder; } // @at-root #main { @at-root .child { color: red; } } @media screen { @supports (font-variant-alternates: styleset(nice-style)) { @at-root (without: media supports) { // with query .absolutely-top-level { background: pink; } } } } // @if/@else if/@else, @debug/@warn/@error @if $num-errors == 0 { @debug "$num-errors is 0"; } @else if $num-errors > 0 { @warn "oops there are #{$num-errors} errors"; } @else { @error "negative errors?!?"; } // @for @for $i from 1 through 3 { .item-#{$i} { width: 2em * $i; } } // @each @each $animal in puma, sea-slug, egret, salamander { .#{$animal}-icon { background-image: url('/images/#{$animal}.png'); } } @each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) { #{$header} { font-size: $size; } } // @while $i: 6; @while $i > 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; } // @mixin @mixin large-text { font-size: 128px; } @mixin sexy-border($color, $width: 1in) { // with arguments, default value border: { color: $color; width: $width; } } @mixin box-shadow($shadows...) { // variable arguments box-shadow: $shadows; } @mixin apply-to-ie6-only { // accepts content block * html { @content; } } // @include .page-title { @include large-text; } p { @include sexy-border(blue); // with arguments } div { @include sexy-border($color: blue, $width: 10cm); // keyword arguments } .primary { @include box-shadow($shadows...); // expand list into arguments } @include apply-to-ie6-only { // passing content block display: block; #main { background: black; } } // @function @function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; } #sidebar { width: grid-width(5); } /* * test cases */ .declarations-or-selectors { // declarations display:block; font-family:arial; font: { family: fantasy; weight: bold; } font: fantasy { size: 20px; style: italic; } #{$property}:block; #{$property}: { color: red; } color:#000; width:#{$width}; background-image:url( \( \) \{ { ); background-image:url( url( { ) ); background-image:url( #{ \) ')' '{' } ); background-image:url( /* ) { */ ); background-image:url( " ) { " ); background-image:url( ' ) { ' ); margin:#{ \#\{ \} \{ '{' } 10px; margin:#{ url( } { ) } 10px; margin:#{ #{ '{' } } 10px; margin:#{ /* } { */ } 10px; margin:#{ " } { " } 10px; margin:#{ ' } { ' } 10px; display:/* { */block; display:/* #{ \*\/ '{' } */block; font-family:" \" \{ { ", serif; font-family:" #{ \" "" '{' } ", serif; font-family:' \' \{ { ', serif; font-family:' #{ \' '' '{' } ', serif; // incorrectly highlighted declarations display:block ; display:block // { ; // selectors input:focus { opacity: 0.5; } div:nth-child(2n+1) { background-color: gray; } div:-moz-full-screen { display: block; } #{$selector}:focus { color: blue; } a:#{$state} { color: blue; } a:focus[id=\]\; ] { color: blue; } a:focus[id=#{ \] "]" ";" }] { color: blue; } a:focus[id=/* ] ; */ ] { color: blue; } a:focus[id=" ] ; " ] { color: blue; } a:focus[id=' ] ; ' ] { color: blue; } a:#{ \#\{ \} \; ';' } { color: blue; } a:#{ url( } ; ) } { color: blue; } a:#{ #{ ';' } } { color: blue; } a:#{ /* } ; */ } { color: blue; } a:#{ " } ; " } { color: blue; } a:#{ ' } ; ' } { color: blue; } a:/* ; */focus { color: blue; } a:/* #{ \*\/ ';' } */focus { color: blue; } a:focus[id=" ] \" \; ; "] { color: blue; } a:focus[id=" #{ \" "" ';' } "] { color: blue; } a:focus[id=' ] \' \; ; '] { color: blue; } a:focus[id=' #{ \' '' ';' } '] { color: blue; } a:focus // ; { color: blue; } } // from file.css //@charset "UTF-8"; // Sass follows the CSS spec when reading stylesheets, // so any @charset rule must be at the top of the document /* * general */ /* whitespace */ #main { color:aqua; float: left!important; margin : 0 ; width : 100% ! important ; } /* case insensitivity */ Body { FONT: 12Px/16pX iTaLiC sans-SERIF; } /* * selectors */ /* simple selectors */ #testID, /* id */ .someclass, /* class */ div, /* type */ *, /* universal */ [lang|="zh"], /* attribute */ [ /* comment */ lang /* comment */ |= /* comment */ "zh" /* comment */ ] { color: black; } /* combinators */ header + main, /* adjacent sibling */ li ~ li, /* general sibling */ ul > li, /* child */ ul ul { /* descendant */ color: blue; } /* pseudo-elements */ :after, ::/* comment */after, ::placeholder, ::/* comment */selection { color: green; } /* pseudo-classes */ :hover, :/* comment */required, :lang(fr), :/* comment */not(div#sidebar.fancy), :nth-child(n+1), :/* comment */nth-last-child(-2n /* comment */ - /* comment */ 30), :nth-of-type(5), :/* comment */nth-last-of-type(even) { color: yellow; } /* pseudo-classes with invalid arguments */ :not(div:before), /* pseudo-element */ :not(input::placeholder), /* pseudo-element */ :not(p:not(:lang(en))), /* nested :not */ :nth-child(1.2n), /* non-integer */ :nth-child(.5), /* non-integer */ :nth-child(n+-1) { /* number sign */ color: red; } /* namespace qualified */ a, /* type in default namespace */ svg|a, /* type in specified namespace */ |a, /* type in no namespace */ *|a, /* type in all namespaces (including no namespace) */ svg|*, /* universal */ svg|[fill] { /* attribute */ color: white; } /* * basic data types */ #main { /* angle */ transform: rotate(+33.333e+3deg); /* color */ color: #f00; color: #f00f; /* #rgba */ color: #ff0000; color: #ff0000ff; /* #rrggbbaa */ color: red; color: lightgoldenrodyellow; color: rebeccapurple; color: currentColor; /* frequency (not currently used for any property) */ content: 44.1kHz; /* integer */ z-index: +255; z-index: -1; /* length */ width: 10px; width: 10.5rem; width: -10e-2vw; /* number */ opacity: .5; opacity: 0.3333; opacity: 1; opacity: 2e-34; /* percentage */ width: 100%; /* string */ content: "double quoted"; content: 'single quoted'; /* time */ transition-duration: .4s; transition-duration: 400ms; /* unicode range */ unicode-range: U+0025-00FF; unicode-range: U+4??; /* wildcard range */ } /* ratio */ @media (min-aspect-ratio: 16/9) {} @media (min-aspect-ratio: 16 /* comment */ / /* comment */ 9) {} /* resolution */ @media (min-resolution: +2.54dpcm) {} /* * identifiers */ /* leading hyphens */ #-here.-there, #-- .--everywhere { /* two hyphens: https://stackoverflow.com/a/30822662 */ color: blue; } /* non-ASCII */ #español, #你好, .❤♫ { color: green; } /* invalid identifiers */ #1id, /* starts with digit */ .-2class { /* starts with hyphen digit */ color: maroon; } /* * escaping */ /* selectors */ #\..\+\ space\@\>, /* special character escape */ #\E9 dition .\0000E9dition .motion_\e9motion, /* Unicode character escape */ .\e33 div, /* trailing space terminates Unicode character escape */ .\e33 div, /* extra space to denote separate tokens */ .\31 23 { /* escape leading digit of identifier */ /* property value */ content: "\E9 dition \ \"\0000E9dition\" \ \e9motion"; font-family: \E9 dition, \"\0000E9dition\", \e9motion; /* function name */ background: \u\72\l(image.png); } /* * functions */ #main { /* url */ background: url("image.svg"); /* function argument keywords */ background-image: linear-gradient(to left top, #fff, blue); grid-template-columns: repeat(2, minmax(max-content, 300px) 1fr) 100px; } /* * style properties */ #main { /* svg */ fill: url(#pattern); text-rendering: optimizeLegibility; /* css3 */ font-variant-east-asian: jis04; size: letter; transition-timing-function: ease-in; /* animatable */ transition-property: height, font-size, visibility; /* custom properties */ --my-custom-color: #fff; color: var(--my-custom-color); background-color: var(--my-custom-color, white); } /* * modifiers */ body { background: pink !important; } /* * media queries */ @media screen, (orientation: portrait) {} @media not (print and (min-monochrome: 16) and (color)) {} @media only screen {} @media not print {} /* * at-rules */ /* @font-face */ @font-face { font-family: MyHelvetica; src: local("Helvetica Neue"), local("HelveticaNeue"), url(MgOpenModerna.ttf); } /* @font-feature-values */ @font-feature-values Font One { @styleset { nice-style: 12; } } .nice-look { font-variant-alternates: styleset(nice-style); } /* @import */ @import URL("fineprint.css"); @import 'custom.css'; @import url('landscape.css') screen and (orientation: landscape), print; /* @keyframes */ @keyframes myanim { from { opacity: 0.0; } 50% { opacity: 0.5; } to { opacity: 1.0; } } /* @media */ @media all { body { background: gray; } @media screen, (orientation: portrait) { body { background: grey; } } } /* @namespace */ @namespace "http://www.w3.org/1999/xhtml"; @namespace svg url(http://www.w3.org/2000/svg); /* @page */ @page { bleed: 1cm; } @page toc, :blank { margin: 2cm; marks: crop cross; } @page index:/*comment*/left { size: A4; @top-right { content: "Page " counter(page); } } /* @supports */ @supports (animation-name: test) { @keyframes 'my-complicated-animation' { 0% { width: 0; } 100% { width: 100%; } } } /* * vendor-specific extensions */ /* pseudo-elements */ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::/* comment */-webkit-inner-spin-button { -webkit-appearance: none; } input[type="number"] { -moz-appearance: textfield; } /* pseudo-classes */ #page:-moz-full-screen, #page:/* comment */-ms-fullscreen, #page:-webkit-full-screen { background: silver; } /* functions */ .footer { background-image: -webkit-linear-gradient(to left top, #fff, blue); } /* style properties */ #sidebar { -ms-overflow-style: -ms-autohiding-scrollbar; } @supports not ((text-align-last: justify) or (-moz-text-align-last: justify)) { body { text-align: justify; } } /* at-rules */ @-webkit-keyframes myanim { from { opacity: 0.0; } 50% { opacity: 0.5; } to { opacity: 1.0; } } 070701000002AE000081A40000000000000000000000016659080600000A4C000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.sh#!/bin/bash echo "Hi there!" # Parameter Expansion xxx${xxx}xxx # Parameter in braces xxx${var/\"}xxx xxx${var/\}}xxx # Escaped characters xxx${var\ /x/y} # Line continue xxx$0000 # One digit parameter xxx$-xxx xxx$$xxx xxx$@xxx # Special parameters xxx$_param-xxx # Starting with '_' xxx${!array[@]}xxx${#array[-1]}xxx${array[0x1+var/2*$(cmd)]/a/b}xxx # Arrays xxx${parameter:-word}xxx${parameter-word}xxx # Use Default Values xxx${parameter:=word}xxx${parameter=word}xxx # Assign Default Values xxx${parameter:?word}xxx${parameter?word}xxx # Indicate Error if Null or Unset xxx${parameter:?}xxx${parameter?}xxx # Indicate Error if Null or Unset xxx${parameter:+word}xxx${parameter+word}xxx # Use Alternative Value xxx${#parameter}xxx # String Length xxx${!parameter}xxx # Names matching prefix xxx${parameter%word}xxx # Remove Smallest Suffix Pattern xxx${parameter%%word}xxx # Remove Largest Suffix Pattern xxx${parameter#word}xxx # Remove Smallest Prefix Pattern xxx${parameter##word}xxx # Remove Largest Prefix Pattern xxx${x:-$(ls ~/*)}xxx xxx${posix:?}xxx xxx${3:+posix}xxx xxx${#HOME}xxx xxx${x%.c}.oxxx xxx${x#$HOME}xxx xxx${x##*/}xxx xxx${x#*}xxx xxx${x#"*"}xxx # Variable definitions var1=val1; var2=val2 array[0x1+var/2*$(cmd)]+=val3 if var=$(cmd); then some; fi test -f xxx && var=xxx || var=yyy echo text | var=xxx cmd & var=yyy declare -i '-r' "-x" var1=val1 var2=$val1 var3=`cmd1` \ var4=$(cmd2) var5=xxx\ yyy var6=("${ar[@]}") var7 # Comment var+=xxx; (var=yyy); { var=zzz; } case $1 in item) var=xxx;; *)declare var=yyy;; esac # For statements for word in hello world do echo $word done for arg; do echo $arg; done for \ arg; do echo $arg; done # Generic command (e.g. echo) echo for case grep $var ${var/x/y} $(cmd) `cmd` \ 'a' "b" \\ | grep 'pattern' > >(tee file) echo echo; echo echo & echo echo echo # Redirections > >> 1>&2 &> 3>&- < 0<&3 3<&- 3<> 1>| cmd<<<"$var" # Here String cmd<= 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 070701000002B0000081A40000000000000000000000016659080600000653000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.sql-- this is a comment CREATE TABLE table_name ( column1 integer NOT NULL PRIMARY KEY, column2 varchar(20), column3 timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, column4 smallint CHECK (column4 >= 0), CONSTRAINT ct1 CHECK (column4 IS NULL OR column2 IS NOT NULL) ); INSERT INTO table_name (column1, column2, column3, ...) VALUES (value1, value2, value3, ...); BEGIN; UPDATE table_name SET balance = balance - 100.0 WHERE account_id = '123'; -- more comment COMMIT; -- Oh no! ROLLBACK; -- Too late... We're $100 shorter... CREATE TABLE other_table ( col1 int4 NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col2 integer REFERENCES table_name ); -- Except first column, all others are fp numbers. (Dot should be included -- in highlight). Note that last column is actually a sum of two floats, -- so '+' should not be matched as part of exponential notation. SELECT 1, 1., 1.1, .1, 1e, 1e2, 1.1e2, 12.12e-3, 7.e+4, 7.e+4.0; -- 7e55 at the end is part of an identifier, not a floating point number. -- (Whole identifier should be in one color). CREATE DOMAIN no_realI7e55 AS integer CHECK (VALUE > 0); -- CREATE OR REPLACE FUNCTION is one statement, so REPLACE should NOT be -- highlighted as function name. CREATE OR REPLACE FUNCTION function_name(IN param_name integer) RETURNS integer LANGUAGE plpgsql AS $$ DECLARE ctr integer; BEGIN -- 20..0 is a range, not two consecutive floats, so both dots should NOT -- be selected as part of any fp number and both numbers should be selected -- as integers. FOR ctr IN REVERSE 20..0 BY 2 LOOP RAISE NOTICE 'Counter: %', ctr; END LOOP; END; $$ 070701000002B1000081A40000000000000000000000016659080600000152000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.str#File level comment data_blockname _key1 value1 #key-value pair _key2 value2#notacomment save_frame loop_ # tabular data _col1 _col2 value1 "valu"e2" 'O'Connor' #string rules ; multi-line strings need semi-colon delimiters ; stop_ # This is NMR-STAR syntax save_ global_ _key1 valueGlobal 070701000002B2000081A40000000000000000000000016659080600000440000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.svhclass my_class extends some_class; // This is a comment. /* This is also a comment, but it contains keywords: bit string, etc */ // Some types. string my_string = "This is a string"; bit [3:0] my_bits = 4'b0z1x; integer my_integer = 32'h0z2ab43x; real my_real = 1.2124155e-123; shortreal my_shortreal = -0.1111e1; int my_int = 53152462; extern function bit my_function( int unsigned something); endclass : my_class function bit my_class::my_function( int unsigned something); /* Display a string. * * This is a slightly awkward string as it has * special characters and line continuations. */ ("Display a string that continues over multiple lines and contains special characters: \n \t \" \'"); // Use a system task. my_int = (my_bits); // (); // Commenting a system task. // my_function(); // Commenting a function. endfunction my_function program test(); my_class c; c = new(); c.my_function(3); endprogram : test 070701000002B3000081A400000000000000000000000166590806000013F8000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.t2tTXT2TAGS SAMPLE Aurelio Jargas %%mtime(%m/%d/%Y) %!encoding: UTF-8 This text is before the introduction. But it's OK. = Introduction = Welcome to the txt2tags sample file. Here you have examples and a brief explanation of all marks. The first 3 lines of this file are used as headers, on the following format: ``` line1: document title line2: author name, email line3: date, version ``` Lines with balanced equal signs = around are titles. % a secret comment! %TODO link to program site http://txt2tags.org = Fonts and Beautifiers = We have two sets of fonts: The NORMAL type that can be improved with beautifiers. The TYPEWRITER type that uses monospaced font for pre-formatted text. We will now enter on a subtitle... == Beautifiers == The text marks for beautifiers are simple, just as you type on a plain text email message. We use double *, /, - and _ to represent **bold**, //italic//, --strike-- and __underline__. The **//bold italic//** style is also supported as a combination. == Pre-Formatted Text == We can put a code sample or other pre-formatted text: ``` here is pre-formatted //marks// are **not** ``interpreted`` ``` And also, it's easy to put a one line pre-formatted text: ``` prompt$ ls /etc Or use ``pre-formatted`` inside sentences. == More Cosmetics == Special entities like email (duh@somewhere.com) and URL (http://www.duh.com) are detected automagically, as long as the horizontal line: -------------------------------------------------------- ^ thin or large v ======================================================== You can also specify an [explicit link http://duh.org] or an [explicit email duh@somewhere.com] with label. And remember, A TAB in front of the line does a quotation. More TABs, more depth (if allowed). Nice. = Lists = A list of items is natural, just putting a **dash** or a **plus** at the beginning of the line. == Plain List == The dash is the default list identifier. For sublists, just add **spaces** at the beginning of the line. More spaces, more sublists. - Earth - America - South America - Brazil - How deep can I go? - Europe - Lots of countries - Mars - Who knows? The list ends with **two** consecutive blank lines. == Numbered List == The same rules as the plain list, just a different identifier (plus). + one + two + three - mixed lists! - what a mess + counting again + ... + four == Definition List == The definition list identifier is a colon, followed by the term. The term contents is placed on the next line. : orange a yellow fruit : apple a green or red fruit : other fruits - wee! - mixing lists + again! + and again! = Tables = Use pipes to compose table rows and cells. Double pipe at the line beginning starts a heading row. Natural spaces specify each cell alignment. | cell 1.1 | cell 1.2 | cell 1.3 | | cell 2.1 | cell 2.2 | cell 2.3 | | cell 3.1 | cell 3.2 | cell 3.3 | || heading 1 | heading 2 | heading 3 | | cell 1.1 | cell 1.2 | cell 1.3 | | cell 2.1 | cell 2.2 | cell 2.3 | |_ heading 1 | cell 1.1 | cell 1.2 | | heading 2 | cell 2.1 | cell 2.2 | | heading 3 | cell 3.1 | cell 3.2 | |/ heading | heading 1 | heading 2 | | heading 1 | cell 1.1 | cell 1.2 | | heading 2 | cell 2.1 | cell 2.2 | Without the last pipe, no border: | cell 1.1 | cell 1.2 | cell 1.3 | cell 2.1 | cell 2.2 | cell 2.3 | cell 3.1 | cell 3.2 | cell 3.3 || heading 1 | heading 2 | heading 3 | cell 1.1 | cell 1.2 | cell 1.3 | cell 2.1 | cell 2.2 | cell 2.3 |_ heading 1 | cell 1.1 | cell 1.2 | heading 2 | cell 2.1 | cell 2.2 | heading 3 | cell 3.1 | cell 3.2 |/ heading | heading 1 | heading 2 | heading 1 | cell 1.1 | cell 1.2 | heading 2 | cell 2.1 | cell 2.2 = Special Entities = Because things were too simple. == Images == The image mark is as simple as it can be: ``[filename]``. [img/photo.jpg] And with some targets the image is linkable : [[img/photo.jpg] http://www.txt2tags.org] - The filename must end in PNG, JPG, GIF, or similar. - No spaces inside the brackets! == Other == When the target needs, special chars like <, > and & are escaped. The handy ``%%date`` macro expands to the current date. So today is %%date on the ISO ``YYYYMMDD`` format. You can also specify the date format with the %? flags, as ``%%date(%m-%d-%Y)`` which gives: %%date(%m-%d-%Y). That's all for now. ------------------------------------------------------- %%% TRANSLATOR: Uncomment and translate the next two lines %Translated by John Smith. %------------------------------------------------------- [img/t2tpowered.png] ([%%infile %%infile]) % vim: tw=55 070701000002B4000081A4000000000000000000000001665908060000012D000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.tera{% extends "master" %} {% set_global number = 1.2 + 4 %} {% import "macros.tera" as macros %} {# Comment #} {{ macros::input(label="Name", type="text") }} {% set result = call_function() %} {{ type|first }}

{{ value|join(sep=",") }}

070701000002B5000081A400000000000000000000000166590806000000B5000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/tests/syntax-highlighting/file.testv1// gtk-source-lang: testv1 // comment /* comment! */ "A string" 'And a string too' bambom - keyword, Others bumbam - keyword, Others2 kwkw - keyword, Keyword Numbers: 0.1 1234 0233 070701000002B6000081A40000000000000000000000016659080600000042000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.tex\documentclass{article} \begin{document} Hi there! \end{document} 070701000002B7000081A400000000000000000000000166590806000002BC000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.texi\input texinfo @setfilename manual @settitle manual @titlepage @title manual @c The following two commands start the copyright page. @page @vskip 0pt plus 1filll @insertcopying @end titlepage @c Output the table of contents at the beginning. @contents @ifnottex @node Top @top manual @insertcopying @end ifnottex @menu * MooScript:: MooScript - builtin scripting language. * Index:: Index. @end menu @node MooScript @chapter MooScript @cindex chapter, first This is the first chapter. @cindex index entry, another Here is a numbered list. @enumerate @item This is the first item. @item This is the second item. @end enumerate @node Index @unnumbered Index @printindex cp @bye 070701000002B8000081A4000000000000000000000001665908060000027E000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.tf// This is a sample Terraform file /* Block comment */ # shell like comment variable enabled { default = "this is a variable" type = string } locals { list = [ "this", "is", "a", "list", ] } data data_type label { id = 1 } resource resource_type resource_name { for_each = var.enabled == false ? [] : toset([for item in var.list: "this is the item ${item}" if item != '' ]) description =<<-EOF this is a heredoc EOF dynamic test { } } 070701000002B9000081A4000000000000000000000001665908060000035E000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.toml# comment [table] key = "value - #" # " comment " lines = ''' multi lines ''' [tabel] #comment date = 1979-05-27T00:32:00.999999-07:00 #date int = 1_000 float = 6.626e-34 hex = 0xDEADBEEF oct = 0o012345 bin = 0b11010110 inf = inf # positive infinity [[more.complex."array#"]] "key" = "text" multi_line_array = [ "]", # ] comment ] ="no key name" #not allowed [...fasd...] # INVALID [a.] # INVALID [a..b] # INVALID [.b] # INVALID [.] # INVALID [error] if you didn't catch this, your parser is broken key = # INVALID string = "after value error" like this array = [ "This might most likely happen in multiline arrays", Like here, "or here, and here" ] End of array comment, forgot the # number = 3.14 pi <--again forgot the #070701000002BA000081A400000000000000000000000166590806000090E7000000000000000000000000000000000000003700000000gtksourceview-5.12.1/tests/syntax-highlighting/file.ts/* * Type */ // Predefined type let a: any; let a: bigint; let a: boolean; let a: null; let a: number; let a: object; let a: string; let a: symbol; let a: undefined; let a: unknown; let a: void; function fn(): never { throw new Error(); } // Parenthesized type let a: ( string ); // Type reference let a: T; let a: Super.Sub; // Bullt-in constructors let a: Array; let a: Function; let a: RegExp; // Built-in utility types let a: ReadonlyArray; let a: Partial; let a: Readonly; // Import type let a: import("module").ModuleType; // Object type let a: { // Property signature property; property?, property: string; readonly property?: string, // Call signature (); (x), (this: void, x: number, y?: string): void; (x, ...rest): void, // Construct signature new (); new (x), new (x: number, y?: string): void; new (x, ...rest): void, // Index signature [index: number]: string; readonly [prop: string]: any, // Method signature method(); method?(x), method?(this: void, x: number, y?: string): void; method(x, ...rest): void, // Mapped type readonly [P in keyof T]?: T[P], -readonly [P in keyof T]+?: T[P]; }; // Array type let a: string[]; // Indexed access type (lookup type) let a: MyType["property"]; // Tuple type let a: [string]; let a: [string, number, boolean?]; // optional element let a: [string, ...number[]]; // rest elements // Type query let a: typeof Super.Sub; // This type let a: this; // String literal type let a: 'string'; let a: "string"; // Numeric literal type let a: 123; let a: 0b0101; let a: 0o777; let a: 0xfff; // Numeric separators with BigInt let a: 1_23n; let a: 0b01_01n; let a: 0o7_77n; let a: 0xf_f_fn; // Boolean literal type let a: true; let a: false; // "unique symbol" subtype let a: unique symbol; let a: unique /* comment */ symbol; let a: unique /* comment */ symbol; let a: unique // comment symbol; // Union / intersection type let a: string | number; let a: string & number; // Type predicate (user-defined type guard) function isString(x: any): x is string {} // "asserts" type predicate (for assertion functions) declare function assert(value: unknown): asserts value; declare function assertIsArrayOfStrings(obj: unknown): asserts obj is string[]; declare function assertNonNull(obj: T): asserts obj is NonNullable; // Indexed type query (keyof) let a: keyof T; // Conditional type let a: T extends number ? number[] : T[]; // Type inference (in conditional types) let a: T extends (infer U)[] ? U : any; let a: T extends (...args: any[]) => infer U ? U : any; let a: T extends Promise ? U : any; // Function type let a: () => void; let a: (x) => T; let a: (this: void, x: number, y?: string) => string; // this parameter let a: (x, ...rest) => T; let a: ([x, y]: [string, number], [z, w]: [string, number]) => void; let a: ({ x: a, y: b }: { x: number, y: string }, { z: c, w: d }: { z: number, w: string }) => void; // Constructor type let a: new () => void; let a: new (x) => T; let a: new (x: number, y?: string) => string; let a: new (x, ...rest) => T; let a: new ([x, y]: [string, number], [z, w]: [string, number]) => void; let a: new ({ x: a, y: b }: { x: number, y: string }, { z: c, w: d }: { z: number, w: string }) => void; // Readonly array / tuple types let a: readonly string[]; let a: readonly /* comment */ [string, string]; // Parenthesized type vs parameters list of function type // Parenthesized let a: (string); let a: ((string: string) => void); // Parameters list let a: (string: string) => void; let a: (string? : string) => void; let a: (string /* comment */ , string) => void; let a: (this: void) => void; let a: (this /* comment */ : void) => void; let a: (...string : string[]) => void; // Incorrectly highlighted let a: (string : string) => void; let a: (this /* comment */ : void) => void; let a: (... string : string[]) => void; let a: (... /* comment */ string : string[]) => void; /* * Type parameters */ function fn() {} function fn() {} function fn() {} function fn() {} /* * Type parameters (for arrow function) / type assertion (cast) */ // Type parameters a = (x) => x; a = (x) => x; a = (x) => x; a = (x) => x; // Type assertion a = obj; a = "abc"; // const assertion /* * Type arguments */ fn(); fn(); /* * Type annotation */ let a: string; /* * TypeScript-specific operators */ /* as operator (type assertion / cast) */ ( obj as string ); ( "abc" as const ); // const assertion // Non-null assertion operator (post-fix !) ( a!.method() ); /* * TypeScript-specific statements and declarations */ /* @ts-ignore / @ts-expect-error comment pragmas */ // Valid pragmas //@ts-ignore /// @ts-expect-error add reason here { // @ts-ignore } /* @ts-expect-error*/ /* * @ts-ignore more reasons */ // Invalid pragmas // @ ts-ignore /// @TS-IGNORE /* @ts-expect-error */ /* @ts-nocheck comment pragmas */ // Valid pragmas //@ts-nocheck /// @TS-NOCHECK text here // Invalid pragmas // @ ts-nocheck //// @ts-nocheck /* @ts-nocheck */ { // @ts-nocheck } /* Triple-slash directives */ // Valid directives /// /// /// /// // Invalid directives /// comment /// /// /// /// /// /// /// /// { /// } /* Decorators (experimental, stage 2 proposal) */ // Class decorator @sealed class Greeter { // Property decorator @(Deco) /* comment */ . /* comment */ utils /* comment */ () /* comment */ . /* comment */ format /* comment */ ("Hello, %s") greeting: string; // Method decorator @ /* comment */ (foo = 'bar', false || validate) greet(@required name: string) { // Parameter decorator return "Hello " + name + ", " + this.greeting; } constructor(@required public greeting: string) {} // Parameter decorator // Accessor decorator @configurable(false) /* comment */ get x() { return this._x; } } /* Ambient declaration */ declare let a; declare const a: number, b: string; declare function fn(); declare function fn(x): T; declare function fn(this: void, x: number, y?: string): string; declare function fn(x, ...rest): T; declare class MyClass extends Super implements Super.Sub {} declare abstract class MyClass extends Super {} declare abstract /* comment */ class MyClass extends Super {} declare enum Color { Red, Green, Blue } declare const enum Num { One = 1, Two, Three } declare global {} declare module Super.Sub {} declare module "module" {} declare module "module"; declare namespace Super.Sub {} /* Enum declaration */ enum Color { Red, Green, Blue } const enum Num { One = 1, Two = '2', Three } /* Interface declaration */ interface MyObj { property, (): void, readonly [index: number]: string, method?(this: void, x: number, y?: string): void } interface Square extends Shape, PenStroke { sideLength: number; } /* Module declaration */ module Super.Sub { // namespace ("internal module") let a = 1; } module "module" { // "external module" let a = 1; } /* Namespace declaration */ namespace Super.Sub { let a = 1; } /* Type alias declaration */ type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; type Container = { value: T }; /* * Modifications to existing statements and declarations */ /* Literals */ // Numeric separators (stage 3 proposal) let decimal = 1_000_000; let binary_integer = 0b1100_0011_1101_0001; let octal_integer = 0o123_456_700; let hex_integer = 0xFF_0C_00_FF; // Numeric separators with BigInt let decimal_bigint = 1_000_000n; let binary_bigint = 0B1100_0011_1101_0001n; let octal_bigint = 0O123_456_700n; let hex_bigint = 0XFF_0C_00_FFn; /* Object literal */ // Property value vs type annotation a = { property: void 1, method(): void {} }; // Type annotations for methods a = { method(this: void, x: number, y?: string, z: number = 0, ...rest: any[]): void {}, get property(): string {}, set property(value: string) {} }; // Type parameters for methods a = { method(x: T): T {}, get(x: T): T {} }; // Incorrectly highlighted as keyword a = { get (x: T): T {} }; /* Function expression / declaration */ // Type annotations a = function (this: void, x: number, y?: string, z: number = 1, ...rest: any[]): void {}; function fn(this: void, x: number, y?: string, z: number = 1, ...rest: any[]): void {} // Type parameters a = function (x: T): T {}; function fn(x: T): T {} /* Grouping / arrow function parameters */ // Type annotations a = (x: number, y?: string, z: number = 0, ...rest: any[]): void => x + y; // Type parameters a = (x: T): T => x; /* Class expression / declaration */ // Abstract class abstract class MyClass {} abstract /* comment */ class MyClass {} abstract /* comment */ class MyClass {} // correctly highlighted, because abstract cannot be followed by a line terminator // Type parameters a = class {}; a = class extends Super {}; class MyClass {} class MyClass extends Super {} // Extends clause with type arguments a = class extends Super {}; class MyClass extends Super {} // Implements clause a = class implements Super.Sub {}; a = class extends Super implements Super.Sub {}; class MyClass implements Super.Sub {} class MyClass extends Super implements Super.Sub {} // Class properties (stage 3 proposal) a = class { property; property = 1; constructor = 1; }; // Type annotation a = class { property: number; property: number = 1; constructor(x: number, y?: string) {} constructor(x: number = 1) {} method(this: void, x: number, y?: string, z: number = 1, ...rest: any[]): void {} get property(): string {} set property(value: string) {} }; // Index members a = class { [index: number]: string; }; // Private fields (stage 3 proposal) a = class { #privateprop; #privateprop: string = ''; setPrivateProp(s: string) { this.#privateprop = s; } }; // Accessibility modifiers for properties / methods a = class { public property; protected method() {} private get property(): string {} }; // Abstract properties / methods a = abstract class { abstract property; abstract method(): void; }; // Read-only properties / index members a = class { readonly property; readonly [prop: string]: any; }; // "declare" properties (no emit when useDefineForClassFields) a = class { declare property; }; // Optional properties / methods a = class { property?; method?(): void; }; // Definite assignment assertion for properties a = class { property!; property!: number; }; // Class method type parameters a = class { method(x: T): T {} }; // Parameter properties for constructor a = class { constructor(public x: number, private readonly y?: string) {} constructor(protected x: number = 1) {} // Accessibility / read-only modifiers do not apply to normal methods method(public x: number, private readonly y?: string) {} }; // Multiple modifiers a = class { public static readonly property; private abstract method(); // "declare" can be in any position declare protected abstract readonly property; protected declare abstract readonly property; protected abstract declare readonly property; protected abstract readonly declare property; }; // Modifier-named properties / methods a = class { public; declare protected?; private!; public: number; protected = 1; private() {} public(x: T): T { return x; } abstract; public abstract?; protected abstract!; private abstract: number; declare abstract = 1; protected abstract() {} private abstract(x: T): T { return x; } readonly; public readonly?; static readonly!; abstract readonly: number; declare readonly = 1; protected readonly() {} private readonly(x: T): T { return x; } declare; static declare?; abstract declare!; readonly declare: number; declare declare = 1; public declare() {} protected declare(x: T): T { return x; } }; // Syntax errors // Multiple accessibility modifiers a = class { public private property; }; a = class { private protected property; }; a = class { protected public property; }; // "static" and "abstract" both present a = class { static abstract property; }; a = class { abstract static property; }; // "static" / "abstract" before accessibility modifier a = class { static private property; }; a = class { abstract protected property; }; // "readonly" before accessibility modifier a = class { readonly public property; }; // "readonly" before "static" / "abstract" a = class { readonly static property; }; a = class { readonly abstract property; }; // Incorrectly highlighted // Property/method name highlighted as keyword a = class { public ; declare private ?; abstract readonly : number; readonly declare = 1; protected abstract () {} }; // Abstract generators not allowed abstract class Foo { abstract *generator(): object; } // Modifiers other than "readonly" do not apply to index members a = class { public [prop: string]: any; protected [prop: string]: any; private [prop: string]: any; static [prop: string]: any; abstract [prop: string]: any; declare [prop: string]: any; }; // Modifiers other than "readonly" do not apply to private fields a = class { public #privateprop; protected #privateprop; private #privateprop; static #privateprop; abstract #privateprop; declare #privateprop; }; // Constructor not highlighted as built-in method a = class { constructor () {} }; // "abstract" and "declare" do not apply to constructors a = class { abstract constructor() {} declare constructor() {} }; /* Expression */ // Type arguments for function calls fn(); fn(); fn < string > /* comment */ (); // Incorrectly highlighted (interpreted as less than / equal than) fn(); fn (); fn /* comment */ (); fn // comment (); // Type arguments for tagged templates myTag`Template literal`; myTag`Template literal`; myTag < string > /* comment */ `Template literal`; // Incorrectly highlighted (interpreted as less than / equal than) myTag`Template literal`; myTag `Template literal`; myTag /* comment */ `Template literal`; myTag // comment `Template literal`; // Type assertion a = obj; a = "abc"; // const assertion /* Export / import declaration */ // Export ambient declaration export declare let a; export declare const a: number, b: string; export declare function fn(); export declare function fn(x): T; export declare function fn(this: void, x: number, y?: string): string; export declare function fn(x, ...rest): T; export declare class MyClass extends Super implements Super.Sub {} export declare abstract class MyClass extends Super {} export declare abstract /* comment */ class MyClass extends Super {} export declare enum Color { Red, Green, Blue } export declare const enum Num { One = 1, Two, Three } export declare module Super.Sub {} export declare module "module" {} export declare namespace Super.Sub {} // Export enum declaration export enum Color { Red, Green, Blue } export const enum Num { One = 1, Two, Three } // Export import alias export import shortname = Long.Namespace.Name; // Export interface declaration export interface MyObj {} export interface Square extends Shape, PenStroke {} // Export module declaration export module Super.Sub {} // Export namespace declaration export namespace Super.Sub {} // Export type alias declaration export type Name = string; export type NameResolver = () => string; export type NameOrResolver = Name | NameResolver; export type Container = { value: T }; // Export assignment export = obj; // Export as namespace (UMD module definition) export as namespace myModule; // Import alias import shortname = Long.Namespace.Name; // Import require import mod = require("module"); // Type-only imports and exports import type T from './mod'; import type { A, B } from './mod'; import type * as Types from './mod'; export type { T }; export type { T } from './mod'; /* Variable declaration */ // Type annotation const a: number; let a: number = 1, b: string; var { a, b }: { a: number, b: string } = { a: 1, b: 'b' }; // Definite assignment assertion let a!: number; // from file.js /* * Identifiers */ var example; function example() {} var príklad; function príklad() {} var 例子; function 例子() {} var $jquery; function _lodash() {} var \u0075nicod\u{65}; function \u0075nicod\u{65}() {} /* * Expressions (in expression statements) */ /* * Literals */ /* Keyword values */ var NULL = null; var TRUE = true; var FALSE = false; /* Number */ var decimal1 = 0; var decimal2 = 123.45; var decimal3 = .66667; var decimal4 = 10e20; var decimal5 = 0.2e+1; var decimal6 = .5E-20; var hex1 = 0xDEADBEEF; var hex2 = 0Xcafebabe; // ES2015 binary and octal numbers let binary1 = 0b1010; let binary2 = 0B00001111; let octal1 = 0o0123; let octal2 = 0O4567; // Legacy octal numbers var legacy_octal1 = 01; var legacy_octal2 = 007; // BigInt (ES2020) var decimal1 = 0n; var decimal2 = 123n; var hex1 = 0xDEADBEEFn; var hex2 = 0Xcafebaben; var binary1 = 0b1010n; var binary2 = 0B00001111n; var octal1 = 0o0123n; var octal2 = 0O4567n; /* String */ // Escape sequences '\b\f\n\r\t\v\0\'\"\\'; // Single character escape "\1\01\001"; // Octal escape (Annex B) '\xA9'; // Hexadecimal escape "\u00a9"; // Unicode escape '\u{1D306}'; // Unicode code point escape /* Array literal */ []; [1]; [1.0, 'two', 0x03]; // Trailing comma [ [1,2,3], [4,5,6], ]; // Spread syntax [1, ...a, 2]; /* Object literal */ a = {}; a = { prop: 'value' }; a = { 'prop': 'value', 1: true, .2: 2 }; // Trailing comma a = { prop: 'value', "extends": 1, }; // Shorthand property names a = { b, c, d }; // Getter / setter a = { _hidden: null, get property() { return _hidden; }, set property(value) { this._hidden = value; }, get: 'get', set() { return 'set'; } }; // Incorrectly highlighted as keyword a = { get : 'get', set () { return 'set'; } }; // Shorthand function notation a = { method() {}, *generator() {}, // Async function (ES2017) async method() {}, async /* comment */ method() {}, async get() {}, async() {},// method called "async" async: false, // property called "async" // Async generator (ES2018) async *generator() {} }; // Computed property names a = { ['prop']: 1, ['method']() {} }; // Spread properties (ES2018) a = { ...b, ...getObj('string') }; // Syntax errors a = { prop: 'val': 'val' }; a = { method() {}: 'val' }; a = { get property() {}: 'val' }; a = { *generator: 'val' }; a = { async prop: 'val' }; a = { ...b: 'val' }; a = { ...b() { return 'b'; } }; /* Regular expression literal */ /abc/; x = /abc/gi; function_with_regex_arg(/abc/); [ /abc/m, /def/u ]; a = { regex: /abc/s }; // s (dotAll): ES2018 (1 === 0) ? /abc/ : /def/; /abc/; /* Comment */ /abc/; // Comment var matches = /abc/.exec('Alphabet ... that should contain abc, right?'); // No regex here a = [thing / thing, thing / thing]; x = a /b/ c / d; // Character groups with backslashes /[ab\\]/; // a, b or backslash /[ab\]]/; // a, b or ] /\\[ab]/; // a or b preceded by backslash /\[ab]/; // Literally "[ab]" // Control escape /\cJ/; // Unicode property escape (ES2018) /\p{General_Category=Letter}/u; /\p{Letter}/u; // Named capture groups (ES2018) /(?\d{4})-(?\d{2})-(?\d{2})/u; /(?foo|bar)/u; /^(?.*).\k$/u; // backreference /* Template literal */ console.log(`The sum of 2 and 2 is ${2 + 2}`); let y = 8; let my_string = `This is a multiline string that also contains a template ${y + (4.1 - 2.2)}`; /* * Built-in values */ // global object values Infinity; NaN; undefined; // global object functions decodeURIComponent(); decodeURI(); encodeURIComponent(); encodeURI(); eval(); isFinite(); isNaN(); parseFloat(); parseInt(); // constructors (subset) Array(); BigInt(); // ES2020 Boolean(); Date(); Error(); Function(); Map(); Object(); Promise(); RegExp(); Set(); String(); Symbol(); // objects JSON.parse(); Math.random(); // object keywords arguments; globalThis; // ES2020 super; this; // dynamic import (ES2020) import("module").then(); import /* comment */ ("module").then(); import // comment ("module").then(); a = await import("module"); a = await import /* comment */ ("module"); a = await import // comment ("module"); // import.meta (ES2020) import.meta; import . /* comment */ meta; import // comment .meta; a = import.meta; a = import . /* comment */ meta; a = import // comment .meta; // new.target new.target; new . /* comment */ target; new // comment .target; // properties (subset) array.length; Math.PI; Number.NaN; object.constructor; Class.prototype; Symbol.asyncIterator; // ES2018 Symbol('desc').description; // ES2019 // methods (subset) array.keys(); date.toString(); object.valueOf(); re.test(); array.includes(); // ES2016 Object.values(); // ES2017 Object.entries(); // ES2017 string.padStart(); // ES2017 string.padEnd(); // ES2017 Object.getOwnPropertyDescriptors(); // ES2017 promise.finally(); // ES2018 Object.fromEntries(); // ES2019 string.trimStart(); // ES2019 string.trimEnd(); // ES2019 array.flat(); // ES2019 array.flatMap(); // ES2019 string.matchAll(); // ES2020 Promise.allSettled(); // ES2020 BigInt.asUintN(); // ES2020 string.replaceAll(); // ES2021 /* * Function expression, arrow function */ a = function () { return 1 }; a = function fn() { return; }; a = function fn(x) {}; a = function fn(x, y) {}; // Arrow function x => -x; () => {}; (x, y) => x + y; (x, y) => { return x + y; }; (x, y) => /* comment */ { return x + y; } /* comment */ ; (x) => ({ a: x }); // return object // Default parameters a = function fn(x, y = 1) {}; (x, y = 1) => x + y; // Parameter without default after default parameters a = function fn(x = 1, y) {}; (x = 1, y) => x + y; // Array destructuring a = function fn([x]) {}; a = function fn([x = 5, y = 7]) {}; // default values a = function fn([x, , y]) {}; // ignoring some returned values (elision) a = function fn([x, ...y]) {}; // rest syntax ([x]) => x; ([x = 5, y = 7]) => x + y; // default values ([x, , y]) => x + y; // ignoring some returned values (elision) ([x, ...y]) => y; // rest syntax // Object destructuring a = function fn({ x }) {}; a = function fn({ a: x, b: y }) {}; // assigning to new variable names a = function fn({ x = 5, y = 7 }) {}; // default values a = function fn({ a: x = 5, b: y = 7 }) {}; // assigning to new variable names and default values a = function fn({ ['a']: x, ['b']: y }) {}; // computed property names a = function fn({ x, y, ...rest }) {}; // rest properties (ES2018) ({ x }) => x; ({ a: x, b: y }) => x + y; // assigning to new variable names ({ x = 5, y = 7 }) => x + y; // default values ({ a: x = 5, b: y = 7 }) => x + y; // assigning to new variable names and default values ({ ['a']: x, ['b']: y }) => x + y; // computed property names ({ x, y, ...rest }) => x; // rest properties (ES2018) // Destructuring and default parameters a = function f([x, y] = [1, 2], {c: z} = {c: 3}) {}; ([x, y] = [1, 2], {c: z} = {c: x + y}) => x + y + z; // Generator function a = function*fn() {}; a = function * fn() {}; // Rest parameters a = function fn(...rest) {}; a = function fn(x, y, ...rest) {}; (...rest) => rest; (x, y, ...rest) => rest; // Async function (ES2017) a = async function fn() {}; a = async /* comment */ function fn() {}; a = async /* comment */ function fn() {}; // correctly highlighted, because async cannot be followed by a line terminator async x => x; async () => {}; async /* comment */ () => {}; async /* comment */ () => {}; // correctly highlighted, because async cannot be followed by a line terminator async(); // incorrectly highlighted // Async generator (ES2018) a = async function * fn() {}; // Trailing comma (ES2017) a = function fn(x, y,) {}; (x, y,) => x + y; // Trailing comma after rest parameters (syntax error) a = function fn(x, y, ...rest,) {}; (x, y, ...rest,) => rest; /* * Class expression */ a = class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} }; a = class extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } }; // Incorrectly highlighted as keyword a = class { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } }; // Properties/methods called "constructor" a = class { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } }; // Incorrectly highlighted as built-in method a = class { static constructor() { this._value = null; } }; /* * Operators * use groupings to test, as there can only be one expression (in the * first grouping item) */ // Grouping ( 1 + 2 ); // Increment / decrement ( ++a ); ( --a ); ( a++ ); ( a-- ); // Keyword unary ( await promise() ); // ES2017 ( delete obj.prop ); ( new Array() ); ( void 1 ); ( typeof 'str' ); ( yield 1 ); ( yield* fn() ); // Arithmetic ( 1 + 2 ); ( 1 - 2 ); ( 1 * 2 ); ( 1 / 2 ); ( 1 % 2 ); ( 1 ** 2 ); // ES2016 ( +1 ); ( -1 ); // Keyword relational ( prop in obj ); ( obj instanceof constructor ); // Comparison ( 1 == 2 ); ( 1 != 2 ); ( 1 === 2 ); ( 1 !== 2 ); ( 1 < 2 ); ( 1 > 2 ); ( 1 <= 2 ); ( 1 >= 2 ); // Bitwise ( 1 & 2 ); ( 1 | 2 ); ( 1 ^ 2 ); ( ~1 ); ( 1 << 2 ); ( 1 >> 2 ); ( 1 >>> 2 ); // Logical ( 1 && 2 ); ( 1 || 2 ); ( !1 ); // Nullish coalescing (ES2020) ( a ?? 1 ); // Assignment ( a = 1 ); ( a += 1 ); ( a -= 1 ); ( a *= 1 ); ( a /= 1 ); ( a %= 1 ); ( a **= 1 ); // ES2016 ( a <<= 1 ); ( a >>= 1 ); ( a >>>= 1 ); ( a &= 1 ); ( a |= 1 ); ( a ^= 1 ); // Array destructuring ( [a, b] = [1, 2] ); ( [a = 5, b = 7] = [1] ); // default values ( [a, , b] = f() ); // ignoring some returned values (elision) ( [a, ...b] = [1, 2, 3] ); // rest syntax // Object destructuring ( {a, b} = { a: 1, b: 2} ); ( { a: foo, b: bar } = { a: 1, b: 2 } ); // assigning to new variable names ( { a = 5, b = 7 } = { a: 1 } ); // default values ( { a: foo = 5, b: bar = 7 } = { a: 1 } ); // assigning to new variable names and default values ( { ['a']: foo, ['b']: bar } = { a: 1, b: 2 } ); // computed property names ( { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 } ); // rest properties (ES2018) // Comma 1, 2 ; // Conditional / ternary ( true ? 1 : 2 ); ( true ? : 2 ); // missing true value (syntax error) obj[ true ? 1, 2 : 3 ]; // comma operator inside true expression (syntax error) /* * Property accessors */ // Dot notation arr.length; ( obj . /* comment */ prototype /* comment */ . /* comment */ constructor /* comment */ ); const pi = Math.PI const num = 0 // Bracket notation arr['length']; ( obj /* comment */ [ /* comment */ 'prototype' /* comment */ ] /* comment */ /* comment */ [ /* comment */ 'constructor' /* comment */ ] /* comment */ ); // Mixed obj ['prototype'] . constructor; obj . prototype ['constructor']; /* * Function call */ fn(); obj.fn(1); obj['fn'](1, 2); // Spread syntax fn(x, y, ...args); // Trailing comma (ES2017) fn(x, y,); /* Tagged template */ myTag`That ${ person } is ${ age }`; /* * Optional chaining (ES2020) */ obj?.prototype; obj?.['constructor']; func?.(1, 2, ...args); foo?.3:0; // ternary operator, not optional chaining /* * Statements and declarations */ /* Use strict directive */ "use strict"; function () { 'use strict'; } // invalid directives " use strict"; 'use strict '; "use strict"; 'use strict'; "hello 'use strict' world"; fn("use strict"); { 'use strict'; } /* Block statement */ { hello(); world(); } { hello(); world() } /* Break statement */ break; break label; break // end statement label; // separate statement { break } /* * Class declaration */ class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} } class Foo extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } } // Incorrectly highlighted as keyword class Foo { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } } // Properties/methods called "constructor" class Foo { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } } // Incorrectly highlighted as built-in method class Foo { static constructor() { this._value = null; } } /* Continue statement */ continue; continue label; continue // end statement label; // separate statement { continue } /* Debugger statement */ debugger; debugger ; /* Export / import statement */ export { a }; export { a, b, }; export { x as a }; export { x as a, y as b, }; export var a; export let a, b; export const a = 1; export var a = 1, b = 2; export function fn() {} export function* fn() {} export class Class {} export default 1; export default function () {} export default function *fn() {} export default class {} export { a as default, b }; export * from 'module'; export * as ns from 'module'; export { a, b } from 'module'; export { x as a, y as b, } from 'module'; export { default } from 'module'; import a from "module"; import * as ns from "module"; import { a } from "module"; import { a, b } from "module"; import { x as a } from "module"; import { x as a, y as b } from "module"; import { default as a } from "module"; import a, { b } from "module"; import a, * as ns from "module"; import "module"; /* For statement */ for (i = 0; i < 10; i++) something(); for (var i = 10; i >= 0; i--) { something(); } for (i = 0, j = 0; i < 10; i++, j++) something(); for (let i = 10, j = 0; i >= 0; i--, j += 1) { something(); } for (prop in obj) {} // matches "in" binary operator instead for (const prop in obj) {} for (val of generator()) {} for (var val of array) {} for await (let x of asyncIterable) {} // ES2018 for /* comment */ await /* comment */ (let x of asyncIterable) {} // ES2018 /* Function declaration statement */ function fn() { return; } async function fn() {} // ES2017 async /* comment */ function fn() {} // ES2017 async /* comment */ function fn() {} // correctly highlighted, because async cannot be followed by a line terminator /* If..else statement */ if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) { lessThan(); } else if (a > 0) { greaterThan(); } else { equal(); } /* Label statement */ outer: for (var i = 0; i < 10; i++) { inner /* comment */ : for (var j = 0; j < 2; j++) {} } loop /* comment */ : for (var i in obj) {} // incorrectly highlighted (though it may appear correct) /* Return statement */ return; return 1; return // end statement 1; // separate statement return ( 1 ); { return a } /* Switch statement */ switch (foo) { case 1: doIt(); break; case '2': doSomethingElse(); break; default: oops(); } /* Throw statement */ throw e; throw new Error(); throw // end statement (syntax error) e; // separate statement throw ( new Error() ); { throw new Error() } /* Try...catch statement */ try { somethingDangerous(); } catch (e) { didntWork(e); } catch { // ES2019 return false; } finally { cleanup(); } /* Variable declaration */ // Declaration only const a; let a, b, c; var a , b , c ; // With assignment const a = 1; let a = 1, b = [2, 3], c = 4; var a = 1 , b = [ 2 , 3 ] , c = 4 ; // Array destructuring var [a, b] = [1, 2]; var [ a , b ] = [ 1 , 2 ] ; var [a = 5, b = 7] = [1]; // default values var [a, , b] = f(); // ignoring some returned values (elision) var [a, ...b] = [1, 2, 3]; // rest syntax // Object destructuring var { a, b } = { a: 1, b: 2 }; var { a , b } = { a : 1 , b : 2 } ; var { a: foo, b: bar } = { a: 1, b: 2 }; // assigning to new variable names var { a = 5, b = 7 } = { a: 1 }; // default values var { a: foo = 5, b: bar = 7 } = { a: 1 }; // assigning to new variable names and default values var { ['a']: foo, ['b']: bar } = { a: 1, b: 2 }; // computed property names var { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 }; // rest properties (ES2018) /* While / do...while statement */ while (true) something(); while (1) { something(); } do something(); while (false); do { something(); } while (0); /* With statement */ with (Math) { a = PI * r * r; x = r * cos(PI); y = r * sin(PI / 2); } /* * JSDoc */ /* Inline tag */ /** {@link String} */ /** * {@link http://example.com | Ex\{am\}ple} */ /** {@link*/ /* Type */ /** {String} */ /** * {Foo.B\{a\}r} */ /** {number*/ /* Block tag */ // No arguments /** @constructor */ /** * @deprecated since 1.1.0 */ /** @public*/ // Generic argument /** @default 3.14159 */ /** * @tutorial tutorial-1 */ /** @variation 2*/ // Event name argument /** @fires earthquakeEvent */ /** * @listens touchstart */ /** @event newEvent*/ // Keyword argument /** @access protected */ /** * @kind module */ /** @access private*/ // Name/namepath argument /** @alias foo */ /** * @extends bar */ /** @typeParam T*/ // Type and name arguments /** @param {String} name - A \{chosen\} \@name */ /** * @member {Object} child */ /** @property {number} num*/ // Borrows block tag /** @borrows foo as bar */ /** * @borrows foo as */ /** @borrows foo*/ // Todo block tag /** @todo write more/less test cases */ 070701000002BB000081A4000000000000000000000001665908060000998B000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.tsx/* * TypeScript JSX-specific conditions */ /* @jsx comment pragma */ // Valid pragmas /*@jsx dom */ /** @JSX preact.h */ /* * @jsx dom additional text */ // Invalid pragmas /* @ jsx dom */ // @jsx dom /* @jsx */ /* @jsx 1 */ /* @jsx dom. */ /* @jsx dom.1 */ /* @jsx dom() */ { /* @jsx dom */ // Incorrectly highlighted } /* Type parameters for arrow function vs JSX element */ // Requires extends clause or multiple parameters a = (x: T) => x; a = < T /* comment */ , U >(x: T, y: U) => x; // Incorrectly highlighted (extra added to close incorrect T elements) a = (x: T) => x; a = (x: T, y: U) => x; a = (x: T) => x; a = < T // comment , U >(x: T, y: U) => x; /* Type arguments in JSX elements */ a = a={10} b="hi"/>; // from file.jsx /* * JSX Elements */ // Element name (
); ( ); ( <例子> ); ( <$jquery> ); ( <\u0075nicod\u{65}> ); ( ); ( ); ( ); // Attributes (
); // spread attributes (
); ( ); (
); // Empty element ( ); // Nested elements (
); // Child expression (
{["1", 2, three].join('+')} {...obj}
); // XML character entity / numeric character references (
>/
); // Invalid characters (
greater than > right brace }
); (
valid alternatives: > {">"} } {'}'}
); // Fragment ( <>
); // from file.ts /* * Type */ // Predefined type let a: any; let a: bigint; let a: boolean; let a: null; let a: number; let a: object; let a: string; let a: symbol; let a: undefined; let a: unknown; let a: void; function fn(): never { throw new Error(); } // Parenthesized type let a: ( string ); // Type reference let a: T; let a: Super.Sub; // Bullt-in constructors let a: Array; let a: Function; let a: RegExp; // Built-in utility types let a: ReadonlyArray; let a: Partial; let a: Readonly; // Import type let a: import("module").ModuleType; // Object type let a: { // Property signature property; property?, property: string; readonly property?: string, // Call signature (); (x), (this: void, x: number, y?: string): void; (x, ...rest): void, // Construct signature new (); new (x), new (x: number, y?: string): void; new (x, ...rest): void, // Index signature [index: number]: string; readonly [prop: string]: any, // Method signature method(); method?(x), method?(this: void, x: number, y?: string): void; method(x, ...rest): void, // Mapped type readonly [P in keyof T]?: T[P], -readonly [P in keyof T]+?: T[P]; }; // Array type let a: string[]; // Indexed access type (lookup type) let a: MyType["property"]; // Tuple type let a: [string]; let a: [string, number, boolean?]; // optional element let a: [string, ...number[]]; // rest elements // Type query let a: typeof Super.Sub; // This type let a: this; // String literal type let a: 'string'; let a: "string"; // Numeric literal type let a: 123; let a: 0b0101; let a: 0o777; let a: 0xfff; // Numeric separators with BigInt let a: 1_23n; let a: 0b01_01n; let a: 0o7_77n; let a: 0xf_f_fn; // Boolean literal type let a: true; let a: false; // "unique symbol" subtype let a: unique symbol; let a: unique /* comment */ symbol; let a: unique /* comment */ symbol; let a: unique // comment symbol; // Union / intersection type let a: string | number; let a: string & number; // Type predicate (user-defined type guard) function isString(x: any): x is string {} // "asserts" type predicate (for assertion functions) declare function assert(value: unknown): asserts value; declare function assertIsArrayOfStrings(obj: unknown): asserts obj is string[]; declare function assertNonNull(obj: T): asserts obj is NonNullable; // Indexed type query (keyof) let a: keyof T; // Conditional type let a: T extends number ? number[] : T[]; // Type inference (in conditional types) let a: T extends (infer U)[] ? U : any; let a: T extends (...args: any[]) => infer U ? U : any; let a: T extends Promise ? U : any; // Function type let a: () => void; let a: (x) => T; let a: (this: void, x: number, y?: string) => string; // this parameter let a: (x, ...rest) => T; let a: ([x, y]: [string, number], [z, w]: [string, number]) => void; let a: ({ x: a, y: b }: { x: number, y: string }, { z: c, w: d }: { z: number, w: string }) => void; // Constructor type let a: new () => void; let a: new (x) => T; let a: new (x: number, y?: string) => string; let a: new (x, ...rest) => T; let a: new ([x, y]: [string, number], [z, w]: [string, number]) => void; let a: new ({ x: a, y: b }: { x: number, y: string }, { z: c, w: d }: { z: number, w: string }) => void; // Readonly array / tuple types let a: readonly string[]; let a: readonly /* comment */ [string, string]; // Parenthesized type vs parameters list of function type // Parenthesized let a: (string); let a: ((string: string) => void); // Parameters list let a: (string: string) => void; let a: (string? : string) => void; let a: (string /* comment */ , string) => void; let a: (this: void) => void; let a: (this /* comment */ : void) => void; let a: (...string : string[]) => void; // Incorrectly highlighted let a: (string : string) => void; let a: (this /* comment */ : void) => void; let a: (... string : string[]) => void; let a: (... /* comment */ string : string[]) => void; /* * Type parameters */ function fn() {} function fn() {} function fn() {} function fn() {} /* * Type parameters (for arrow function) / type assertion (cast) */ // Type parameters //a = (x) => x; // not considered a type parameters list in typescript jsx a = (x) => x; a = (x) => x; a = (x) => x; // Type assertion /* type assertions should be done using the "as" operator in typescript jsx a = obj; a = "abc"; // const assertion */ /* * Type arguments */ fn(); fn(); /* * Type annotation */ let a: string; /* * TypeScript-specific operators */ /* as operator (type assertion / cast) */ ( obj as string ); ( "abc" as const ); // const assertion // Non-null assertion operator (post-fix !) ( a!.method() ); /* * TypeScript-specific statements and declarations */ /* @ts-ignore / @ts-expect-error comment pragmas */ // Valid pragmas //@ts-ignore /// @ts-expect-error add reason here { // @ts-ignore } /* @ts-expect-error*/ /* * @ts-ignore more reasons */ // Invalid pragmas // @ ts-ignore /// @TS-IGNORE /* @ts-expect-error */ /* @ts-nocheck comment pragmas */ // Valid pragmas //@ts-nocheck /// @TS-NOCHECK text here // Invalid pragmas // @ ts-nocheck //// @ts-nocheck /* @ts-nocheck */ { // @ts-nocheck } /* Triple-slash directives */ // Valid directives /// /// /// /// // Invalid directives /// comment /// /// /// /// /// /// /// /// { /// } /* Decorators (experimental, stage 2 proposal) */ // Class decorator @sealed class Greeter { // Property decorator @(Deco) /* comment */ . /* comment */ utils /* comment */ () /* comment */ . /* comment */ format /* comment */ ("Hello, %s") greeting: string; // Method decorator @ /* comment */ (foo = 'bar', false || validate) greet(@required name: string) { // Parameter decorator return "Hello " + name + ", " + this.greeting; } constructor(@required public greeting: string) {} // Parameter decorator // Accessor decorator @configurable(false) /* comment */ get x() { return this._x; } } /* Ambient declaration */ declare let a; declare const a: number, b: string; declare function fn(); declare function fn(x): T; declare function fn(this: void, x: number, y?: string): string; declare function fn(x, ...rest): T; declare class MyClass extends Super implements Super.Sub {} declare abstract class MyClass extends Super {} declare abstract /* comment */ class MyClass extends Super {} declare enum Color { Red, Green, Blue } declare const enum Num { One = 1, Two, Three } declare global {} declare module Super.Sub {} declare module "module" {} declare module "module"; declare namespace Super.Sub {} /* Enum declaration */ enum Color { Red, Green, Blue } const enum Num { One = 1, Two = '2', Three } /* Interface declaration */ interface MyObj { property, (): void, readonly [index: number]: string, method?(this: void, x: number, y?: string): void } interface Square extends Shape, PenStroke { sideLength: number; } /* Module declaration */ module Super.Sub { // namespace ("internal module") let a = 1; } module "module" { // "external module" let a = 1; } /* Namespace declaration */ namespace Super.Sub { let a = 1; } /* Type alias declaration */ type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; type Container = { value: T }; /* * Modifications to existing statements and declarations */ /* Literals */ // Numeric separators (stage 3 proposal) let decimal = 1_000_000; let binary_integer = 0b1100_0011_1101_0001; let octal_integer = 0o123_456_700; let hex_integer = 0xFF_0C_00_FF; // Numeric separators with BigInt let decimal_bigint = 1_000_000n; let binary_bigint = 0B1100_0011_1101_0001n; let octal_bigint = 0O123_456_700n; let hex_bigint = 0XFF_0C_00_FFn; /* Object literal */ // Property value vs type annotation a = { property: void 1, method(): void {} }; // Type annotations for methods a = { method(this: void, x: number, y?: string, z: number = 0, ...rest: any[]): void {}, get property(): string {}, set property(value: string) {} }; // Type parameters for methods a = { method(x: T): T {}, get(x: T): T {} }; // Incorrectly highlighted as keyword a = { get (x: T): T {} }; /* Function expression / declaration */ // Type annotations a = function (this: void, x: number, y?: string, z: number = 1, ...rest: any[]): void {}; function fn(this: void, x: number, y?: string, z: number = 1, ...rest: any[]): void {} // Type parameters a = function (x: T): T {}; function fn(x: T): T {} /* Grouping / arrow function parameters */ // Type annotations a = (x: number, y?: string, z: number = 0, ...rest: any[]): void => x + y; // Type parameters a = (x: T): T => x; /* Class expression / declaration */ // Abstract class abstract class MyClass {} abstract /* comment */ class MyClass {} abstract /* comment */ class MyClass {} // correctly highlighted, because abstract cannot be followed by a line terminator // Type parameters a = class {}; a = class extends Super {}; class MyClass {} class MyClass extends Super {} // Extends clause with type arguments a = class extends Super {}; class MyClass extends Super {} // Implements clause a = class implements Super.Sub {}; a = class extends Super implements Super.Sub {}; class MyClass implements Super.Sub {} class MyClass extends Super implements Super.Sub {} // Class properties (stage 3 proposal) a = class { property; property = 1; constructor = 1; }; // Type annotation a = class { property: number; property: number = 1; constructor(x: number, y?: string) {} constructor(x: number = 1) {} method(this: void, x: number, y?: string, z: number = 1, ...rest: any[]): void {} get property(): string {} set property(value: string) {} }; // Index members a = class { [index: number]: string; }; // Private fields (stage 3 proposal) a = class { #privateprop; #privateprop: string = ''; setPrivateProp(s: string) { this.#privateprop = s; } }; // Accessibility modifiers for properties / methods a = class { public property; protected method() {} private get property(): string {} }; // Abstract properties / methods a = abstract class { abstract property; abstract method(): void; }; // Read-only properties / index members a = class { readonly property; readonly [prop: string]: any; }; // "declare" properties (no emit when useDefineForClassFields) a = class { declare property; }; // Optional properties / methods a = class { property?; method?(): void; }; // Definite assignment assertion for properties a = class { property!; property!: number; }; // Class method type parameters a = class { method(x: T): T {} }; // Parameter properties for constructor a = class { constructor(public x: number, private readonly y?: string) {} constructor(protected x: number = 1) {} // Accessibility / read-only modifiers do not apply to normal methods method(public x: number, private readonly y?: string) {} }; // Multiple modifiers a = class { public static readonly property; private abstract method(); // "declare" can be in any position declare protected abstract readonly property; protected declare abstract readonly property; protected abstract declare readonly property; protected abstract readonly declare property; }; // Modifier-named properties / methods a = class { public; declare protected?; private!; public: number; protected = 1; private() {} public(x: T): T { return x; } abstract; public abstract?; protected abstract!; private abstract: number; declare abstract = 1; protected abstract() {} private abstract(x: T): T { return x; } readonly; public readonly?; static readonly!; abstract readonly: number; declare readonly = 1; protected readonly() {} private readonly(x: T): T { return x; } declare; static declare?; abstract declare!; readonly declare: number; declare declare = 1; public declare() {} protected declare(x: T): T { return x; } }; // Syntax errors // Multiple accessibility modifiers a = class { public private property; }; a = class { private protected property; }; a = class { protected public property; }; // "static" and "abstract" both present a = class { static abstract property; }; a = class { abstract static property; }; // "static" / "abstract" before accessibility modifier a = class { static private property; }; a = class { abstract protected property; }; // "readonly" before accessibility modifier a = class { readonly public property; }; // "readonly" before "static" / "abstract" a = class { readonly static property; }; a = class { readonly abstract property; }; // Incorrectly highlighted // Property/method name highlighted as keyword a = class { public ; declare private ?; abstract readonly : number; readonly declare = 1; protected abstract () {} }; // Abstract generators not allowed abstract class Foo { abstract *generator(): object; } // Modifiers other than "readonly" do not apply to index members a = class { public [prop: string]: any; protected [prop: string]: any; private [prop: string]: any; static [prop: string]: any; abstract [prop: string]: any; declare [prop: string]: any; }; // Modifiers other than "readonly" do not apply to private fields a = class { public #privateprop; protected #privateprop; private #privateprop; static #privateprop; abstract #privateprop; declare #privateprop; }; // Constructor not highlighted as built-in method a = class { constructor () {} }; // "abstract" and "declare" do not apply to constructors a = class { abstract constructor() {} declare constructor() {} }; /* Expression */ // Type arguments for function calls fn(); fn(); fn < string > /* comment */ (); // Incorrectly highlighted (interpreted as less than / equal than) fn(); fn (); fn /* comment */ (); fn // comment (); // Type arguments for tagged templates myTag`Template literal`; myTag`Template literal`; myTag < string > /* comment */ `Template literal`; // Incorrectly highlighted (interpreted as less than / equal than) myTag`Template literal`; myTag `Template literal`; myTag /* comment */ `Template literal`; myTag // comment `Template literal`; // Type assertion /* type assertions should be done using the "as" operator in typescript jsx a = obj; a = "abc"; // const assertion */ /* Export / import declaration */ // Export ambient declaration export declare let a; export declare const a: number, b: string; export declare function fn(); export declare function fn(x): T; export declare function fn(this: void, x: number, y?: string): string; export declare function fn(x, ...rest): T; export declare class MyClass extends Super implements Super.Sub {} export declare abstract class MyClass extends Super {} export declare abstract /* comment */ class MyClass extends Super {} export declare enum Color { Red, Green, Blue } export declare const enum Num { One = 1, Two, Three } export declare module Super.Sub {} export declare module "module" {} export declare namespace Super.Sub {} // Export enum declaration export enum Color { Red, Green, Blue } export const enum Num { One = 1, Two, Three } // Export import alias export import shortname = Long.Namespace.Name; // Export interface declaration export interface MyObj {} export interface Square extends Shape, PenStroke {} // Export module declaration export module Super.Sub {} // Export namespace declaration export namespace Super.Sub {} // Export type alias declaration export type Name = string; export type NameResolver = () => string; export type NameOrResolver = Name | NameResolver; export type Container = { value: T }; // Export assignment export = obj; // Export as namespace (UMD module definition) export as namespace myModule; // Import alias import shortname = Long.Namespace.Name; // Import require import mod = require("module"); // Type-only imports and exports import type T from './mod'; import type { A, B } from './mod'; import type * as Types from './mod'; export type { T }; export type { T } from './mod'; /* Variable declaration */ // Type annotation const a: number; let a: number = 1, b: string; var { a, b }: { a: number, b: string } = { a: 1, b: 'b' }; // Definite assignment assertion let a!: number; // from file.js /* * Identifiers */ var example; function example() {} var príklad; function príklad() {} var 例子; function 例子() {} var $jquery; function _lodash() {} var \u0075nicod\u{65}; function \u0075nicod\u{65}() {} /* * Expressions (in expression statements) */ /* * Literals */ /* Keyword values */ var NULL = null; var TRUE = true; var FALSE = false; /* Number */ var decimal1 = 0; var decimal2 = 123.45; var decimal3 = .66667; var decimal4 = 10e20; var decimal5 = 0.2e+1; var decimal6 = .5E-20; var hex1 = 0xDEADBEEF; var hex2 = 0Xcafebabe; // ES2015 binary and octal numbers let binary1 = 0b1010; let binary2 = 0B00001111; let octal1 = 0o0123; let octal2 = 0O4567; // Legacy octal numbers var legacy_octal1 = 01; var legacy_octal2 = 007; // BigInt (ES2020) var decimal1 = 0n; var decimal2 = 123n; var hex1 = 0xDEADBEEFn; var hex2 = 0Xcafebaben; var binary1 = 0b1010n; var binary2 = 0B00001111n; var octal1 = 0o0123n; var octal2 = 0O4567n; /* String */ // Escape sequences '\b\f\n\r\t\v\0\'\"\\'; // Single character escape "\1\01\001"; // Octal escape (Annex B) '\xA9'; // Hexadecimal escape "\u00a9"; // Unicode escape '\u{1D306}'; // Unicode code point escape /* Array literal */ []; [1]; [1.0, 'two', 0x03]; // Trailing comma [ [1,2,3], [4,5,6], ]; // Spread syntax [1, ...a, 2]; /* Object literal */ a = {}; a = { prop: 'value' }; a = { 'prop': 'value', 1: true, .2: 2 }; // Trailing comma a = { prop: 'value', "extends": 1, }; // Shorthand property names a = { b, c, d }; // Getter / setter a = { _hidden: null, get property() { return _hidden; }, set property(value) { this._hidden = value; }, get: 'get', set() { return 'set'; } }; // Incorrectly highlighted as keyword a = { get : 'get', set () { return 'set'; } }; // Shorthand function notation a = { method() {}, *generator() {}, // Async function (ES2017) async method() {}, async /* comment */ method() {}, async get() {}, async() {},// method called "async" async: false, // property called "async" // Async generator (ES2018) async *generator() {} }; // Computed property names a = { ['prop']: 1, ['method']() {} }; // Spread properties (ES2018) a = { ...b, ...getObj('string') }; // Syntax errors a = { prop: 'val': 'val' }; a = { method() {}: 'val' }; a = { get property() {}: 'val' }; a = { *generator: 'val' }; a = { async prop: 'val' }; a = { ...b: 'val' }; a = { ...b() { return 'b'; } }; /* Regular expression literal */ /abc/; x = /abc/gi; function_with_regex_arg(/abc/); [ /abc/m, /def/u ]; a = { regex: /abc/s }; // s (dotAll): ES2018 (1 === 0) ? /abc/ : /def/; /abc/; /* Comment */ /abc/; // Comment var matches = /abc/.exec('Alphabet ... that should contain abc, right?'); // No regex here a = [thing / thing, thing / thing]; x = a /b/ c / d; // Character groups with backslashes /[ab\\]/; // a, b or backslash /[ab\]]/; // a, b or ] /\\[ab]/; // a or b preceded by backslash /\[ab]/; // Literally "[ab]" // Control escape /\cJ/; // Unicode property escape (ES2018) /\p{General_Category=Letter}/u; /\p{Letter}/u; // Named capture groups (ES2018) /(?\d{4})-(?\d{2})-(?\d{2})/u; /(?foo|bar)/u; /^(?.*).\k$/u; // backreference /* Template literal */ console.log(`The sum of 2 and 2 is ${2 + 2}`); let y = 8; let my_string = `This is a multiline string that also contains a template ${y + (4.1 - 2.2)}`; /* * Built-in values */ // global object values Infinity; NaN; undefined; // global object functions decodeURIComponent(); decodeURI(); encodeURIComponent(); encodeURI(); eval(); isFinite(); isNaN(); parseFloat(); parseInt(); // constructors (subset) Array(); BigInt(); // ES2020 Boolean(); Date(); Error(); Function(); Map(); Object(); Promise(); RegExp(); Set(); String(); Symbol(); // objects JSON.parse(); Math.random(); // object keywords arguments; globalThis; // ES2020 super; this; // dynamic import (ES2020) import("module").then(); import /* comment */ ("module").then(); import // comment ("module").then(); a = await import("module"); a = await import /* comment */ ("module"); a = await import // comment ("module"); // import.meta (ES2020) import.meta; import . /* comment */ meta; import // comment .meta; a = import.meta; a = import . /* comment */ meta; a = import // comment .meta; // new.target new.target; new . /* comment */ target; new // comment .target; // properties (subset) array.length; Math.PI; Number.NaN; object.constructor; Class.prototype; Symbol.asyncIterator; // ES2018 Symbol('desc').description; // ES2019 // methods (subset) array.keys(); date.toString(); object.valueOf(); re.test(); array.includes(); // ES2016 Object.values(); // ES2017 Object.entries(); // ES2017 string.padStart(); // ES2017 string.padEnd(); // ES2017 Object.getOwnPropertyDescriptors(); // ES2017 promise.finally(); // ES2018 Object.fromEntries(); // ES2019 string.trimStart(); // ES2019 string.trimEnd(); // ES2019 array.flat(); // ES2019 array.flatMap(); // ES2019 string.matchAll(); // ES2020 Promise.allSettled(); // ES2020 BigInt.asUintN(); // ES2020 string.replaceAll(); // ES2021 /* * Function expression, arrow function */ a = function () { return 1 }; a = function fn() { return; }; a = function fn(x) {}; a = function fn(x, y) {}; // Arrow function x => -x; () => {}; (x, y) => x + y; (x, y) => { return x + y; }; (x, y) => /* comment */ { return x + y; } /* comment */ ; (x) => ({ a: x }); // return object // Default parameters a = function fn(x, y = 1) {}; (x, y = 1) => x + y; // Parameter without default after default parameters a = function fn(x = 1, y) {}; (x = 1, y) => x + y; // Array destructuring a = function fn([x]) {}; a = function fn([x = 5, y = 7]) {}; // default values a = function fn([x, , y]) {}; // ignoring some returned values (elision) a = function fn([x, ...y]) {}; // rest syntax ([x]) => x; ([x = 5, y = 7]) => x + y; // default values ([x, , y]) => x + y; // ignoring some returned values (elision) ([x, ...y]) => y; // rest syntax // Object destructuring a = function fn({ x }) {}; a = function fn({ a: x, b: y }) {}; // assigning to new variable names a = function fn({ x = 5, y = 7 }) {}; // default values a = function fn({ a: x = 5, b: y = 7 }) {}; // assigning to new variable names and default values a = function fn({ ['a']: x, ['b']: y }) {}; // computed property names a = function fn({ x, y, ...rest }) {}; // rest properties (ES2018) ({ x }) => x; ({ a: x, b: y }) => x + y; // assigning to new variable names ({ x = 5, y = 7 }) => x + y; // default values ({ a: x = 5, b: y = 7 }) => x + y; // assigning to new variable names and default values ({ ['a']: x, ['b']: y }) => x + y; // computed property names ({ x, y, ...rest }) => x; // rest properties (ES2018) // Destructuring and default parameters a = function f([x, y] = [1, 2], {c: z} = {c: 3}) {}; ([x, y] = [1, 2], {c: z} = {c: x + y}) => x + y + z; // Generator function a = function*fn() {}; a = function * fn() {}; // Rest parameters a = function fn(...rest) {}; a = function fn(x, y, ...rest) {}; (...rest) => rest; (x, y, ...rest) => rest; // Async function (ES2017) a = async function fn() {}; a = async /* comment */ function fn() {}; a = async /* comment */ function fn() {}; // correctly highlighted, because async cannot be followed by a line terminator async x => x; async () => {}; async /* comment */ () => {}; async /* comment */ () => {}; // correctly highlighted, because async cannot be followed by a line terminator async(); // incorrectly highlighted // Async generator (ES2018) a = async function * fn() {}; // Trailing comma (ES2017) a = function fn(x, y,) {}; (x, y,) => x + y; // Trailing comma after rest parameters (syntax error) a = function fn(x, y, ...rest,) {}; (x, y, ...rest,) => rest; /* * Class expression */ a = class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} }; a = class extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } }; // Incorrectly highlighted as keyword a = class { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } }; // Properties/methods called "constructor" a = class { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } }; // Incorrectly highlighted as built-in method a = class { static constructor() { this._value = null; } }; /* * Operators * use groupings to test, as there can only be one expression (in the * first grouping item) */ // Grouping ( 1 + 2 ); // Increment / decrement ( ++a ); ( --a ); ( a++ ); ( a-- ); // Keyword unary ( await promise() ); // ES2017 ( delete obj.prop ); ( new Array() ); ( void 1 ); ( typeof 'str' ); ( yield 1 ); ( yield* fn() ); // Arithmetic ( 1 + 2 ); ( 1 - 2 ); ( 1 * 2 ); ( 1 / 2 ); ( 1 % 2 ); ( 1 ** 2 ); // ES2016 ( +1 ); ( -1 ); // Keyword relational ( prop in obj ); ( obj instanceof constructor ); // Comparison ( 1 == 2 ); ( 1 != 2 ); ( 1 === 2 ); ( 1 !== 2 ); ( 1 < 2 ); ( 1 > 2 ); ( 1 <= 2 ); ( 1 >= 2 ); // Bitwise ( 1 & 2 ); ( 1 | 2 ); ( 1 ^ 2 ); ( ~1 ); ( 1 << 2 ); ( 1 >> 2 ); ( 1 >>> 2 ); // Logical ( 1 && 2 ); ( 1 || 2 ); ( !1 ); // Nullish coalescing (ES2020) ( a ?? 1 ); // Assignment ( a = 1 ); ( a += 1 ); ( a -= 1 ); ( a *= 1 ); ( a /= 1 ); ( a %= 1 ); ( a **= 1 ); // ES2016 ( a <<= 1 ); ( a >>= 1 ); ( a >>>= 1 ); ( a &= 1 ); ( a |= 1 ); ( a ^= 1 ); // Array destructuring ( [a, b] = [1, 2] ); ( [a = 5, b = 7] = [1] ); // default values ( [a, , b] = f() ); // ignoring some returned values (elision) ( [a, ...b] = [1, 2, 3] ); // rest syntax // Object destructuring ( {a, b} = { a: 1, b: 2} ); ( { a: foo, b: bar } = { a: 1, b: 2 } ); // assigning to new variable names ( { a = 5, b = 7 } = { a: 1 } ); // default values ( { a: foo = 5, b: bar = 7 } = { a: 1 } ); // assigning to new variable names and default values ( { ['a']: foo, ['b']: bar } = { a: 1, b: 2 } ); // computed property names ( { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 } ); // rest properties (ES2018) // Comma 1, 2 ; // Conditional / ternary ( true ? 1 : 2 ); ( true ? : 2 ); // missing true value (syntax error) obj[ true ? 1, 2 : 3 ]; // comma operator inside true expression (syntax error) /* * Property accessors */ // Dot notation arr.length; ( obj . /* comment */ prototype /* comment */ . /* comment */ constructor /* comment */ ); const pi = Math.PI const num = 0 // Bracket notation arr['length']; ( obj /* comment */ [ /* comment */ 'prototype' /* comment */ ] /* comment */ /* comment */ [ /* comment */ 'constructor' /* comment */ ] /* comment */ ); // Mixed obj ['prototype'] . constructor; obj . prototype ['constructor']; /* * Function call */ fn(); obj.fn(1); obj['fn'](1, 2); // Spread syntax fn(x, y, ...args); // Trailing comma (ES2017) fn(x, y,); /* Tagged template */ myTag`That ${ person } is ${ age }`; /* * Optional chaining (ES2020) */ obj?.prototype; obj?.['constructor']; func?.(1, 2, ...args); foo?.3:0; // ternary operator, not optional chaining /* * Statements and declarations */ /* Use strict directive */ "use strict"; function () { 'use strict'; } // invalid directives " use strict"; 'use strict '; "use strict"; 'use strict'; "hello 'use strict' world"; fn("use strict"); { 'use strict'; } /* Block statement */ { hello(); world(); } { hello(); world() } /* Break statement */ break; break label; break // end statement label; // separate statement { break } /* * Class declaration */ class Foo { constructor() { } method(x, y) { return x + y; } *generator() {} } class Foo extends Bar { constructor() { this._value = null; } get property() { return this._value; } set property(x) { this._value = x; } async method() { return 'async'; } async *generator() { return 'generator'; } static method() { return 'static'; } static get property() { return this.staticval; } static set property(x) { this.staticval = x; } static async method() { return 'async'; } static async *generator() { return 'generator'; } get() { return this.val; } set(v) { this.val = v; } async() { return 'async'; } static() { return 'static'; } static get() { return this.val; } static set(v) { this.val = v; } static async() { return 'async'; } static static() { return 'static'; } static static () { return 'static'; } } // Incorrectly highlighted as keyword class Foo { get () { return this.val; } set (v) { this.val = v; } static () { return 'static'; } static get () { return this.val; } static set (v) { this.val = v; } } // Properties/methods called "constructor" class Foo { *constructor() { this._value = null; } get constructor() { this._value = null; } set constructor() { this._value = null; } async constructor() { this._value = null; } async *constructor() { this._value = null; } } // Incorrectly highlighted as built-in method class Foo { static constructor() { this._value = null; } } /* Continue statement */ continue; continue label; continue // end statement label; // separate statement { continue } /* Debugger statement */ debugger; debugger ; /* Export / import statement */ export { a }; export { a, b, }; export { x as a }; export { x as a, y as b, }; export var a; export let a, b; export const a = 1; export var a = 1, b = 2; export function fn() {} export function* fn() {} export class Class {} export default 1; export default function () {} export default function *fn() {} export default class {} export { a as default, b }; export * from 'module'; export * as ns from 'module'; export { a, b } from 'module'; export { x as a, y as b, } from 'module'; export { default } from 'module'; import a from "module"; import * as ns from "module"; import { a } from "module"; import { a, b } from "module"; import { x as a } from "module"; import { x as a, y as b } from "module"; import { default as a } from "module"; import a, { b } from "module"; import a, * as ns from "module"; import "module"; /* For statement */ for (i = 0; i < 10; i++) something(); for (var i = 10; i >= 0; i--) { something(); } for (i = 0, j = 0; i < 10; i++, j++) something(); for (let i = 10, j = 0; i >= 0; i--, j += 1) { something(); } for (prop in obj) {} // matches "in" binary operator instead for (const prop in obj) {} for (val of generator()) {} for (var val of array) {} for await (let x of asyncIterable) {} // ES2018 for /* comment */ await /* comment */ (let x of asyncIterable) {} // ES2018 /* Function declaration statement */ function fn() { return; } async function fn() {} // ES2017 async /* comment */ function fn() {} // ES2017 async /* comment */ function fn() {} // correctly highlighted, because async cannot be followed by a line terminator /* If..else statement */ if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) lessThan(); else if (a > 0) greaterThan(); else equal(); if (a < 0) { lessThan(); } else if (a > 0) { greaterThan(); } else { equal(); } /* Label statement */ outer: for (var i = 0; i < 10; i++) { inner /* comment */ : for (var j = 0; j < 2; j++) {} } loop /* comment */ : for (var i in obj) {} // incorrectly highlighted (though it may appear correct) /* Return statement */ return; return 1; return // end statement 1; // separate statement return ( 1 ); { return a } /* Switch statement */ switch (foo) { case 1: doIt(); break; case '2': doSomethingElse(); break; default: oops(); } /* Throw statement */ throw e; throw new Error(); throw // end statement (syntax error) e; // separate statement throw ( new Error() ); { throw new Error() } /* Try...catch statement */ try { somethingDangerous(); } catch (e) { didntWork(e); } catch { // ES2019 return false; } finally { cleanup(); } /* Variable declaration */ // Declaration only const a; let a, b, c; var a , b , c ; // With assignment const a = 1; let a = 1, b = [2, 3], c = 4; var a = 1 , b = [ 2 , 3 ] , c = 4 ; // Array destructuring var [a, b] = [1, 2]; var [ a , b ] = [ 1 , 2 ] ; var [a = 5, b = 7] = [1]; // default values var [a, , b] = f(); // ignoring some returned values (elision) var [a, ...b] = [1, 2, 3]; // rest syntax // Object destructuring var { a, b } = { a: 1, b: 2 }; var { a , b } = { a : 1 , b : 2 } ; var { a: foo, b: bar } = { a: 1, b: 2 }; // assigning to new variable names var { a = 5, b = 7 } = { a: 1 }; // default values var { a: foo = 5, b: bar = 7 } = { a: 1 }; // assigning to new variable names and default values var { ['a']: foo, ['b']: bar } = { a: 1, b: 2 }; // computed property names var { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 }; // rest properties (ES2018) /* While / do...while statement */ while (true) something(); while (1) { something(); } do something(); while (false); do { something(); } while (0); /* With statement */ with (Math) { a = PI * r * r; x = r * cos(PI); y = r * sin(PI / 2); } /* * JSDoc */ /* Inline tag */ /** {@link String} */ /** * {@link http://example.com | Ex\{am\}ple} */ /** {@link*/ /* Type */ /** {String} */ /** * {Foo.B\{a\}r} */ /** {number*/ /* Block tag */ // No arguments /** @constructor */ /** * @deprecated since 1.1.0 */ /** @public*/ // Generic argument /** @default 3.14159 */ /** * @tutorial tutorial-1 */ /** @variation 2*/ // Event name argument /** @fires earthquakeEvent */ /** * @listens touchstart */ /** @event newEvent*/ // Keyword argument /** @access protected */ /** * @kind module */ /** @access private*/ // Name/namepath argument /** @alias foo */ /** * @extends bar */ /** @typeParam T*/ // Type and name arguments /** @param {String} name - A \{chosen\} \@name */ /** * @member {Object} child */ /** @property {number} num*/ // Borrows block tag /** @borrows foo as bar */ /** * @borrows foo as */ /** @borrows foo*/ // Todo block tag /** @todo write more/less test cases */ 070701000002BC000081A400000000000000000000000166590806000001E7000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.twig{{ var }} {{ var|escape }} {{ var|e }} {# shortcut to escape a variable #} {% for user in users %} * {{ user.name }} {% else %} No users have been found. {% endfor %} {% extends "layout.html" %} {% block content %} Content of the page... {% endblock %} {% autoescape "html" %} {{ var }} {{ var|raw }} {# var won't be escaped #} {{ var|escape }} {# var won't be doubled-escaped #} {% endautoescape %} {{ include('page.html', sandboxed = true) }} 070701000002BD000081A400000000000000000000000166590806000001EA000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.wrenSystem.print("Hello, world!") class Wren { flyTo(city) { System.print("Flying to %(city)") } } // https://wren.io/values.html#numbers var numbers = [ 0, 1234, -5678, 3.14159, 1.0, -12.34, 0.0314159e02, 0.0314159e+02, 314.159e-02, 0xcaffe2 ] var int = 12 var float = 0.004 // var invalidHex = 0xG // var invalidOctal = 08u var adjectives = Fiber.new { ["small", "clean", "fast"].each {|word| Fiber.yield(word) } } while (!adjectives.isDone) System.print(adjectives.call()) 070701000002BE000081A4000000000000000000000001665908060000006E000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/file.xml momomomo 070701000002BF000081A400000000000000000000000166590806000003F9000000000000000000000000000000000000003600000000gtksourceview-5.12.1/tests/syntax-highlighting/file.y%{ #include #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, $1234); } | program stmt_or_error { $$ = node_list_add (parser, MS_NODE_LIST ($1), $2); } ; stmt_or_error: error ';' { $$ = NULL; } | stmt ';' { $$ = ; } ; variable: IDENTIFIER { $$ = node_var (parser, $1); } ; %% 070701000002C0000081A400000000000000000000000166590806000004A8000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/syntax-highlighting/file.yaml# ASCII Art --- | \//||\/|| // || ||__ --- > Mark McGwire's year was crippled by a knee injury. unicode: "Sosa did fine.\u263A" control: "\b1998\t1999\t2000\n" hex esc: "\x0d\x0a is \r\n" single: '"Howdy!" he cried.' quoted: ' # Not a ''comment''.' tie-fighter: '|\-*-/|' plain: This unquoted scalar spans many lines. quoted: "So does this quoted scalar.\n" canonical: 1.23015e+3 exponential: 12.3015e+02 fixed: 1230.15 negative infinity: -.inf not a number: .NaN null: booleans: [ true, false ] string: '012345' url: https://example.com https://example.com invoice: 34843 date : 2001-01-23 bill-to: &id001 given : Chris family : Dumars address: lines: | 458 Walkman Dr. Suite #292 city : Royal Oak state : MI postal : 48046 ship-to: *id001 product: - sku : BL394D quantity : 4 description : Basketball price : 450.00 - sku : BL4438H quantity : 1 description : Super Hoop price : 2392.00 tax : 251.42 total: 4443.52 comments: Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338. 070701000002C1000081A400000000000000000000000166590806000000F6000000000000000000000000000000000000003500000000gtksourceview-5.12.1/tests/syntax-highlighting/gtkrc# -- THEME AUTO-WRITTEN DO NOT EDIT include "/usr/share/themes/Clearlooks/gtk-2.0/gtkrc" style "user-font" { font_name="Tahoma 11" } widget_class "*" style "user-font" include "/home/muntyan/.gtkrc-2.0.mine" # -- THEME AUTO-WRITTEN DO NOT EDIT 070701000002C2000081A400000000000000000000000166590806000000D6000000000000000000000000000000000000003B00000000gtksourceview-5.12.1/tests/syntax-highlighting/meson.buildproject('gtksourceview', 'c') glib = dependency('glib-2.0') escapes = 'backslash \\ single quote \' newline \n octal \777 and more' executable( 'hello', sources : [ 'hello.c', ], dependencies : glib, ) 070701000002C3000081A40000000000000000000000016659080600000124000000000000000000000000000000000000003800000000gtksourceview-5.12.1/tests/syntax-highlighting/todo.txt(A) Call Mom @Phone +Family (A) Schedule annual checkup +Health (B) Outline chapter 5 +Novel @Computer (C) Add cover sheets @Office +TPSReports Plan backyard herb garden @Home Pick up milk @GroceryStore Research self-publishing services +Novel @Computer x Download Todo.txt mobile app @Phone 070701000002C4000081A40000000000000000000000016659080600004A7F000000000000000000000000000000000000002D00000000gtksourceview-5.12.1/tests/test-completion.c/* * This file is part of GtkSourceView * * Copyright (C) 2007 - Jesús Barbero Rodríguez * Copyright (C) 2013 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include #include #define TEST_TYPE_PROPOSAL (test_proposal_get_type()) G_DECLARE_FINAL_TYPE (TestProposal, test_proposal, TEST, PROPOSAL, GObject) struct _TestProposal { GObject parent_instance; char *label; char *text; char *markup; char *info; char *icon_name; GIcon *gicon; GtkIconPaintable *icon; }; G_DEFINE_TYPE_WITH_CODE (TestProposal, test_proposal, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_COMPLETION_PROPOSAL, NULL)) static void test_proposal_dispose (GObject *object) { TestProposal *p = TEST_PROPOSAL (object); g_clear_pointer (&p->label, g_free); g_clear_pointer (&p->text, g_free); g_clear_pointer (&p->markup, g_free); g_clear_pointer (&p->info, g_free); g_clear_pointer (&p->icon_name, g_free); g_clear_object (&p->gicon); g_clear_object (&p->icon); G_OBJECT_CLASS (test_proposal_parent_class)->dispose (object); } static void test_proposal_class_init (TestProposalClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = test_proposal_dispose; } static void test_proposal_init (TestProposal *self) { } static TestProposal * test_proposal_new (void) { return g_object_new (TEST_TYPE_PROPOSAL, NULL); } struct _TestProvider { GObject parent; GList *proposals; gint priority; gchar *title; GtkIconPaintable *provider_icon; GtkIconPaintable *item_icon; GIcon *item_gicon; /* If it's a random provider, a subset of 'proposals' are chosen on * each populate. Otherwise, all the proposals are shown. */ guint is_random : 1; }; struct _TestProviderClass { GObjectClass parent_class; }; static void test_provider_iface_init (GtkSourceCompletionProviderInterface *iface); #define TEST_TYPE_PROVIDER (test_provider_get_type()) G_DECLARE_FINAL_TYPE (TestProvider, test_provider, TEST, PROVIDER, GObject) static GtkSourceCompletionWords *word_provider; static GtkSourceCompletionSnippets *snippet_provider; static TestProvider *fixed_provider; static TestProvider *random_provider; static GMainLoop *main_loop; G_DEFINE_TYPE_WITH_CODE (TestProvider, test_provider, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_COMPLETION_PROVIDER, test_provider_iface_init)) static gchar * test_provider_get_title (GtkSourceCompletionProvider *provider) { return g_strdup (((TestProvider *)provider)->title); } static gint test_provider_get_priority (GtkSourceCompletionProvider *provider, GtkSourceCompletionContext *context) { return ((TestProvider *)provider)->priority; } static GList * select_random_proposals (GList *all_proposals) { GList *selection = NULL; GList *prop; for (prop = all_proposals; prop != NULL; prop = g_list_next (prop)) { if (g_random_boolean ()) { selection = g_list_prepend (selection, prop->data); } } return selection; } static void test_provider_populate_async (GtkSourceCompletionProvider *completion_provider, GtkSourceCompletionContext *context, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { TestProvider *provider = (TestProvider *)completion_provider; GListStore *results; GList *proposals; GTask *task; if (provider->is_random) { proposals = select_random_proposals (provider->proposals); } else { proposals = provider->proposals; } task = g_task_new (completion_provider, cancellable, callback, user_data); results = g_list_store_new (GTK_SOURCE_TYPE_COMPLETION_PROPOSAL); for (const GList *iter = proposals; iter; iter = iter->next) { g_list_store_append (results, iter->data); } g_task_return_pointer (task, results, g_object_unref); if (provider->is_random) { g_list_free (proposals); } } static GListModel * test_provider_populate_finish (GtkSourceCompletionProvider *provider, GAsyncResult *result, GError **error) { return g_task_propagate_pointer (G_TASK (result), error); } static void test_provider_display (GtkSourceCompletionProvider *provider, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal, GtkSourceCompletionCell *cell) { TestProposal *p = (TestProposal *)proposal; GtkSourceCompletionColumn column; g_assert (TEST_IS_PROVIDER (provider)); g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (context)); g_assert (TEST_IS_PROPOSAL (p)); g_assert (GTK_SOURCE_IS_COMPLETION_CELL (cell)); column = gtk_source_completion_cell_get_column (cell); if (column == GTK_SOURCE_COMPLETION_COLUMN_TYPED_TEXT) { g_assert (p->markup || p->text || p->label); if (p->markup) gtk_source_completion_cell_set_markup (cell, p->markup); else if (p->label) gtk_source_completion_cell_set_text (cell, p->label); else gtk_source_completion_cell_set_text (cell, p->text); } else if (column == GTK_SOURCE_COMPLETION_COLUMN_COMMENT || column == GTK_SOURCE_COMPLETION_COLUMN_DETAILS) { gchar *str = g_strdup (p->info); if (str != NULL) { g_strstrip (str); } if (str != NULL) { gtk_source_completion_cell_set_text (cell, str); } else { gtk_source_completion_cell_set_text (cell, NULL); } } else if (column == GTK_SOURCE_COMPLETION_COLUMN_ICON) { if (p->icon_name) gtk_source_completion_cell_set_icon_name (cell, p->icon_name); else if (p->gicon) gtk_source_completion_cell_set_gicon (cell, p->gicon); else gtk_source_completion_cell_set_icon_name (cell, NULL); } } static void test_provider_activate (GtkSourceCompletionProvider *provider, GtkSourceCompletionContext *context, GtkSourceCompletionProposal *proposal) { GtkTextBuffer *buffer; GtkTextIter begin, end; gtk_source_completion_context_get_bounds (context, &begin, &end); buffer = gtk_text_iter_get_buffer (&begin); if (TEST_IS_PROPOSAL (proposal)) { gtk_text_buffer_begin_user_action (buffer); gtk_text_buffer_delete (buffer, &begin, &end); gtk_text_buffer_insert (buffer, &begin, TEST_PROPOSAL (proposal)->text, -1); gtk_text_buffer_end_user_action (buffer); } } static void test_provider_iface_init (GtkSourceCompletionProviderInterface *iface) { iface->get_title = test_provider_get_title; iface->populate_async = test_provider_populate_async; iface->populate_finish = test_provider_populate_finish; iface->get_priority = test_provider_get_priority; iface->display = test_provider_display; iface->activate = test_provider_activate; } static void test_provider_dispose (GObject *gobject) { TestProvider *self = (TestProvider *)gobject; g_list_free_full (self->proposals, g_object_unref); self->proposals = NULL; g_clear_object (&self->provider_icon); g_clear_object (&self->item_icon); g_clear_object (&self->item_gicon); G_OBJECT_CLASS (test_provider_parent_class)->dispose (gobject); } static void test_provider_finalize (GObject *gobject) { TestProvider *self = (TestProvider *)gobject; g_free (self->title); self->title = NULL; G_OBJECT_CLASS (test_provider_parent_class)->finalize (gobject); } static void test_provider_class_init (TestProviderClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = test_provider_dispose; gobject_class->finalize = test_provider_finalize; } static void test_provider_init (TestProvider *self) { GtkIconTheme *theme; GIcon *icon; GIcon *emblem_icon; GEmblem *emblem; theme = gtk_icon_theme_get_for_display (gdk_display_get_default ()); /* Just use some defaults for icons here. Normally we would create these with * the widget to get proper direction, scale, etc. */ self->provider_icon = gtk_icon_theme_lookup_icon (theme, "dialog-information", NULL, 16, 1, GTK_TEXT_DIR_LTR, GTK_ICON_LOOKUP_PRELOAD); self->item_icon = gtk_icon_theme_lookup_icon (theme, "trophy-gold", NULL, 16, 1, GTK_TEXT_DIR_LTR, GTK_ICON_LOOKUP_PRELOAD); icon = g_themed_icon_new ("trophy-gold"); emblem_icon = g_themed_icon_new ("emblem-urgent"); emblem = g_emblem_new (emblem_icon); self->item_gicon = g_emblemed_icon_new (icon, emblem); g_object_unref (icon); g_object_unref (emblem_icon); g_object_unref (emblem); } static void test_provider_set_fixed (TestProvider *provider, gint nb_proposals) { TestProposal *item; GList *proposals = NULL; gint i; g_list_free_full (provider->proposals, g_object_unref); item = test_proposal_new (); item->markup = g_strdup ("A very long proposal. I repeat, a very long proposal!"); item->text = g_strdup ("A very long proposal. I repeat, a very long proposal!"); item->info = g_strdup ("To test the horizontal scrollbar and the markup."); proposals = g_list_prepend (proposals, item); item = test_proposal_new (); item->markup = g_strdup ("A proposal with a symbolic icon"); item->text = g_strdup ("Test setting the icon-name property"); item->icon_name = g_strdup ("face-cool-symbolic"); proposals = g_list_prepend (proposals, item); item = test_proposal_new (); item->markup = g_strdup ("A proposal with an emblem GIcon"); item->text = g_strdup ("Test setting the GIcon property"); item->gicon = g_object_ref (provider->item_gicon); proposals = g_list_prepend (proposals, item); for (i = nb_proposals - 1; i > 0; i--) { gchar *name = g_strdup_printf ("Proposal %d", i); item = test_proposal_new (); item->label = name; item->text = g_strdup (name); item->icon = g_object_ref (provider->item_icon); item->info = g_strdup ("The extra info of the proposal.\nA second line."); proposals = g_list_prepend (proposals, item); } provider->proposals = proposals; provider->is_random = 0; } static void test_provider_set_random (TestProvider *provider, gint nb_proposals) { GList *proposals = NULL; gint i; g_list_free_full (provider->proposals, g_object_unref); for (i = 0; i < nb_proposals; i++) { TestProposal *item; gchar *padding = g_strnfill ((i * 3) % 10, 'o'); gchar *name = g_strdup_printf ("Propo%ssal %d", padding, i); item = test_proposal_new (); item->label = name; item->text = g_strdup (name); item->icon = g_object_ref (provider->item_icon); proposals = g_list_prepend (proposals, item); g_free (padding); } provider->proposals = proposals; provider->is_random = 1; } static void add_remove_provider (GtkCheckButton *button, GtkSourceCompletion *completion, GtkSourceCompletionProvider *provider) { g_return_if_fail (provider != NULL); if (gtk_check_button_get_active (button)) { gtk_source_completion_add_provider (completion, provider); } else { gtk_source_completion_remove_provider (completion, provider); } } static void enable_word_provider_toggled_cb (GtkCheckButton *button, GtkSourceCompletion *completion) { add_remove_provider (button, completion, GTK_SOURCE_COMPLETION_PROVIDER (word_provider)); } static void enable_fixed_provider_toggled_cb (GtkCheckButton *button, GtkSourceCompletion *completion) { add_remove_provider (button, completion, GTK_SOURCE_COMPLETION_PROVIDER (fixed_provider)); } static void enable_random_provider_toggled_cb (GtkCheckButton *button, GtkSourceCompletion *completion) { add_remove_provider (button, completion, GTK_SOURCE_COMPLETION_PROVIDER (random_provider)); } static void nb_proposals_changed_cb (GtkSpinButton *spin_button, TestProvider *provider) { gint nb_proposals = gtk_spin_button_get_value_as_int (spin_button); if (provider->is_random) { test_provider_set_random (provider, nb_proposals); } else { test_provider_set_fixed (provider, nb_proposals); } } static void enable_snippet_provider_toggled_cb (GtkCheckButton *button, GtkSourceCompletion *completion) { add_remove_provider (button, completion, GTK_SOURCE_COMPLETION_PROVIDER (snippet_provider)); } static void create_completion (GtkSourceView *source_view, GtkSourceCompletion *completion) { /* Snippets completion provider */ snippet_provider = gtk_source_completion_snippets_new (); g_object_set (snippet_provider, "priority", 20, NULL); gtk_source_completion_add_provider (completion, GTK_SOURCE_COMPLETION_PROVIDER (snippet_provider)); /* Words completion provider */ word_provider = gtk_source_completion_words_new (NULL); gtk_source_completion_words_register (word_provider, gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view))); gtk_source_completion_add_provider (completion, GTK_SOURCE_COMPLETION_PROVIDER (word_provider)); g_object_set (word_provider, "priority", 10, NULL); /* Fixed provider: the proposals don't change */ fixed_provider = g_object_new (test_provider_get_type (), NULL); test_provider_set_fixed (fixed_provider, 3); fixed_provider->priority = 5; fixed_provider->title = g_strdup ("Fixed Provider"); gtk_source_completion_add_provider (completion, GTK_SOURCE_COMPLETION_PROVIDER (fixed_provider)); /* Random provider: the proposals vary on each populate */ random_provider = g_object_new (test_provider_get_type (), NULL); test_provider_set_random (random_provider, 10); random_provider->priority = 1; random_provider->title = g_strdup ("Random Provider"); gtk_source_completion_add_provider (completion, GTK_SOURCE_COMPLETION_PROVIDER (random_provider)); } static void create_window (void) { GtkSourceLanguageManager *lm = gtk_source_language_manager_get_default (); GtkSourceLanguage *l = gtk_source_language_manager_get_language (lm, "c"); GtkBuilder *builder; GError *error = NULL; GtkWindow *window; GtkTextBuffer *buffer; GtkSourceView *source_view; GtkSourceCompletion *completion; GtkCheckButton *remember_info_visibility; GtkCheckButton *select_on_show; GtkCheckButton *show_icons; GtkCheckButton *enable_word_provider; GtkCheckButton *enable_fixed_provider; GtkCheckButton *enable_random_provider; GtkCheckButton *enable_snippet_provider; GtkSpinButton *nb_fixed_proposals; GtkSpinButton *nb_random_proposals; builder = gtk_builder_new (); gtk_builder_add_from_resource (builder, "/org/gnome/gtksourceview/tests/ui/test-completion.ui", &error); if (error != NULL) { g_error ("Impossible to load test-completion.ui: %s", error->message); } window = GTK_WINDOW (gtk_builder_get_object (builder, "window")); source_view = GTK_SOURCE_VIEW (gtk_builder_get_object (builder, "source_view")); remember_info_visibility = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "checkbutton_remember_info_visibility")); select_on_show = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "checkbutton_select_on_show")); show_icons = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "checkbutton_show_icons")); enable_word_provider = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "checkbutton_word_provider")); enable_snippet_provider = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "checkbutton_snippet_provider")); enable_fixed_provider = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "checkbutton_fixed_provider")); enable_random_provider = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "checkbutton_random_provider")); nb_fixed_proposals = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "spinbutton_nb_fixed_proposals")); nb_random_proposals = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "spinbutton_nb_random_proposals")); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view)); gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer), l); gtk_source_view_set_enable_snippets (source_view, TRUE); completion = gtk_source_view_get_completion (source_view); g_signal_connect_swapped (window, "destroy", G_CALLBACK (g_main_loop_quit), main_loop); g_object_bind_property (completion, "select-on-show", select_on_show, "active", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); g_object_bind_property (completion, "remember-info-visibility", remember_info_visibility, "active", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); g_object_bind_property (completion, "show-icons", show_icons, "active", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); create_completion (source_view, completion); g_signal_connect (enable_snippet_provider, "toggled", G_CALLBACK (enable_snippet_provider_toggled_cb), completion); g_signal_connect (enable_word_provider, "toggled", G_CALLBACK (enable_word_provider_toggled_cb), completion); g_signal_connect (enable_fixed_provider, "toggled", G_CALLBACK (enable_fixed_provider_toggled_cb), completion); g_signal_connect (enable_random_provider, "toggled", G_CALLBACK (enable_random_provider_toggled_cb), completion); g_signal_connect (nb_fixed_proposals, "value-changed", G_CALLBACK (nb_proposals_changed_cb), fixed_provider); g_signal_connect (nb_random_proposals, "value-changed", G_CALLBACK (nb_proposals_changed_cb), random_provider); g_object_unref (builder); gtk_window_present (window); } int main (int argc, char *argv[]) { main_loop = g_main_loop_new (NULL, FALSE); gtk_init (); gtk_source_init (); create_window (); g_main_loop_run (main_loop); /* Not really useful, except for debugging memory leaks. */ g_object_unref (word_provider); g_object_unref (snippet_provider); g_object_unref (fixed_provider); g_object_unref (random_provider); return 0; } 070701000002C5000081A400000000000000000000000166590806000000CB000000000000000000000000000000000000003900000000gtksourceview-5.12.1/tests/test-completion.gresource.xml test-completion.ui 070701000002C6000081A40000000000000000000000016659080600002DE3000000000000000000000000000000000000002E00000000gtksourceview-5.12.1/tests/test-completion.ui 100000 3 1 10 100000 10 1 10 1 6 6 6 6 10 5 10 Remember info visibility 1 0 0 Select first on show 1 0 1 Show icons 1 0 3 0 1 start Providers 0 2 start General options 0 0 10 Word provider 1 1 0 0 Fixed provider 1 1 0 1 Random provider 1 1 0 2 Snippet provider 1 1 0 3 0 3 start Fixed provider proposals 0 4 10 5 Number of proposals: 0 0 1 3 adjustment1 3 1 0 0 0 0 5 start Random provider proposals 0 6 10 5 Max number of proposals: 0 0 1 adjustment2 10 1 0 0 7 0 0 600 1 True True True True 2 2 True True 1 0 070701000002C7000081A4000000000000000000000001665908060000069B000000000000000000000000000000000000002A00000000gtksourceview-5.12.1/tests/test-int2str.c/* * This file is part of GtkSourceView * * Copyright 2019 - Christian Hergert * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include #include gint main (gint argc, gchar *argv[]) { GTimer *timer = g_timer_new (); gdouble d; guint i; g_timer_reset (timer); for (i = 0; i < 20000; i++) { const gchar *str; _gtk_source_utils_int_to_string (i, &str); } d = g_timer_elapsed (timer, NULL); g_print ("int_to_string: %lf\n", d); g_timer_reset (timer); for (i = 0; i < 20000; i++) { gchar tmpbuf[12]; g_snprintf (tmpbuf, 12, "%u", i); } d = g_timer_elapsed (timer, NULL); g_print (" g_snprintf: %lf\n", d); /* Make sure implementation is correct */ for (i = 0; i < 20000; i++) { const gchar *str1; gint len1, len2; gchar str2[12]; len1 = _gtk_source_utils_int_to_string (i, &str1); len2 = g_snprintf (str2, sizeof str2, "%u", i); g_assert_cmpint (len1, ==, len2); g_assert_cmpstr (str1, ==, str2); } return 0; } 070701000002C8000081A40000000000000000000000016659080600000538000000000000000000000000000000000000002700000000gtksourceview-5.12.1/tests/test-load.c#include #include static void finished_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GError *error = NULL; GMainLoop *loop = user_data; if (!gtk_source_file_loader_load_finish (GTK_SOURCE_FILE_LOADER (object), result, &error)) g_printerr ("Error loading file: %s\n", error->message); g_clear_error (&error); g_main_loop_quit (loop); } int main (int argc, char *argv[]) { GFile *file = NULL; GMainLoop *loop = NULL; GtkSourceBuffer *buffer = NULL; GtkSourceFile *sfile = NULL; GtkSourceFileLoader *loader = NULL; if (argc != 2) { g_printerr ("usage: %s FILENAME\n", argv[0]); return EXIT_FAILURE; } gtk_source_init (); loop = g_main_loop_new (NULL, FALSE); file = g_file_new_for_commandline_arg (argv[1]); buffer = gtk_source_buffer_new (NULL); sfile = gtk_source_file_new (); gtk_source_file_set_location (sfile, file); loader = gtk_source_file_loader_new (buffer, sfile); gtk_source_file_loader_load_async (loader, G_PRIORITY_DEFAULT, NULL, NULL, NULL, NULL, finished_cb, loop); g_main_loop_run (loop); g_object_unref (loader); g_object_unref (sfile); g_object_unref (buffer); g_object_unref (file); g_main_loop_unref (loop); return EXIT_SUCCESS; } 070701000002C9000081A40000000000000000000000016659080600000A32000000000000000000000000000000000000002A00000000gtksourceview-5.12.1/tests/test-preview.c#include static gboolean close_request (GtkWidget *window, GMainLoop *main_loop) { g_main_loop_quit (main_loop); return FALSE; } static void on_activate (GtkSourceStyleSchemePreview *preview, GtkBox *box) { GtkSourceStyleScheme *scheme; g_assert (GTK_SOURCE_IS_STYLE_SCHEME_PREVIEW (preview)); g_assert (GTK_IS_BOX (box)); scheme = gtk_source_style_scheme_preview_get_scheme (preview); g_print ("Selected: %s\n", gtk_source_style_scheme_get_name (scheme)); for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (box)); child; child = gtk_widget_get_next_sibling (child)) { gtk_source_style_scheme_preview_set_selected (GTK_SOURCE_STYLE_SCHEME_PREVIEW (child), FALSE); } gtk_source_style_scheme_preview_set_selected (preview, TRUE); } int main (int argc, char *argv[]) { GMainLoop *main_loop = g_main_loop_new (NULL, FALSE); GtkSourceStyleSchemeManager *manager; const char * const *ids; GtkScrolledWindow *scroller; GtkWindow *window; GtkBox *box; gtk_init (); gtk_source_init (); manager = gtk_source_style_scheme_manager_get_default (); ids = gtk_source_style_scheme_manager_get_scheme_ids (manager); window = g_object_new (GTK_TYPE_WINDOW, "default-width", 120, "default-height", 500, NULL); scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW, "propagate-natural-width", TRUE, "hscrollbar-policy", GTK_POLICY_NEVER, "min-content-height", 250, NULL); box = g_object_new (GTK_TYPE_BOX, "margin-top", 12, "margin-bottom", 12, "margin-start", 12, "margin-end", 12, "orientation", GTK_ORIENTATION_VERTICAL, "spacing", 12, NULL); for (guint i = 0; ids[i]; i++) { GtkSourceStyleScheme *scheme = gtk_source_style_scheme_manager_get_scheme (manager, ids[i]); GtkWidget *preview = gtk_source_style_scheme_preview_new (scheme); g_signal_connect (preview, "activate", G_CALLBACK (on_activate), box); gtk_box_append (box, preview); } gtk_window_set_child (window, GTK_WIDGET (scroller)); gtk_scrolled_window_set_child (scroller, GTK_WIDGET (box)); gtk_window_present (window); g_signal_connect (window, "close-request", G_CALLBACK (close_request), main_loop); g_main_loop_run (main_loop); g_main_loop_unref (main_loop); return 0; } 070701000002CA000081A40000000000000000000000016659080600002112000000000000000000000000000000000000003600000000gtksourceview-5.12.1/tests/test-search-performances.c/* * This file is part of GtkSourceView * * Copyright (C) 2013 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include #include static GMainLoop *main_loop; /* This measures the execution times for: * - basic search: with gtk_text_iter_forward_search(); * - "smart" search: the first search with gtk_text_iter_forward_search(), later * searches with gtk_text_iter_forward_to_tag_toggle(); * - regex search. * * For the "smart" search, only the first search is measured. Later searches * are really fast (going to the previous/next occurrence is done in O(log n)). * Different search flags are also tested. We can see a big difference between * the case sensitive search and case insensitive. */ #define NB_LINES 100000 static void on_notify_search_occurrences_count_cb (GtkSourceSearchContext *search_context, GParamSpec *spec, GTimer *timer) { g_print ("smart asynchronous search, case sensitive: %lf seconds.\n", g_timer_elapsed (timer, NULL)); g_main_loop_quit (main_loop); } int main (int argc, char *argv[]) { GtkSourceBuffer *buffer; GtkSourceSearchContext *search_context; GtkSourceSearchSettings *search_settings; GtkTextIter iter; GtkTextIter match_end; GTimer *timer; gint i; GtkTextSearchFlags flags; gchar *regex_pattern; gtk_init (); buffer = gtk_source_buffer_new (NULL); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); for (i = 0; i < NB_LINES; i++) { gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter, "A line of text to fill the text buffer. Is it long enough?\n", -1); } gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &iter, "foo\n", -1); /* Basic search, no flags */ timer = g_timer_new (); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); flags = 0; while (gtk_text_iter_forward_search (&iter, "foo", flags, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("basic forward search, no flags: %lf seconds.\n", g_timer_elapsed (timer, NULL)); /* Basic search, with flags always enabled by gsv */ g_timer_start (timer); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); flags = GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY; while (gtk_text_iter_forward_search (&iter, "foo", flags, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("basic forward search, visible and text only flags: %lf seconds.\n", g_timer_elapsed (timer, NULL)); /* Basic search, with default flags in gsv */ g_timer_start (timer); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); flags = GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY | GTK_TEXT_SEARCH_CASE_INSENSITIVE; while (gtk_text_iter_forward_search (&iter, "foo", flags, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("basic forward search, all flags: %lf seconds.\n", g_timer_elapsed (timer, NULL)); /* Smart forward search, with default flags in gsv */ search_settings = gtk_source_search_settings_new (); search_context = gtk_source_search_context_new (buffer, search_settings); g_timer_start (timer); gtk_source_search_settings_set_search_text (search_settings, "foo"); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); while (gtk_source_search_context_forward (search_context, &iter, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("smart synchronous forward search, case insensitive: %lf seconds.\n", g_timer_elapsed (timer, NULL)); /* Smart forward search, case sensitive */ g_timer_start (timer); gtk_source_search_settings_set_search_text (search_settings, NULL); gtk_source_search_settings_set_case_sensitive (search_settings, TRUE); gtk_source_search_settings_set_search_text (search_settings, "foo"); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); while (gtk_source_search_context_forward (search_context, &iter, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("smart synchronous forward search, case sensitive: %lf seconds.\n", g_timer_elapsed (timer, NULL)); /* Regex search: search "foo" */ g_timer_start (timer); gtk_source_search_settings_set_search_text (search_settings, NULL); gtk_source_search_settings_set_regex_enabled (search_settings, TRUE); gtk_source_search_settings_set_search_text (search_settings, "foo"); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); while (gtk_source_search_context_forward (search_context, &iter, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("regex search: 'foo' (no partial matches): %lf seconds.\n", g_timer_elapsed (timer, NULL)); /* Regex search: search "fill" */ g_timer_start (timer); gtk_source_search_settings_set_search_text (search_settings, "fill"); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); while (gtk_source_search_context_forward (search_context, &iter, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("regex search: 'fill' (no partial matches): %lf seconds.\n", g_timer_elapsed (timer, NULL)); /* Regex search: search single lines */ g_timer_start (timer); gtk_source_search_settings_set_search_text (search_settings, ".*"); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); while (gtk_source_search_context_forward (search_context, &iter, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("regex search: match single lines (no partial matches): %lf seconds.\n", g_timer_elapsed (timer, NULL)); /* Regex search: search matches of 3 lines */ g_timer_start (timer); /* The space at the beginning of the pattern permits to not have contiguous * matches. There is a performance issue with contiguous matches. */ gtk_source_search_settings_set_search_text (search_settings, " (.*\n){3}"); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); while (gtk_source_search_context_forward (search_context, &iter, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("regex search: matches of 3 lines (small partial matches): %lf seconds.\n", g_timer_elapsed (timer, NULL)); /* Regex search: search matches of really big chunks */ g_timer_start (timer); regex_pattern = g_strdup_printf (" (.*\n){%d}", NB_LINES / 10); gtk_source_search_settings_set_search_text (search_settings, regex_pattern); g_free (regex_pattern); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter); while (gtk_source_search_context_forward (search_context, &iter, NULL, &match_end, NULL)) { iter = match_end; } g_timer_stop (timer); g_print ("regex search: 10 matches of %d lines (big partial matches): %lf seconds.\n", NB_LINES / 10, g_timer_elapsed (timer, NULL)); /* Smart search, case sensitive, asynchronous */ /* The asynchronous overhead doesn't depend on the search flags, it * depends on the maximum number of lines to scan in one batch, and * (obviously), on the buffer size. * You can tune SCAN_BATCH_SIZE in gtksourcesearchcontext.c to see a * difference in the overhead. */ g_signal_connect (search_context, "notify::occurrences-count", G_CALLBACK (on_notify_search_occurrences_count_cb), timer); g_timer_start (timer); gtk_source_search_settings_set_search_text (search_settings, NULL); gtk_source_search_settings_set_regex_enabled (search_settings, FALSE); gtk_source_search_settings_set_search_text (search_settings, "foo"); main_loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (main_loop); return 0; } 070701000002CB000081A400000000000000000000000166590806000036E4000000000000000000000000000000000000002900000000gtksourceview-5.12.1/tests/test-search.c/* * This file is part of GtkSourceView * * Copyright (C) 2013 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include #include #define TEST_TYPE_SEARCH (test_search_get_type ()) #define TEST_SEARCH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_SEARCH, TestSearch)) #define TEST_SEARCH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_SEARCH, TestSearchClass)) #define TEST_IS_SEARCH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_SEARCH)) #define TEST_IS_SEARCH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_TYPE_SEARCH)) #define TEST_SEARCH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_SEARCH, TestSearchClass)) typedef struct _TestSearch TestSearch; typedef struct _TestSearchClass TestSearchClass; typedef struct _TestSearchPrivate TestSearchPrivate; struct _TestSearch { GtkGrid parent; TestSearchPrivate *priv; }; struct _TestSearchClass { GtkGridClass parent_class; }; struct _TestSearchPrivate { GtkSourceView *source_view; GtkSourceBuffer *source_buffer; GtkSourceSearchContext *search_context; GtkSourceSearchSettings *search_settings; GtkEntry *replace_entry; GtkLabel *label_occurrences; GtkLabel *label_regex_error; guint idle_update_label_id; }; GType test_search_get_type (void); G_DEFINE_TYPE_WITH_PRIVATE (TestSearch, test_search, GTK_TYPE_GRID) static GMainLoop *main_loop; static void open_file (TestSearch *search, const gchar *filename) { gchar *contents; GError *error = NULL; GtkSourceLanguageManager *language_manager; GtkSourceLanguage *language; GtkTextIter iter; /* In a realistic application you would use GtkSourceFile of course. */ if (!g_file_get_contents (filename, &contents, NULL, &error)) { g_error ("Impossible to load file: %s", error->message); } gtk_text_buffer_set_text (GTK_TEXT_BUFFER (search->priv->source_buffer), contents, -1); language_manager = gtk_source_language_manager_get_default (); language = gtk_source_language_manager_get_language (language_manager, "c"); gtk_source_buffer_set_language (search->priv->source_buffer, language); gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (search->priv->source_buffer), &iter); gtk_text_buffer_select_range (GTK_TEXT_BUFFER (search->priv->source_buffer), &iter, &iter); g_free (contents); } static void update_label_occurrences (TestSearch *search) { gint occurrences_count; GtkTextIter select_start; GtkTextIter select_end; gint occurrence_pos; gchar *text; occurrences_count = gtk_source_search_context_get_occurrences_count (search->priv->search_context); gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer), &select_start, &select_end); occurrence_pos = gtk_source_search_context_get_occurrence_position (search->priv->search_context, &select_start, &select_end); if (occurrences_count == -1) { text = g_strdup (""); } else if (occurrence_pos == -1) { text = g_strdup_printf ("%u occurrences", occurrences_count); } else { text = g_strdup_printf ("%d of %u", occurrence_pos, occurrences_count); } gtk_label_set_text (search->priv->label_occurrences, text); g_free (text); } static void update_label_regex_error (TestSearch *search) { GError *error; error = gtk_source_search_context_get_regex_error (search->priv->search_context); if (error == NULL) { gtk_label_set_text (search->priv->label_regex_error, ""); gtk_widget_hide (GTK_WIDGET (search->priv->label_regex_error)); } else { gtk_label_set_text (search->priv->label_regex_error, error->message); gtk_widget_show (GTK_WIDGET (search->priv->label_regex_error)); g_clear_error (&error); } } static void search_entry_changed_cb (TestSearch *search, GtkEntry *entry) { const gchar *text = gtk_editable_get_text (GTK_EDITABLE (entry)); gchar *unescaped_text = gtk_source_utils_unescape_search_text (text); gtk_source_search_settings_set_search_text (search->priv->search_settings, unescaped_text); g_free (unescaped_text); } static void select_search_occurrence (TestSearch *search, const GtkTextIter *match_start, const GtkTextIter *match_end) { GtkTextMark *insert; gtk_text_buffer_select_range (GTK_TEXT_BUFFER (search->priv->source_buffer), match_start, match_end); insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (search->priv->source_buffer)); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (search->priv->source_view), insert); } static void backward_search_finished (GtkSourceSearchContext *search_context, GAsyncResult *result, TestSearch *search) { GtkTextIter match_start; GtkTextIter match_end; if (gtk_source_search_context_backward_finish (search_context, result, &match_start, &match_end, NULL, NULL)) { select_search_occurrence (search, &match_start, &match_end); } } static void button_previous_clicked_cb (TestSearch *search, GtkButton *button) { GtkTextIter start_at; gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer), &start_at, NULL); gtk_source_search_context_backward_async (search->priv->search_context, &start_at, NULL, (GAsyncReadyCallback)backward_search_finished, search); } static void forward_search_finished (GtkSourceSearchContext *search_context, GAsyncResult *result, TestSearch *search) { GtkTextIter match_start; GtkTextIter match_end; if (gtk_source_search_context_forward_finish (search_context, result, &match_start, &match_end, NULL, NULL)) { select_search_occurrence (search, &match_start, &match_end); } } static void button_next_clicked_cb (TestSearch *search, GtkButton *button) { GtkTextIter start_at; gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer), NULL, &start_at); gtk_source_search_context_forward_async (search->priv->search_context, &start_at, NULL, (GAsyncReadyCallback)forward_search_finished, search); } static void button_replace_clicked_cb (TestSearch *search, GtkButton *button) { GtkTextIter match_start; GtkTextIter match_end; GtkTextIter iter; GtkEntryBuffer *entry_buffer; gint replace_length; gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer), &match_start, &match_end); entry_buffer = gtk_entry_get_buffer (search->priv->replace_entry); replace_length = gtk_entry_buffer_get_bytes (entry_buffer); gtk_source_search_context_replace (search->priv->search_context, &match_start, &match_end, gtk_editable_get_text (GTK_EDITABLE (search->priv->replace_entry)), replace_length, NULL); gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (search->priv->source_buffer), NULL, &iter); gtk_source_search_context_forward_async (search->priv->search_context, &iter, NULL, (GAsyncReadyCallback)forward_search_finished, search); } static void button_replace_all_clicked_cb (TestSearch *search, GtkButton *button) { GtkEntryBuffer *entry_buffer = gtk_entry_get_buffer (search->priv->replace_entry); gint replace_length = gtk_entry_buffer_get_bytes (entry_buffer); gtk_source_search_context_replace_all (search->priv->search_context, gtk_editable_get_text (GTK_EDITABLE (search->priv->replace_entry)), replace_length, NULL); } static gboolean update_label_idle_cb (TestSearch *search) { search->priv->idle_update_label_id = 0; update_label_occurrences (search); return G_SOURCE_REMOVE; } static void mark_set_cb (GtkTextBuffer *buffer, GtkTextIter *location, GtkTextMark *mark, TestSearch *search) { GtkTextMark *insert; GtkTextMark *selection_bound; insert = gtk_text_buffer_get_insert (buffer); selection_bound = gtk_text_buffer_get_selection_bound (buffer); if ((mark == insert || mark == selection_bound) && search->priv->idle_update_label_id == 0) { search->priv->idle_update_label_id = g_idle_add ((GSourceFunc)update_label_idle_cb, search); } } static void highlight_toggled_cb (TestSearch *search, GtkToggleButton *button) { gtk_source_search_context_set_highlight (search->priv->search_context, gtk_toggle_button_get_active (button)); } static void match_case_toggled_cb (TestSearch *search, GtkToggleButton *button) { gtk_source_search_settings_set_case_sensitive (search->priv->search_settings, gtk_toggle_button_get_active (button)); } static void at_word_boundaries_toggled_cb (TestSearch *search, GtkToggleButton *button) { gtk_source_search_settings_set_at_word_boundaries (search->priv->search_settings, gtk_toggle_button_get_active (button)); } static void wrap_around_toggled_cb (TestSearch *search, GtkToggleButton *button) { gtk_source_search_settings_set_wrap_around (search->priv->search_settings, gtk_toggle_button_get_active (button)); } static void regex_toggled_cb (TestSearch *search, GtkToggleButton *button) { gtk_source_search_settings_set_regex_enabled (search->priv->search_settings, gtk_toggle_button_get_active (button)); } static void test_search_dispose (GObject *object) { TestSearch *search = TEST_SEARCH (object); g_clear_object (&search->priv->source_buffer); g_clear_object (&search->priv->search_context); g_clear_object (&search->priv->search_settings); G_OBJECT_CLASS (test_search_parent_class)->dispose (object); } static void test_search_class_init (TestSearchClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = test_search_dispose; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gtksourceview/tests/ui/test-search.ui"); gtk_widget_class_bind_template_child_private (widget_class, TestSearch, source_view); gtk_widget_class_bind_template_child_private (widget_class, TestSearch, replace_entry); gtk_widget_class_bind_template_child_private (widget_class, TestSearch, label_occurrences); gtk_widget_class_bind_template_child_private (widget_class, TestSearch, label_regex_error); gtk_widget_class_bind_template_callback (widget_class, search_entry_changed_cb); gtk_widget_class_bind_template_callback (widget_class, button_previous_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, button_next_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, button_replace_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, button_replace_all_clicked_cb); /* It is also possible to bind the properties with * g_object_bind_property(), between the check buttons and the source * buffer. But GtkBuilder and Glade don't support that yet. */ gtk_widget_class_bind_template_callback (widget_class, highlight_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, match_case_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, at_word_boundaries_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, wrap_around_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, regex_toggled_cb); } static void test_search_init (TestSearch *search) { search->priv = test_search_get_instance_private (search); gtk_widget_init_template (GTK_WIDGET (search)); search->priv->source_buffer = GTK_SOURCE_BUFFER ( gtk_text_view_get_buffer (GTK_TEXT_VIEW (search->priv->source_view))); g_object_ref (search->priv->source_buffer); open_file (search, TOP_SRCDIR "/gtksourceview/gtksourcesearchcontext.c"); search->priv->search_settings = gtk_source_search_settings_new (); search->priv->search_context = gtk_source_search_context_new (search->priv->source_buffer, search->priv->search_settings); g_signal_connect_swapped (search->priv->search_context, "notify::occurrences-count", G_CALLBACK (update_label_occurrences), search); g_signal_connect (search->priv->source_buffer, "mark-set", G_CALLBACK (mark_set_cb), search); g_signal_connect_swapped (search->priv->search_context, "notify::regex-error", G_CALLBACK (update_label_regex_error), search); update_label_regex_error (search); } static TestSearch * test_search_new (void) { return g_object_new (test_search_get_type (), NULL); } gint main (gint argc, gchar *argv[]) { GtkWidget *window; TestSearch *search; main_loop = g_main_loop_new (NULL, FALSE); gtk_init (); window = gtk_window_new (); gtk_window_set_default_size (GTK_WINDOW (window), 700, 500); g_signal_connect_swapped (window, "destroy", G_CALLBACK (g_main_loop_quit), main_loop); search = test_search_new (); gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (search)); gtk_window_present (GTK_WINDOW (window)); g_main_loop_run (main_loop); return 0; } 070701000002CC000081A400000000000000000000000166590806000000C7000000000000000000000000000000000000003500000000gtksourceview-5.12.1/tests/test-search.gresource.xml test-search.ui 070701000002CD000081A400000000000000000000000166590806000026CE000000000000000000000000000000000000002A00000000gtksourceview-5.12.1/tests/test-search.ui 070701000002CE000081A400000000000000000000000166590806000014FE000000000000000000000000000000000000003000000000gtksourceview-5.12.1/tests/test-space-drawing.c/* * This file is part of GtkSourceView * * Copyright (C) 2015 - Université Catholique de Louvain * * GtkSourceView 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. * * GtkSourceView 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, see . * * Author: Sébastien Wilmet */ #include static GMainLoop *main_loop; static void fill_buffer (GtkTextBuffer *buffer, GtkTextTag *tag) { GtkTextIter iter; gtk_text_buffer_set_text (buffer, "", 0); gtk_text_buffer_get_start_iter (buffer, &iter); gtk_text_buffer_insert (buffer, &iter, "---\n" "\tText without draw-spaces tag.\n" "\tNon-breaking whitespace: .\n" "\tTrailing spaces.\t  \n" "---\n\n", -1); gtk_text_buffer_insert_with_tags (buffer, &iter, "---\n" "\tText with draw-spaces tag.\n" "\tNon-breaking whitespace: .\n" "\tTrailing spaces.\t  \n" "---", -1, tag, NULL); } static void create_window (void) { GtkWidget *window; GtkWidget *hgrid; GtkWidget *panel_grid; GtkWidget *scrolled_window; GtkWidget *matrix_checkbutton; GtkWidget *tag_set_checkbutton; GtkWidget *tag_checkbutton; GtkWidget *implicit_trailing_newline_checkbutton; GtkSourceView *view; GtkSourceBuffer *buffer; GtkTextTag *tag; GtkSourceSpaceDrawer *space_drawer; window = gtk_window_new (); gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); g_signal_connect_swapped (window, "destroy", G_CALLBACK (g_main_loop_quit), main_loop); hgrid = gtk_grid_new (); gtk_orientable_set_orientation (GTK_ORIENTABLE (hgrid), GTK_ORIENTATION_HORIZONTAL); view = GTK_SOURCE_VIEW (gtk_source_view_new ()); g_object_set (view, "hexpand", TRUE, "vexpand", TRUE, NULL); gtk_text_view_set_monospace (GTK_TEXT_VIEW (view), TRUE); buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); tag = gtk_source_buffer_create_source_tag (buffer, NULL, "draw-spaces", FALSE, NULL); fill_buffer (GTK_TEXT_BUFFER (buffer), tag); space_drawer = gtk_source_view_get_space_drawer (view); gtk_source_space_drawer_set_types_for_locations (space_drawer, GTK_SOURCE_SPACE_LOCATION_ALL, GTK_SOURCE_SPACE_TYPE_NBSP); gtk_source_space_drawer_set_types_for_locations (space_drawer, GTK_SOURCE_SPACE_LOCATION_TRAILING, GTK_SOURCE_SPACE_TYPE_ALL); panel_grid = gtk_grid_new (); gtk_orientable_set_orientation (GTK_ORIENTABLE (panel_grid), GTK_ORIENTATION_VERTICAL); gtk_grid_attach (GTK_GRID (hgrid), panel_grid, 0, 0, 1, 1); gtk_grid_set_row_spacing (GTK_GRID (panel_grid), 6); g_object_set (panel_grid, "margin-top", 6, "margin-bottom", 6, "margin-start", 6, "margin-end", 6, NULL); matrix_checkbutton = gtk_check_button_new_with_label ("GtkSourceSpaceDrawer enable-matrix"); gtk_grid_attach (GTK_GRID (panel_grid), matrix_checkbutton, 0, 0, 1, 1); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (matrix_checkbutton), TRUE); g_object_bind_property (matrix_checkbutton, "active", space_drawer, "enable-matrix", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); tag_set_checkbutton = gtk_check_button_new_with_label ("GtkSourceTag draw-spaces-set"); gtk_grid_attach (GTK_GRID (panel_grid), tag_set_checkbutton, 0, 1, 1, 1); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tag_set_checkbutton), TRUE); g_object_bind_property (tag_set_checkbutton, "active", tag, "draw-spaces-set", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); tag_checkbutton = gtk_check_button_new_with_label ("GtkSourceTag draw-spaces"); gtk_grid_attach (GTK_GRID (panel_grid), tag_checkbutton, 0, 2, 1, 1); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tag_checkbutton), FALSE); g_object_bind_property (tag_checkbutton, "active", tag, "draw-spaces", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); implicit_trailing_newline_checkbutton = gtk_check_button_new_with_label ("Implicit trailing newline"); gtk_widget_set_margin_top (implicit_trailing_newline_checkbutton, 12); gtk_grid_attach (GTK_GRID (panel_grid), implicit_trailing_newline_checkbutton, 0, 3, 1, 1); g_object_bind_property (buffer, "implicit-trailing-newline", implicit_trailing_newline_checkbutton, "active", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); scrolled_window = gtk_scrolled_window_new (); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), GTK_WIDGET (view)); gtk_grid_attach (GTK_GRID (hgrid), scrolled_window, 1, 0, 1, 1); gtk_window_set_child (GTK_WINDOW (window), hgrid); gtk_window_present (GTK_WINDOW (window)); } gint main (gint argc, gchar **argv) { main_loop = g_main_loop_new (NULL, FALSE); gtk_init (); create_window (); g_main_loop_run (main_loop); return 0; } 070701000002CF000081A40000000000000000000000016659080600009054000000000000000000000000000000000000002900000000gtksourceview-5.12.1/tests/test-widget.c/* * This file is part of GtkSourceView * * Copyright (C) 2001 - Mikael Hermansson * Copyright (C) 2003 - Gustavo Giráldez * Copyright (C) 2014 - Sébastien Wilmet * * GtkSourceView 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. * * GtkSourceView 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, see . */ #include #include #define TEST_TYPE_WIDGET (test_widget_get_type()) #define TEST_TYPE_HOVER_PROVIDER (test_hover_provider_get_type()) G_DECLARE_FINAL_TYPE (TestWidget, test_widget, TEST, WIDGET, GtkGrid) G_DECLARE_FINAL_TYPE (TestHoverProvider, test_hover_provider, TEST, HOVER_PROVIDER, GObject) struct _TestWidget { GtkGrid parent_instance; GtkSourceView *view; GtkSourceBuffer *buffer; GtkSourceFile *file; GtkSourceMap *map; GtkCheckButton *show_top_border_window_checkbutton; GtkCheckButton *show_map_checkbutton; GtkCheckButton *draw_spaces_checkbutton; GtkCheckButton *smart_backspace_checkbutton; GtkCheckButton *indent_width_checkbutton; GtkCheckButton *vim_checkbutton; GtkSpinButton *indent_width_spinbutton; GtkLabel *cursor_position_info; GtkSourceStyleSchemeChooserButton *chooser_button; GtkIMContext *vim_im_context; GtkComboBoxText *background_pattern; GtkWidget *top; GtkScrolledWindow *scrolledwindow1; GtkEventController *vim_controller; GtkLabel *command_bar; }; struct _TestHoverProvider { GObject parent_instance; }; static void hover_provider_iface_init (GtkSourceHoverProviderInterface *iface); G_DEFINE_TYPE (TestWidget, test_widget, GTK_TYPE_GRID) G_DEFINE_TYPE_WITH_CODE (TestHoverProvider, test_hover_provider, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_HOVER_PROVIDER, hover_provider_iface_init)) #define MARK_TYPE_1 "one" #define MARK_TYPE_2 "two" static GMainLoop *main_loop; static gchar *last_dir; static const char *cmd_filename; static void remove_all_marks (GtkSourceBuffer *buffer) { GtkTextIter start; GtkTextIter end; gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end); gtk_source_buffer_remove_source_marks (buffer, &start, &end, NULL); } static GtkSourceLanguage * get_language_for_file (GtkTextBuffer *buffer, const gchar *filename) { GtkSourceLanguageManager *manager; GtkSourceLanguage *language; GtkTextIter start; GtkTextIter end; gchar *text; gchar *content_type; gboolean result_uncertain; gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_get_iter_at_offset (buffer, &end, 1024); text = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE); content_type = g_content_type_guess (filename, (guchar*) text, strlen (text), &result_uncertain); if (result_uncertain) { g_free (content_type); content_type = NULL; } manager = gtk_source_language_manager_get_default (); language = gtk_source_language_manager_guess_language (manager, filename, content_type); g_message ("Detected '%s' mime type for file %s, chose language %s", content_type != NULL ? content_type : "(null)", filename, language != NULL ? gtk_source_language_get_id (language) : "(none)"); g_free (content_type); g_free (text); return language; } static GtkSourceLanguage * get_language_by_id (const gchar *id) { GtkSourceLanguageManager *manager; manager = gtk_source_language_manager_get_default (); return gtk_source_language_manager_get_language (manager, id); } static GtkSourceLanguage * get_language (GtkTextBuffer *buffer, GFile *location) { GtkSourceLanguage *language = NULL; GtkTextIter start; GtkTextIter end; gchar *text; gchar *lang_string; gtk_text_buffer_get_start_iter (buffer, &start); end = start; gtk_text_iter_forward_line (&end); #define LANG_STRING "gtk-source-lang:" text = gtk_text_iter_get_slice (&start, &end); lang_string = strstr (text, LANG_STRING); if (lang_string != NULL) { gchar **tokens; lang_string += strlen (LANG_STRING); g_strchug (lang_string); tokens = g_strsplit_set (lang_string, " \t\n", 2); if (tokens != NULL && tokens[0] != NULL) { language = get_language_by_id (tokens[0]); } g_strfreev (tokens); } if (language == NULL) { gchar *filename = g_file_get_path (location); language = get_language_for_file (buffer, filename); g_free (filename); } g_free (text); return language; } static void print_language_style_ids (GtkSourceLanguage *language) { gchar **styles; g_assert_nonnull (language); styles = gtk_source_language_get_style_ids (language); if (styles == NULL) { g_print ("No styles in language '%s'\n", gtk_source_language_get_name (language)); } else { gchar **ids; g_print ("Styles in language '%s':\n", gtk_source_language_get_name (language)); for (ids = styles; *ids != NULL; ids++) { const gchar *name = gtk_source_language_get_style_name (language, *ids); g_print ("- %s (name: '%s')\n", *ids, name); } g_strfreev (styles); } g_print ("\n"); } static void load_cb (GtkSourceFileLoader *loader, GAsyncResult *result, TestWidget *self) { GtkTextIter iter; GFile *location; GtkSourceLanguage *language = NULL; GError *error = NULL; gtk_source_file_loader_load_finish (loader, result, &error); if (error != NULL) { g_warning ("Error while loading the file: %s", error->message); g_clear_error (&error); g_clear_object (&self->file); goto end; } /* move cursor to the beginning */ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (self->buffer), &iter); gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (self->buffer), &iter); gtk_widget_grab_focus (GTK_WIDGET (self->view)); location = gtk_source_file_loader_get_location (loader); language = get_language (GTK_TEXT_BUFFER (self->buffer), location); gtk_source_buffer_set_language (self->buffer, language); if (language != NULL) { print_language_style_ids (language); } else { gchar *path = g_file_get_path (location); g_print ("No language found for file '%s'\n", path); g_free (path); } end: g_object_unref (loader); } static void open_file (TestWidget *self, GFile *file) { GtkSourceFileLoader *loader; g_clear_object (&self->file); self->file = gtk_source_file_new (); gtk_source_file_set_location (self->file, file); loader = gtk_source_file_loader_new (self->buffer, self->file); remove_all_marks (self->buffer); gtk_source_file_loader_load_async (loader, G_PRIORITY_DEFAULT, NULL, NULL, NULL, NULL, (GAsyncReadyCallback) load_cb, self); } static void show_line_numbers_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_source_view_set_show_line_numbers (self->view, enabled); } static void show_line_marks_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_source_view_set_show_line_marks (self->view, enabled); } static void show_right_margin_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_source_view_set_show_right_margin (self->view, enabled); } static void right_margin_position_value_changed_cb (TestWidget *self, GtkSpinButton *button) { gint position = gtk_spin_button_get_value_as_int (button); gtk_source_view_set_right_margin_position (self->view, position); gtk_source_view_set_right_margin_position (GTK_SOURCE_VIEW (self->map), position); gtk_widget_queue_resize (GTK_WIDGET (self->map)); } static void highlight_syntax_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_source_buffer_set_highlight_syntax (self->buffer, enabled); } static void highlight_matching_bracket_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_source_buffer_set_highlight_matching_brackets (self->buffer, enabled); } static void highlight_current_line_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_source_view_set_highlight_current_line (self->view, enabled); } static void wrap_lines_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (self->view), enabled ? GTK_WRAP_WORD : GTK_WRAP_NONE); } static void auto_indent_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_source_view_set_auto_indent (self->view, enabled); } static void indent_spaces_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_source_view_set_insert_spaces_instead_of_tabs (self->view, enabled); } static void tab_width_value_changed_cb (TestWidget *self, GtkSpinButton *button) { gint tab_width = gtk_spin_button_get_value_as_int (button); gtk_source_view_set_tab_width (self->view, tab_width); } static void update_indent_width (TestWidget *self) { gint indent_width = -1; if (gtk_check_button_get_active (self->indent_width_checkbutton)) { indent_width = gtk_spin_button_get_value_as_int (self->indent_width_spinbutton); } gtk_source_view_set_indent_width (self->view, indent_width); } static void smart_home_end_changed_cb (TestWidget *self, GtkComboBox *combo) { GtkSourceSmartHomeEndType type; gint active = gtk_combo_box_get_active (combo); switch (active) { case 0: type = GTK_SOURCE_SMART_HOME_END_DISABLED; break; case 1: type = GTK_SOURCE_SMART_HOME_END_BEFORE; break; case 2: type = GTK_SOURCE_SMART_HOME_END_AFTER; break; case 3: type = GTK_SOURCE_SMART_HOME_END_ALWAYS; break; default: type = GTK_SOURCE_SMART_HOME_END_DISABLED; break; } gtk_source_view_set_smart_home_end (self->view, type); } static void backward_string_clicked_cb (TestWidget *self) { GtkTextIter iter; GtkTextMark *insert; insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self->buffer)); gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self->buffer), &iter, insert); if (gtk_source_buffer_iter_backward_to_context_class_toggle (self->buffer, &iter, "string")) { gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (self->buffer), &iter); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (self->view), insert); } gtk_widget_grab_focus (GTK_WIDGET (self->view)); } static void forward_string_clicked_cb (TestWidget *self) { GtkTextIter iter; GtkTextMark *insert; insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self->buffer)); gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self->buffer), &iter, insert); if (gtk_source_buffer_iter_forward_to_context_class_toggle (self->buffer, &iter, "string")) { gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (self->buffer), &iter); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (self->view), insert); } gtk_widget_grab_focus (GTK_WIDGET (self->view)); } static void on_chooser_response_cb (TestWidget *self, gint response, GtkFileChooserDialog *chooser) { g_assert (TEST_IS_WIDGET (self)); g_assert (GTK_IS_FILE_CHOOSER_DIALOG (chooser)); if (response == GTK_RESPONSE_OK) { GFile *folder; GFile *file; file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (chooser)); folder = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (chooser)); if (file != NULL && folder != NULL) { g_free (last_dir); last_dir = g_file_get_path (folder); open_file (self, file); } g_clear_object (&folder); g_clear_object (&file); } gtk_window_destroy (GTK_WINDOW (chooser)); } static void open_button_clicked_cb (TestWidget *self) { GtkWidget *main_window; GtkWidget *chooser; main_window = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self->view))); chooser = gtk_file_chooser_dialog_new ("Open file...", GTK_WINDOW (main_window), GTK_FILE_CHOOSER_ACTION_OPEN, "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_OK, NULL); if (last_dir == NULL) { last_dir = g_strdup (TOP_SRCDIR "/gtksourceview"); } if (last_dir != NULL && g_path_is_absolute (last_dir)) { GFile *folder; folder = g_file_new_for_path (last_dir); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser), folder, NULL); g_object_unref (folder); } g_signal_connect_object (chooser, "response", G_CALLBACK (on_chooser_response_cb), self, G_CONNECT_SWAPPED); gtk_window_present (GTK_WINDOW (chooser)); } #define NON_BLOCKING_PAGINATION #ifndef NON_BLOCKING_PAGINATION static void begin_print (GtkPrintOperation *operation, GtkPrintContext *context, GtkSourcePrintCompositor *compositor) { gint n_pages; while (!gtk_source_print_compositor_paginate (compositor, context)) ; n_pages = gtk_source_print_compositor_get_n_pages (compositor); gtk_print_operation_set_n_pages (operation, n_pages); } #else static gboolean paginate (GtkPrintOperation *operation, GtkPrintContext *context, GtkSourcePrintCompositor *compositor) { g_print ("Pagination progress: %.2f %%\n", gtk_source_print_compositor_get_pagination_progress (compositor) * 100.0); if (gtk_source_print_compositor_paginate (compositor, context)) { gint n_pages; g_assert_cmpint (gtk_source_print_compositor_get_pagination_progress (compositor), ==, 1.0); g_print ("Pagination progress: %.2f %%\n", gtk_source_print_compositor_get_pagination_progress (compositor) * 100.0); n_pages = gtk_source_print_compositor_get_n_pages (compositor); gtk_print_operation_set_n_pages (operation, n_pages); return TRUE; } return FALSE; } #endif #define ENABLE_CUSTOM_OVERLAY static void draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, GtkSourcePrintCompositor *compositor) { #ifdef ENABLE_CUSTOM_OVERLAY /* This part of the code shows how to add a custom overlay to the printed text generated by GtkSourcePrintCompositor */ cairo_t *cr; PangoLayout *layout; PangoFontDescription *desc; PangoRectangle rect; cr = gtk_print_context_get_cairo_context (context); cairo_save (cr); layout = gtk_print_context_create_pango_layout (context); pango_layout_set_text (layout, "Draft", -1); desc = pango_font_description_from_string ("Sans Bold 120"); pango_layout_set_font_description (layout, desc); pango_font_description_free (desc); pango_layout_get_extents (layout, NULL, &rect); cairo_move_to (cr, (gtk_print_context_get_width (context) - ((double) rect.width / (double) PANGO_SCALE)) / 2, (gtk_print_context_get_height (context) - ((double) rect.height / (double) PANGO_SCALE)) / 2); pango_cairo_layout_path (cr, layout); /* Font Outline */ cairo_set_source_rgba (cr, 0.85, 0.85, 0.85, 0.80); cairo_set_line_width (cr, 0.5); cairo_stroke_preserve (cr); /* Font Fill */ cairo_set_source_rgba (cr, 0.8, 0.8, 0.8, 0.60); cairo_fill (cr); g_object_unref (layout); cairo_restore (cr); #endif /* To print page_nr you only need to call the following function */ gtk_source_print_compositor_draw_page (compositor, context, page_nr); } static void end_print (GtkPrintOperation *operation, GtkPrintContext *context, GtkSourcePrintCompositor *compositor) { g_object_unref (compositor); } #define LINE_NUMBERS_FONT_NAME "Sans 8" #define HEADER_FONT_NAME "Sans 11" #define FOOTER_FONT_NAME "Sans 11" #define BODY_FONT_NAME "Monospace 9" /* #define SETUP_FROM_VIEW */ #undef SETUP_FROM_VIEW static void print_button_clicked_cb (TestWidget *self) { gchar *basename = NULL; GtkSourcePrintCompositor *compositor; GtkPrintOperation *operation; if (self->file != NULL) { GFile *location; location = gtk_source_file_get_location (self->file); if (location != NULL) { basename = g_file_get_basename (location); } } #ifdef SETUP_FROM_VIEW compositor = gtk_source_print_compositor_new_from_view (self->view); #else compositor = gtk_source_print_compositor_new (self->buffer); gtk_source_print_compositor_set_tab_width (compositor, gtk_source_view_get_tab_width (self->view)); gtk_source_print_compositor_set_wrap_mode (compositor, gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (self->view))); gtk_source_print_compositor_set_print_line_numbers (compositor, 1); gtk_source_print_compositor_set_body_font_name (compositor, BODY_FONT_NAME); /* To test line numbers font != text font */ gtk_source_print_compositor_set_line_numbers_font_name (compositor, LINE_NUMBERS_FONT_NAME); gtk_source_print_compositor_set_header_format (compositor, TRUE, "Printed on %A", "test-widget", "%F"); gtk_source_print_compositor_set_footer_format (compositor, TRUE, "%T", basename, "Page %N/%Q"); gtk_source_print_compositor_set_print_header (compositor, TRUE); gtk_source_print_compositor_set_print_footer (compositor, TRUE); gtk_source_print_compositor_set_header_font_name (compositor, HEADER_FONT_NAME); gtk_source_print_compositor_set_footer_font_name (compositor, FOOTER_FONT_NAME); #endif operation = gtk_print_operation_new (); gtk_print_operation_set_job_name (operation, basename); gtk_print_operation_set_show_progress (operation, TRUE); #ifndef NON_BLOCKING_PAGINATION g_signal_connect (G_OBJECT (operation), "begin-print", G_CALLBACK (begin_print), compositor); #else g_signal_connect (G_OBJECT (operation), "paginate", G_CALLBACK (paginate), compositor); #endif g_signal_connect (G_OBJECT (operation), "draw-page", G_CALLBACK (draw_page), compositor); g_signal_connect (G_OBJECT (operation), "end-print", G_CALLBACK (end_print), compositor); gtk_print_operation_run (operation, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, NULL, NULL); g_object_unref (operation); g_free (basename); } static void update_cursor_position_info (TestWidget *self) { gchar *msg; gint offset; gint line; guint column; GtkTextIter iter; gchar **classes; gchar **classes_ptr; GString *classes_str; GtkSourceLanguage *lang; const char *language = "none"; gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self->buffer), &iter, gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self->buffer))); offset = gtk_text_iter_get_offset (&iter); line = gtk_text_iter_get_line (&iter) + 1; column = gtk_source_view_get_visual_column (self->view, &iter) + 1; classes = gtk_source_buffer_get_context_classes_at_iter (self->buffer, &iter); classes_str = g_string_new (""); for (classes_ptr = classes; classes_ptr != NULL && *classes_ptr != NULL; classes_ptr++) { if (classes_ptr != classes) { g_string_append (classes_str, ", "); } g_string_append_printf (classes_str, "%s", *classes_ptr); } g_strfreev (classes); if ((lang = gtk_source_buffer_get_language (self->buffer))) language = gtk_source_language_get_id (lang); msg = g_strdup_printf ("language: %s offset: %d, line: %d, column: %u, classes: %s", language, offset, line, column, classes_str->str); gtk_label_set_text (self->cursor_position_info, msg); g_free (msg); g_string_free (classes_str, TRUE); } static void mark_set_cb (GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextMark *mark, TestWidget *self) { if (mark == gtk_text_buffer_get_insert (buffer)) { update_cursor_position_info (self); } } static void line_mark_activated_cb (GtkSourceGutter *gutter, const GtkTextIter *iter, guint button, GdkModifierType state, gint n_presses, TestWidget *self) { GSList *mark_list; const gchar *mark_type; if ((state & GDK_SHIFT_MASK) != 0) mark_type = MARK_TYPE_2; else mark_type = MARK_TYPE_1; /* get the marks already in the line */ mark_list = gtk_source_buffer_get_source_marks_at_line (self->buffer, gtk_text_iter_get_line (iter), mark_type); if (mark_list != NULL) { /* just take the first and delete it */ gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (self->buffer), GTK_TEXT_MARK (mark_list->data)); } else { /* no mark found: create one */ gtk_source_buffer_create_source_mark (self->buffer, NULL, mark_type, iter); } g_slist_free (mark_list); } static void bracket_matched_cb (GtkSourceBuffer *buffer, GtkTextIter *iter, GtkSourceBracketMatchType state) { GEnumClass *eclass; GEnumValue *evalue; eclass = G_ENUM_CLASS (g_type_class_ref (GTK_SOURCE_TYPE_BRACKET_MATCH_TYPE)); evalue = g_enum_get_value (eclass, state); g_print ("Bracket match state: '%s'\n", evalue->value_nick); g_type_class_unref (eclass); if (state == GTK_SOURCE_BRACKET_MATCH_FOUND) { g_return_if_fail (iter != NULL); g_print ("Matched bracket: '%c' at row: %"G_GINT32_FORMAT", col: %"G_GINT32_FORMAT"\n", gtk_text_iter_get_char (iter), gtk_text_iter_get_line (iter) + 1, gtk_text_iter_get_line_offset (iter) + 1); } } static gchar * mark_tooltip_func (GtkSourceMarkAttributes *attrs, GtkSourceMark *mark, GtkSourceView *view) { GtkTextBuffer *buffer; GtkTextIter iter; gint line; gint column; buffer = gtk_text_mark_get_buffer (GTK_TEXT_MARK (mark)); gtk_text_buffer_get_iter_at_mark (buffer, &iter, GTK_TEXT_MARK (mark)); line = gtk_text_iter_get_line (&iter) + 1; column = gtk_text_iter_get_line_offset (&iter); if (g_strcmp0 (gtk_source_mark_get_category (mark), MARK_TYPE_1) == 0) { return g_strdup_printf ("Line: %d, Column: %d", line, column); } else { return g_strdup_printf ("Line: %d\nColumn: %d", line, column); } } static void add_source_mark_attributes (GtkSourceView *view) { GdkRGBA color; GtkSourceMarkAttributes *attrs; attrs = gtk_source_mark_attributes_new (); gdk_rgba_parse (&color, "lightgreen"); gtk_source_mark_attributes_set_background (attrs, &color); gtk_source_mark_attributes_set_icon_name (attrs, "list-add"); g_signal_connect_object (attrs, "query-tooltip-markup", G_CALLBACK (mark_tooltip_func), view, 0); gtk_source_view_set_mark_attributes (view, MARK_TYPE_1, attrs, 1); g_object_unref (attrs); attrs = gtk_source_mark_attributes_new (); gdk_rgba_parse (&color, "pink"); gtk_source_mark_attributes_set_background (attrs, &color); gtk_source_mark_attributes_set_icon_name (attrs, "list-remove"); g_signal_connect_object (attrs, "query-tooltip-markup", G_CALLBACK (mark_tooltip_func), view, 0); gtk_source_view_set_mark_attributes (view, MARK_TYPE_2, attrs, 2); g_object_unref (attrs); } static void on_background_pattern_changed (GtkComboBox *combobox, TestWidget *self) { gchar *text; text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combobox)); if (g_strcmp0 (text, "Grid") == 0) { gtk_source_view_set_background_pattern (self->view, GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID); } else { gtk_source_view_set_background_pattern (self->view, GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE); } g_free (text); } static void enable_snippets_toggled_cb (TestWidget *self, GtkCheckButton *button) { gboolean enabled = gtk_check_button_get_active (button); gtk_source_view_set_enable_snippets (self->view, enabled); } static GtkSourceHoverProvider * create_hover_provider (void) { return g_object_new (TEST_TYPE_HOVER_PROVIDER, NULL); } static void enable_hover_toggled_cb (TestWidget *self, GtkCheckButton *button) { static GtkSourceHoverProvider *test_hover_provider; GtkSourceHover *hover = gtk_source_view_get_hover (self->view); gboolean enabled = gtk_check_button_get_active (button); if (test_hover_provider == NULL) { test_hover_provider = create_hover_provider (); } if (enabled) gtk_source_hover_add_provider (hover, test_hover_provider); else gtk_source_hover_remove_provider (hover, test_hover_provider); } static void vim_checkbutton_toggled_cb (TestWidget *self, GtkCheckButton *button) { g_assert (TEST_IS_WIDGET (self)); g_assert (GTK_IS_CHECK_BUTTON (button)); if (gtk_check_button_get_active (button)) { if (!self->vim_controller) { self->vim_im_context = gtk_source_vim_im_context_new (); gtk_im_context_set_client_widget (self->vim_im_context, GTK_WIDGET (self->view)); g_object_bind_property (self->vim_im_context, "command-bar-text", self->command_bar, "label", 0); self->vim_controller = gtk_event_controller_key_new (); gtk_event_controller_set_propagation_phase (self->vim_controller, GTK_PHASE_CAPTURE); gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (self->vim_controller), GTK_IM_CONTEXT (self->vim_im_context)); gtk_widget_add_controller (GTK_WIDGET (self->view), self->vim_controller); } } else { if (self->vim_controller) { g_clear_object (&self->vim_im_context); gtk_widget_remove_controller (GTK_WIDGET (self->view), self->vim_controller); self->vim_controller = NULL; } } } static void test_widget_dispose (GObject *object) { TestWidget *self = TEST_WIDGET (object); g_clear_object (&self->buffer); g_clear_object (&self->file); G_OBJECT_CLASS (test_widget_parent_class)->dispose (object); } static void test_widget_class_init (TestWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = test_widget_dispose; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gtksourceview/tests/ui/test-widget.ui"); gtk_widget_class_bind_template_callback (widget_class, open_button_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, print_button_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, highlight_syntax_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, highlight_matching_bracket_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, show_line_numbers_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, show_line_marks_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, show_right_margin_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, right_margin_position_value_changed_cb); gtk_widget_class_bind_template_callback (widget_class, highlight_current_line_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, wrap_lines_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, auto_indent_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, indent_spaces_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, tab_width_value_changed_cb); gtk_widget_class_bind_template_callback (widget_class, backward_string_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, forward_string_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, smart_home_end_changed_cb); gtk_widget_class_bind_template_callback (widget_class, enable_snippets_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, enable_hover_toggled_cb); gtk_widget_class_bind_template_callback (widget_class, vim_checkbutton_toggled_cb); gtk_widget_class_bind_template_child (widget_class, TestWidget, view); gtk_widget_class_bind_template_child (widget_class, TestWidget, map); gtk_widget_class_bind_template_child (widget_class, TestWidget, show_top_border_window_checkbutton); gtk_widget_class_bind_template_child (widget_class, TestWidget, show_map_checkbutton); gtk_widget_class_bind_template_child (widget_class, TestWidget, draw_spaces_checkbutton); gtk_widget_class_bind_template_child (widget_class, TestWidget, smart_backspace_checkbutton); gtk_widget_class_bind_template_child (widget_class, TestWidget, indent_width_checkbutton); gtk_widget_class_bind_template_child (widget_class, TestWidget, indent_width_spinbutton); gtk_widget_class_bind_template_child (widget_class, TestWidget, cursor_position_info); gtk_widget_class_bind_template_child (widget_class, TestWidget, chooser_button); gtk_widget_class_bind_template_child (widget_class, TestWidget, background_pattern); gtk_widget_class_bind_template_child (widget_class, TestWidget, scrolledwindow1); gtk_widget_class_bind_template_child (widget_class, TestWidget, vim_checkbutton); gtk_widget_class_bind_template_child (widget_class, TestWidget, command_bar); gtk_widget_class_add_binding_action (widget_class, GDK_KEY_w, GDK_CONTROL_MASK, "window.close", NULL); } static void show_top_border_window_toggled_cb (GtkCheckButton *checkbutton, TestWidget *self) { gint size; size = gtk_check_button_get_active (checkbutton) ? 20 : 0; if (self->top == NULL) { self->top = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_text_view_set_gutter (GTK_TEXT_VIEW (self->view), GTK_TEXT_WINDOW_TOP, GTK_WIDGET (self->top)); } gtk_widget_set_size_request (self->top, -1, size); } static gboolean boolean_to_scrollbar_policy (GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data) { if (g_value_get_boolean (from_value)) g_value_set_enum (to_value, GTK_POLICY_EXTERNAL); else g_value_set_enum (to_value, GTK_POLICY_AUTOMATIC); return TRUE; } static void test_widget_init (TestWidget *self) { GtkSourceSpaceDrawer *space_drawer; GtkSourceStyleScheme *style_scheme; GFile *file; self = test_widget_get_instance_private (self); gtk_widget_init_template (GTK_WIDGET (self)); self->buffer = GTK_SOURCE_BUFFER ( gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view))); g_signal_connect_swapped (self->buffer, "notify::language", G_CALLBACK (update_cursor_position_info), self); g_object_ref (self->buffer); g_signal_connect (self->show_top_border_window_checkbutton, "toggled", G_CALLBACK (show_top_border_window_toggled_cb), self); g_signal_connect_swapped (self->indent_width_checkbutton, "toggled", G_CALLBACK (update_indent_width), self); g_signal_connect_swapped (self->indent_width_spinbutton, "value-changed", G_CALLBACK (update_indent_width), self); g_signal_connect (self->buffer, "mark-set", G_CALLBACK (mark_set_cb), self); g_signal_connect_swapped (self->buffer, "changed", G_CALLBACK (update_cursor_position_info), self); g_signal_connect (self->buffer, "bracket-matched", G_CALLBACK (bracket_matched_cb), NULL); add_source_mark_attributes (self->view); g_signal_connect (self->view, "line-mark-activated", G_CALLBACK (line_mark_activated_cb), self); g_object_bind_property (self->chooser_button, "style-scheme", self->buffer, "style-scheme", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); style_scheme = gtk_source_style_scheme_manager_get_scheme ( gtk_source_style_scheme_manager_get_default (), "Adwaita"); gtk_source_buffer_set_style_scheme (self->buffer, style_scheme); g_object_bind_property (self->show_map_checkbutton, "active", self->map, "visible", G_BINDING_SYNC_CREATE); g_object_bind_property_full (self->show_map_checkbutton, "active", self->scrolledwindow1, "vscrollbar-policy", G_BINDING_SYNC_CREATE, boolean_to_scrollbar_policy, NULL, NULL, NULL); g_object_bind_property (self->smart_backspace_checkbutton, "active", self->view, "smart-backspace", G_BINDING_SYNC_CREATE); g_signal_connect (self->background_pattern, "changed", G_CALLBACK (on_background_pattern_changed), self); space_drawer = gtk_source_view_get_space_drawer (self->view); g_object_bind_property (self->draw_spaces_checkbutton, "active", space_drawer, "enable-matrix", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); if (cmd_filename) file = g_file_new_for_commandline_arg (cmd_filename); else file = g_file_new_for_path (TOP_SRCDIR "/gtksourceview/gtksourcebuffer.c"); open_file (self, file); g_object_unref (file); } static TestWidget * test_widget_new (void) { return g_object_new (test_widget_get_type (), NULL); } static gboolean test_hover_provider_populate (GtkSourceHoverProvider *provider, GtkSourceHoverContext *context, GtkSourceHoverDisplay *display, GError **error) { GtkTextIter begin, end; if (gtk_source_hover_context_get_bounds (context, &begin, &end)) { gchar *text = gtk_text_iter_get_slice (&begin, &end); GtkWidget *label = gtk_label_new (text); gtk_source_hover_display_append (display, label); g_free (text); } return TRUE; } static void hover_provider_iface_init (GtkSourceHoverProviderInterface *iface) { iface->populate = test_hover_provider_populate; } static void test_hover_provider_class_init (TestHoverProviderClass *klass) { } static void test_hover_provider_init (TestHoverProvider *self) { } static void setup_search_paths (void) { GtkSourceSnippetManager *snippets; GtkSourceStyleSchemeManager *styles; GtkSourceLanguageManager *languages; static const gchar *snippets_path[] = { TOP_SRCDIR"/data/snippets", NULL }; static const gchar *langs_path[] = { TOP_SRCDIR"/data/language-specs", NULL }; snippets = gtk_source_snippet_manager_get_default (); gtk_source_snippet_manager_set_search_path (snippets, snippets_path); /* Allow use of system styles, but prefer in-tree */ styles = gtk_source_style_scheme_manager_get_default (); gtk_source_style_scheme_manager_prepend_search_path (styles, TOP_SRCDIR"/data/styles"); languages = gtk_source_language_manager_get_default (); gtk_source_language_manager_set_search_path (languages, langs_path); } int main (int argc, char *argv[]) { GtkWidget *window; TestWidget *test_widget; main_loop = g_main_loop_new (NULL, FALSE); if (argc == 2 && g_file_test (argv[1], G_FILE_TEST_IS_REGULAR)) { cmd_filename = argv[1]; } g_set_prgname ("test-widget"); gtk_init (); gtk_source_init (); setup_search_paths (); window = gtk_window_new (); gtk_window_set_default_size (GTK_WINDOW (window), 900, 600); gtk_window_set_title (GTK_WINDOW (window), "GtkSourceView Test"); g_signal_connect_swapped (window, "destroy", G_CALLBACK (g_main_loop_quit), main_loop); test_widget = test_widget_new (); gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (test_widget)); gtk_widget_show (window); g_main_loop_run (main_loop); gtk_source_finalize (); return 0; } 070701000002D0000081A400000000000000000000000166590806000000C7000000000000000000000000000000000000003500000000gtksourceview-5.12.1/tests/test-widget.gresource.xml test-widget.ui 070701000002D1000081A4000000000000000000000001665908060000577E000000000000000000000000000000000000002A00000000gtksourceview-5.12.1/tests/test-widget.ui 1 16 1 10 1 200 80 1 10 1 16 8 1 10 070701000002D2000041ED0000000000000000000000026659080600000000000000000000000000000000000000000000001F00000000gtksourceview-5.12.1/testsuite070701000002D3000081A400000000000000000000000166590806000003F5000000000000000000000000000000000000002800000000gtksourceview-5.12.1/testsuite/def.lang