Repository: dajva/rg.el Branch: master Commit: a614e7d7709c Files: 45 Total size: 405.9 KB Directory structure: gitextract_p8leny0j/ ├── .ert-runner ├── .github/ │ └── workflows/ │ ├── build.yml │ └── documentation.yml ├── .gitignore ├── Cask ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── docs/ │ ├── configuration.org │ ├── contribute.org │ ├── gpl.org │ ├── index.org │ ├── org-bootstrap.el │ ├── rst/ │ │ ├── Makefile │ │ ├── conf.py │ │ └── elispdomain.py │ ├── usage.org │ └── utils.org ├── rg-header.el ├── rg-history.el ├── rg-ibuffer.el ├── rg-info-hack.el ├── rg-isearch.el ├── rg-menu.el ├── rg-result.el ├── rg.el ├── rgel.info ├── test/ │ ├── data/ │ │ ├── bar.baz │ │ ├── bar.el │ │ ├── context.el │ │ ├── dirlocals/ │ │ │ └── .dir-locals.el │ │ ├── foo.baz │ │ ├── foo.el │ │ └── limenu.el │ ├── package-bootstrap.el │ ├── rg-header.el-test.el │ ├── rg-history.el-test.el │ ├── rg-isearch.el-test.el │ ├── rg-menu.el-test.el │ ├── rg.el-test.el │ ├── style-check.el │ ├── test-helper.el │ └── wgrep-rg.el-test.el └── wgrep-rg.el ================================================ FILE CONTENTS ================================================ ================================================ FILE: .ert-runner ================================================ -L . ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: release: types: - created pull_request: schedule: - cron: '0 6 1 * *' jobs: tests: runs-on: ubuntu-latest strategy: matrix: emacs_version: - 28.1 - 28.2 - 29.1 - 29.4 - 30.1 - 30.2 steps: - name: Setup emacs-ci-nix for ${{ matrix.emacs_version }} uses: purcell/setup-emacs@master with: version: ${{ matrix.emacs_version }} - name: Setup Cask uses: conao3/setup-cask@master - name: Checkout repository uses: actions/checkout@v4 - name: Install ripgrep env: REPO: BurntSushi/ripgrep run: | RELEASE_TAG=$(curl -sL --fail \ -H 'Accept: application/vnd.github.v3+json' \ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ 'https://api.github.com/repos/BurntSushi/ripgrep/releases/latest' \ | jq -r '.name') PACKAGE_NAME=ripgrep-${RELEASE_TAG}-x86_64-unknown-linux-musl.tar.gz curl -sL --fail \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ "https://api.github.com/repos/${REPO}/releases/tags/${RELEASE_TAG}" \ | jq -r ".assets | .[] | select(.name==\"${PACKAGE_NAME}\") | .url" \ | tee asset.url curl -sL --fail \ -H "Accept: application/octet-stream" \ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ -o "${PACKAGE_NAME}" \ "$(cat asset.url)" tar xf ${PACKAGE_NAME} echo "$PWD/$(basename -s .tar.gz $PACKAGE_NAME)" >> $GITHUB_PATH - name: Install emacs deps run: make EMACS_VERSION=${{ matrix.emacs_version }} deps - name: Run tests run: | emacs --version rg --version make EMACS_VERSION=${{ matrix.emacs_version }} test - name: Coveralls uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} flag-name: emacs-${{ matrix.emacs_version }} parallel: true finish: needs: tests runs-on: ubuntu-latest steps: - name: Coveralls Finished uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true ================================================ FILE: .github/workflows/documentation.yml ================================================ name: Documentation on: push: release: types: - created jobs: documentation: runs-on: ubuntu-latest steps: - name: Setup emacs-ci-nix for emacs 28.1 uses: purcell/setup-emacs@master with: version: 28.1 - name: Setup Cask uses: conao3/setup-cask@master - name: Install sphinx run: sudo apt-get --yes install python3-sphinx - name: Checkout repository uses: actions/checkout@v4 - name: Install emacs deps run: make deps - name: Changed files id: files uses: jitterbit/get-changed-files@v1 continue-on-error: true - name: Extract ref id: extract_ref run: echo ::set-output name=ref::${GITHUB_REF#refs/*/} - name: Generate info documentation if: github.event_name != 'release' && contains(steps.files.outputs.all, 'docs/') run: | make info git config user.name "GitHub Actions Bot" git config user.email "<>" git add rgel.info git commit -m 'Generate info documentation' git push # Clean to avoid pushing build artifacts in the steps below make clean-docs - name: Generate restructured text run: make rst - name: Set documentation branch id: doc_branch run: | if [[ ${{ github.event_name }} = "release" ]]; then echo ::set-output name=branch::release else echo ::set-output name=branch::master fi - name: Deploy to branch ${{ steps.doc_branch.outputs.branch }} if: github.ref == 'refs/heads/master' || github.event_name == 'release' uses: JamesIves/github-pages-deploy-action@v4.6.4 with: branch: ${{ steps.doc_branch.outputs.branch }} folder: docs/rst repository-name: dajva/rg.el-docs token: ${{ secrets.DOCUMENTATION_DEPLOYMENT }} - name: Tag release ${{ steps.extract_ref.outputs.ref }} if: github.event_name == 'release' env: RELEASE_VERSION: ${{ steps.extract_ref.outputs.ref }} DOCUMENTATION_DEPLOYMENT: ${{ secrets.DOCUMENTATION_DEPLOYMENT }} run: | git clone https://github.com/dajva/rg.el-docs cd rg.el-docs git checkout release git tag $RELEASE_VERSION git push https://x-access-token:${DOCUMENTATION_DEPLOYMENT}@github.com/dajva/rg.el-docs $RELEASE_VERSION ================================================ FILE: .gitignore ================================================ .cask/* dist/* *.elc ================================================ FILE: Cask ================================================ (source gnu) (source melpa) (package-file "rg.el") (files "rg.el" "rg-history.el" "rg-result.el" "rg-header.el" "rg-ibuffer.el" "rg-info-hack.el" "rg-isearch.el" "rg-menu.el" "wgrep-rg.el") (development (depends-on "cl-lib" "0.5") (depends-on "find-file-in-project") (depends-on "flycheck") (depends-on "package-lint") (depends-on "s") (depends-on "seq") (depends-on "transient") (depends-on "undercover") (depends-on "wgrep") (depends-on "ox-rst" :git "https://github.com/dajva/ox-rst")) ================================================ FILE: Dockerfile ================================================ FROM ubuntu SHELL ["/bin/bash", "-c"] RUN apt-get -y update # noninteractive is needed to avoid config of tzlocal package RUN DEBIAN_FRONTEND=noninteractive apt-get -y install curl xz-utils make git python3 ripgrep sudo python3-sphinx python3-sphinx-rtd-theme texinfo RUN install -d -m755 -o $(id ubuntu -u) -g $(id ubuntu -g) /nix USER ubuntu ENV USER ubuntu WORKDIR /home/ubuntu # Install nix RUN curl -L https://nixos.org/nix/install | sh # Install cachix RUN . ~/.nix-profile/etc/profile.d/nix.sh && \ nix-env -iA cachix -f https://cachix.org/api/v1/install # Install emacs from nix-emacs-ci ARG EMACS_VERSION=28-2 RUN . ~/.nix-profile/etc/profile.d/nix.sh && \ cachix use emacs-ci && \ nix-env -iA emacs-${EMACS_VERSION} -f https://github.com/purcell/nix-emacs-ci/archive/master.tar.gz && \ emacs --version # Install cask RUN . ~/.nix-profile/etc/profile.d/nix.sh && \ curl -fsSL https://raw.githubusercontent.com/cask/cask/master/go | python3 ENV PATH=/home/ubuntu/.cask/bin:$PATH ENTRYPOINT ["bash", "-c", "source ~/.nix-profile/etc/profile.d/nix.sh && \"$@\"", "-s"] ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: Makefile ================================================ EMACS_VERSION=29-4 DOCKER_IMAGE=rg.el-test-emacs-$(EMACS_VERSION) ifdef USE_DOCKER DOCKER_WRAPPER=docker run --workdir /app --mount type=bind,source="$(PWD)",target=/app $(DOCKER_IMAGE) %: run_make @: .PHONY: run_make run_make: $(DOCKER_WRAPPER) $(MAKE) EMACS_VERSION=$(EMACS_VERSION) $(MAKECMDGOALS) else # actual Makefile PKG_NAME = $(shell cask info | head -1 | cut -f2 -d" ") PKG_VERSION = $(shell cask version) PKG_FULL_NAME = $(PKG_NAME)-$(PKG_VERSION) SOURCES = $(shell cask files) OBJECTS = $(SOURCES:.el=.elc) STYLE_CHECK= -L test -L . -l test/style-check.el DISABLE_DEFALIAS_CHECK= --eval "(defun package-lint--check-defalias (prefix def))" # This setup testing similar to ert-runner for legacy reasons. # All files under test/ that matches the source files. TEST_FILES = $(filter $(patsubst %,test/%-test.el, $(SOURCES)), $(wildcard test/*.el)) # Load test-helper.el first, then all test files. LOAD_TEST_FILES = -L test -l test-helper $(patsubst %,-l %,$(TEST_FILES:test/%.el=%)) SPHINX-BUILD = sphinx-build DOC_DIR = docs ORG_DOCS= $(wildcard $(DOC_DIR)/*.org) RST_OUT_DIR = $(DOC_DIR)/rst RST_DOCS = $(addprefix $(RST_OUT_DIR)/,$(patsubst %.org,%.rst,$(notdir $(ORG_DOCS)))) all: deps test docker-build: docker build --build-arg EMACS_VERSION=$(EMACS_VERSION) -t $(DOCKER_IMAGE) . test: ert-test style-check package-lint build-test package-test build-test: clean build cask clean-elc $(RST_DOCS): | $(RST_OUT_DIR) $(RST_OUT_DIR): mkdir $(RST_OUT_DIR) $(RST_OUT_DIR)/%.rst: docs/%.org RST_OUT_DIR=$(abspath $(RST_OUT_DIR)) cask emacs --batch -Q -L . -l docs/org-bootstrap.el $< --funcall rg-export-to-rst rst: $(RST_DOCS) html: rst $(SPHINX-BUILD) -b html $(RST_OUT_DIR) $(RST_OUT_DIR)/_build/html info: rst $(SPHINX-BUILD) -b texinfo $(RST_OUT_DIR) $(RST_OUT_DIR)/_build/info make -C $(RST_OUT_DIR)/_build/info cp $(RST_OUT_DIR)/_build/info/rgel.info . docs: html clean-docs: rm $(RST_OUT_DIR)/*.rst make -C $(RST_OUT_DIR) clean clean: cask clean-elc build: $(OBJECTS) %.elc: %.el cask emacs -batch -Q -L . -eval "(progn (setq byte-compile-error-on-warn t) (batch-byte-compile))" $< package-test: -@rm -r dist 2> /dev/null || true -@rm -r /tmp/$(PKG_FULL_NAME)-elpa 2> /dev/null || true cask package PKG_FULL_NAME=$(PKG_FULL_NAME) emacs -batch -Q -l test/package-bootstrap.el \ --eval "(progn (package-install-file (expand-file-name \"dist/$(PKG_FULL_NAME).tar\")) (rg \"rg\" \"elisp\" \"/tmp/$(PKG_FULL_NAME)-elpa\"))" style-check: cask emacs -batch -Q $(STYLE_CHECK) -f run-emacs-lisp-flycheck-and-exit $(SOURCES) package-lint: cask emacs -batch -Q $(STYLE_CHECK) $(DISABLE_DEFALIAS_CHECK) -f run-package-lint-and-exit rg.el unit-test: cask emacs --batch -l ert $(LOAD_TEST_FILES) --eval="(ert-run-tests-batch-and-exit \"rg-unit\")" integration-test: cask emacs --batch -l ert $(LOAD_TEST_FILES) --eval="(ert-run-tests-batch-and-exit \"rg-integration\")" ert-test: cask emacs --batch -l ert $(LOAD_TEST_FILES) -f ert-run-tests-batch-and-exit deps: cask install .PHONY: all test build-test clean clean-docs package-test style-check package-lint unit-test integration-test ert-test deps deps_execute deps_prepare deps_cleanup docs endif ================================================ FILE: README.md ================================================ # rg.el [![License GPL 3](https://img.shields.io/badge/license-GPL_3-green.svg?style=flat)](LICENSE) [![MELPA Stable](https://stable.melpa.org/packages/rg-badge.svg)](https://stable.melpa.org/#/rg) [![MELPA](http://melpa.org/packages/rg-badge.svg)](http://melpa.org/#/rg) [![Build Status](https://github.com/dajva/rg.el/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/dajva/rg.el/actions/workflows/build.yml) [![Coverage Status](https://coveralls.io/repos/github/dajva/rg.el/badge.svg)](https://coveralls.io/github/dajva/rg.el) Use [ripgrep](https://github.com/BurntSushi/ripgrep) in Emacs. Ripgrep is a replacement for both grep like (search one file) and ag like (search many files) tools. It's fast and versatile and written in Rust. For some introduction and benchmarks, see [ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}](http://blog.burntsushi.net/ripgrep/). ![screenshot](screenshot.png) ## Installation This package is available on [MELPA Stable](https://stable.melpa.org/#/rg) and [MELPA](http://melpa.org/#/rg). Install with `M-x package-install` RET `rg` from within Emacs. It is also available in GNU Guix as [emacs-rg](https://guix.gnu.org/en/packages/emacs-rg-2.2.0/). Install with `guix package -i emacs-rg`. If you want to install manually just put `rg.el` and the rest of the elisp files somewhere in your load path and add require the package: ``` el (require 'rg) ``` `rg` and friends are autoloaded symbols which means it's also possible to defer loading if you have autoloading setup. ### Setup key bindings This will setup the default key bindings in a non lazy way. If you care about startup performance see the next example. ``` el (rg-enable-default-bindings) ``` See [documentation](https://rgel.readthedocs.io) for how to handle lazy loading. ### Use old defaults `rg.el` 2.0.0 will use new default settings to improve look and feel, more consistent key bindings etc. If you want to use the old defaults add this to your `init.el`: ``` el (rg-use-old-defaults) ``` ### rg-menu If you prefer to use a [magit](https://github.com/magit/magit) like interface as a complement to regular key maps, replace `(rg-enable-default-bindings)` with `(rg-enable-menu)`. The menus are built with [transient](https://github.com/magit/transient), which means that the menus can be modified in the same way as in magit. ## Documentation Info documentation is included in the package. Online documentation: https://rgel.readthedocs.io ## Contribute - Install [cask](http://cask.github.io/). - Install dependencies: ``` Shell make deps ``` - Run tests: ``` Shell make test ``` ## License See [LICENSE](LICENSE). ================================================ FILE: docs/configuration.org ================================================ #+TITLE: Configuration #+AUTHOR: David Landell #+EMAIL: david.landell@sunnyhill.email #+DATE: 2019 #+LANGUAGE: en #+OPTIONS: broken-links:auto, toc:nil, email:nil, num:nil, ^:nil, author:nil, date:nil #+INCLUDE: "utils.org" * Customization :PROPERTIES: :CUSTOM_ID: customization :END: Customization is done via the Emacs customization system. The group =rg= is the main group of the package. #+BEGIN_SRC elisp M-x customize-group [RET] rg [RET] #+END_SRC :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-executable '(executable-find "rg")) #+END_SRC The /ripgrep/ executable to use. Could be an absolute path or just the base name if the executable is in the path. The default is using =executable-find= to locate the command. If you want to use this package with tramp it might be better to set it to just "rg" in order to let the OS find the binary where it's invoked. From Emacs 27.1, the tramp use case is by default handled automatically. See [[opt:rg-executable-per-connection]] for details. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-executable-per-connection) #+END_SRC This setting only has effect in Emacs 27.1 or later. Handle the [[opt:rg-executable]] automatically for different hosts if used with tramp. =executable-find= for "rg" binary will be invoked on remote hosts to determine the path to ripgrep. The result is stored per connection. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-custom-type-aliases) #+END_SRC An association list that maps file type aliases to a space delimited string with file globs. These are combined with the /ripgrep/ builtin file aliases. Example: #+BEGIN_SRC elisp (setq rg-custom-type-aliases '(("foo" . "*.foo *.bar") ("baz" . "*.baz *.qux"))) #+END_SRC You may also add lambdas to =rg-custom-type-aliases= to add aliases dynamically based on mode, directory, project, etc. #+BEGIN_SRC elisp (add-to-list 'rg-custom-type-aliases (lambda () (when (in-frontend-app) (cons "ui" "*.js *.hbs *.json")))) #+END_SRC :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-prioritized-type-aliases) #+END_SRC A list of aliases that are prioritized among ripgrep's builtin aliases when selecting the alias based on the buffer file name. This list contains only the alias names and the order between the items does not matter. Example: #+BEGIN_SRC elisp (setq rg-custom-type-aliases '("cpp" "puppet")) #+END_SRC :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-default-alias-fallback) #+END_SRC This setting controls the default alias used when no alias can be recognized for the current buffer. =all= or =everything= are reasonable values for this variable. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-command-line-flags) #+END_SRC A list of command line flags that will be appended to the /ripgrep/ command line. Must either be a list of flags or a function that returns a list of flags. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-group-result) #+END_SRC Controls the layout of the results buffer. If non =nil=, each file name is displayed once and matches are grouped under that filename instead of repeating the filename on each match. This is essentially the layout of the =--no-heading= /ripgrep/ command line flag. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-show-columns) #+END_SRC Controls if column numbers are used in the search result. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-ignore-case) #+END_SRC Setting that controls if case sensitive search is made or not. It can essentially be *on*, *off* or *smart*. The *smart* setting will trigger an analyze of the search string and if it's all lower case, the search will be case /insensitive/, otherwise it will be case /sensitive/. The following values are valid: - *case-fold-search* - A non nil value of =case-fold-search= will trigger smart case behavior. - *smart* - Smart case behavior. - *force* - Always ignore case. - *nil* - Always consider case. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-hide-command) #+END_SRC Hide most of command line by default. This is enabled by default and can be set to =nil= to show full command line. This can be toggled in the results buffer by clicking on the command line. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-keymap-prefix) #+END_SRC This variable sets the default prefix used for the global key bindings. Note that =rg-enable-default-bindings= needs to be invoked for the bindings to be enabled. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-use-transient-menu) #+END_SRC Controls whether =rg-menu= will be used by default or not. It's also possible to enable the menu explicitly with #+BEGIN_SRC elisp (rg-enable-menu) #+END_SRC :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-show-header) #+END_SRC Controls if the search info header is shown in the result buffer. This is enabled by default but can be disabled by setting this variable to =nil=. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-buffer-name) #+END_SRC Controls the name of the results buffer. It may be /string/ or /function/. This name will be surrounded by =*= to yield the final buffer name so if this setting is =foo= the buffer name will be =*foo*=. One useful case of using it is to have separate result buffers per project. One can set this variable in `dir-locals` file or set it to function. Example, this function will set results buffer name based on `project-current`: #+BEGIN_SRC elisp (defun my-rg-buffer-name () (let ((p (project-current))) (if p (format "rg %s" (abbreviate-file-name (cdr p))) "rg"))) #+END_SRC :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-ignore-ripgreprc) #+END_SRC Controls if the [[https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#configuration-file][ripgreprc]] file should be ignored or not. If =nil=, the config file will be used, otherwise it will be ignored. The default is to ignore this file in order to avoid that conflicting settings have impact on this package's behavior. Setting this to =nil= may affect core functionality of this package. Especially changing colors can affect parsing of the output and result in a broken results buffer. :END: *** Position numbers alignment :PROPERTIES: :CUSTOM_ID: position-numbers-alignment :END: When operating /rg/ in grouped output mode ([[opt:rg-group-result]] is non nil), it's possible to control how the line and column numbers are displayed in the result buffer. Example settings: #+BEGIN_SRC elisp (setq rg-align-position-numbers t) (setq rg-align-line-number-field-length 3) (setq rg-align-column-number-field-length 3) (setq rg-align-line-column-separator "#") (setq rg-align-position-content-separator "|") #+END_SRC Will yield the following format: #+BEGIN_EXAMPLE File: matched_file.foo 1# 2|match1 888# 10|match2 #+END_EXAMPLE :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-align-position-numbers) #+END_SRC Setting this to =t= will align line and column numbers in columns padded with white space. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-align-line-number-field-length) #+END_SRC Defines the length of the line number field. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-align-column-number-field-length) #+END_SRC Defines the length of the column number field. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-align-line-column-separator) #+END_SRC Separator string used between line and column numbers. =nil= means use default separator from /ripgrep/. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-align-position-content-separator) #+END_SRC Separator string used between the position numbers and matched content. =nil= means use default separator from /ripgrep/. :END: * Faces All faces are in the subgroup =rg-face= of the main group =rg=. #+BEGIN_SRC elisp M-x customize-group [RET] rg-face [RET] #+END_SRC *** Results buffer :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-match-face) #+END_SRC Face used to highlight matches in result. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-error-face) #+END_SRC Face used to highlight errors when invoking /ripgrep/. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-context-face) #+END_SRC Face used to highlight context lines in /ripgrep/ output when =--context-lines= flag is used. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-info-face) #+END_SRC Face used to highlight general info in results buffer. For instance the number of matches found. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-warning-face) #+END_SRC Face used to highlight warnings in the /ripgrep/ output. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-filename-face) #+END_SRC Face used to highlight filenames in the output. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-file-tag-face) #+END_SRC Face used for the =File:= tag in grouped results output. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-line-number-face) #+END_SRC Face used on line numbers. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-column-number-face) #+END_SRC Face used on column numbers. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-match-position-face) #+END_SRC Face added to file positions. This is the start of a matching line and depending on configuration may be, file name, column number and line number. :END: *** Header line :PROPERTIES: :CUSTOM_ID: header_line_config :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-toggle-on-face) #+END_SRC Face used for flags that are toggled =on=. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-toggle-off-face) #+END_SRC Face used for flags that are toggled =off=. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-literal-face) #+END_SRC Face used the on the =literal= marker in the header line. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-face-info 'rg-regexp-face) #+END_SRC Face used the on the =regexp= marker in the header line. :END: * Configuration functions :PROPERTIES: :CUSTOM_ID: configuration_functions :END: :FUNCTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-function-info 'rg-enable-default-bindings) #+END_SRC Enable the default keyboard bindings for the package with prefix key. If [[opt:rg-use-transient-menu]] is on this will enable the menu instead of activating the global bindings. If =prefix= is not provided [[opt:rg-keymap-prefix]] will be used. :END: :FUNCTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-function-info 'rg-enable-menu) #+END_SRC Enable the [[file:usage.org::#the_menu][rg-menu]] with prefix key. This bypass [[opt:rg-use-transient-menu]] setting. If =prefix= is not provided [[opt:rg-keymap-prefix]] will be used. :END: :FUNCTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-function-info 'rg-use-old-defaults) #+END_SRC This function is provided to keep backwards compatibility with versions older than 2.0.0. In this version default settings as well as key bindings changed and to bring back the old defaults call this function in your init file. :END: * Hooks :PROPERTIES: :CUSTOM_ID: hooks :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-finish-functions) #+END_SRC Functions to call when a ripgrep search is finished. Each function is called with two arguments: the compilation buffer, and a string describing how the process finished. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-filter-hook) #+END_SRC Hook run after new content has been inserted in in the rg buffer. This hook is called every time the rg buffer has been updated with new content and filtered internally by the package. :END: :OPTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-custom-info 'rg-mode-hook) #+END_SRC Hook run after entering rg mode. :END: * Configuration macros :PROPERTIES: :CUSTOM_ID: configuration_macros :END: :FUNCTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-function-info 'rg-define-toggle) #+END_SRC This is a macro that can be used to define custom /ripgrep/ flag toggling functions in the result buffer. The macro takes the flag (and potential value) as an argument and optionally binds the toggle function to a key. If =default= is non nil the flag is used by default. The function defined by this macro will be named as the flag name stripped with leading dashes and prefixed with =rg-custom-toggle-flag-=. #+BEGIN_SRC elisp (rg-define-toggle "-uu" "I" t) #+END_SRC Creates a function named =rg-custom-toggle-flag-uu= that is on by default and bound to =I= in /rg/ result buffer. #+BEGIN_SRC elisp (rg-define-toggle "--context 3" (kbd "C-c c")) #+END_SRC Creates a function named =rg-custom-toggle-flag-context= that is off by default and bound to =C-c c= in /rg/ result buffer. :END: :FUNCTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-function-info 'rg-define-search) #+END_SRC This macro can be used to define custom search functions in a declarative style. Default implementations for common behavior is available and custom forms can also be used. It optionally starts with a string that is used as the docstring for the defined function. The rest of the arguments contain key value pairs according to the specification below. All keys are optional with specified default if left out. - *:query* - Method for retrieving the search string. Allowed values are =point= which means extract thing at point and =ask= which means prompt the user for a string. Any form that evaluates to a string is allowed. Default is =ask=. - *:format* - Specifies if =:query= is interpreted literally (=literal=) or as a regexp (=regexp=). If it is a form, eg. =(not current-prefix-arg)=, and is non-nil the =:query= is interpreted literally, otherwise as a regexp. Default is =regexp=. - *:files* - Form that evaluates to a file alias or custom file glob. =current= means extract alias from current buffer file name, =ask= will prompt the user. Default is =ask=. - *:dir* - Root search directory. Allowed values are =ask= for user prompt, =current= for current dir and =project= for project root. Any form that evaluates to a directory string is also allowed. Default is =ask=. - *:confirm* - =never=, =always=, or =prefix= are allowed values. Specifies if the the final search command line string can be modified and confirmed the user. Default is =never=. - *:flags* - =ask= or a list of command line flags that will be used when invoking the search. - *:menu* - Bind the command into =rg-menu=. Must be a list with three items in it. The first item is the description of the group in which the new command will appear. If the group does not exist a new will be created. The second item is the key binding for this new command (ether a key vector or a key description string) and the third item is the description of the command that will appear in the menu. Examples: #+BEGIN_SRC elisp (rg-define-search search-everything-at-home "Search files including hidden in home directory" :query ask :format literal :files "everything" :flags ("--hidden") :dir (getenv "HOME") :menu ("Search" "h" "Home")) (rg-define-search rg-emacs "Search the emacs lisp source code." :dir "/usr/share/emacs/25.2/lisp/" :flags '("-z") :files "*.{el,el.gz}" :menu ("Custom" "L" "lisp")) #+END_SRC :END: * Use with evil-mode Some key bindings clash with /evil-mode/. Recommendation is to use evil /motion/ state for the results buffer and then switch to evil /normal/ mode when editing in /wgrep-mode/. Some adjustments need to be done to avoid the clashes though. This is a start of a configuration. This let /rg-mode/'s key bindings override the motion state map bindings based on that these motion keys are not important in an /rg/ results buffer. Adjust this to your preferred use case: #+begin_src elisp (with-eval-after-load 'rg (advice-add 'wgrep-change-to-wgrep-mode :after #'evil-normal-state) (advice-add 'wgrep-to-original-mode :after #'evil-motion-state) (defvar rg-mode-map) (add-to-list 'evil-motion-state-modes 'rg-mode) (evil-add-hjkl-bindings rg-mode-map 'motion "e" #'wgrep-change-to-wgrep-mode "g" #'rg-recompile "t" #'rg-rerun-change-literal)) #+end_src * Customizing the menu :PROPERTIES: :CUSTOM_ID: customizing_the_menu :END: The menu can be modified from the emacs configuration file. To add a new *switch* before the option triggered by =-n= at suffix level 3: #+BEGIN_SRC elisp (transient-insert-suffix 'rg-menu "-n" '(3 "-o" "Only print matches" "--only-matching")) #+END_SRC To add a new *option* before the option triggered by =-g= at suffix level 4: #+BEGIN_SRC elisp (transient-insert-suffix 'rg-menu "-g" '(4 "-f" "Pattern file" "--file=")) #+END_SRC The === in =--file== triggers argument input for the flag. To remove an item from the menu specify the trigger key in the transient remove command. For example, to remove the =Search hidden files= switch use the following: #+BEGIN_SRC elisp (transient-remove-suffix 'rg-menu "-h") #+END_SRC Please refer to the [[https://magit.vc/manual/transient/Modifying-Existing-Transients.html#Modifying-Existing-Transients][transient]] documentation for details on customizing the menu. This package also adds a convenience function for appending new *commands* to the menu in the groups at the bottom. :FUNCTION: #+BEGIN_SRC elisp :results value raw :exports results (rg-function-info 'rg-menu-transient-insert) #+END_SRC This inserts a new command under =group= if it exists, otherwise a new group is created. =key=, =description= and =command= is as for the =transient-insert-suffix= function. For example to insert a new command under =Search= group: #+BEGIN_SRC elisp (rg-menu-transient-insert "Search" "m" "My search" 'my-search-command) #+END_SRC It's usually better to use the =:menu= key of the [[func:rg-define-search]] macro to define a search function and adding it to the menu in one go. :END: ================================================ FILE: docs/contribute.org ================================================ #+TITLE: Contribute #+AUTHOR: David Landell #+EMAIL: david.landell@sunnyhill.email #+DATE: 2019 #+LANGUAGE: en #+OPTIONS: broken-links:auto, toc:nil, email:nil, num:nil, ^:nil, author:nil, date:nil #+INCLUDE: "utils.org" Contributions are very welcome. Development is done in the [[https://github.com/dajva/rg.el][GitHub repository]]. If you find a bug, please report it in the [[https://github.com/dajva/rg.el/issues][issue tracker]]. * Pull requests :PROPERTIES: :CUSTOM_ID: pull_requests :END: If you want to submit a patch, please submit a [[https://github.com/dajva/rg.el/pulls][GitHub pull request]]. If you want to submit any larger code changes, please create an issue first for discussion. Some features does not fit well into this package and there is also good to agree on the general design before doing any major work. The minimum requirements for a pull request to be accepted is that all existing tests pass and test coverage should not decrease. Often a patch also needs additional tests, new/changed documentation etc. Don't strive to submit a perfect pull request directly. It's often better to submit something simple that shows the main direction of the new code in order to discuss the best way to proceed and what additions are needed. * Docker :PROPERTIES: :CUSTOM_ID: docker :END: Docker can be used to run the tests or generate documentation locally without installing dependencies on the host and to test with different emacs versions. To use docker, just set the =USE_DOCKER= variable when running the tests. The =EMACS_VERSION= variable can be used to select emacs version. Note that dash ('-') is used instead of points ('.') in the version numbering. So emacs 28.2 is specified as =28-2=. Emacs are installed from https://github.com/purcell/nix-emacs-ci so only emacs versions supported in that repository will work. - Build docker container: #+begin_src bash # Don't use the USE_DOCKER variable here make EMACS_VERSION=30-2 docker-build #+end_src - To run all the tests in docker image: #+begin_src bash make USE_DOCKER=true test #+end_src - Use a specific emacs version: #+begin_src bash make USE_DOCKER=true EMACS_VERSION=snapshot #+end_src - Generate html documentation: #+begin_src bash make USE_DOCKER=true html #+end_src * Tests :PROPERTIES: :CUSTOM_ID: tests :END: [[https://cask.readthedocs.io/][Cask]] is used for testing. The tests are written using the Emacs built in ERT framework and follows the conventions of [[https://github.com/rejeep/ert-runner.el][ert runner]] although ert_runner is no longer used. There are also compilation tests, style check, package verification etc. *** Setup :PROPERTIES: :CUSTOM_ID: tests_setup :END: - [[https://cask.readthedocs.io/en/latest/guide/installation.html][Install cask]] - Install all developer dependencies: #+BEGIN_SRC bash make deps #+END_SRC *** Running :PROPERTIES: :CUSTOM_ID: tests_running :END: - Run the whole test suite: #+BEGIN_SRC bash make test #+END_SRC - Run only the unit/integration tests: #+BEGIN_SRC bash make ert-test #+END_SRC - Manually test the package with Emacs: #+BEGIN_SRC bash cask emacs -Q -L . --eval="(progn (require 'rg) (enable-default-bindings))" #+END_SRC * Documentation :PROPERTIES: :CUSTOM_ID: documentation :END: The documentation is written in org mode. The export target is [[https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html][restructured text]] suitable for the [[http://www.sphinx-doc.org/en/master/][Sphinx]] documentation generator. Sphinx is used to export the output from org mode to info and HTML documentation. The resulting .rst files are used for the online documentation on [[https://readthedocs.io]]. The end user documentation is generated after committing to the main repository. It's advisable to build both html and info documentation locally and verify the output to make sure the changes looks as expected. *** Setup :PROPERTIES: :CUSTOM_ID: documentation_setup :END: - [[http://www.sphinx-doc.org/en/master/usage/installation.html][Install Sphinx]] #+BEGIN_SRC bash sudo apt install python3-sphinx python3-sphinx-rtd-theme #+END_SRC - Install makeinfo #+begin_src bash sudo apt install texinfo #+end_src *** Building :PROPERTIES: :CUSTOM_ID: documentation_building :END: - HTML documentation #+BEGIN_SRC bash make html #+END_SRC Open =docs/rst/_build/html/index.html= in a browser. - Info documentation #+BEGIN_SRC bash make info #+END_SRC To view in emacs: #+BEGIN_SRC elisp C-u M-x info [RET] #+END_SRC Then select the =docs/rst/_build/info/rgel.info= file. ================================================ FILE: docs/gpl.org ================================================ #+TITLE: GNU General Public License #+AUTHOR: David Landell #+EMAIL: david.landell@sunnyhill.email #+DATE: 2019 #+LANGUAGE: en #+OPTIONS: broken-links:auto, toc:nil, email:nil, num:nil, ^:nil, author:nil, date:nil #+BEGIN_EXPORT rst .. _gpl: #+END_EXPORT #+INCLUDE: "../LICENSE" example ================================================ FILE: docs/index.org ================================================ #+TITLE: Rg Manual #+AUTHOR: David Landell #+EMAIL: david.landell@sunnyhill.email #+DATE: 2019 #+LANGUAGE: en #+OPTIONS: broken-links:auto, toc:nil, email:nil, num:nil, ^:nil, author:nil, date:nil /rg.el/ is an Emacs search package based on the [[https://github.com/BurntSushi/ripgrep][ripgrep]] command line tool. It allows you to interactively create searches, doing automatic searches based on the editing context, refining and modifying search results and much more. It is also highly configurable to be able to fit different users' needs. Throughout this manual this emacs package will be referred to as /rg/ while the command line utility will be referred to as /ripgrep/. If you are used to built-in Emacs =rgrep= command, transitioning to /rg/ should be simple. /rg/ provides a lot of extra features but the basics are similar. The big benefit of using /ripgrep/ instead of /grep/ as a backend is speed. Especially when searching large source code repositories where /ripgrep/ really shines. Please read [[http://blog.burntsushi.net/ripgrep/][this blog post]] for some speed comparisons with other tools. #+BEGIN_EXPORT rst .. toctree:: :maxdepth: 2 usage configuration contribute #+END_EXPORT * License :PROPERTIES: :CUSTOM_ID: license :END: /rg/ is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. /rg/ 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 [[file:gpl.org::#gpl][GNU General Public License]] for more details. #+BEGIN_EXPORT rst .. toctree:: :maxdepth: 2 gpl #+END_EXPORT ================================================ FILE: docs/org-bootstrap.el ================================================ ;;; org-bootstrap.el --- Description -*- lexical-binding: t; -*- ;; Copyright (C) 2019 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: (require 'ox-rst) (require 'package) (require 'rg) ;; General org mode settings (setq org-odd-levels-only t) (setq org-confirm-babel-evaluate nil) ;; org ox-rst settings (setq org-rst-pygments-langs '((lisp "common-lisp") (cc "c++") (cperl "perl") (latex "tex") (shell-script "bash") (caml "ocaml") (sqlite3 "sqlite"))) (setq org-rst-code-block 'code-block) (setq org-rst-link-use-ref-role t) (setq org-rst-file-link-use-ref-role t) (defun rg-format-todo (todo todo-type priority title tags contents) (when (eq todo-type 'todo) (let* ((padding " ") (formated-contents (when contents (concat padding (mapconcat 'identity (split-string contents "\n") (concat "\n" padding)))))) (concat (format (concat ".. todo:: %s\n\n") title) formated-contents "\n")))) (setq org-rst-format-inlinetask-function #'rg-format-todo) (defun rg-format-drawer (name contents info) (let* ((lines (split-string contents "\n")) (heading (car lines)) (body (cdr lines))) (format ".. %s:: %s\n%s\n\n" (downcase name) heading (concat " " (mapconcat #'identity body (concat "\n" " ")))))) (setq org-rst-format-drawer-function #'rg-format-drawer) (defun rg-func-role-link (path desc backend) (rg-ref-role-link "func" path desc backend)) (defun rg-cmd-role-link (path desc backend) (rg-ref-role-link "cmd" path desc backend)) (defun rg-opt-role-link (path desc backend) (rg-ref-role-link "opt" path desc backend)) (defun rg-var-role-link (path desc backend) (rg-ref-role-link "var" path desc backend)) (defun rg-ref-role-link (role path desc backend) (when (eq backend 'rst) (if desc (format ":%s:`%s <%s>`" role desc path) (format ":%s:`%s`" role path)))) (push '("func" :export rg-func-role-link ) org-link-parameters) (push '("cmd" :export rg-cmd-role-link ) org-link-parameters) (push '("opt" :export rg-opt-role-link ) org-link-parameters) (push '("var" :export rg-var-role-link ) org-link-parameters) (add-to-list 'load-path nil) (defvar rst-out-dir (getenv "RST_OUT_DIR")) (defun rg-export-to-rst () (interactive) (let ((outfile (org-export-output-file-name ".rst" nil rst-out-dir))) (org-export-to-file 'rst outfile))) (provide 'org-bootstrap) ;;; org-bootstrap.el ends here ================================================ FILE: docs/rst/Makefile ================================================ # Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ================================================ FILE: docs/rst/conf.py ================================================ # -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = u'rg.el' copyright = u'2019, David Landell' author = u'David Landell' # The short X.Y version version = u'' # The full version, including alpha/beta/rc tags release = u'2.4.0' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.todo', 'elispdomain' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst'] # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store', 'README.rst'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # # html_theme = 'alabaster' html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'rgeldoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'rgel.tex', u'rg.el Documentation', u'David Landell', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'rgel', u'rg.el Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'rgel', u'RG User Manual', author, 'RG', 'Search like a King with ripgrep in Emacs.', 'Emacs'), ] texinfo_elements = {'preamble': """ @definfoenclose strong,*,* @definfoenclose emph,_,_ """} texinfo_show_urls='inline' texinfo_no_detailmenu=True # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # -- Extension configuration ------------------------------------------------- # -- Options for todo extension ---------------------------------------------- # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True add_function_parentheses=False ================================================ FILE: docs/rst/elispdomain.py ================================================ # -*- coding: utf-8 -*- """ Emacs Lisp Domain for Sphinx Copyright 2014 by Jorgen Schäfer """ from sphinx import addnodes from sphinx.domains import Domain, ObjType from sphinx.locale import _ from sphinx.directives import ObjectDescription from sphinx.roles import XRefRole from sphinx.util.nodes import make_refnode class ELispMarkup(ObjectDescription): def add_target_and_index(self, name, sig, signode): targetname = self.objtype + '-' + name if targetname not in self.state.document.ids: signode['names'].append(targetname) signode['ids'].append(targetname) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) objects = self.env.domaindata['el']['objects'] key = (self.objtype, name) if key in objects: self.state_machine.reporter.warning( 'duplicate description of %s %s, ' % (self.objtype, name) + 'other instance in ' + self.env.doc2path(objects[key]), line=self.lineno) objects[key] = self.env.docname indextext = self.get_index_text(self.objtype, name) if indextext: self.indexnode['entries'].append(('single', indextext, targetname, '', None)) class ELispFunction(ELispMarkup): def get_index_text(self, objectname, name): return "{} (function)".format(name) def handle_signature(self, sig, signode): if sig.startswith("("): sig = sig[1:] if sig.endswith(")"): sig = sig[:-1] name, args = sig.split(" ", 1) params = addnodes.desc_parameterlist() if args: params += addnodes.desc_name(name, name + " ") else: params += addnodes.desc_name(name, name) for arg in args.split(): params += addnodes.desc_parameter(arg, arg) signode += params return name class ELispVariable(ELispMarkup): def get_index_text(self, objectname, name): return "{} (variable)".format(name) def handle_signature(self, sig, signode): signode += addnodes.desc_name(sig, sig) return sig class ELispCommand(ELispMarkup): option_spec = { 'kbd': lambda x: x } def get_index_text(self, objectname, name): return "{} (command)".format(name) def handle_signature(self, sig, signode): kbd = self.options.get("kbd") name = sig if kbd: description = "{} ({})".format(kbd, name) else: description = "M-x {}".format(sig) signode += addnodes.desc_name(description, description) return name class ELispOption(ELispMarkup): option_spec = { 'default': lambda x: x } def get_index_text(self, objectname, name): return "{} (customize option)".format(name) def handle_signature(self, sig, signode): default = self.options.get("default") if default: description = "{} [{}]".format(sig, default) else: description = sig signode += addnodes.desc_name(description, description) return sig class ELispDomain(Domain): """Emacs Lisp language domain.""" name = 'el' label = 'ELisp' object_types = { 'function': ObjType(_('function'), 'func'), 'variable': ObjType(_('variable'), 'var'), 'command': ObjType(_('command'), 'cmd'), 'option': ObjType(_('variable'), 'opt'), } directives = { 'function': ELispFunction, 'variable': ELispVariable, 'command': ELispCommand, 'option': ELispOption, } roles = { 'func': XRefRole(), 'var': XRefRole(), 'cmd': XRefRole(), 'opt': XRefRole(), } initial_data = { 'objects': {}, # fullname, type -> docname } def clear_doc(self, docname): for (fullname, type_), docname in self.data['objects'].items(): if fullname == docname: del self.data['objects'][fullname] def resolve_xref(self, env, fromdocname, builder, type_, target, node, contnode): objects = self.data['objects'] objtypes = self.objtypes_for_role(type_) for objtype in objtypes: if (objtype, target) in objects: return make_refnode(builder, fromdocname, objects[objtype, target], objtype + "-" + target, contnode, target + " " + objtype) def get_objects(self): for (name, type_), docname in self.data['objects'].items(): yield name, name, type_, docname, type_ + '-' + name, 1 def setup(app): app.add_domain(ELispDomain) ================================================ FILE: docs/usage.org ================================================ #+TITLE: Usage #+AUTHOR: David Landell #+EMAIL: david.landell@sunnyhill.email #+DATE: 2019 #+LANGUAGE: en #+OPTIONS: broken-links:auto, toc:nil, email:nil, num:nil, ^:nil, author:nil, date:nil #+INCLUDE: "utils.org" * Installation :PROPERTIES: :CUSTOM_ID: installation :END: This version of /rg/ is supported on GNU Emacs {{{elisp((rg-emacs-min-version))}}} or later on Linux systems. It might work on older Emacsen and on other systems but such configurations are not tested. Patches for other OS:es are welcome. :RUBRIC: MELPA :END: Packages are published on [[https://stable.melpa.org/#/rg][MELPA Stable]] and [[http://melpa.org/#/rg][MELPA]]. From within Emacs, run =M-x package-install [RET] rg [RET]= to install from those sources. Enable default key bindings: #+BEGIN_SRC elisp (rg-enable-default-bindings) #+END_SRC The above will enable the default key map {{{elisp_code((rg-default-keymap))}}} under the default prefix key {{{elisp_code((edmacro-format-keys rg-keymap-prefix))}}}. :RUBRIC: Manual :END: Releases can alternatively be downloaded from [[https://github.com/dajva/rg.el/releases/latest][GitHub]] and installed manually. Put all elisp files in main directory in your load path and =require= the package in your init file. #+BEGIN_SRC elisp (require 'rg) (rg-enable-default-bindings) #+END_SRC You would also need to make sure all package requirements are met. For this version these are: #+BEGIN_SRC elisp :results value raw :exports results (mapconcat (lambda (dep) (format "- *%s* _%s_" (car dep) (cdr dep))) rg-package-deps "\n") #+END_SRC /rg/ is using autoloaded symbols which means it's also possible to defer loading if you have autoloading setup. That usually comes out of the box with =package-install=. :RUBRIC: Lazy loading :END: For lazy loading you don't want to call directly into the package during startup. Use a setup similar to this instead: #+BEGIN_SRC elisp (global-set-key (kbd "C-c s") #'rg-menu) (with-eval-after-load 'rg ;; Your settings goes here. ) #+END_SRC If you don't want to use the transient menu interface, the following is needed to achieve lazy loading: #+BEGIN_SRC elisp ;; Workaround for emacs' lack of autoloaded keymaps. ;; This is essentially what use-package do. (defun rg-autoload-keymap () (interactive) (if (not (require 'rg nil t)) (user-error (format "Cannot load rg")) (let ((key-vec (this-command-keys-vector))) (global-set-key key-vec rg-global-map) (setq unread-command-events (mapcar (lambda (ev) (cons t ev)) (listify-key-sequence key-vec)))))) (global-set-key (kbd "C-c s") #'rg-autoload-keymap) (with-eval-after-load 'rg ;; Your settings goes here. ) #+END_SRC :RUBRIC: wgrep :END: This package use [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] for editing capabilities in the rg results buffer. No setup is needed. :RUBRIC: Isearch integration :END: Optional [[#isearch_search][isearch integration]] can be enabled to allow you to extend isearch to trigger ripgrep searching. Enable it in your configuration with: #+BEGIN_SRC elisp (require 'rg-isearch) (define-key isearch-mode-map "\M-sr" 'rg-isearch-menu) #+END_SRC For the evil use case where isearch-mode is exited after first search hit, users would also want to add the binding to the =global-map= or similar. :RUBRIC: Interaction with the /ripgrep/ configuration file :END: The /ripgrep/ binary allows using a [[https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#configuration-file][configuration file]] to set default values for command line flags. This package requires specific command line flags to function correctly and using a /ripgrep/ configuration may conflict with these requirements. Therefore the configuration file is ignored by default. This can be changed by the [[opt:rg-ignore-ripgreprc][rg-ignore-ripgreprc]] setting. :NOTE: Using the /ripgrep/ configuration file may break functionality of this package if you are not careful. :END: :RUBRIC: Interaction with xterm-color :END: This package is not written to be used with custom output colors provided by external packages like /xterm-color/. It relies on the color escape sequences so stripping these will break in unexpected ways. If you are using such packages, the advice is to hook such functionality into =compilation-filter-hook= instead of advising =compilation-filter=. * Searching :PROPERTIES: :CUSTOM_ID: searching :END: Searching is done by invoking one of the different frontend commands. This package is built around recursive search based on three parameters; a single /directory/, /file type/ filter, and a search /pattern/. These three parameters can interactively be selected or figured out automatically by the package, depending on which command that is used. The underlying /ripgrep/ binary has the file type filter concept built in. You have a high level of control over which files to search and which to ignore. This is partly what makes it so fast, ignoring uninteresting files. In addition to the base parameters there are a lot of options that control how a search is done. These are typically selected from the [[#the_menu][rg-menu]] interface. *** Case sensitivity Considering case when searching is an important feature of any search tool. This package gives you a lot of control over how to handle case sensitive and case insensitive search. It can be forced to *on* or *off* and set to *smart case*. The latter is similar to the /ripgrep/ =--smart-case= flag but is not using the flag directly. One thing to note about this is that the case insensitive setting controls the behavior when starting a new search. In the results buffer the setting is fixed to *on* or *off* but can be toggled easily with a key binding. See [[opt:rg-ignore-case][rg-ignore-case]] customization for the details of the configuration. *** Interactive search :PROPERTIES: :CUSTOM_ID: basic_search :END: Two commands implements fully interactive search, where all the base parameters are selected from the mini buffer. :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg 'rg-global-map) #+END_SRC This command prompts for /query/, /file type/ and /directory/ and tries to suggest reasonable default values. The /query/ string is interpreted as a regular expression. Default for /query/ is the thing at point and for /directory/ it is the current directory. If the type of the currently visited file is recognized, the corresponding [[#file_type_aliases][file type alias]] is suggested as the /file type/ parameter. Invoking this command with the /universal argument/ will trigger confirmation and potential modification of the [[#full_command_line_search][full command line]] that will invoke the /ripgrep/ binary. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-literal 'rg-global-map) #+END_SRC This command works in the same way as [[cmd:rg][rg]] but interprets the /query/ string literally and not as a regular expression. Invoking this command with the /universal argument/ will trigger confirmation and potential modification of the [[#full_command_line_search][full command line]] that will invoke the /ripgrep/ binary. :END: *** Project search :PROPERTIES: :CUSTOM_ID: project_search :END: A common scenario is to search through a whole project while visiting a file in the project. This essentially means identifying the project root and use that as the top /directory/ when invoking the /ripgrep/ binary. /rg/ supports several ways of identifying a project. Emacs' major project packages are supported including [[https://www.projectile.mx/en/latest/][projectile]], [[https://github.com/technomancy/find-file-in-project][find-file-in-project]] and builtin [[https://github.com/emacs-mirror/emacs/blob/master/lisp/progmodes/project.el][project.el]]. If none of these are used, the fallback is Emacs' =vc-backend=. :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-project 'rg-global-map) #+END_SRC Search in the current project. The /directory/ is selected via one of Emacs' project packages while /query string/ and /file type/ are prompted for. The /query string/ is interpreted as a regular expression. :END: *** Do what I mean :PROPERTIES: :CUSTOM_ID: do_what_i_mean :END: The *DWIM* family of search commands tries to be smart by figure out the search parameters from the context without prompting. Thanks to /ripgrep's/ speed, this allows for new ways of searching by invoking a dwim command and then /refine/ the search from the results buffer. These commands use the word (with the definition of word depending on context) under cursor as the /query/ string. The /file type/ parameter is taken from the type of the currently visited file. If the current file type can not be identified all file types known to /ripgrep/ are used. The fallback can be customized with [[opt:rg-default-alias-fallback]]. The /directory/ parameter varies between these commands. :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-dwim-project-dir 'rg-global-map) #+END_SRC Do a *DWIM* search in the current [[#project_search][project]]. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-dwim-current-dir 'rg-global-map) #+END_SRC Do a *DWIM* search in the current directory. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-dwim-current-file 'rg-global-map) #+END_SRC Do a *DWIM* search in the current file. The /current file/ in this context is actually a file /pattern/ exactly matching the current file name in a search starting from current directory. Most of the time this means a single file but if there are multiple files with the same name in a sub directory, those will be searched as well. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-dwim 'rg-global-map) #+END_SRC This command combines all the *DWIM* commands to one. The default search is in the [[cmd:rg-dwim-project-dir][project dir]]. With one /universal argument/ [[cmd:rg-dwim-current-dir][current directory]] is used and with double /universal arguments/ a [[cmd:rg-dwim-current-file][file search]] is done. :END: *** Isearch search :PROPERTIES: :CUSTOM_ID: isearch_search :END: Isearch integration is optional and need to be enabled explicitly in your emacs configuration. See [[#installation][installation]] for more info. This functionality is similar to emacs built in occur package but offers some additional choices for the search and provides the full functionality of the rg search result buffer. When enabled, the choosen binding can be used from isearch to trigger a menu for extending the isearch to do a ripgrep search in current file, current directory or current project. *** File type aliases :PROPERTIES: :CUSTOM_ID: file_type_aliases :END: File type aliases are used in /ripgrep/ to filter out the files to search in. The /ripgrep/ binary comes with a default set of aliases that can be extended or overridden from this package by customizing [[opt:rg-custom-type-aliases]]. An alias is a mapping between a name and a list of [[https://en.wikipedia.org/wiki/Glob_%2528programming%2529][glob patterns]] matching the files of interest. Selecting an alias when searching is done with completing read of the defined aliases. It is also possible to enter a custom glob pattern if there is no suitable alias defined for the file type. /rg/ defines some internal aliases: | Name | Meaning | |--------------+-------------------------------------------------------------------| | *all* | all defined types including [[opt:rg-custom-type-aliases][rg-custom-type-aliases]] | | *everything* | all files. No filtering on type is done. | | *custom* | used internally in this package for mapping custom glob patterns. | |--------------+-------------------------------------------------------------------| :WARNING: Do not use any of the internal aliases in [[opt:rg-custom-type-aliases][rg-custom-type-aliases]]. That would interfere with the package internal usage. :END: *** The menu :PROPERTIES: :CUSTOM_ID: the_menu :END: The global [[opt:rg-keymap-prefix][prefix key]] may be bound to a transient prefix command, which means that the key binding will popup a menu. This package is using the same popup menu backend called [[https://magit.vc/manual/transient][transient]] as the [[https://magit.vc/manual/magit][magit]] package. If you are familiar with magit this should feels like home. The menu is mostly interesting when you want to give specific command line flags to the /ripgrep/ binary. When you just want to do a quick search based on the defaults the menu basically acts as a normal keymap. Pressing the =rg-menu= [[opt:rg-keymap-prefix][prefix key]] will popup the menu where command line flags can be selected before triggering the wanted search function. The menu can be customized via the transient API as usual. This package contains some shortcuts to directly add a new command to the menu when defining the command via the [[func:rg-define-search]] macro. #+BEGIN_SRC elisp (rg-define-search rg-word :format literal :flags ("--word-regexp") :menu ("Custom" "w" "Word")) #+END_SRC The =:menu= keyword in the above invocation will trigger insertion of a new menu item bound to key =w= with description *Word*. The new menu item will be put under the *Custom* group. This group is not available in the original menu so it will be created. The menu can be triggered from the [[#results_buffer][results buffer]] with the =m= key. The commands in the menu differs, depending on from where it's triggered but the available options are the same. The menu does not show all options by default. The visible options can be controlled by the transient suffix levels documented [[https://magit.vc/manual/transient/Enabling-and-Disabling-Suffixes.html#Enabling-and-Disabling-Suffixes][here]]. To modify what is enabled at the default level 4 press =C-x l= to enter edit mode when the menu is visible. Then select the option by pressing the key sequence that activates the option and choose the level 4 for that option. It's also possible to use the transient edit mode for modifying the overall level of the menu to enable more options at once. * Results buffer :PROPERTIES: :CUSTOM_ID: results_buffer :END: The results of a search is shown in the results buffer. This buffer displays search parameters, the full command line and the output of the /ripgrep/ binary. It supports basic navigation between search results editing of the file contents directly from the search buffer and also modification of the current search. The results buffer is a modified /compilation/ buffer and some key bindings and functionality is inherited from the parent and from /grep mode/. *** Navigation Navigation works mostly as in grep/compilation buffers. :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'compilation-next-error 'rg-mode-map (kbd "M-n")) #+END_SRC Move to next line with a match. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'compilation-previous-error 'rg-mode-map (kbd "M-p")) #+END_SRC Move to previous line with a match. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'next-error-no-select 'rg-mode-map) #+END_SRC Move to next line with a match, show that file in other buffer and highlight the match. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'previous-error-no-select 'rg-mode-map) #+END_SRC Move to previous line with a match, show that file in other buffer and highlight the match. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-next-file 'rg-mode-map) #+END_SRC Move to next file header if the results is grouped under a file header (See [[opt:rg-group-result]]). :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-prev-file 'rg-mode-map) #+END_SRC Move to previous file header if the results is grouped under a file header (See [[opt:rg-group-result]]). :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'compilation-next-file 'rg-mode-map) #+END_SRC Move first match in previous file. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'compilation-previous-file 'rg-mode-map) #+END_SRC Move last match in previous file. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'compile-goto-error 'rg-mode-map) #+END_SRC Visit match in file. :END: If [[opt:rg-group-result][rg-group-result]] is enabled, the [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html][Imenu]] facility is configured to jump across files. *** Refine search From the results buffer it's easy to change the search parameters. Some bindings toggle a flag while others allow you to interactively change the [[#searching][base parameters]]. :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-rerun-change-dir 'rg-mode-map) #+END_SRC Interactively change search /directory/. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-rerun-change-files 'rg-mode-map) #+END_SRC Interactively change searched /file types/. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-rerun-change-literal 'rg-mode-map) #+END_SRC Interactively change /search string/ interpret the string literally. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-rerun-change-regexp 'rg-mode-map) #+END_SRC Interactively change /search string/ interpret the string as a regular expression. :END: :TIP: [[cmd:rg-rerun-change-regexp]] and [[cmd:rg-rerun-change-literal]] are used for switching between regular expression and literal search. So for quick switching between search modes with the same search string, just press the respective key and then =RET=. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-recompile 'rg-mode-map) #+END_SRC Rerun the current search without changing any parameters. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-rerun-toggle-case 'rg-mode-map) #+END_SRC Toggle case sensitivity of search. The state of the flag is shown in the *[case]* header field. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-rerun-toggle-ignore 'rg-mode-map) #+END_SRC Toggle if ignore files are respected. The state of the flag is shown in the *[ign]* header field. :END: :TIP: It is possible to create and bind your own toggle flags with the macro [[func:rg-define-toggle]]. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-menu 'rg-mode-map) #+END_SRC Fire up [[#the_menu][the menu]] for full access to options and flags. :END: *** Full command line search :PROPERTIES: :CUSTOM_ID: full_command_line_search :END: Some search commands (See [[cmd:rg]] or [[cmd:rg-literal]]) allow you to edit the final command line before invoking the search by giving a /universal argument/. This can be used to invoke features of the /ripgrep/ binary that is not supported in this package's interface. This could be specific flags, searching in multiple directories etc. :NOTE: Using full command line search will disable refinement of the search from the result buffer. :END: *** History navigation :PROPERTIES: :CUSTOM_ID: history_navigation :END: Each search result is stored in the search history, which is a per results buffer property. History can be navigated back and forward, the forward history is cleared when a new search is done. :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-back-history 'rg-mode-map) #+END_SRC Navigate back in history. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-forward-history 'rg-mode-map) #+END_SRC Navigate forward in history. :END: :TIP: The key bindings here are slightly inconvenient so invoking this via [[#the_menu][the menu]] by pressing =m b= and =m w= is more ergonomic. :END: *** Edit and apply (wgrep) :PROPERTIES: :CUSTOM_ID: edit_and_apply :END: The results buffer supports inline editing via the [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] package. This is setup automatically when /rg/ is loaded. :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'wgrep-change-to-wgrep-mode 'rg-mode-map) #+END_SRC Make the search results editable by enabling =wgrep= mode. When done press =C-c C-c= to commit your changes to the underlying files or =C-c C-k= to drop the changes. :END: * Search management :PROPERTIES: :CUSTOM_ID: search_management :END: The result buffer is named {{{elisp_code((format "\*%s\*" rg-buffer-name))}}} and /rg/ reuse the same result buffer for new searches. If you want to store a search while continuing doing new searches there are two ways of doing that. :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-save-search 'rg-mode-map) #+END_SRC Save the search buffer by renaming it to a unique new name. This is available both outside and inside a result buffer. Outside of the result buffer it's bound to {{{elisp_code((rg-key-for-command #'rg-save-search 'rg-global-map))}}}. If you want to keep all search buffers until manually killed you can use this snippet in your init file. #+BEGIN_SRC elisp (defadvice rg-run (before rg-run-before activate) (rg-save-search)) #+END_SRC :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-save-search-as-name 'rg-mode-map) #+END_SRC Save the search buffer and interactively give it a specific name. This is available both outside and inside a result buffer. Outside of the result buffer it's bound to {{{elisp_code((rg-key-for-command #'rg-save-search-as-name 'rg-global-map))}}}. :END: The default buffer name can be customized with [[opt:rg-buffer-name]]. This setting considers dir local variables and it's even possible to use a function to get a really dynamic setup. Having a lot of search buffers floating around can easily get messy. To help keeping this under control there is a search manager. The manager is simply a modified =ibuffer= that lists all the results buffers, shows some data about the searches and make it possible to kill of some unused etc. :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-list-searches 'rg-mode-map) #+END_SRC Open the search manager. This is available both in result buffer and globally bound to {{{elisp_code((rg-key-for-command #'rg-list-searches 'rg-global-map))}}}. :END: :COMMAND: #+BEGIN_SRC elisp :results value raw :exports results (rg-command-info #'rg-kill-saved-searches 'rg-global-map) #+END_SRC Kill all saved searches except for the one that matches [[opt:rg-buffer-name]]. This is available both in result buffer and globally bound to {{{elisp_code((rg-key-for-command #'rg-kill-saved-searches 'rg-global-map))}}}. :END: :WARNING: If you have a dynamic [[opt:rg-buffer-name]] setup, only one buffer that matches your current criteria (dir locals or project for instance) will be kept. So be careful when killing saved searches to avoid losing important search results. :END: * Multi line search :PROPERTIES: :CUSTOM_ID: multi_line_search :END: By default, ripgrep does matching per line. The =--multiline= flag can be used for enabling matching over multiple lines. This flag is available in the [[#the_menu][rg-menu]] as an option. The =--multiline= flag does not match new line characters with the =.= as one might expect though. A separate flag is used to allow this, =--multiline-dotall=. The casual user of multi line search commonly want this flag on by default so it's recommended to add this to [[opt:rg-command-line-flags]] to avoid having to trigger this flag manually from the menu. See the ripgrep manual page for more info about the multi line flags. ================================================ FILE: docs/utils.org ================================================ #+MACRO: elisp src_elisp[:results value raw :exports results]{$1} #+MACRO: elisp_code src_elisp[:results value :exports results]{$1} #+NAME: utils #+BEGIN_SRC elisp :results output drawer :exports none (require 'cl-lib) (require 'cus-edit) (require 'edmacro) (require 'help) (require 'package) (require 'rg) (defvar rg-package-version nil) (defvar rg-package-deps nil) (rg-enable-menu) (defun rg-package-info () (let ((file-name "../rg.el")) (with-temp-buffer (insert-file-contents file-name) (condition-case nil (package-buffer-info) (error "Invalid package"))))) (defun rg-extract-package-info () (let ((desc (rg-package-info))) (setf rg-package-version (package-version-join (package-desc-version desc))) (dolist (req (package-desc-reqs desc)) (push (cons (car req) (package-version-join (cadr req))) rg-package-deps)))) (defun rg-package-deps () (when (not rg-package-deps) (rg-extract-package-info)) rg-package-deps) (defun rg-package-version () (when (not rg-package-version) (rg-extract-package-info)) rg-package-version) (defun rg-emacs-min-version () (cdr (assq 'emacs (rg-package-deps)))) (defun rg-default-keymap () (if rg-use-transient-menu "rg-menu" "rg-global-map")) (defun rg-custom-info (sym &optional default) (let* ((type (custom-variable-type sym)) (value (or default (if (and (consp type) (eq (car type) 'key-sequence)) (edmacro-format-keys (symbol-value sym)) (symbol-value sym))))) (format "%s\n:default: %S" sym value))) (defun rg-face-info (face) (cl-assert (facep face)) (format "%s\n:default: %s" face (face-attribute face :inherit))) (defun rg-key-for-command (sym map &optional wanted-key) (cl-assert (commandp sym)) (when-let ((keys (where-is-internal sym (symbol-value map)))) (let* ((key (edmacro-format-keys (or (and wanted-key (cl-find wanted-key keys :test 'equal)) (car keys)))) (prefix (when (and key (eq map 'rg-global-map)) (edmacro-format-keys rg-keymap-prefix)))) (if prefix (format "%s %s" prefix key) (format "%s" key))))) (defun rg-command-info (sym map &optional wanted-key) (if-let ((key-sequence (rg-key-for-command sym map wanted-key))) (format "%s\n:kbd: %s" sym key-sequence) (format "%s\n" sym))) (defun rg-function-info (sym) (cl-assert (fboundp sym)) (let ((args (mapconcat #'symbol-name (help-function-arglist sym) " "))) (format "(%s %s)\n" sym args))) #+END_SRC call_utils() #+BEGIN_EXPORT rst .. default-domain:: el #+END_EXPORT ================================================ FILE: rg-header.el ================================================ ;;; rg-header.el --- Header line for rg-mode -*- lexical-binding: t; -*- ;; Copyright (C) 2018 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;; Header line format for rg-mode result buffer. ;;; Code: (require 'mouse) (declare-function rg-cur-search-pattern "rg-result.el") ;; Customization (defcustom rg-header-max-search-string-length nil "The max line length of header line search string item." :type '(choice (const :tag "Don't truncate" nil) (number :tag "The max width")) :group 'rg) ;; Faces (defface rg-toggle-on-face '((t :inherit rg-file-tag-face)) "face for toggle \"on\" text in header." :group 'rg-face) (defface rg-toggle-off-face '((t :inherit rg-error-face)) "face for toggle \"off\" text in header." :group 'rg-face) (defface rg-literal-face '((t :inherit rg-filename-face)) "face for literal label in header." :group 'rg-face) (defface rg-regexp-face '((t :inherit compilation-line-number)) "face for regexp label in header." :group 'rg-face) ;; Defuns (defun rg-header-render-label (labelform) "Return a fontified header label. LABELFORM is either a string to render or a form where the `car' is a conditional and the two following items are then and else specs. Specs are lists where the the `car' is the labels string and the `cadr' is font to use for that string." (list '(:propertize "[" font-lock-face (header-line bold)) (cond ((stringp labelform) `(:propertize ,labelform font-lock-face (header-line bold))) ((listp labelform) (let* ((condition (nth 0 labelform)) (then (nth 1 labelform)) (else (nth 2 labelform))) `(:eval (if ,condition (propertize ,(nth 0 then) 'font-lock-face '(,(nth 1 then) header-line bold)) (propertize ,(nth 0 else) 'font-lock-face '(,(nth 1 else) header-line bold)))))) (t (error "Not a string or list"))) '(:propertize "]" font-lock-face (header-line bold)) '(": "))) (defun rg-header-render-toggle (on) "Return a fontified toggle symbol. If ON is non nil, render \"on\" string, otherwise render \"off\" string." `(:eval (let* ((on ,on) (value (if on "on " "off")) (face (if on 'rg-toggle-on-face 'rg-toggle-off-face))) (propertize value 'font-lock-face `(bold ,face))))) (defun rg-header-mouse-action (command help &rest items) "Add a keymap with mouse click action for COMMAND. When hoovering HELP is shown as a tooltip. ITEMS is the header line items that the map will be applied to." (let ((map (make-sparse-keymap))) (define-key map (if (or (> emacs-major-version 30) (and (= emacs-major-version 30) (>= emacs-minor-version 2))) [header-line mouse-1] [header-line mouse-2]) (lambda (click) (interactive "e") (mouse-select-window click) (call-interactively command))) `(:propertize ,items mouse-face header-line-highlight help-echo ,help keymap ,map))) (defun rg-header-truncate-search-pattern (search) "Truncate SEARCH if it exceeds `rg-header-max-search-string-length'." (if (rg-header-truncates-p search) (truncate-string-to-width search rg-header-max-search-string-length 0 nil t) search)) (defun rg-header-truncates-p (search) "Verify that SEARCH would be truncated." (and (numberp rg-header-max-search-string-length) (< rg-header-max-search-string-length (length search)))) (defun rg-header-search-help () "Get the search help for the current buffer." (let ((pattern (rg-cur-search-pattern))) (if (rg-header-truncates-p pattern) (concat "Change search string: " pattern) "Change search string"))) ;; Use full-command here to avoid dependency on rg-search ;; struct. Should be properly fixed. (defun rg-create-header-line (search full-command) "Create the header line for SEARCH. If FULL-COMMAND specifies if the full command line search was done." (let ((itemspace " ")) (setq header-line-format (if full-command (list (rg-header-render-label "command line") "no refinement") (list (rg-header-mouse-action 'rg-rerun-toggle-rexexp-literal "Toggle literal/regexp" (rg-header-render-label `((rg-search-literal ,search) ("literal" rg-literal-face) ("regexp" rg-regexp-face)))) (rg-header-mouse-action 'rg-rerun-change-query (rg-header-search-help) `(:eval (rg-header-truncate-search-pattern (rg-search-pattern ,search)))) itemspace (rg-header-render-label "files") (rg-header-mouse-action 'rg-rerun-change-files "Change file types" `(:eval (rg-search-files ,search))) itemspace (rg-header-render-label "case") (rg-header-mouse-action 'rg-rerun-toggle-case "Toggle case" (rg-header-render-toggle `(not (member "-i" (rg-search-flags ,search))))) itemspace (rg-header-render-label "ign") (rg-header-mouse-action 'rg-rerun-toggle-ignore "Toggle ignore" (rg-header-render-toggle `(not (member "--no-ignore" (rg-search-flags ,search))))) itemspace (rg-header-render-label "hits") '(:eval (format "%d" rg-hit-count))))))) (provide 'rg-header) ;;; rg-header.el ends here ================================================ FILE: rg-history.el ================================================ ;;; rg-history.el --- History navigation in rg.el -*- lexical-binding: t; -*- ;; Copyright (C) 2018 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: (require 'cl-lib) (require 'subr-x) (cl-defstruct (rg-history (:constructor rg-history-create) (:copier nil)) past ; list of searches for backward navigation present ; current search future) ; list of searches for forward navigation (defun rg-history-push (item instance) "Push a new ITEM to the rg-history INSTANCE." (when-let (present (rg-history-present instance)) (push present (rg-history-past instance))) (setf (rg-history-present instance) item) (setf (rg-history-future instance) nil)) (defun rg-history-back (instance) "Move back in the rg-history INSTANCE. Return the new current search." (when-let (prev (pop (rg-history-past instance))) (push (rg-history-present instance) (rg-history-future instance)) (setf (rg-history-present instance) prev))) (defun rg-history-forward (instance) "Move forward in the rg-history INSTANCE. Return the new current search." (when-let (next (pop (rg-history-future instance))) (push (rg-history-present instance) (rg-history-past instance)) (setf (rg-history-present instance) next))) (provide 'rg-history) ;;; rg-history.el ends here ================================================ FILE: rg-ibuffer.el ================================================ ;;; rg-ibuffer.el --- List search buffers for rg-mode -*- lexical-binding: t; -*- ;; Copyright (C) 2018 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;; Use ibuffer to list active searches. ;;; Code: (require 'ibuf-ext) (require 'ibuffer) (require 'rg-result) (defconst rg-search-list-buffer-name "*Searches rg*") (defun rg-ibuffer-search-updated() "This function is executed when search list buffer is updated." (when-let ((list-buffer (get-buffer rg-search-list-buffer-name))) (ignore-errors (with-current-buffer list-buffer (ibuffer-update nil t))))) (defun rg-ibuffer-buffer-killed () "Function run when the search list buffer is killed." (remove-hook 'rg-filter-hook #'rg-ibuffer-search-updated)) (define-ibuffer-column rg-search-term (:name "Search" :props ('face 'rg-match-face)) (ignore mark) (if rg-cur-search (rg-search-pattern rg-cur-search) "N/A")) (define-ibuffer-column rg-hit-count (:name "Hits") (ignore mark) (number-to-string rg-hit-count)) (define-ibuffer-column rg-search-dir (:name "Directory" :props ('face 'rg-filename-face)) (ignore mark) (if rg-cur-search (rg-search-dir rg-cur-search) "N/A")) (define-ibuffer-column rg-file-types (:name "Type") (ignore mark) (if rg-cur-search (rg-search-files rg-cur-search) "N/A")) ;;;###autoload (defun rg-list-searches () "List all `rg-mode' buffers in `ibuffer'." (interactive) (let ((other-window (equal current-prefix-arg '(4)))) (ibuffer other-window rg-search-list-buffer-name '((mode . rg-mode)) nil nil nil '((mark " " (name 16 16 nil :elide) " " (rg-search-term 18 18 nil :elide) " " (rg-hit-count 7 7) " " (rg-file-types 7 7) " " (process 10 10) (rg-search-dir 20 -1 nil :elide) " "))) (add-hook 'rg-filter-hook #'rg-ibuffer-search-updated) (with-current-buffer rg-search-list-buffer-name (ibuffer-auto-mode) (set (make-local-variable 'ibuffer-use-header-line) nil) (ibuffer-clear-filter-groups) (add-hook 'kill-buffer-hook #'rg-ibuffer-buffer-killed nil t)))) (provide 'rg-ibuffer) ;;; rg-ibuffer.el ends here ================================================ FILE: rg-info-hack.el ================================================ ;;; rg-info-hack.el --- Make sure rg.el info documentation looks good -*- lexical-binding: t; -*- ;; Copyright (C) 2019 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: (require 'info) (define-advice info-insert-file-contents (:after (&rest _) sphinx-info-insert-file-contents) "Hack to hide \"See\" in references. This makes `Info-hide-note-references' buffer-local and automatically set to `hide' iff it can be determined that this file is the rg.el documentation and was created from a Texinfo file generated by Sphinx." (set (make-local-variable 'Info-hide-note-references) (default-value 'Info-hide-note-references)) (save-excursion (save-restriction (widen) (goto-char (point-min)) (when (and (search-forward "* RG:" nil t) (search-forward "Generated by Sphinx" (save-excursion (search-forward "\x1f" nil t)) t)) (set (make-local-variable 'Info-hide-note-references) 'hide))))) (provide 'rg-info-hack) ;;; rg-info-hack.el ends here ================================================ FILE: rg-isearch.el ================================================ ;;; rg-isearch.el --- rg integration into isearch -*- lexical-binding: t; -*- ;; Copyright (C) 2020 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;; This file provides rg isearch integration. ;; Add this to your configuration to bind chosen key in isearch minibuffer: ;; ;; (define-key isearch-mode-map "\M-sr" 'rg-isearch-menu) ;; ;; See info node `(rgel)Isearch search' for documentation or online at https://rgel.readthedocs.io/. ;;; Code: (require 'rg) (defun rg-get-isearch-string () "Extract the isearch string from the last isearch." ;; Mimic what `isearch-occur' does. (or (cond ;; We can't handle arbitrary functions so just use the string. ((functionp isearch-regexp-function) isearch-string) ;; isearch-occur handles this as word search so insert word boundary (isearch-regexp-function (concat "\b" isearch-string "\b")) (isearch-regexp isearch-string) (t isearch-string)) "")) ;;;###autoload (autoload 'rg-isearch-current-file "rg-isearch.el" "" t) (rg-define-search rg-isearch-current-file "Run ripgrep on current file searching for latest isearch string." :dir current :query (rg-get-isearch-string) :format literal :files (rg-get-buffer-file-name) :dir current) ;;;###autoload (autoload 'rg-isearch-current-dir "rg-isearch.el" "" t) (rg-define-search rg-isearch-current-dir "Run ripgrep in current directory searching for latest isearch string in files matching the current file type." :query (rg-get-isearch-string) :format literal :files current :dir current) ;;;###autoload (autoload 'rg-isearch-project "rg-isearch.el" "" t) (rg-define-search rg-isearch-project "Run ripgrep in current project searching for latest isearch string in files matching the current file type." :dir project :query (rg-get-isearch-string) :format literal :files current) ;;;###autoload (autoload 'rg-isearch-menu "rg-isearch.el" "" t) (transient-define-prefix rg-isearch-menu () "Show menu for rg isearch commands." [:description "Search with ripgrep" (3 "f" "File" rg-isearch-current-file--transient) (3 "d" "Dir" rg-isearch-current-dir--transient) (3 "p" "Project" rg-isearch-project--transient)]) (provide 'rg-isearch) ;;; rg-isearch.el ends here ================================================ FILE: rg-menu.el ================================================ ;;; rg-menu.el --- Menu interface for rg -*- lexical-binding: t; -*- ;; Copyright (C) 2017 David Landell ;; ;; Author: David Landell ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;; Support a menu interface for rg.el based on transient. ;;; Code: (require 'rg-result) (require 'seq) (require 'transient) (require 'cl-lib) ;; Forward declarations (declare-function rg "rg.el") (declare-function rg-literal "rg.el") (declare-function rg-dwim "rg.el") (declare-function rg-project "rg.el") (declare-function rg-dwim-current-dir "rg.el") (declare-function rg-dwim-current-file "rg.el") (declare-function rg-list-searches "rg.el") (declare-function rg-save-search "rg.el") (declare-function rg-save-search-as-name "rg.el") (defvar rg-keymap-prefix) (defvar rg-command-line-flags-function) (defun rg-menu-search-initial-value () "Provide initial value for `rg-menu' transient command." (when (eq major-mode 'rg-mode) (rg-search-flags rg-cur-search))) (eval-and-compile (defun rg-menu-create-search-body (func) "Call FUNC from search menu with the flags set in the transient menu." `((let* ((transient-flags (transient-args transient-current-command)) (function-flags (funcall rg-command-line-flags-function nil)) (rg-command-line-flags-function (lambda (flags) (append function-flags transient-flags flags)))) (call-interactively #',func)))) (defun rg-menu-create-rerun-body (func) "Call FUNC from rerun menu with flags extracted with ARGFUNC. If INTERACTIVE is non nil, call func interactively, otherwise call it regularly." `((let ((transient-flags (transient-args transient-current-command)) (current-flags (rg-search-flags rg-cur-search))) (setf (rg-search-flags rg-cur-search) (seq-uniq (append current-flags transient-flags)))) (if (commandp #',func) (call-interactively #',func) (,func)))) (defun rg-menu-assemble-transient-wrapper (func body) "Create a defun with name `FUNC--transient' with BODY." (let ((func-name (concat (symbol-name func) "--transient")) (doc-string (format "Transient wrapper around `%s' for `rg-menu'." (symbol-name func)))) `(progn (defun ,(intern func-name) () (interactive) ,@body) (put ',(intern func-name) 'function-documentation ;; quote to defer evalutation until func is available '(concat ,doc-string "\n\n" (documentation ',func))))))) (defmacro rg-menu-wrap-transient-search (func) "Wrap FUNC with a command that apply transient arguments to the search. FUNC is an initial search function and not a rerun function." (rg-menu-assemble-transient-wrapper func (rg-menu-create-search-body func))) (defmacro rg-menu-wrap-transient-rerun (func) "Wrap FUNC with a command that apply transient arguments to the rerun. FUNC is an rerun function invoked from an `rg-mode' buffer." (rg-menu-assemble-transient-wrapper func (rg-menu-create-rerun-body func))) ;; Regular rg entries (rg-menu-wrap-transient-search rg) (rg-menu-wrap-transient-search rg-literal) (rg-menu-wrap-transient-search rg-dwim) (rg-menu-wrap-transient-search rg-project) (rg-menu-wrap-transient-search rg-dwim-current-dir) (rg-menu-wrap-transient-search rg-dwim-current-file) (rg-menu-wrap-transient-search rg-list-searches) (rg-menu-wrap-transient-search rg-save-search) (rg-menu-wrap-transient-search rg-save-search-as-name) (rg-menu-wrap-transient-search rg-back-history) (rg-menu-wrap-transient-search rg-forward-history) ;; Rerun rg entries (rg-menu-wrap-transient-rerun rg-rerun) (rg-menu-wrap-transient-rerun rg-rerun-change-regexp) (rg-menu-wrap-transient-rerun rg-rerun-change-literal) (rg-menu-wrap-transient-rerun rg-rerun-change-files) (rg-menu-wrap-transient-rerun rg-rerun-change-dir) ;;;###autoload (autoload 'rg-menu "rg-menu.el" "" t) (transient-define-prefix rg-menu () "Show menu buffer for rg commands." :man-page "rg" :value 'rg-menu-search-initial-value ["Switches" (3 "-h" "Search hidden files" "--hidden") (6 "-a" "Search binary files" "--text") (4 "-z" "Search zipped files" "--search-zip") (4 "-v" "Invert match" "--invert-match") (4 "-U" "Multi line" "--multiline") (4 "-." "Dot all" "--multiline-dotall") (3 "-w" "Search words" "--word-regexp") (5 "-x" "Search lines" "--line-regexp") (5 "-P" "Use PCRE2 regexps" "--pcre2") (4 "-1" "Don't cross file system" "--one-file-system") (6 "-L" "Follow symlinks" "--follow") (3 "-n" "Override ignore files" "--no-ignore")] ["Options" (3 "-C" "Show context" "--context=") (6 "-B" "Show context before" "--before-context=") (6 "-A" "Show context after" "--after-context=") (4 "-M" "Omit long lines" "--max-columns=") (4 "-m" "Max matches per file" "--max-count=") (6 "-s" "Ignore large files" "--max-filesize=") (4 "-g" "Filter files glob" "--glob=") (6 "-i" "Filter files glob (no case)" "--iglob=") (4 "-T" "Exclude files types" "--type-not=") (5 "-S" "Sort result" "--sort=") (5 "-R" "Reverse sort result" "--sortr=") (6 "-E" "Force encoding" "--encoding=") (6 "-r" "Replace match" "--replace=")] [[:if-not-mode rg-mode :description "Search" (3 "d" "Dwim project" rg-dwim--transient) (4 "c" "Dwim current directory" rg-dwim-current-dir--transient) (4 "f" "Dwim current file" rg-dwim-current-file--transient) (3 "r" "Regex" rg--transient) (3 "t" "Literal" rg-literal--transient) (3 "p" "Project" rg-project--transient)] [:if-mode rg-mode :description "Rerun" (3 "g" "Go" rg-rerun--transient) (3 "r" "Change regex" rg-rerun-change-regexp--transient) (3 "t" "Change literal" rg-rerun-change-literal--transient) (3 "f" "Change files" rg-rerun-change-files--transient) (3 "d" "Change directory" rg-rerun-change-dir--transient)] [:description "Manage" (4 "l" "List" rg-list-searches--transient) (4 "s" "Save" rg-save-search--transient) (4 "S" "Save as name" rg-save-search-as-name--transient)] [:if-mode rg-mode :description "History" (3 "b" "Back" rg-back-history--transient) (3 "w" "Forward" rg-forward-history--transient)]]) (eval-and-compile (defun rg-menu-group-at-location-p (desc loc) "Check if group with description DESC exist at location LOC." (let ((suffix (transient-get-suffix 'rg-menu loc))) (and suffix (string= (plist-get (seq-elt suffix 1) :description) desc)))) (defun rg-menu-get-loc-of-group (desc) "Find the location of the group with description DESC. Returns the transient location coordinates for the group or NIL if not found." (let ((loc (list 2 0))) (ignore-errors (while (not (rg-menu-group-at-location-p desc loc)) (cl-incf (cl-second loc))) loc))) (defun rg-menu-transient-insert (group key description command) "Insert a new suffix into the rg-menu under GROUP. GROUP is the description of an existing group. If the group does not exist A new group will be created. KEY and DESCRIPTION defines the binding and description of the new menu entry. COMMAND is a transient wrapped command." (unless (stringp group) (user-error "'%S' is not a string" group)) (unless (or (stringp key) (vectorp key)) (user-error "'%S' should be a key description string or a key vector" key)) (unless (stringp description) (user-error "'%S' is not a string" description)) (if-let (group-loc (rg-menu-get-loc-of-group group)) (transient-append-suffix 'rg-menu (append group-loc '(-1)) (list 3 key description command)) (transient-append-suffix 'rg-menu '(-1 -1) (vector group (list 3 key description command)))))) ;;;###autoload (defun rg-enable-menu (&optional prefix) "Bind `rg-menu' to PREFIX key. If prefix is not supplied `rg-keymap-prefix' is used." (interactive) (setq prefix (or prefix rg-keymap-prefix)) (when prefix (global-set-key prefix #'rg-menu) ;; If it's already bound it might have been rebound so keep that ;; instead of overriding. (unless (where-is-internal #'rg-menu (list rg-mode-map) t) (define-key rg-mode-map "m" #'rg-menu)))) (provide 'rg-menu) ;;; rg-menu.el ends here ================================================ FILE: rg-result.el ================================================ ;;; rg-result.el ---- Result buffer implementation for rg.el. -*- lexical-binding: t; -*- ;; Copyright (C) 2018 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: (require 'cl-lib) (require 'grep) (require 'mouse) (require 'rg-header) (require 'rg-history) (require 'subr-x) (require 'wgrep-rg) ;; Forward declarations. (declare-function rg-build-command "rg.el") (declare-function rg-get-type-aliases "rg.el") (declare-function rg-list-searches "rg.el") (declare-function rg-read-pattern "rg.el") (declare-function rg-menu "rg-menu.el") ;; Customizations/public vars (defcustom rg-use-transient-menu t "Use transient menu instead of a the global keymap." :type 'boolean :group 'rg :package-version '(rg . "2.0.0")) (defcustom rg-show-columns nil "If t, show the columns of the matches in the output buffer." :type 'boolean :group 'rg) (defcustom rg-group-result t "Group matches in the same file together. If nil, the file name is repeated at the beginning of every match line." :type 'boolean :group 'rg :package-version '(rg . "2.0.0")) (defcustom rg-show-header t "Show header in results buffer if non nil." :type 'boolean :group 'rg) (defcustom rg-hide-command t "Hide most of rg command line when non nil." :type 'boolean :group 'rg) (defcustom rg-align-position-numbers t "If non nil, alignment of line and column numbers is turned on." :type 'boolean :group 'rg :package-version '(rg . "2.0.0")) (defcustom rg-align-line-number-field-length 4 "Field length of aligned line numbers." :type 'integer :group 'rg) (defcustom rg-align-column-number-field-length 3 "Field length of aligned column numbers." :type 'integer :group 'rg) (defcustom rg-align-line-column-separator " " "Separator used between line and column numbers. Depends on `rg-show-columns'. Default is ':'." :type 'string :group 'rg :package-version '(rg . "2.0.0")) (defcustom rg-align-position-content-separator " " "Separator used between position numbers and the matching file content. Default is ':'." :type 'string :group 'rg :package-version '(rg . "2.0.0")) (defvar rg-filter-hook nil "Hook for new content in the rg buffer. This hook is called every time the rg buffer has been updated with new content and filtered through the `rg-filter' function.") ;; Faces (defgroup rg-face nil "Settings for rg faces." :group 'rg) (defface rg-match-face `((t :inherit ,grep-match-face)) "Face for match highlight." :group 'rg-face) (defface rg-error-face `((t :inherit ,grep-error-face)) "Face for error." :group 'rg-face) (defface rg-context-face `((t :inherit ,grep-context-face)) "Face for context lines." :group 'rg-face) (defface rg-info-face '((t :inherit compilation-info)) "Face for info." :group 'rg-face) (defface rg-warning-face '((t :inherit compilation-warning)) "Face for warning." :group 'rg-face) (defface rg-filename-face '((t :inherit rg-info-face)) "Face for filename." :group 'rg-face) (defface rg-file-tag-face '((t :inherit rg-info-face)) "Face for file tag in grouped layout." :group 'rg-face) (defface rg-line-number-face '((t :inherit compilation-line-number)) "Face for line numbers." :group 'rg-face) (defface rg-column-number-face '((t :inherit compilation-column-number)) "Face for column numbers." :group 'rg-face) (defface rg-match-position-face '((t :inherit default)) "Face that is being appended to file positions. This is the start of each matching line. This includes line number and, depending on configuration, column number and file name." :group 'rg-face) ;; Internal vars and structs (cl-defstruct (rg-search (:constructor rg-search-create) (:constructor rg-search-new (pattern files dir)) (:copier rg-search-copy)) pattern ; search pattern files ; files to search dir ; base directory full-command ; full-command (t or nil) literal ; literal patterh (t or nil) flags) ; search specific flags (defvar-local rg-cur-search nil "Stores parameters of last search. Becomes buffer local in `rg-mode' buffers.") (put 'rg-cur-search 'permanent-local t) (defvar-local rg-search-history nil "Stores the search history per rg-mode buffer.") (put 'rg-search-history 'permanent-local t) (defvar-local rg-hit-count 0 "Stores number of hits in a search.") (defvar-local rg-match-positions nil "Stores position of matches in a search. Each element is consists by (match-beginning-marker . match-string-length).") (defvar-local rg-recompile nil "Is `recompile' in progress or `compile-start'.") (defconst rg-mode-font-lock-keywords '(;; Command output lines. (": \\(.+\\): \\(?:Permission denied\\|No such \\(?:file or directory\\|device or address\\)\\)$" 1 'rg-error-face) ("^rg[/a-zA-z]* started.*" (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t)) ("^rg[/a-zA-z]* finished \\(?:(\\([0-9]+ matches found\\))\\|with \\(no matches found\\)\\).*" (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) (1 'rg-info-face nil t) (2 'rg-warning-face nil t)) ("^rg[/a-zA-z]* \\(exited abnormally\\|interrupt\\|killed\\|terminated\\)\\(?:.*with code \\([0-9]+\\)\\)?.*" (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) (1 'rg-error-face) (2 'rg-error-face nil t)) ;; "filename-linenumber-" or "linenumber-" format is used for ;; context lines in rg ("^ *\\(?:.+?-\\)?[0-9]+-.*\n" (0 'rg-context-face)) ("^.*rg \\(--color=always .*$\\)" (0 rg-command-line-properties) (1 (rg-hidden-command-line-properties))) ("^-\\*- mode: rg; default-directory: \"\\(.*\\)\" -\\*-$" (1 rg-directory-properties)) ("\\[\\.\\.\\. [0-9]+ more match\\(es\\)*\\]$" (0 'rg-info-face nil t)))) (defvar rg-menu-map (let ((map (make-sparse-keymap "RipGrep"))) (define-key map [rg-toggle-command-hiding] '(menu-item "Toggle command visibility" rg-toggle-command-hiding :help "Toggle showing verbose command options")) (define-key map [rg-enable-wgrep] '(menu-item "Edit buffer" wgrep-change-to-wgrep-mode :help "Edit buffer and save changes.")) (define-key map [rg-save-search] '(menu-item "Save search" rg-save-search-as-name :help "Save current search.")) (define-key map [rg-search-list] '(menu-item "List searches" rg-list-searches :help "List all ripgrep search buffers.")) (define-key map [rg-change-dir] '(menu-item "Change dir" rg-rerun-change-dir :help "Rerun search in another directory.")) (define-key map [rg-change-files] '(menu-item "Change file type" rg-rerun-change-files :help "Rerun search on other file types.")) (define-key map [rg-change-regexp] '(menu-item "Change regexp" rg-rerun-change-regexp :help "Run regexp search with changed pattern.")) (define-key map [rg-change-literal] '(menu-item "Change literal" rg-rerun-change-literal :help "Run literal search with changed search string.")) (define-key map [rg-kill-compilation] '(menu-item "Kill Ripgrep" kill-compilation :help "Kill the currently running rg process")) (define-key map [rg-another] '(menu-item "Another search..." (lambda () (interactive) (rg-save-search) (call-interactively 'rg)) :help "Save current search results and start a new search.")) (define-key map [rg-recompile] '(menu-item "Go..." rg-recompile :help "Rerun search")) map)) (defvar rg-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map compilation-minor-mode-map) (define-key map " " 'scroll-up-command) (define-key map [?\S-\ ] 'scroll-down-command) (define-key map "\^?" 'scroll-down-command) (define-key map "\C-c\C-f" 'next-error-follow-minor-mode) (define-key map "\r" 'compile-goto-error) ;; ? (define-key map "n" 'next-error-no-select) (define-key map "p" 'previous-error-no-select) (define-key map "{" 'compilation-previous-file) (define-key map "}" 'compilation-next-file) (define-key map "\t" 'compilation-next-error) (define-key map [backtab] 'compilation-previous-error) (define-key map "c" 'rg-rerun-toggle-case) (define-key map "d" 'rg-rerun-change-dir) (define-key map "f" 'rg-rerun-change-files) (define-key map "g" 'rg-recompile) (define-key map "i" 'rg-rerun-toggle-ignore) (define-key map "L" 'rg-list-searches) (define-key map "r" 'rg-rerun-change-regexp) (define-key map "s" 'rg-save-search) (define-key map "S" 'rg-save-search-as-name) (define-key map "t" 'rg-rerun-change-literal) (define-key map "e" 'wgrep-change-to-wgrep-mode) (define-key map "\M-N" 'rg-next-file) (define-key map "\M-P" 'rg-prev-file) (define-key map "\C-c>" 'rg-forward-history) (define-key map "\C-c<" 'rg-back-history) (when rg-use-transient-menu (define-key map "m" #'rg-menu)) ;; Set up the menu-bar (define-key map [menu-bar rg] (cons "RipGrep" rg-menu-map)) map) "The keymap for `rg-mode'.") (defvar rg-ellipsis (if (char-displayable-p ?…) "[…]" "[...]") "Used when hiding command line.") (defvar rg-finish-functions '() "Functions to call when a ripgrep process finishes. Each function is called with two arguments: the compilation buffer, and a string describing how the process finished.") ;; Defuns (defun rg-create-mouse-map (command) "Create a mouse-map for the COMMAND. This makes sure point moves to click and that the clicked window is selected." (let ((map (make-sparse-keymap))) (define-key map [down-mouse-2] 'mouse-set-point) (define-key map [mouse-2] command) (define-key map "\C-m" command) map)) ;; This solution was mostly copied from emacs grep.el and adjusted to ;; be more usable. (defvar rg-command-line-properties (let ((map (rg-create-mouse-map 'rg-toggle-command-hiding))) `(face rg-context-face mouse-face highlight help-echo "RET, mouse-2: show unabbreviated command" keymap ,map)) "Properties for graying out and keymap for hiding command line.") (defun rg-hidden-command-line-properties () "Return properties of button-like ellipsis on part of rg command line." (append '(face nil rg-command-hidden-part t) (when rg-hide-command `(display ,rg-ellipsis)))) (defun rg-toggle-command-hiding () "Toggle showing the hidden part of rg command line." (interactive) (with-silent-modifications (let* ((beg (next-single-property-change (point-min) 'rg-command-hidden-part)) (end (when beg (next-single-property-change beg 'rg-command-hidden-part)))) (if end (if (get-text-property beg 'display) (remove-list-of-text-properties beg end '(display)) (add-text-properties beg end `(display ,rg-ellipsis))) (user-error "No abbreviated part to hide/show"))))) (defvar rg-directory-properties (let ((map (rg-create-mouse-map 'rg-rerun-change-dir))) `(face rg-filename-face mouse-face highlight help-echo "RET, mouse-2: Change search directory" keymap ,map)) "Properties for `default-directory' in header.") (defun rg-list-toggle (elem list) "Remove ELEM from LIST if present or add it if not present. Returns the new list." (if (member elem list) (delete elem list) (push elem list))) (defun rg-process-setup () "Setup compilation variables and buffer for `rg'. Set up `compilation-exit-message-function'." (set (make-local-variable 'compilation-exit-message-function) (lambda (status code msg) (if (eq status 'exit) ;; This relies on the fact that `compilation-start' ;; sets buffer-modified to nil before running the command, ;; so the buffer is still unmodified if there is no output. (cond ((and (zerop code) (buffer-modified-p)) `(,(format "finished (%d matches found)\n" rg-hit-count) . "matched")) ((not (buffer-modified-p)) '("finished with no matches found\n" . "no match")) (t (cons msg code))) (cons msg code))))) (defun rg-prepend-space (text length) "Prepend TEXT with LENGTH number of spaces." (let ((space-count (- length (length text)))) (concat (when (> space-count 0) (make-string space-count ?\s)) text))) (defun rg-perform-position-numbers-alignment (line-number &optional column-number context-marker ) "Return aligned LINE-NUMBER, COLUMN-NUMBER and CONTEXT-MARKER." (let* ((line-col-separator (or rg-align-line-column-separator ":")) (pos-content-separator (or rg-align-position-content-separator ":")) (pos-content-separator (if rg-show-columns pos-content-separator (propertize pos-content-separator 'invisible t))) (line-number-width (if (and rg-show-columns context-marker) ;; Context lines should be aligned to column numbers (+ rg-align-line-number-field-length (1+ rg-align-column-number-field-length)) rg-align-line-number-field-length)) (column-number (when column-number (if rg-show-columns column-number (propertize column-number 'invisible t))))) (cl-assert (if column-number (not context-marker) context-marker)) (concat (rg-prepend-space line-number line-number-width) (if column-number (concat (if rg-show-columns line-col-separator pos-content-separator) (rg-prepend-space column-number rg-align-column-number-field-length) pos-content-separator) context-marker)))) (defun rg-format-line-and-column-numbers (beg end) "Align numbers in region defined by BEG and END." (goto-char beg) (while (re-search-forward "^\033\\[[0]*m\033\\[32m\\([0-9]*?\\)\033\\[[0]*m\\(:\\|-\\)\\(?:\033\\[[0]*m\\([0-9]*?\\)\033\\[[0]*m:\\)?" end 1) (let* ((line-match (match-string 1)) (col-separator-match (match-string 2)) (context-marker (when (equal col-separator-match "-") col-separator-match)) (column-match (match-string 3))) (cond (rg-align-position-numbers (replace-match (rg-perform-position-numbers-alignment line-match column-match context-marker) t t)) ((and column-match (not rg-show-columns)) (replace-match (propertize col-separator-match 'invisible t) t t nil 2) (replace-match (propertize column-match 'invisible t) t t nil 3)))))) (defun rg-filter () "Handle match highlighting escape sequences inserted by the rg process. This function is called from `compilation-filter-hook'." (save-excursion (forward-line 0) (let ((end (point)) beg temp-positions) (goto-char compilation-filter-start) (forward-line 0) (setq beg (point)) (when (zerop rg-hit-count) (newline)) ;; Only operate on whole lines so we don't get caught with part of an ;; escape sequence in one chunk and the rest in another. (when (< (point) end) (setq end (copy-marker end)) ;; Add File: in front of filename (when rg-group-result (while (re-search-forward "^\033\\[[0]*m\033\\[35m\\(.*?\\)\033\\[[0]*m$" end 1) (replace-match (concat (propertize "File:" 'rg-file-message t 'face nil 'font-lock-face 'rg-file-tag-face) " " (propertize (match-string 1) 'face nil 'font-lock-face 'rg-filename-face)) t t)) (goto-char beg)) ;; Highlight rg matches and delete marking sequences. (while (re-search-forward "\033\\[[0]*m\033\\[[3]*1m\033\\[[3]*1m\\(.*?\\)\033\\[[0]*m" end 1) (replace-match (propertize (match-string 1) 'face nil 'font-lock-face 'rg-match-face) t t) (push (cons (copy-marker (match-beginning 0)) (length (match-string 0))) temp-positions) (cl-incf rg-hit-count)) (rg-format-line-and-column-numbers beg end) ;; Delete all remaining escape sequences (goto-char beg) (while (re-search-forward "\033\\[[0-9;]*[0mK]" end 1) (replace-match "" t t)) (goto-char beg) (setq rg-match-positions (nconc rg-match-positions (nreverse temp-positions))) (run-hooks 'rg-filter-hook))))) ;; The regexp and filter functions below were taken from ag.el ;; Kudos to the people from https://github.com/Wilfred/ag.el for these. (defconst rg-file-line-column-pattern-nogroup "^\\([^\n:]+?\\):\\([1-9][0-9]*\\):\\([1-9][0-9]*\\):" "A regexp pattern that groups output. Groups into filename,line number and column number.") (defun rg-file-line-column-pattern-group () "A regexp pattern to match line number and column number with grouped output." (concat "^ *\\([1-9][0-9]*\\)" (regexp-quote (or (and rg-align-position-numbers rg-align-line-column-separator) ":")) " *\\([1-9][0-9]*\\)" (regexp-quote (or (and rg-align-position-numbers rg-align-position-content-separator) ":")))) (defconst rg-file-line-pattern-nogroup "^\\([^\n:]+?\\):\\([1-9][0-9]*\\):" "A regexp pattern that groups output into filename, line number.") (defun rg-file-line-pattern-group () "A regexp pattern to match line number with grouped output." (concat "^ *\\([1-9][0-9]*\\)" (regexp-quote (or (and rg-align-position-numbers rg-align-position-content-separator) ":")))) (defun rg-match-grouped-filename () "Match filename backwards when a line/column match is found." (save-match-data (save-excursion (when (re-search-backward "^File: \\(.*\\)$" (point-min) t) (list (match-string 1)))))) (defun rg-set-compilation-error-regexps () "Set the compilation mode regexps for errors for rg-mode buffers." (set (make-local-variable 'compilation-error-regexp-alist) '(rg-group-with-column rg-nogroup-with-column rg-group-no-column rg-nogroup-no-column)) (set (make-local-variable 'compilation-error-regexp-alist-alist) (list (cons 'rg-nogroup-no-column (list rg-file-line-pattern-nogroup 1 2)) (cons 'rg-nogroup-with-column (list rg-file-line-column-pattern-nogroup 1 2 3)) (cons 'rg-group-with-column (list (rg-file-line-column-pattern-group) 'rg-match-grouped-filename 1 2)) (cons 'rg-group-no-column (list (rg-file-line-pattern-group) 'rg-match-grouped-filename 1))))) (define-compilation-mode rg-mode "rg" "Major mode for `rg' search results. Commands: \\ \\[rg-rerun-change-dir]\t Repeat this search in another directory (`rg-rerun-change-dir'). \\[rg-rerun-change-files]\t Repeat this search with another file pattern (`rg-rerun-change-files'). \\[rg-rerun-change-regexp]\t Change the search regexp for the current search (`rg-rerun-change-regexp'). \\[rg-rerun-change-literal]\t Change the search literal for the current search (`rg-rerun-change-literal'). \\[rg-rerun-toggle-ignore]\t Repeat search with toggled '--no-ignore' flag (`rg-rerun-toggle-ignore'). \\[rg-rerun-toggle-case]\t Repeat search with toggled case insensitive setting (`rg-rerun-toggle-case'). \\[rg-save-search-as-name]\t Save search result, prompt for new name (`rg-save-search-as-name'). \\[rg-save-search]\t Save search result to some unique name (`rg-save-search'). \\[wgrep-change-to-wgrep-mode]\t Change mode to `wgrep'. \\{rg-mode-map}" (set (make-local-variable 'tool-bar-map) grep-mode-tool-bar-map) (set (make-local-variable 'compilation-error-face) 'rg-filename-face) (set (make-local-variable 'compilation-message-face) 'rg-match-position-face) (set (make-local-variable 'compilation-line-face) 'rg-line-number-face) (set (make-local-variable 'compilation-column-face) 'rg-column-number-face) ;; compilation-directory-matcher can't be nil, so we set it to a regexp that ;; can never match. (set (make-local-variable 'compilation-directory-matcher) '("\\`a\\`")) (set (make-local-variable 'compilation-process-setup-function) 'rg-process-setup) (set (make-local-variable 'compilation-disable-input) t) (set (make-local-variable 'compilation-error-screen-columns) nil) (unless rg-search-history (setq rg-search-history (rg-history-create))) (set (make-local-variable 'compilation-filter-hook) '(rg-filter)) ;; Set compilation error regexps as the last compilation-mode-hook to be ;; able to override these if they were set by other hooks. (add-hook 'compilation-mode-hook 'rg-set-compilation-error-regexps 90 'local)) (defun rg-maybe-show-header () "Recreate header if enabled." (when rg-show-header (rg-create-header-line 'rg-cur-search (rg-search-full-command rg-cur-search)))) (defun rg-mode-init (search) "Initiate `rg-mode' with SEARCH in current buffer." (unless (eq major-mode 'rg-mode) (error "Function rg-mode-init called in non rg mode buffer")) (hack-dir-local-variables-non-file-buffer) (setq rg-cur-search search) (rg-history-push (rg-search-copy rg-cur-search) rg-search-history) (rg-maybe-show-header) (rg-configure-imenu)) (defun rg-recompile () "Rerun the current search." (interactive) (let ((rg-recompile t)) (recompile)) (hack-dir-local-variables-non-file-buffer) (rg-maybe-show-header) (rg-configure-imenu)) (defun rg-rerun (&optional no-history) "Run `recompile' with `compilation-arguments' taken from `rg-cur-search'. If NO-HISTORY is non nil skip adding the search to the search history." (let ((pattern (rg-search-pattern rg-cur-search)) (files (rg-search-files rg-cur-search)) (dir (rg-search-dir rg-cur-search)) (literal (rg-search-literal rg-cur-search)) (flags (rg-search-flags rg-cur-search))) ;; compilation-directory is used as search dir and ;; default-directory is used as the base for file paths. (setq compilation-directory dir) (setq default-directory compilation-directory) (setcar compilation-arguments (or (rg-search-full-command rg-cur-search) (rg-build-command pattern files literal flags))) (unless no-history (rg-history-push (rg-search-copy rg-cur-search) rg-search-history)) (rg-recompile))) (defun rg-navigate-file-message (pos limit direction) "Return position of next 'rg-file-message text property. POS is the start position of the search and LIMIT is the limit of the search. If the property is not found within LIMIT, LIMIT is returned. If DIRECTION is positive search forward in the buffer, otherwise search backward." (let ((prop-change-func (if (> direction 0) 'next-single-property-change 'previous-single-property-change))) (while (progn (setq pos (funcall prop-change-func pos 'rg-file-message nil limit)) (and (not (eq pos limit)) (not (get-text-property pos 'rg-file-message))))) pos)) (defun rg-navigate-file-group (steps) "Move point to the a matched result group in the compilation buffer. STEPS decides how many groups to move past. Negative value means backwards and positive means forwards." (let (move-to (pos (point)) (steps-left (abs steps)) (limit (if (< steps 0) (point-min) (point-max)))) (while (and (> steps-left 0) (not (equal pos limit))) (setq pos (rg-navigate-file-message pos limit steps)) (unless (eq pos limit) (setq move-to pos)) (setq steps-left (- steps-left 1))) (when move-to (goto-char move-to)))) (defun rg-rerun-toggle-flag (flag) "Toggle FLAG in `rg-cur-search`." (setf (rg-search-flags rg-cur-search) (rg-list-toggle flag (rg-search-flags rg-cur-search))) (rg-rerun)) (defun rg-rerun-toggle-case () "Rerun last search with toggled case sensitivity setting." (interactive) (rg-rerun-toggle-flag "-i")) (defun rg-rerun-toggle-ignore () "Rerun last search with toggled '--no-ignore' flag." (interactive) (rg-rerun-toggle-flag "--no-ignore")) (defun rg-rerun-toggle-rexexp-literal () "Switch between literal and regexp and rerun last search." (interactive) (setf (rg-search-literal rg-cur-search) (not (rg-search-literal rg-cur-search))) (rg-rerun)) (defun rg-rerun-change-query () "Rerun last search and change search string." (interactive) (rg-rerun-change-search-string (rg-search-literal rg-cur-search))) (defun rg-rerun-change-search-string (literal) "Rerun last search but prompt for new search pattern. IF LITERAL is non nil this will trigger a literal search, otherwise a regexp search." (let ((pattern (rg-search-pattern rg-cur-search)) (read-from-minibuffer-orig (symbol-function 'read-from-minibuffer))) ;; Override read-from-minibuffer in order to insert the original ;; pattern in the input area. (cl-letf (((symbol-function #'read-from-minibuffer) (lambda (prompt &optional _ &rest args) (apply read-from-minibuffer-orig prompt pattern args)))) (setf (rg-search-pattern rg-cur-search) (rg-read-pattern literal pattern))) (setf (rg-search-literal rg-cur-search) literal) (rg-rerun))) (defun rg-rerun-change-regexp () "Rerun last search but prompt for new regexp." (interactive) (rg-rerun-change-search-string nil)) (defun rg-rerun-change-literal () "Rerun last search but prompt for new literal." (interactive) (rg-rerun-change-search-string t)) (defun rg-rerun-change-files() "Rerun last search but prompt for new files." (interactive) (let ((files (rg-search-files rg-cur-search))) (setf (rg-search-files rg-cur-search) (completing-read (concat "Repeat search in files (default: [" files "]): ") (rg-get-type-aliases) nil nil nil 'rg-files-history files)) (rg-rerun))) (defun rg-rerun-change-dir() "Rerun last search but prompt for new dir." (interactive) (setf (rg-search-dir rg-cur-search) (read-directory-name "In directory: " (rg-search-dir rg-cur-search) nil)) (rg-rerun)) (defun rg-next-file (n) "Move point to next file's first match. Prefix arg N decides how many files to navigate. When `rg-group-result' is nil this is the same as invoking `compilation-next-file', otherwise this will navigate to the next file with grouped matches." (interactive "p") (if rg-group-result (when (rg-navigate-file-group n) (forward-line)) (compilation-next-file n))) (defun rg-prev-file (n) "Move point to previous file's first match. Prefix arg N decides how many files to navigate. When `rg-group-result' is nil this is the same as invoking `compilation-previous-file', otherwise this will navigate to the previous file with grouped matches." (interactive "p") (if rg-group-result (let ((steps ;; On match rows we move 2 steps back to get to previous ;; file, otherwise 1 step. The later is on file ;; headings, space between files and at the end of search results. (if (or (get-text-property (point) 'rg-file-message) (save-excursion (beginning-of-line) (looking-at "^\\(?:rg finished .*\\)*$"))) n (+ n 1)))) (when (rg-navigate-file-group (- steps)) (forward-line))) (compilation-previous-file n))) (defun rg-back-history () "Navigate back in the search history." (interactive) (if-let (prev (rg-history-back rg-search-history)) (progn (setq rg-cur-search (rg-search-copy prev)) (rg-rerun 'no-history)) (message "No more history elements for back."))) (defun rg-forward-history () "Navigate forward in the search history." (interactive) (if-let (next (rg-history-forward rg-search-history)) (progn (setq rg-cur-search (rg-search-copy next)) (rg-rerun 'no-history)) (message "No more history elements for forward."))) (defun rg-configure-imenu () "Add files with matches to imenu if rg-group-result is enabled." (when rg-group-result (setq imenu-create-index-function (lambda () (goto-char (point-min)) (let ((elements nil) (filepath nil) (nextfile (point-min))) (while (setq nextfile (rg-navigate-file-message nextfile nil 1)) (save-excursion (goto-char nextfile) (and (looking-at-p "^File: ") (forward-char 6)) (setq filepath (buffer-substring-no-properties (point) (line-end-position)))) (push (cons filepath nextfile) elements)) (nreverse elements)))))) (defun rg-cur-search-pattern () "Get the current search pattern." (rg-search-pattern rg-cur-search)) (provide 'rg-result) ;; Local Variables: ;; byte-compile-warnings: (not docstrings) ;; End: ;;; rg-result.el ends here ================================================ FILE: rg.el ================================================ ;;; rg.el --- A search tool based on ripgrep -*- lexical-binding: t; -*- ;; Copyright (C) 1985-1987, 1993-1999, 2001-2015 Free Software ;; Foundation, Inc. ;; Copyright (C) 2016-2018 David Landell ;; ;; Author: David Landell ;; Roland McGrath ;; Version: 2.4.1 ;; URL: https://github.com/dajva/rg.el ;; Package-Requires: ((emacs "28.1") (transient "0.9.2") (wgrep "2.1.10")) ;; Keywords: matching, tools ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;; A search package based on the ripgrep command line tool. ;; It allows you to interactively create searches, doing automatic ;; searches based on the editing context, refining and modifying ;; search results and much more. It is also highly configurable to be ;; able to fit different users' needs. ;; If you are used to built-in Emacs rgrep command, transitioning to ;; rg should be simple. rg provides a lot of extra features ;; but the basics are similar. ;; ;; The big benefit of using ripgrep instead of grep as a backend is ;; speed. Especially when searching large source code repositories ;; where ripgrep really shines. ;; See info node `(rgel)Top' for documentation or online at https://rgel.readthedocs.io. ;;; Code: (require 'cl-lib) (require 'cus-edit) (require 'grep) (require 'rg-ibuffer) (require 'rg-menu) (require 'rg-result) (require 'rg-info-hack) (require 'subr-x) (require 'vc) (require 'files-x) ;; Customizations/public vars (defgroup rg nil "Settings for rg." :group 'tools :group 'external) (defcustom rg-custom-type-aliases '() "A list of file type aliases that are added to the ripgrep built in aliases. Each list element may be a (string . string) cons containing the name of the type alias and the file patterns, or a lambda returning a similar cons cell. A lambda should return nil if it currently has no type aliases to contribute." :type '(repeat (choice (cons string string) function)) :group 'rg) (defcustom rg-prioritized-type-aliases '() "A list of file type aliases that are prioritized. When detecting the file type from the current buffer these aliases are selected if there are conflicting aliases for a file type. Contains only the alias names and need to match alias names of ripgrep's built in aliases. The order of the list decides the priority among the types in the list." :type '(repeat string) :group 'rg) (defcustom rg-executable (executable-find "rg") "Ripgrep executable." :type 'string :group 'rg) (defcustom rg-executable-per-connection t "Invoke `executable-find' per host and store as connection local variable. Only works in Emacs 27.1 or later." :type 'boolean :group 'rg) (defcustom rg-command-line-flags nil "List of command line flags for rg. Alternatively a function returning a list of flags." :type '(choice function (repeat string)) :group 'rg) (defcustom rg-ignore-case 'case-fold-search "Decides which mode of case insensitive search that is enabled. CASE-FOLD-SEARCH means that the variable `case-fold-search' will trigger smart-case functionality if non nil. SMART means that case insensitive search will be triggered if the search pattern contains only lower case. If the pattern contains upper case letters, case sensitive search will be performed. This is similar to the rg '--smart-case' flag. FORCE will force case insensitive search regardless of the content of the search pattern. NIL means case sensitive search will be forced." :type '(choice (const :tag "Case Fold Search" case-fold-search) (const :tag "Smart" smart) (const :tag "Force" force) (const :tag "Off" nil)) :group 'rg) ;;;###autoload (defcustom rg-keymap-prefix "\C-cs" "Prefix for global `rg' keymap." :type 'key-sequence :group 'rg) (defcustom rg-default-alias-fallback "everything" "The default file alias to use when no alias can be determined. This must be a string that can be match against the types returned from `rg-get-type-aliases'." :type 'string :group 'rg) (defcustom rg-buffer-name "rg" "Search results buffer name. Can be string or function." :type '(choice string function) :group 'rg) (defcustom rg-ignore-ripgreprc t "Ignore the ripgrep config file. Disabling this setting can break functionality of this package." :type 'boolean :group 'rg) (defcustom rg-w32-unicode nil "Enable Unicode support on Windows. A workaround for NTEmacs subprocess not supporting Unicode arguments." :type 'boolean :group 'rg) (defcustom rg-w32-ripgrep-proxy (expand-file-name "rg-w32-ripgrep-proxy.bat" user-emacs-directory) "An automatically generated temporary batch file. Used to proxy ripgrep Unicode arguments." :type 'string :group 'rg) ;;;###autoload (defvar rg-command-line-flags-function 'identity "Function to modify command line flags of a search. The argument of the function is an optional list of search specific command line flags and the function shall return a list of command line flags to use.") ;; Internal vars and structs (defvar rg-builtin-type-aliases-cache nil "Cache for \"rg --type-list\".") (defvar rg-prioritized-type-aliases-cache nil "Cache for `rg-prioritized-type-aliases'.") (defvar rg-initial-toggle-flags nil "List of command line flags set by default by `rg-define-toggle' macro.") (defvar rg-history nil "History for full rg commands.") (defvar rg-files-history nil "History for files args.") (defvar rg-pattern-history nil "History for search patterns.") (defvar-local rg--executable-local 'unknown) (defvar rg-required-command-line-flags '("--color=always" "--colors=match:fg:red" "--colors=path:fg:magenta" "--colors=line:fg:green" "--colors=column:none" "-n")) (defconst rg-internal-type-aliases '(("all" . "all defined type aliases") ; rg --type=all ("everything" . "*")) ; rg without '--type' arg "Internal type aliases for special purposes. These are not produced by \"rg --type-list\" but we need them anyway.") (defvar rg-global-map (let ((map (make-sparse-keymap))) (define-key map "d" 'rg-dwim) (define-key map "k" 'rg-kill-saved-searches) (define-key map "l" 'rg-list-searches) (define-key map "p" 'rg-project) (define-key map "r" 'rg) (define-key map "s" 'rg-save-search) (define-key map "S" 'rg-save-search-as-name) (define-key map "t" 'rg-literal) map) "The global keymap for `rg'.") ;; Defuns (defun rg-has-connection-local-executable (criteria) "Return non nil if there is a connection local executable for HOST." (when (and (fboundp 'hack-connection-local-variables) (boundp 'connection-local-variables-alist)) (hack-connection-local-variables criteria) (assoc 'rg-executable connection-local-variables-alist))) (defun rg-find-executable () "Determine which rg executable to use." (let* ((remote-host (file-remote-p default-directory 'host)) (criteria `(:application rg :machine ,remote-host))) (if (and remote-host rg-executable-per-connection ;; Below are just to make byte compiler happy (fboundp 'connection-local-set-profile-variables) (fboundp 'connection-local-set-profiles) (fboundp 'hack-connection-local-variables-apply) (fboundp 'with-connection-local-variables)) (progn ;; Find executable on remote host once (when (not (rg-has-connection-local-executable criteria)) (let ((rg-vars-symbol (intern (concat "rg-vars-" remote-host))) (rg-exec (with-no-warnings (executable-find "rg" t)))) (connection-local-set-profile-variables rg-vars-symbol `((rg-executable . ,rg-exec))) (connection-local-set-profiles criteria rg-vars-symbol))) ;; Here there should be a remote executable available if found (hack-connection-local-variables-apply criteria) rg-executable) ;; Use local executable for local buffer (default-value 'rg-executable)))) (defun rg-executable () "Return the \"rg\" executable. Raises an error if it can not be found." (let ((executable (rg-find-executable))) (if executable (shell-quote-argument executable) (user-error "No 'rg' executable found in host %s" (or (file-remote-p default-directory 'host) "localhost"))))) (defun rg--buffer-name () "Wrapper for variable `rg-buffer-name'. Return string or call function." (if (functionp rg-buffer-name) (funcall rg-buffer-name) rg-buffer-name)) (defun rg-buffer-name (&optional name-of-mode) "Return search results buffer name. NAME-OF-MODE is needed to pass this function to `compilation-start'." (ignore name-of-mode) (if rg-recompile (buffer-name) (format "*%s*" (rg--buffer-name)))) (defun rg-build-type-add-args () "Build a list of --type-add: \"foo:*.foo\" flags. Do this for each type in `rg-custom-type-aliases'." (mapcar (lambda (typedef) (let ((name (car typedef)) (globs (cdr typedef))) (mapconcat (lambda (glob) (concat "--type-add=" (shell-quote-argument (concat name ":" glob)))) (split-string globs) " "))) (rg-get-custom-type-aliases))) (defun rg-is-custom-file-pattern (files) "Return non nil if FILES is a custom file pattern." (not (assoc files (rg-get-type-aliases)))) (defun rg-build-command (pattern files literal flags) "Create the command line for PATTERN and FILES. LITERAL determines if search will be literal or regexp based and FLAGS are command line flags to use for the search." (let ((command-line (append rg-required-command-line-flags (when (or rg-show-columns rg-group-result) (list "--column")) (rg-build-type-add-args) (if (functionp rg-command-line-flags) (funcall rg-command-line-flags) rg-command-line-flags) flags (list (if rg-group-result "--heading" "--no-heading")) (when rg-ignore-ripgreprc (list "--no-config")) (when (rg-is-custom-file-pattern files) (list (concat "--type-add=" (shell-quote-argument (concat "custom:" files))))) (when literal (list "--fixed-strings")) (when (not (equal files "everything")) (list "--type=")) (list "-e ") (when (member system-type '(darwin windows-nt)) (list "."))))) (let ((command (grep-expand-template (mapconcat 'identity (cons (rg-executable) (delete-dups command-line)) " ") pattern (if (rg-is-custom-file-pattern files) "custom" files)))) (cond ((and (eq system-type 'windows-nt) rg-w32-unicode) (with-temp-file rg-w32-ripgrep-proxy (set-buffer-multibyte t) (setq buffer-file-coding-system 'utf-8-dos) (insert (format "@echo off\n")) (insert (format "chcp 65001 > nul\n")) (insert (format "%s\n" command))) rg-w32-ripgrep-proxy) (t command))))) (defun rg-invoke-rg-type-list () "Invokes rg --type-list and return the result." (shell-command-to-string (concat (rg-executable) " --type-list"))) (defun rg-list-builtin-type-aliases () "Invokes rg --type-list and puts the result in an alist." (let ((type-list (delete "" (split-string (rg-invoke-rg-type-list) "\n")))) (mapcar (lambda (type-alias) (setq type-alias (split-string type-alias ":" t)) (cons (string-trim (car type-alias)) (string-trim (mapconcat 'identity (split-string (cadr type-alias) "," t ) " ")))) type-list))) (defun rg-get-custom-type-aliases () "Get alist of custom type aliases. Any lambda elements will be evaluated, and nil results will be filtered out." (delq nil (mapcar (lambda (ct) (if (functionp ct) (funcall ct) ct)) rg-custom-type-aliases))) (defun rg-get-type-aliases (&optional skip-internal) "Return supported type aliases. If SKIP-INTERNAL is non nil the `rg-internal-type-aliases' will be excluded." ;; Read from ripgrep once or if prioritized aliases changed. (unless (and rg-builtin-type-aliases-cache (equal rg-prioritized-type-aliases (mapcar #'car rg-prioritized-type-aliases-cache))) (let ((builtin-aliases (rg-list-builtin-type-aliases))) (if rg-prioritized-type-aliases (progn (let ((prioritized-type-aliases (seq-filter (lambda (item) (member (car item) rg-prioritized-type-aliases)) builtin-aliases))) ;; Reorder the the found aliases to the same order as rg-prioritized-type-aliases (setq rg-prioritized-type-aliases-cache (seq-map (lambda (item) (assoc item prioritized-type-aliases)) rg-prioritized-type-aliases)) ;; Remove the prioritzed aliases from the builtin cache ;; to avoid duplicates. (setq rg-builtin-type-aliases-cache (seq-remove (lambda (item) (member (car item) rg-prioritized-type-aliases)) builtin-aliases)))) ;; Simple case, just assign (setq rg-builtin-type-aliases-cache builtin-aliases) (setq rg-prioritized-type-aliases-cache nil)))) (append (rg-get-custom-type-aliases) rg-prioritized-type-aliases-cache rg-builtin-type-aliases-cache (unless skip-internal rg-internal-type-aliases))) (defun rg-default-alias () "Return the default alias by matching alias globs with the buffer file name." (let* ((bufname (or (buffer-file-name) (replace-regexp-in-string "<[0-9]+>\\'" "" (buffer-name)))) (filename (and bufname (stringp bufname) (file-name-nondirectory bufname)))) (or (when filename (cl-find-if (lambda (alias) (string-match (mapconcat 'wildcard-to-regexp (split-string (cdr alias) nil t) "\\|") filename)) (rg-get-type-aliases t))) ;; Default when an alias for the file can't be determined (or (cl-find-if (lambda (alias) (string= rg-default-alias-fallback (car alias))) (rg-get-type-aliases)) (progn (message "Warning: rg-default-alias-fallback customization does not match any alias. Using \"all\".") (car rg-internal-type-aliases)))))) (defun rg-tag-default () "Get the marked area or thing at point. Returns nil if nothing at point." (or (and transient-mark-mode mark-active (/= (point) (mark)) (buffer-substring-no-properties (point) (mark))) (funcall (or find-tag-default-function (get major-mode 'find-tag-default-function) 'find-tag-default)))) (defun rg-read-files () "Read files argument for interactive rg." (let ((default-alias (rg-default-alias))) (completing-read (concat "Search in files" (if default-alias (concat " (default: [" (car default-alias) "] = " (cdr default-alias) ")")) ": ") (rg-get-type-aliases) nil nil nil 'rg-files-history (car default-alias)))) (defun rg-read-pattern (literal &optional default) "Read search pattern argument from user. If LITERAL is non nil prompt for literal string. DEFAULT is the default pattern to use at the prompt." (let ((default (or default (rg-tag-default))) (prompt (concat (if literal "Literal" "Regexp") " search for"))) (read-regexp prompt default 'rg-pattern-history))) (defun rg-project-root (file) "Find the project root of the given FILE." (or (when (and (require 'projectile nil t) (fboundp 'projectile-project-root)) (projectile-project-root)) (when (and (require 'find-file-in-project nil t) (fboundp 'ffip-project-root)) (ffip-project-root)) (when (and (require 'project nil t) (fboundp 'project-current)) (if-let ((project (project-current))) (cond ((fboundp 'project-root) (project-root project)) ((fboundp 'project-roots) (car (project-roots project)))))) (let ((file (expand-file-name (or file default-directory)))) (condition-case nil (vc-call-backend (vc-responsible-backend file) 'root file) (error (file-name-directory file)))))) (defun rg-run (pattern files dir &optional literal confirm flags) "Execute rg command with supplied PATTERN, FILES and DIR. If LITERAL is nil interpret PATTERN as regexp, otherwise as a literal. CONFIRM allows the user to confirm and modify the command before executing. FLAGS is additional command line flags to use in the search." (unless (and (stringp pattern) (> (length pattern) 0)) (signal 'user-error '("Empty string: No search done"))) (wgrep-rg-warn-ag-setup) (unless (and (file-directory-p dir) (file-readable-p dir)) (setq dir default-directory)) (rg-apply-case-flag pattern) (setq dir (file-name-as-directory (expand-file-name dir))) (let* ((flags (append rg-initial-toggle-flags flags)) (default-directory dir) (command (rg-build-command pattern files literal flags)) confirmed) (if confirm (setq confirmed (read-from-minibuffer "Confirm: " command nil nil 'rg-history)) (add-to-history 'rg-history command)) (let ((search (rg-search-create :pattern pattern :files files :dir dir :literal literal :flags flags))) (when (and confirmed (not (string= confirmed command))) (setq command confirmed) ;; If user changed command we can't know the parts of the ;; search and needs to disable result buffer modifications. (setf (rg-search-full-command search) command)) ;; Since `rg-buffer-name' can be changed through dir-locals file we ;; need to apply variables from dir-locals that resides in search dir. ;; This way the results buffer will receive correct name on `compilation-start'. ;; `hack-dir-local-variables' is searching dir-locals file in ;; `buffer-file-name' or `default-directory' directory. ;; Temporary buffer must be created here to make sure that dir-locals ;; file is loaded from `default-directory' defined above and ;; not from `buffer-file-name' in case search is started from file buffer. (with-temp-buffer (hack-dir-local-variables-non-file-buffer) ;; Setting process-setup-function makes exit-message-function work ;; even when async processes aren't supported. (with-current-buffer (compilation-start command 'rg-mode #'rg-buffer-name) (rg-mode-init search))))) (if (eq next-error-last-buffer (current-buffer)) (setq default-directory dir))) (defun rg-apply-case-flag (pattern) "Make sure -i is added to the command if needed. The value of the `rg-ignore-case' variable and the case of the supplied PATTERN influences the result. See `rg-ignore-case' for more detailed info." (if (or (eq rg-ignore-case 'force) (and (or (eq rg-ignore-case 'smart) (and (eq rg-ignore-case 'case-fold-search) case-fold-search)) (isearch-no-upper-case-p pattern t))) (setq rg-initial-toggle-flags (add-to-list 'rg-initial-toggle-flags "-i" )) (setq rg-initial-toggle-flags (delete "-i" rg-initial-toggle-flags)))) (defun rg-get-rename-target () "Return the buffer that will be target for renaming." (if (eq major-mode 'rg-mode) (current-buffer) (get-buffer (rg-buffer-name)))) (defun rg-get-buffer-file-name () "Wrapper for function `buffer-file-name'. Return the result of function `buffer-file-name' if buffer has an associated file, otherwise raise a user error." (if (buffer-file-name) (file-name-nondirectory (buffer-file-name)) (user-error "Buffer does not have an associated file"))) (defalias 'kill-rg 'kill-compilation) (defalias 'rg-kill-current 'kill-compilation "Kill the ongoing ripgrep search.") (make-obsolete 'kill-rg 'rg-kill-current "1.7.1") (defun rg-print-environment () "Print the environmet in which this package is running. The result is also copied to the `kill-ring'. Should be attached to bug reports." (interactive) (let* ((settings (thread-last (custom-group-members 'rg nil) (seq-filter (lambda (item) (eq (cadr item) 'custom-variable))) (mapcar (lambda (member) (cons (car member) (symbol-value (car member))))))) (rg-version (car (split-string (shell-command-to-string (format "%s --version" (rg-executable))) "\n"))) (compilation-filter-advised (when-let (advice-alist (get 'compilation-filter 'ad-advice-info)) (cdr (assoc 'active advice-alist)))) (environment (concat "--------- RG environment ---------\n" (format "emacs-version: %s\n" (car (split-string (emacs-version) "\n"))) (format "system: %s\n" system-type) (format "ripgrep-version: %s\n" rg-version) (format "compilation-filter-hook: %S\n" compilation-filter-hook) (format "compilation-filter-advised: %S\n" compilation-filter-advised) (mapconcat (lambda (setting) (format "%S: %S" (car setting) (cdr setting))) settings "\n") "\n------------------ END ------------------"))) (message environment) (kill-new environment))) ;;;###autoload (defmacro rg-define-toggle (flag &optional key default) "Define a command line flag that can be toggled from the rg result buffer. This will create a function with prefix \"rg-custom-toggle-flag-\" concatenated with the FLAG name, stripped of any leading dashes. Flag must be a form that will be evaluated to a string at macro expansion time. For instance, if FLAG is \"--invert-match\" the function name will be `rg-custom-toggle-flag-invert-match'. If the flag contains a value that will be excluded from the function name. Optional KEY is a key binding that is added to `rg-mode-map'. If the optional DEFAULT parameter is non nil the flag will be enabled by default." (let* ((flagvalue (eval flag)) (flagname (thread-last (car (split-string flagvalue " " t)) (string-remove-prefix "-") (string-remove-prefix "-"))) (funname (concat "rg-custom-toggle-flag-" flagname))) `(progn ,(if default `(setq rg-initial-toggle-flags (add-to-list 'rg-initial-toggle-flags ,flagvalue)) `(setq rg-initial-toggle-flags (delete ,flagvalue rg-initial-toggle-flags))) ,(when key `(define-key rg-mode-map ,key (quote ,(intern funname)))) (defun ,(intern funname) () ,(format "Rerun last search with flag '%s' toggled." flagvalue) (interactive) (rg-rerun-toggle-flag ,flagvalue))))) (defun rg-save-search-as-name (newname) "Save the search result in current result buffer. NEWNAME will be added to the result buffer name. New searches will use the standard buffer unless the search is done from a saved buffer in which case the saved buffer will be reused." (interactive "sSave search as name: ") (when-let ((buffer (rg-get-rename-target))) (with-current-buffer buffer (rename-buffer (format "*%s %s*" (rg--buffer-name) newname))))) (defun rg-save-search () "Save the search result in current result buffer. The result buffer will be renamed by the `rename-uniquify' function. To choose a custom name, use `rg-save-search-as-name' instead. New searches will use the standard buffer unless the search is done from a saved buffer in which case the saved buffer will be reused." (interactive) (when-let ((buffer (rg-get-rename-target))) (with-current-buffer buffer (rename-uniquely) ;; If the new buffer name became default result buffer name, just rename ;; again to make sure the result is saved. (when (equal (buffer-name) (rg-buffer-name)) (rename-uniquely))))) (defun rg-kill-saved-searches () "Kill all saved rg buffers. The default result buffer will be kept." (interactive) (when (y-or-n-p "Confirm kill all saved rg searches? ") (dolist (buf (buffer-list)) (when (with-current-buffer buf (and (eq major-mode 'rg-mode) (not (equal (buffer-name) (rg-buffer-name))))) (kill-buffer buf))))) ;;;###autoload (defun rg-enable-default-bindings (&optional prefix) "Enable the global `rg' default key bindings under PREFIX key. If prefix is not supplied `rg-keymap-prefix' is used." (interactive) (when-let ((prefix (or prefix rg-keymap-prefix))) (if rg-use-transient-menu (rg-enable-menu prefix) (global-set-key prefix rg-global-map)))) ;;;###autoload (defun rg-use-old-defaults () "Restore default settings pre version 2.0.0." (define-key rg-mode-map "\C-f" 'rg-forward-history) (define-key rg-mode-map "\C-c>" nil) (define-key rg-mode-map "\C-b" 'rg-back-history) (define-key rg-mode-map "\C-c<" nil) (define-key rg-mode-map "\C-n" 'rg-next-file) (define-key rg-mode-map "\M-N" nil) (define-key rg-mode-map "\C-p" 'rg-prev-file) (define-key rg-mode-map "\M-P" nil) (define-key rg-mode-map "l" 'rg-list-searches) (define-key rg-mode-map "L" nil) (define-key rg-mode-map "w" 'wgrep-change-to-wgrep-mode) (define-key rg-mode-map "e" nil) (setf rg-group-result nil) (setf rg-align-position-numbers nil) (setf rg-align-line-column-separator nil) (setf rg-align-position-content-separator nil) (setf rg-use-transient-menu nil) (setf rg-default-alias-fallback "all")) (eval-and-compile (defun rg-set-search-defaults (args) "Set defaults for required search options missing from ARGS. If the :confirm option is missing, set it to NEVER, if the :format option is missing, set it to REGEXP, and if the :query option is missing, set it to ASK" (unless (plist-get args :confirm) (setq args (plist-put args :confirm 'never))) (unless (plist-get args :format) (setq args (plist-put args :format 'regexp))) (unless (plist-get args :query) (setq args (plist-put args :query 'ask))) (unless (plist-get args :files) (setq args (plist-put args :files 'ask))) (unless (plist-get args :dir) (setq args (plist-put args :dir 'ask))) args)) (eval-and-compile (defun rg-ensure-quoted (arg) "Ensure that ARG is quoted." (if (and (consp arg) (eq (car arg) 'quote)) arg `(quote ,arg))) (defun rg-ensure-unquoted (arg) "Ensure that ARG is quoted." (if (and (consp arg) (eq (car arg) 'quote)) (cadr arg) arg))) (eval-when-compile ;; parse :format arg, default (non-nil) means to use regexp, otherwise ;; do a literal search (defsubst rg-parse-format-literal (format-opt) (pcase format-opt ('regexp nil) ; default to regexp search ('literal t) (_ format-opt)))) ; otherwise evaluate form (eval-and-compile (defun rg-search-parse-local-bindings (search-cfg) "Parse local bindings for search functions from SEARCH-CFG." (let* ((confirm-opt (plist-get search-cfg :confirm)) (format-opt (plist-get search-cfg :format)) (query-opt (plist-get search-cfg :query)) (alias-opt (plist-get search-cfg :files)) (dir-opt (plist-get search-cfg :dir)) (flags-opt (plist-get search-cfg :flags)) (literal-opt (rg-parse-format-literal format-opt)) (binding-list `((literal ,literal-opt)))) ;; confirm binding (cond ((eq confirm-opt 'never) (setq binding-list (append binding-list `((confirm nil))))) ((eq confirm-opt 'always) (setq binding-list (append binding-list `((confirm t))))) ((eq confirm-opt 'prefix) (setq binding-list (append binding-list '((confirm (equal current-prefix-arg '(4)))))))) ;; query binding (unless (eq query-opt 'ask) (let ((query (cond ((eq query-opt 'point) '(or (rg-tag-default) (rg-read-pattern literal))) (t query-opt)))) (setq binding-list (append binding-list `((query ,query)))))) ;; dir binding (unless (eq dir-opt 'ask) (let ((dirs (cond ((eq dir-opt 'project) '(rg-project-root buffer-file-name)) ((eq dir-opt 'current) 'default-directory) (t dir-opt)))) (setq binding-list (append binding-list `((dir ,dirs)))))) ;; file alias binding (unless (eq alias-opt 'ask) (let ((files (if (eq alias-opt 'current) '(car (rg-default-alias)) alias-opt))) (setq binding-list (append binding-list `((files ,files)))))) (when (eq flags-opt 'ask) (setq flags-opt 'flags)) (setq flags-opt (rg-ensure-quoted flags-opt)) (setq binding-list (append binding-list `((flags (funcall rg-command-line-flags-function ,flags-opt))))) binding-list))) (eval-and-compile (defun rg-search-parse-interactive-args (search-cfg) "Parse interactive args from SEARCH-CFG for search functions." (let* ((query-opt (plist-get search-cfg :query)) (format-opt (plist-get search-cfg :format)) (literal (rg-parse-format-literal format-opt)) (dir-opt (plist-get search-cfg :dir)) (files-opt (plist-get search-cfg :files)) (flags-opt (plist-get search-cfg :flags)) (iargs '())) (when (eq query-opt 'ask) (setq iargs (append iargs `((query . (rg-read-pattern ,literal)))))) (when (eq files-opt 'ask) (setq iargs (append iargs '((files . (rg-read-files)))))) (when (eq dir-opt 'ask) (setq iargs (append iargs '((dir . (read-directory-name "In directory: " nil default-directory t)))))) (when (eq flags-opt 'ask) (setq iargs (append iargs '((flags . (split-string (read-string "Command line flags: "))))))) iargs))) (eval-and-compile (defun rg-search-parse-menu-arg (search-cfg name) "Parse :menu option in SEARCH-CFG. Returns forms for binding function with NAME into rg-menu." (when-let ((menu-opt (rg-ensure-unquoted (plist-get search-cfg :menu)))) (unless (and (consp menu-opt) (= (length menu-opt) 3)) (user-error "'%S' should be a list of length 3" menu-opt)) `((rg-menu-transient-insert ,@menu-opt ',(intern (concat (symbol-name name) "--transient"))))))) (defconst rg-elisp-font-lock-keywords '(("(\\(rg-define-search\\)\\_>[ \t']*\\(\\(?:\\sw\\|\\s_\\)+\\)?" (1 font-lock-keyword-face) (2 font-lock-function-name-face nil t)))) (font-lock-add-keywords 'emacs-lisp-mode rg-elisp-font-lock-keywords) ;;;###autoload (defmacro rg-define-search (name &rest args) "Define an rg search functions named NAME. ARGS is a search specification that defines parameters of a search. It optionally starts with a string that is used as the docstring for the defined function. The rest of ARGS contains key value pairs according to the specification below. All keys are optional with specified default if left out. :query Method for retrieving the search string. Allowed values are `point' which means extract thing at point and `ask' which means prompt the user for a string. Any form that evaluates to a string is allowed. Default is `ask'. :format Specifies if :query is interpreted literally (`literal') or as a regexp (`regexp'). If it is a form, eg. (not `current-prefix-arg'), and is non-nil the :query is interpreted literally, otherwise as a regexp. Default is `regexp'. :files Form that evaluates to a file alias or custom file glob. `current' means extract alias from current buffer file name, `ask' will prompt the user. Default is `ask'. :dir Root search directory. Allowed values are `ask' for user prompt, `current' for current dir and `project' for project root. Any form that evaluates to a directory string is also allowed. Default is `ask'. :confirm `never', `always', or `prefix' are allowed values. Specifies if the the final search command line string can be modified and confirmed by the user. Default is `never'. :flags `ask' or a list of command line flags that will be used when invoking the search. :menu Bind the command into `rg-menu'. Must be a list with three items in it. The first item is the description of the group in which the new command will appear. If the group does not exist a new will be created. The second item is the key binding for this new command (ether a key vector or a key description string) and the third item is the description of the command that will appear in the menu. Example: \(rg-define-search search-home-dir-in-elisp \"Doc string.\" :query ask :format literal :files \"elisp\" :dir (getenv \"HOME\"\)\) :menu (\"Custom\" \"H\" \"Home dir\")" (declare (indent defun)) (let* ((body (macroexp-parse-body args)) (decls (car body)) (search-cfg (rg-set-search-defaults (cdr body))) (local-bindings (rg-search-parse-local-bindings search-cfg)) (iargs (rg-search-parse-interactive-args search-cfg)) (menu-forms (rg-search-parse-menu-arg search-cfg name))) `(progn (defun ,name ,(mapcar 'car iargs) ,@decls (interactive (list ,@(mapcar 'cdr iargs))) (let* ,local-bindings (rg-run query files dir literal confirm flags))) (rg-menu-wrap-transient-search ,name) ,@menu-forms))) ;;;###autoload (autoload 'rg-project "rg.el" "" t) (rg-define-search rg-project "Run ripgrep in current project searching for REGEXP in FILES. The project root will will be determined by either common project packages like projectile and `find-file-in-project' or the source version control system." :dir project) ;;;###autoload (autoload 'rg-dwim-project-dir "rg.el" "" t) (rg-define-search rg-dwim-project-dir "Search for thing at point in files matching the current file under the project root directory." :query point :format literal :files current :dir project) ;;;###autoload (autoload 'rg-dwim-current-dir "rg.el" "" t) (rg-define-search rg-dwim-current-dir "Search for thing at point in files matching the current file under the current directory." :query point :format literal :files current :dir current) ;;;###autoload (autoload 'rg-dwim-current-file "rg.el" "" t) (rg-define-search rg-dwim-current-file "Search for thing at point in files matching the current file name (as a pattern) under the current directory." :query point :format literal :files (rg-get-buffer-file-name) :dir current) ;;;###autoload (defun rg-dwim (&optional curdir) "Run ripgrep without user interaction figuring out the intention by magic(!). The default magic searches for thing at point in files matching current file under project root directory. With \\[universal-argument] prefix (CURDIR), search is done in current dir instead of project root. With repeated \\[universal-argument] prefix, search is done in the current dir and using the current variable `buffer-file-name' as a pattern. Subdirectories are still searched, so different files with the same name pattern still will be searched." (interactive "P") (cond ((eq 4 (and (consp curdir) (car curdir))) (rg-dwim-current-dir)) ((eq 16 (and (consp curdir) (car curdir))) (rg-dwim-current-file)) (t (rg-dwim-project-dir)))) ;;;###autoload (autoload 'rg-literal "rg.el" "" t) (rg-define-search rg-literal "Run ripgrep, searching for literal PATTERN in FILES in directory DIR. With \\[universal-argument] prefix (CONFIRM), you can edit the constructed shell command line before it is executed." :format literal :confirm prefix) ;;;###autoload (autoload 'rg "rg.el" "" t) (rg-define-search rg "Run ripgrep, searching for REGEXP in FILES in directory DIR. The search is limited to file names matching shell pattern FILES. FILES may use abbreviations defined in `rg-custom-type-aliases' or ripgrep builtin type aliases, e.g. entering `elisp' is equivalent to `*.el'. REGEXP is a regexp as defined by the ripgrep executable. With \\[universal-argument] prefix (CONFIRM), you can edit the constructed shell command line before it is executed. Collect output in a buffer. While ripgrep runs asynchronously, you can use \\[next-error] (M-x `next-error'), or \\\\[compile-goto-error] \ in the rg output buffer, to go to the lines where rg found matches." :confirm prefix) (provide 'rg) ;; Local Variables: ;; byte-compile-warnings: (not cl-functions) ;; End: ;;; rg.el ends here ================================================ FILE: rgel.info ================================================ This is rgel.info, produced by makeinfo version 7.1 from rgel.texi. rg.el 2.4.0, Oct 04, 2025 David Landell Copyright © 2019, David Landell INFO-DIR-SECTION Emacs START-INFO-DIR-ENTRY * RG: (rgel.info). Search like a King with ripgrep in Emacs. END-INFO-DIR-ENTRY Generated by Sphinx 7.2.6.  File: rgel.info, Node: Top, Next: Usage, Up: (dir) RG User Manual ************** rg.el 2.4.0, Oct 04, 2025 David Landell Copyright © 2019, David Landell 'rg.el' is an Emacs search package based on the ripgrep (https://github.com/BurntSushi/ripgrep) command line tool. It allows you to interactively create searches, doing automatic searches based on the editing context, refining and modifying search results and much more. It is also highly configurable to be able to fit different users’ needs. Throughout this manual this emacs package will be referred to as 'rg' while the command line utility will be referred to as 'ripgrep'. If you are used to built-in Emacs ‘rgrep’ command, transitioning to 'rg' should be simple. 'rg' provides a lot of extra features but the basics are similar. The big benefit of using 'ripgrep' instead of 'grep' as a backend is speed. Especially when searching large source code repositories where 'ripgrep' really shines. Please read this blog post (http://blog.burntsushi.net/ripgrep/) for some speed comparisons with other tools. * Menu: * Usage:: * Configuration:: * Contribute:: * License:: * Index::  File: rgel.info, Node: Usage, Next: Configuration, Prev: Top, Up: Top 1 Usage ******* * Menu: * Installation:: * Searching:: * Results buffer:: * Search management:: * Multi line search::  File: rgel.info, Node: Installation, Next: Searching, Up: Usage 1.1 Installation ================ This version of 'rg' is supported on GNU Emacs 26.1 or later on Linux systems. It might work on older Emacsen and on other systems but such configurations are not tested. Patches for other OS:es are welcome. MELPA ..... Packages are published on MELPA Stable (https://stable.melpa.org/#/rg) and MELPA (http://melpa.org/#/rg). From within Emacs, run ‘M-x package-install [RET] rg [RET]’ to install from those sources. Enable default key bindings: (rg-enable-default-bindings) The above will enable the default key map ‘rg-menu’ under the default prefix key ‘C-c s’. Manual ...... Releases can alternatively be downloaded from GitHub (https://github.com/dajva/rg.el/releases/latest) and installed manually. Put all elisp files in main directory in your load path and ‘require’ the package in your init file. (require 'rg) (rg-enable-default-bindings) You would also need to make sure all package requirements are met. For this version these are: - 'wgrep' 2.1.10 - 'transient' 0.9.2 - 'emacs' 26.1 'rg' is using autoloaded symbols which means it’s also possible to defer loading if you have autoloading setup. That usually comes out of the box with ‘package-install’. Lazy loading ............ For lazy loading you don’t want to call directly into the package during startup. Use a setup similar to this instead: (global-set-key (kbd "C-c s") #'rg-menu) (with-eval-after-load 'rg ;; Your settings goes here. ) If you don’t want to use the transient menu interface, the following is needed to achieve lazy loading: ;; Workaround for emacs' lack of autoloaded keymaps. ;; This is essentially what use-package do. (defun rg-autoload-keymap () (interactive) (if (not (require 'rg nil t)) (user-error (format "Cannot load rg")) (let ((key-vec (this-command-keys-vector))) (global-set-key key-vec rg-global-map) (setq unread-command-events (mapcar (lambda (ev) (cons t ev)) (listify-key-sequence key-vec)))))) (global-set-key (kbd "C-c s") #'rg-autoload-keymap) (with-eval-after-load 'rg ;; Your settings goes here. ) wgrep ..... This package use wgrep (https://github.com/mhayashi1120/Emacs-wgrep) for editing capabilities in the rg results buffer. No setup is needed. Isearch integration ................... Optional *note isearch integration: 6. can be enabled to allow you to extend isearch to trigger ripgrep searching. Enable it in your configuration with: (require 'rg-isearch) (define-key isearch-mode-map "\M-sr" 'rg-isearch-menu) For the evil use case where isearch-mode is exited after first search hit, users would also want to add the binding to the ‘global-map’ or similar. Interaction with the 'ripgrep' configuration file ................................................. The 'ripgrep' binary allows using a configuration file (https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#configuration-file) to set default values for command line flags. This package requires specific command line flags to function correctly and using a 'ripgrep' configuration may conflict with these requirements. Therefore the configuration file is ignored by default. This can be changed by the *note rg-ignore-ripgreprc: 7. setting. Note: Using the 'ripgrep' configuration file may break functionality of this package if you are not careful. Interaction with xterm-color ............................ This package is not written to be used with custom output colors provided by external packages like 'xterm-color'. It relies on the color escape sequences so stripping these will break in unexpected ways. If you are using such packages, the advice is to hook such functionality into ‘compilation-filter-hook’ instead of advising ‘compilation-filter’.  File: rgel.info, Node: Searching, Next: Results buffer, Prev: Installation, Up: Usage 1.2 Searching ============= Searching is done by invoking one of the different frontend commands. This package is built around recursive search based on three parameters; a single 'directory', 'file type' filter, and a search 'pattern'. These three parameters can interactively be selected or figured out automatically by the package, depending on which command that is used. The underlying 'ripgrep' binary has the file type filter concept built in. You have a high level of control over which files to search and which to ignore. This is partly what makes it so fast, ignoring uninteresting files. In addition to the base parameters there are a lot of options that control how a search is done. These are typically selected from the *note rg-menu: a. interface. * Menu: * Case sensitivity:: * Interactive search:: * Project search:: * Do what I mean:: * Isearch search:: * File type aliases:: * The menu::  File: rgel.info, Node: Case sensitivity, Next: Interactive search, Up: Searching 1.2.1 Case sensitivity ---------------------- Considering case when searching is an important feature of any search tool. This package gives you a lot of control over how to handle case sensitive and case insensitive search. It can be forced to 'on' or 'off' and set to 'smart case'. The latter is similar to the 'ripgrep' ‘--smart-case’ flag but is not using the flag directly. One thing to note about this is that the case insensitive setting controls the behavior when starting a new search. In the results buffer the setting is fixed to 'on' or 'off' but can be toggled easily with a key binding. See *note rg-ignore-case: c. customization for the details of the configuration.  File: rgel.info, Node: Interactive search, Next: Project search, Prev: Case sensitivity, Up: Searching 1.2.2 Interactive search ------------------------ Two commands implements fully interactive search, where all the base parameters are selected from the mini buffer. -- ELisp Command: C-c s r (rg) This command prompts for 'query', 'file type' and 'directory' and tries to suggest reasonable default values. The 'query' string is interpreted as a regular expression. Default for 'query' is the thing at point and for 'directory' it is the current directory. If the type of the currently visited file is recognized, the corresponding *note file type alias: 10. is suggested as the 'file type' parameter. Invoking this command with the 'universal argument' will trigger confirmation and potential modification of the *note full command line: 11. that will invoke the 'ripgrep' binary. -- ELisp Command: C-c s t (rg-literal) This command works in the same way as *note rg: f. but interprets the 'query' string literally and not as a regular expression. Invoking this command with the 'universal argument' will trigger confirmation and potential modification of the *note full command line: 11. that will invoke the 'ripgrep' binary.  File: rgel.info, Node: Project search, Next: Do what I mean, Prev: Interactive search, Up: Searching 1.2.3 Project search -------------------- A common scenario is to search through a whole project while visiting a file in the project. This essentially means identifying the project root and use that as the top 'directory' when invoking the 'ripgrep' binary. 'rg' supports several ways of identifying a project. Emacs’ major project packages are supported including projectile (https://www.projectile.mx/en/latest/), find-file-in-project (https://github.com/technomancy/find-file-in-project) and builtin project.el (https://github.com/emacs-mirror/emacs/blob/master/lisp/progmodes/project.el). If none of these are used, the fallback is Emacs’ ‘vc-backend’. -- ELisp Command: C-c s p (rg-project) Search in the current project. The 'directory' is selected via one of Emacs’ project packages while 'query string' and 'file type' are prompted for. The 'query string' is interpreted as a regular expression.  File: rgel.info, Node: Do what I mean, Next: Isearch search, Prev: Project search, Up: Searching 1.2.4 Do what I mean -------------------- The 'DWIM' family of search commands tries to be smart by figure out the search parameters from the context without prompting. Thanks to 'ripgrep’s' speed, this allows for new ways of searching by invoking a dwim command and then 'refine' the search from the results buffer. These commands use the word (with the definition of word depending on context) under cursor as the 'query' string. The 'file type' parameter is taken from the type of the currently visited file. If the current file type can not be identified all file types known to 'ripgrep' are used. The fallback can be customized with *note rg-default-alias-fallback: 18. The 'directory' parameter varies between these commands. -- ELisp Command: M-x rg-dwim-project-dir Do a 'DWIM' search in the current *note project: 14. -- ELisp Command: M-x rg-dwim-current-dir Do a 'DWIM' search in the current directory. -- ELisp Command: M-x rg-dwim-current-file Do a 'DWIM' search in the current file. The 'current file' in this context is actually a file 'pattern' exactly matching the current file name in a search starting from current directory. Most of the time this means a single file but if there are multiple files with the same name in a sub directory, those will be searched as well. -- ELisp Command: C-c s d (rg-dwim) This command combines all the 'DWIM' commands to one. The default search is in the *note project dir: 19. With one 'universal argument' *note current directory: 1a. is used and with double 'universal arguments' a *note file search: 1b. is done.  File: rgel.info, Node: Isearch search, Next: File type aliases, Prev: Do what I mean, Up: Searching 1.2.5 Isearch search -------------------- Isearch integration is optional and need to be enabled explicitly in your emacs configuration. See *note installation: 5. for more info. This functionality is similar to emacs built in occur package but offers some additional choices for the search and provides the full functionality of the rg search result buffer. When enabled, the choosen binding can be used from isearch to trigger a menu for extending the isearch to do a ripgrep search in current file, current directory or current project.  File: rgel.info, Node: File type aliases, Next: The menu, Prev: Isearch search, Up: Searching 1.2.6 File type aliases ----------------------- File type aliases are used in 'ripgrep' to filter out the files to search in. The 'ripgrep' binary comes with a default set of aliases that can be extended or overridden from this package by customizing *note rg-custom-type-aliases: 1f. An alias is a mapping between a name and a list of glob patterns (https://en.wikipedia.org/wiki/Glob_%2528programming%2529) matching the files of interest. Selecting an alias when searching is done with completing read of the defined aliases. It is also possible to enter a custom glob pattern if there is no suitable alias defined for the file type. 'rg' defines some internal aliases: Name Meaning -------------------------------------------------------------------------------------------------------------- 'all' all defined types including *note rg-custom-type-aliases: 1f. 'everything' all files. No filtering on type is done. 'custom' used internally in this package for mapping custom glob patterns. Warning: Do not use any of the internal aliases in *note rg-custom-type-aliases: 1f. That would interfere with the package internal usage.  File: rgel.info, Node: The menu, Prev: File type aliases, Up: Searching 1.2.7 The menu -------------- The global *note prefix key: 21. may be bound to a transient prefix command, which means that the key binding will popup a menu. This package is using the same popup menu backend called transient (https://magit.vc/manual/transient) as the magit (https://magit.vc/manual/magit) package. If you are familiar with magit this should feels like home. The menu is mostly interesting when you want to give specific command line flags to the 'ripgrep' binary. When you just want to do a quick search based on the defaults the menu basically acts as a normal keymap. Pressing the ‘rg-menu’ *note prefix key: 21. will popup the menu where command line flags can be selected before triggering the wanted search function. The menu can be customized via the transient API as usual. This package contains some shortcuts to directly add a new command to the menu when defining the command via the *note rg-define-search: 22. macro. (rg-define-search rg-word :format literal :flags ("--word-regexp") :menu ("Custom" "w" "Word")) The ‘:menu’ keyword in the above invocation will trigger insertion of a new menu item bound to key ‘w’ with description 'Word'. The new menu item will be put under the 'Custom' group. This group is not available in the original menu so it will be created. The menu can be triggered from the *note results buffer: 23. with the ‘m’ key. The commands in the menu differs, depending on from where it’s triggered but the available options are the same. The menu does not show all options by default. The visible options can be controlled by the transient suffix levels documented here (https://magit.vc/manual/transient/Enabling-and-Disabling-Suffixes.html#Enabling-and-Disabling-Suffixes). To modify what is enabled at the default level 4 press ‘C-x l’ to enter edit mode when the menu is visible. Then select the option by pressing the key sequence that activates the option and choose the level 4 for that option. It’s also possible to use the transient edit mode for modifying the overall level of the menu to enable more options at once.  File: rgel.info, Node: Results buffer, Next: Search management, Prev: Searching, Up: Usage 1.3 Results buffer ================== The results of a search is shown in the results buffer. This buffer displays search parameters, the full command line and the output of the 'ripgrep' binary. It supports basic navigation between search results editing of the file contents directly from the search buffer and also modification of the current search. The results buffer is a modified 'compilation' buffer and some key bindings and functionality is inherited from the parent and from 'grep mode'. * Menu: * Navigation:: * Refine search:: * Full command line search:: * History navigation:: * Edit and apply (wgrep): Edit and apply wgrep.  File: rgel.info, Node: Navigation, Next: Refine search, Up: Results buffer 1.3.1 Navigation ---------------- Navigation works mostly as in grep/compilation buffers. -- ELisp Command: M-n (compilation-next-error) Move to next line with a match. -- ELisp Command: M-p (compilation-previous-error) Move to previous line with a match. -- ELisp Command: n (next-error-no-select) Move to next line with a match, show that file in other buffer and highlight the match. -- ELisp Command: p (previous-error-no-select) Move to previous line with a match, show that file in other buffer and highlight the match. -- ELisp Command: M-N (rg-next-file) Move to next file header if the results is grouped under a file header (See *note rg-group-result: 2b.). -- ELisp Command: M-P (rg-prev-file) Move to previous file header if the results is grouped under a file header (See *note rg-group-result: 2b.). -- ELisp Command: } (compilation-next-file) Move first match in previous file. -- ELisp Command: { (compilation-previous-file) Move last match in previous file. -- ELisp Command: RET (compile-goto-error) Visit match in file. If *note rg-group-result: 2b. is enabled, the Imenu (https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html) facility is configured to jump across files.  File: rgel.info, Node: Refine search, Next: Full command line search, Prev: Navigation, Up: Results buffer 1.3.2 Refine search ------------------- From the results buffer it’s easy to change the search parameters. Some bindings toggle a flag while others allow you to interactively change the *note base parameters: 9. -- ELisp Command: d (rg-rerun-change-dir) Interactively change search 'directory'. -- ELisp Command: f (rg-rerun-change-files) Interactively change searched 'file types'. -- ELisp Command: t (rg-rerun-change-literal) Interactively change 'search string' interpret the string literally. -- ELisp Command: r (rg-rerun-change-regexp) Interactively change 'search string' interpret the string as a regular expression. Tip: *note rg-rerun-change-regexp: 34. and *note rg-rerun-change-literal: 33. are used for switching between regular expression and literal search. So for quick switching between search modes with the same search string, just press the respective key and then ‘RET’. -- ELisp Command: g (rg-recompile) Rerun the current search without changing any parameters. -- ELisp Command: c (rg-rerun-toggle-case) Toggle case sensitivity of search. The state of the flag is shown in the '[case]' header field. -- ELisp Command: i (rg-rerun-toggle-ignore) Toggle if ignore files are respected. The state of the flag is shown in the '[ign]' header field. Tip: It is possible to create and bind your own toggle flags with the macro *note rg-define-toggle: 38. -- ELisp Command: m (rg-menu) Fire up *note the menu: a. for full access to options and flags.  File: rgel.info, Node: Full command line search, Next: History navigation, Prev: Refine search, Up: Results buffer 1.3.3 Full command line search ------------------------------ Some search commands (See *note rg: f. or *note rg-literal: 12.) allow you to edit the final command line before invoking the search by giving a 'universal argument'. This can be used to invoke features of the 'ripgrep' binary that is not supported in this package’s interface. This could be specific flags, searching in multiple directories etc. Note: Using full command line search will disable refinement of the search from the result buffer.  File: rgel.info, Node: History navigation, Next: Edit and apply wgrep, Prev: Full command line search, Up: Results buffer 1.3.4 History navigation ------------------------ Each search result is stored in the search history, which is a per results buffer property. History can be navigated back and forward, the forward history is cleared when a new search is done. -- ELisp Command: C-c < (rg-back-history) Navigate back in history. -- ELisp Command: C-c > (rg-forward-history) Navigate forward in history. Tip: The key bindings here are slightly inconvenient so invoking this via *note the menu: a. by pressing ‘m b’ and ‘m w’ is more ergonomic.  File: rgel.info, Node: Edit and apply wgrep, Prev: History navigation, Up: Results buffer 1.3.5 Edit and apply (wgrep) ---------------------------- The results buffer supports inline editing via the wgrep (https://github.com/mhayashi1120/Emacs-wgrep) package. This is setup automatically when 'rg' is loaded. -- ELisp Command: e (wgrep-change-to-wgrep-mode) Make the search results editable by enabling ‘wgrep’ mode. When done press ‘C-c C-c’ to commit your changes to the underlying files or ‘C-c C-k’ to drop the changes.  File: rgel.info, Node: Search management, Next: Multi line search, Prev: Results buffer, Up: Usage 1.4 Search management ===================== The result buffer is named ‘*rg*’ and 'rg' reuse the same result buffer for new searches. If you want to store a search while continuing doing new searches there are two ways of doing that. -- ELisp Command: s (rg-save-search) Save the search buffer by renaming it to a unique new name. This is available both outside and inside a result buffer. Outside of the result buffer it’s bound to ‘C-c s s’. If you want to keep all search buffers until manually killed you can use this snippet in your init file. (defadvice rg-run (before rg-run-before activate) (rg-save-search)) -- ELisp Command: S (rg-save-search-as-name) Save the search buffer and interactively give it a specific name. This is available both outside and inside a result buffer. Outside of the result buffer it’s bound to ‘C-c s S’. The default buffer name can be customized with *note rg-buffer-name: 46. This setting considers dir local variables and it’s even possible to use a function to get a really dynamic setup. Having a lot of search buffers floating around can easily get messy. To help keeping this under control there is a search manager. The manager is simply a modified ‘ibuffer’ that lists all the results buffers, shows some data about the searches and make it possible to kill of some unused etc. -- ELisp Command: L (rg-list-searches) Open the search manager. This is available both in result buffer and globally bound to ‘C-c s l’. -- ELisp Command: C-c s k (rg-kill-saved-searches) Kill all saved searches except for the one that matches *note rg-buffer-name: 46. This is available both in result buffer and globally bound to ‘C-c s k’. Warning: If you have a dynamic *note rg-buffer-name: 46. setup, only one buffer that matches your current criteria (dir locals or project for instance) will be kept. So be careful when killing saved searches to avoid losing important search results.  File: rgel.info, Node: Multi line search, Prev: Search management, Up: Usage 1.5 Multi line search ===================== By default, ripgrep does matching per line. The ‘--multiline’ flag can be used for enabling matching over multiple lines. This flag is available in the *note rg-menu: a. as an option. The ‘--multiline’ flag does not match new line characters with the ‘.’ as one might expect though. A separate flag is used to allow this, ‘--multiline-dotall’. The casual user of multi line search commonly want this flag on by default so it’s recommended to add this to *note rg-command-line-flags: 4b. to avoid having to trigger this flag manually from the menu. See the ripgrep manual page for more info about the multi line flags.  File: rgel.info, Node: Configuration, Next: Contribute, Prev: Usage, Up: Top 2 Configuration *************** * Menu: * Customization:: * Faces:: * Configuration functions:: * Hooks:: * Configuration macros:: * Use with evil-mode:: * Customizing the menu::  File: rgel.info, Node: Customization, Next: Faces, Up: Configuration 2.1 Customization ================= Customization is done via the Emacs customization system. The group ‘rg’ is the main group of the package. M-x customize-group [RET] rg [RET] -- ELisp Variable: rg-executable [(executable-find "rg")] The 'ripgrep' executable to use. Could be an absolute path or just the base name if the executable is in the path. The default is using ‘executable-find’ to locate the command. If you want to use this package with tramp it might be better to set it to just “rg” in order to let the OS find the binary where it’s invoked. From Emacs 27.1, the tramp use case is by default handled automatically. See *note rg-executable-per-connection: 51. for details. -- ELisp Variable: rg-executable-per-connection [t] This setting only has effect in Emacs 27.1 or later. Handle the *note rg-executable: 50. automatically for different hosts if used with tramp. ‘executable-find’ for “rg” binary will be invoked on remote hosts to determine the path to ripgrep. The result is stored per connection. -- ELisp Variable: rg-custom-type-aliases [nil] An association list that maps file type aliases to a space delimited string with file globs. These are combined with the 'ripgrep' builtin file aliases. Example: (setq rg-custom-type-aliases '(("foo" . "*.foo *.bar") ("baz" . "*.baz *.qux"))) You may also add lambdas to ‘rg-custom-type-aliases’ to add aliases dynamically based on mode, directory, project, etc. (add-to-list 'rg-custom-type-aliases (lambda () (when (in-frontend-app) (cons "ui" "*.js *.hbs *.json")))) -- ELisp Variable: rg-prioritized-type-aliases [nil] A list of aliases that are prioritized among ripgrep’s builtin aliases when selecting the alias based on the buffer file name. This list contains only the alias names and the order between the items does not matter. Example: (setq rg-custom-type-aliases '("cpp" "puppet")) -- ELisp Variable: rg-default-alias-fallback ["everything"] This setting controls the default alias used when no alias can be recognized for the current buffer. ‘all’ or ‘everything’ are reasonable values for this variable. -- ELisp Variable: rg-command-line-flags [nil] A list of command line flags that will be appended to the 'ripgrep' command line. Must either be a list of flags or a function that returns a list of flags. -- ELisp Variable: rg-group-result [t] Controls the layout of the results buffer. If non ‘nil’, each file name is displayed once and matches are grouped under that filename instead of repeating the filename on each match. This is essentially the layout of the ‘--no-heading’ 'ripgrep' command line flag. -- ELisp Variable: rg-show-columns [nil] Controls if column numbers are used in the search result. -- ELisp Variable: rg-ignore-case [case-fold-search] Setting that controls if case sensitive search is made or not. It can essentially be 'on', 'off' or 'smart'. The 'smart' setting will trigger an analyze of the search string and if it’s all lower case, the search will be case 'insensitive', otherwise it will be case 'sensitive'. The following values are valid: - 'case-fold-search' - A non nil value of ‘case-fold-search’ will trigger smart case behavior. - 'smart' - Smart case behavior. - 'force' - Always ignore case. - 'nil' - Always consider case. -- ELisp Variable: rg-hide-command [t] Hide most of command line by default. This is enabled by default and can be set to ‘nil’ to show full command line. This can be toggled in the results buffer by clicking on the command line. -- ELisp Variable: rg-keymap-prefix ["C-c s"] This variable sets the default prefix used for the global key bindings. Note that ‘rg-enable-default-bindings’ needs to be invoked for the bindings to be enabled. -- ELisp Variable: rg-use-transient-menu [t] Controls whether ‘rg-menu’ will be used by default or not. It’s also possible to enable the menu explicitly with (rg-enable-menu) -- ELisp Variable: rg-show-header [t] Controls if the search info header is shown in the result buffer. This is enabled by default but can be disabled by setting this variable to ‘nil’. -- ELisp Variable: rg-buffer-name ["rg"] Controls the name of the results buffer. It may be 'string' or 'function'. This name will be surrounded by ‘*’ to yield the final buffer name so if this setting is ‘foo’ the buffer name will be ‘*foo*’. One useful case of using it is to have separate result buffers per project. One can set this variable in 'dir-locals' file or set it to function. Example, this function will set results buffer name based on 'project-current': (defun my-rg-buffer-name () (let ((p (project-current))) (if p (format "rg %s" (abbreviate-file-name (cdr p))) "rg"))) -- ELisp Variable: rg-ignore-ripgreprc [t] Controls if the ripgreprc (https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#configuration-file) file should be ignored or not. If ‘nil’, the config file will be used, otherwise it will be ignored. The default is to ignore this file in order to avoid that conflicting settings have impact on this package’s behavior. Setting this to ‘nil’ may affect core functionality of this package. Especially changing colors can affect parsing of the output and result in a broken results buffer. * Menu: * Position numbers alignment::  File: rgel.info, Node: Position numbers alignment, Up: Customization 2.1.1 Position numbers alignment -------------------------------- When operating 'rg' in grouped output mode (*note rg-group-result: 2b. is non nil), it’s possible to control how the line and column numbers are displayed in the result buffer. Example settings: (setq rg-align-position-numbers t) (setq rg-align-line-number-field-length 3) (setq rg-align-column-number-field-length 3) (setq rg-align-line-column-separator "#") (setq rg-align-position-content-separator "|") Will yield the following format: File: matched_file.foo 1# 2|match1 888# 10|match2 -- ELisp Variable: rg-align-position-numbers [t] Setting this to ‘t’ will align line and column numbers in columns padded with white space. -- ELisp Variable: rg-align-line-number-field-length [4] Defines the length of the line number field. -- ELisp Variable: rg-align-column-number-field-length [3] Defines the length of the column number field. -- ELisp Variable: rg-align-line-column-separator [" "] Separator string used between line and column numbers. ‘nil’ means use default separator from 'ripgrep'. -- ELisp Variable: rg-align-position-content-separator [" "] Separator string used between the position numbers and matched content. ‘nil’ means use default separator from 'ripgrep'.  File: rgel.info, Node: Faces, Next: Configuration functions, Prev: Customization, Up: Configuration 2.2 Faces ========= All faces are in the subgroup ‘rg-face’ of the main group ‘rg’. M-x customize-group [RET] rg-face [RET] * Menu: * Results buffer: Results buffer<2>. * Header line::  File: rgel.info, Node: Results buffer<2>, Next: Header line, Up: Faces 2.2.1 Results buffer -------------------- -- ELisp Variable: rg-match-face [match] Face used to highlight matches in result. -- ELisp Variable: rg-error-face [compilation-error] Face used to highlight errors when invoking 'ripgrep'. -- ELisp Variable: rg-context-face [shadow] Face used to highlight context lines in 'ripgrep' output when ‘--context-lines’ flag is used. -- ELisp Variable: rg-info-face [compilation-info] Face used to highlight general info in results buffer. For instance the number of matches found. -- ELisp Variable: rg-warning-face [compilation-warning] Face used to highlight warnings in the 'ripgrep' output. -- ELisp Variable: rg-filename-face [rg-info-face] Face used to highlight filenames in the output. -- ELisp Variable: rg-file-tag-face [rg-info-face] Face used for the ‘File:’ tag in grouped results output. -- ELisp Variable: rg-line-number-face [compilation-line-number] Face used on line numbers. -- ELisp Variable: rg-column-number-face [compilation-column-number] Face used on column numbers. -- ELisp Variable: rg-match-position-face [default] Face added to file positions. This is the start of a matching line and depending on configuration may be, file name, column number and line number.  File: rgel.info, Node: Header line, Prev: Results buffer<2>, Up: Faces 2.2.2 Header line ----------------- -- ELisp Variable: rg-toggle-on-face [rg-file-tag-face] Face used for flags that are toggled ‘on’. -- ELisp Variable: rg-toggle-off-face [rg-error-face] Face used for flags that are toggled ‘off’. -- ELisp Variable: rg-literal-face [rg-filename-face] Face used the on the ‘literal’ marker in the header line. -- ELisp Variable: rg-regexp-face [compilation-line-number] Face used the on the ‘regexp’ marker in the header line.  File: rgel.info, Node: Configuration functions, Next: Hooks, Prev: Faces, Up: Configuration 2.3 Configuration functions =========================== -- ELisp Function: (rg-enable-default-bindings &optional, prefix) Enable the default keyboard bindings for the package with prefix key. If *note rg-use-transient-menu: 55. is on this will enable the menu instead of activating the global bindings. If ‘prefix’ is not provided *note rg-keymap-prefix: 21. will be used. -- ELisp Function: (rg-enable-menu &optional, prefix) Enable the *note rg-menu: a. with prefix key. This bypass *note rg-use-transient-menu: 55. setting. If ‘prefix’ is not provided *note rg-keymap-prefix: 21. will be used. -- ELisp Function: (rg-use-old-defaults) This function is provided to keep backwards compatibility with versions older than 2.0.0. In this version default settings as well as key bindings changed and to bring back the old defaults call this function in your init file.  File: rgel.info, Node: Hooks, Next: Configuration macros, Prev: Configuration functions, Up: Configuration 2.4 Hooks ========= -- ELisp Variable: rg-finish-functions [nil] Functions to call when a ripgrep search is finished. Each function is called with two arguments: the compilation buffer, and a string describing how the process finished. -- ELisp Variable: rg-filter-hook [nil] Hook run after new content has been inserted in in the rg buffer. This hook is called every time the rg buffer has been updated with new content and filtered internally by the package. -- ELisp Variable: rg-mode-hook [(wgrep-rg-setup)] Hook run after entering rg mode.  File: rgel.info, Node: Configuration macros, Next: Use with evil-mode, Prev: Hooks, Up: Configuration 2.5 Configuration macros ======================== -- ELisp Function: (rg-define-toggle flag, &optional, key, default) This is a macro that can be used to define custom 'ripgrep' flag toggling functions in the result buffer. The macro takes the flag (and potential value) as an argument and optionally binds the toggle function to a key. If ‘default’ is non nil the flag is used by default. The function defined by this macro will be named as the flag name stripped with leading dashes and prefixed with ‘rg-custom-toggle-flag-’. (rg-define-toggle "-uu" "I" t) Creates a function named ‘rg-custom-toggle-flag-uu’ that is on by default and bound to ‘I’ in 'rg' result buffer. (rg-define-toggle "--context 3" (kbd "C-c c")) Creates a function named ‘rg-custom-toggle-flag-context’ that is off by default and bound to ‘C-c c’ in 'rg' result buffer. -- ELisp Function: (rg-define-search name, &rest, args) This macro can be used to define custom search functions in a declarative style. Default implementations for common behavior is available and custom forms can also be used. It optionally starts with a string that is used as the docstring for the defined function. The rest of the arguments contain key value pairs according to the specification below. All keys are optional with specified default if left out. - ':query' - Method for retrieving the search string. Allowed values are ‘point’ which means extract thing at point and ‘ask’ which means prompt the user for a string. Any form that evaluates to a string is allowed. Default is ‘ask’. - ':format' - Specifies if ‘:query’ is interpreted literally (‘literal’) or as a regexp (‘regexp’). If it is a form, eg. ‘(not current-prefix-arg)’, and is non-nil the ‘:query’ is interpreted literally, otherwise as a regexp. Default is ‘regexp’. - ':files' - Form that evaluates to a file alias or custom file glob. ‘current’ means extract alias from current buffer file name, ‘ask’ will prompt the user. Default is ‘ask’. - ':dir' - Root search directory. Allowed values are ‘ask’ for user prompt, ‘current’ for current dir and ‘project’ for project root. Any form that evaluates to a directory string is also allowed. Default is ‘ask’. - ':confirm' - ‘never’, ‘always’, or ‘prefix’ are allowed values. Specifies if the the final search command line string can be modified and confirmed the user. Default is ‘never’. - ':flags' - ‘ask’ or a list of command line flags that will be used when invoking the search. - ':menu' - Bind the command into ‘rg-menu’. Must be a list with three items in it. The first item is the description of the group in which the new command will appear. If the group does not exist a new will be created. The second item is the key binding for this new command (ether a key vector or a key description string) and the third item is the description of the command that will appear in the menu. Examples: (rg-define-search search-everything-at-home "Search files including hidden in home directory" :query ask :format literal :files "everything" :flags ("--hidden") :dir (getenv "HOME") :menu ("Search" "h" "Home")) (rg-define-search rg-emacs "Search the emacs lisp source code." :dir "/usr/share/emacs/25.2/lisp/" :flags '("-z") :files "*.{el,el.gz}" :menu ("Custom" "L" "lisp"))  File: rgel.info, Node: Use with evil-mode, Next: Customizing the menu, Prev: Configuration macros, Up: Configuration 2.6 Use with evil-mode ====================== Some key bindings clash with 'evil-mode'. Recommendation is to use evil 'motion' state for the results buffer and then switch to evil 'normal' mode when editing in 'wgrep-mode'. Some adjustments need to be done to avoid the clashes though. This is a start of a configuration. This let 'rg-mode'’s key bindings override the motion state map bindings based on that these motion keys are not important in an 'rg' results buffer. Adjust this to your preferred use case: (with-eval-after-load 'rg (advice-add 'wgrep-change-to-wgrep-mode :after #'evil-normal-state) (advice-add 'wgrep-to-original-mode :after #'evil-motion-state) (defvar rg-mode-map) (add-to-list 'evil-motion-state-modes 'rg-mode) (evil-add-hjkl-bindings rg-mode-map 'motion "e" #'wgrep-change-to-wgrep-mode "g" #'rg-recompile "t" #'rg-rerun-change-literal))  File: rgel.info, Node: Customizing the menu, Prev: Use with evil-mode, Up: Configuration 2.7 Customizing the menu ======================== The menu can be modified from the emacs configuration file. To add a new 'switch' before the option triggered by ‘-n’ at suffix level 3: (transient-insert-suffix 'rg-menu "-n" '(3 "-o" "Only print matches" "--only-matching")) To add a new 'option' before the option triggered by ‘-g’ at suffix level 4: (transient-insert-suffix 'rg-menu "-g" '(4 "-f" "Pattern file" "--file=")) The ‘=’ in ‘--file=’ triggers argument input for the flag. To remove an item from the menu specify the trigger key in the transient remove command. For example, to remove the ‘Search hidden files’ switch use the following: (transient-remove-suffix 'rg-menu "-h") Please refer to the transient (https://magit.vc/manual/transient/Modifying-Existing-Transients.html#Modifying-Existing-Transients) documentation for details on customizing the menu. This package also adds a convenience function for appending new 'commands' to the menu in the groups at the bottom. -- ELisp Function: (rg-menu-transient-insert group, key, description, command) This inserts a new command under ‘group’ if it exists, otherwise a new group is created. ‘key’, ‘description’ and ‘command’ is as for the ‘transient-insert-suffix’ function. For example to insert a new command under ‘Search’ group: (rg-menu-transient-insert "Search" "m" "My search" 'my-search-command) It’s usually better to use the ‘:menu’ key of the *note rg-define-search: 22. macro to define a search function and adding it to the menu in one go.  File: rgel.info, Node: Contribute, Next: License, Prev: Configuration, Up: Top 3 Contribute ************ Contributions are very welcome. Development is done in the GitHub repository (https://github.com/dajva/rg.el). If you find a bug, please report it in the issue tracker (https://github.com/dajva/rg.el/issues). * Menu: * Pull requests:: * Docker:: * Tests:: * Documentation::  File: rgel.info, Node: Pull requests, Next: Docker, Up: Contribute 3.1 Pull requests ================= If you want to submit a patch, please submit a GitHub pull request (https://github.com/dajva/rg.el/pulls). If you want to submit any larger code changes, please create an issue first for discussion. Some features does not fit well into this package and there is also good to agree on the general design before doing any major work. The minimum requirements for a pull request to be accepted is that all existing tests pass and test coverage should not decrease. Often a patch also needs additional tests, new/changed documentation etc. Don’t strive to submit a perfect pull request directly. It’s often better to submit something simple that shows the main direction of the new code in order to discuss the best way to proceed and what additions are needed.  File: rgel.info, Node: Docker, Next: Tests, Prev: Pull requests, Up: Contribute 3.2 Docker ========== Docker can be used to run the tests or generate documentation locally without installing dependencies on the host and to test with different emacs versions. To use docker, just set the ‘USE_DOCKER’ variable when running the tests. The ‘EMACS_VERSION’ variable can be used to select emacs version. Note that dash (‘-’) is used instead of points (‘.’) in the version numbering. So emacs 28.2 is specified as ‘28-2’. Emacs are installed from ‘https://github.com/purcell/nix-emacs-ci’ so only emacs versions supported in that repository will work. - Build docker container: # Don't use the USE_DOCKER variable here make EMACS_VERSION=26-2 docker-build - To run all the tests in docker image: make USE_DOCKER=true test - Use a specific emacs version: make USE_DOCKER=true EMACS_VERSION=snapshot - Generate html documentation: make USE_DOCKER=true html  File: rgel.info, Node: Tests, Next: Documentation, Prev: Docker, Up: Contribute 3.3 Tests ========= Cask (https://cask.readthedocs.io/) is used for testing. The tests are written using the Emacs built in ERT framework and follows the conventions of ert runner (https://github.com/rejeep/ert-runner.el) although ert_runner is no longer used. There are also compilation tests, style check, package verification etc. * Menu: * Setup:: * Running::  File: rgel.info, Node: Setup, Next: Running, Up: Tests 3.3.1 Setup ----------- - Install cask (https://cask.readthedocs.io/en/latest/guide/installation.html) - Install all developer dependencies: make deps  File: rgel.info, Node: Running, Prev: Setup, Up: Tests 3.3.2 Running ------------- - Run the whole test suite: make test - Run only the unit/integration tests: make ert-test - Manually test the package with Emacs: cask emacs -Q -L . --eval="(progn (require 'rg) (enable-default-bindings))"  File: rgel.info, Node: Documentation, Prev: Tests, Up: Contribute 3.4 Documentation ================= The documentation is written in org mode. The export target is restructured text (https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) suitable for the Sphinx (http://www.sphinx-doc.org/en/master/) documentation generator. Sphinx is used to export the output from org mode to info and HTML documentation. The resulting .rst files are used for the online documentation on ‘https://readthedocs.io’. The end user documentation is generated after committing to the main repository. It’s advisable to build both html and info documentation locally and verify the output to make sure the changes looks as expected. * Menu: * Setup: Setup<2>. * Building::  File: rgel.info, Node: Setup<2>, Next: Building, Up: Documentation 3.4.1 Setup ----------- - Install Sphinx (http://www.sphinx-doc.org/en/master/usage/installation.html) sudo apt install python3-sphinx python3-sphinx-rtd-theme - Install makeinfo sudo apt install texinfo  File: rgel.info, Node: Building, Prev: Setup<2>, Up: Documentation 3.4.2 Building -------------- - HTML documentation make html Open ‘docs/rst/_build/html/index.html’ in a browser. - Info documentation make info To view in emacs: C-u M-x info [RET] Then select the ‘docs/rst/_build/info/rgel.info’ file.  File: rgel.info, Node: License, Next: Index, Prev: Contribute, Up: Top 4 License ********* 'rg' is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 'rg' 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 *note GNU General Public License: 94. for more details. * Menu: * GNU General Public License::  File: rgel.info, Node: GNU General Public License, Up: License 4.1 GNU General Public License ============================== GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .  File: rgel.info, Node: Index, Prev: License, Up: Top Index ***** [index] * Menu: * compilation-next-error (command): Navigation. (line 8) * compilation-next-file (command): Navigation. (line 36) * compilation-previous-error (command): Navigation. (line 12) * compilation-previous-file (command): Navigation. (line 40) * compile-goto-error (command): Navigation. (line 44) * next-error-no-select (command): Navigation. (line 16) * previous-error-no-select (command): Navigation. (line 21) * rg (command): Interactive search. (line 9) * rg-align-column-number-field-length (customize option): Position numbers alignment. (line 33) * rg-align-line-column-separator (customize option): Position numbers alignment. (line 37) * rg-align-line-number-field-length (customize option): Position numbers alignment. (line 29) * rg-align-position-content-separator (customize option): Position numbers alignment. (line 42) * rg-align-position-numbers (customize option): Position numbers alignment. (line 24) * rg-back-history (command): History navigation. (line 10) * rg-buffer-name (customize option): Customization. (line 128) * rg-column-number-face (customize option): Results buffer<2>. (line 40) * rg-command-line-flags (customize option): Customization. (line 68) * rg-context-face (customize option): Results buffer<2>. (line 14) * rg-custom-type-aliases (customize option): Customization. (line 29) * rg-default-alias-fallback (customize option): Customization. (line 62) * rg-define-search (function): Configuration macros. (line 28) * rg-define-toggle (function): Configuration macros. (line 6) * rg-dwim (command): Do what I mean. (line 35) * rg-dwim-current-dir (command): Do what I mean. (line 23) * rg-dwim-current-file (command): Do what I mean. (line 27) * rg-dwim-project-dir (command): Do what I mean. (line 19) * rg-enable-default-bindings (function): Configuration functions. (line 6) * rg-enable-menu (function): Configuration functions. (line 13) * rg-error-face (customize option): Results buffer<2>. (line 10) * rg-executable (customize option): Customization. (line 11) * rg-executable-per-connection (customize option): Customization. (line 21) * rg-file-tag-face (customize option): Results buffer<2>. (line 32) * rg-filename-face (customize option): Results buffer<2>. (line 28) * rg-filter-hook (customize option): Hooks. (line 13) * rg-finish-functions (customize option): Hooks. (line 6) * rg-forward-history (command): History navigation. (line 14) * rg-group-result (customize option): Customization. (line 74) * rg-hide-command (customize option): Customization. (line 103) * rg-ignore-case (customize option): Customization. (line 86) * rg-ignore-ripgreprc (customize option): Customization. (line 146) * rg-info-face (customize option): Results buffer<2>. (line 19) * rg-keymap-prefix (customize option): Customization. (line 109) * rg-kill-saved-searches (command): Search management. (line 43) * rg-line-number-face (customize option): Results buffer<2>. (line 36) * rg-list-searches (command): Search management. (line 38) * rg-literal (command): Interactive search. (line 23) * rg-literal-face (customize option): Header line. (line 14) * rg-match-face (customize option): Results buffer<2>. (line 6) * rg-match-position-face (customize option): Results buffer<2>. (line 44) * rg-menu (command): Refine search. (line 51) * rg-menu-transient-insert (function): Customizing the menu. (line 33) * rg-mode-hook (customize option): Hooks. (line 19) * rg-next-file (command): Navigation. (line 26) * rg-prev-file (command): Navigation. (line 31) * rg-prioritized-type-aliases (customize option): Customization. (line 50) * rg-project (command): Project search. (line 17) * rg-recompile (command): Refine search. (line 34) * rg-regexp-face (customize option): Header line. (line 18) * rg-rerun-change-dir (command): Refine search. (line 10) * rg-rerun-change-files (command): Refine search. (line 14) * rg-rerun-change-literal (command): Refine search. (line 18) * rg-rerun-change-regexp (command): Refine search. (line 23) * rg-rerun-toggle-case (command): Refine search. (line 38) * rg-rerun-toggle-ignore (command): Refine search. (line 43) * rg-save-search (command): Search management. (line 10) * rg-save-search-as-name (command): Search management. (line 22) * rg-show-columns (customize option): Customization. (line 82) * rg-show-header (customize option): Customization. (line 122) * rg-toggle-off-face (customize option): Header line. (line 10) * rg-toggle-on-face (customize option): Header line. (line 6) * rg-use-old-defaults (function): Configuration functions. (line 19) * rg-use-transient-menu (customize option): Customization. (line 115) * rg-warning-face (customize option): Results buffer<2>. (line 24) * wgrep-change-to-wgrep-mode (command): Edit and apply wgrep. (line 10)  Tag Table: Node: Top317 Ref: index doc496 Ref: 0496 Node: Usage1496 Ref: usage doc1573 Ref: 11573 Ref: usage rg-manual1573 Ref: 21573 Ref: usage usage1573 Ref: 31573 Node: Installation1694 Ref: usage id11764 Ref: 41764 Ref: usage installation1764 Ref: 51764 Node: Searching5722 Ref: usage id25815 Ref: 85815 Ref: usage searching5815 Ref: 95815 Node: Case sensitivity6733 Ref: usage case-sensitivity6820 Ref: b6820 Node: Interactive search7513 Ref: usage basic-search7623 Ref: d7623 Ref: usage interactive-search7623 Ref: e7623 Ref: usage command-rg7790 Ref: f7790 Ref: usage command-rg-literal8463 Ref: 128463 Node: Project search8839 Ref: usage id38947 Ref: 138947 Ref: usage project-search8947 Ref: 148947 Ref: usage command-rg-project9618 Ref: 159618 Node: Do what I mean9892 Ref: usage do-what-i-mean9996 Ref: 169996 Ref: usage id49996 Ref: 179996 Ref: usage command-rg-dwim-project-dir10739 Ref: 1910739 Ref: usage command-rg-dwim-current-dir10842 Ref: 1a10842 Ref: usage command-rg-dwim-current-file10937 Ref: 1b10937 Ref: usage command-rg-dwim11343 Ref: 1c11343 Node: Isearch search11649 Ref: usage id511756 Ref: 1d11756 Ref: usage isearch-search11756 Ref: 611756 Node: File type aliases12301 Ref: usage file-type-aliases12402 Ref: 1012402 Ref: usage id612402 Ref: 1e12402 Node: The menu13765 Ref: usage id713843 Ref: 2013843 Ref: usage the-menu13843 Ref: a13843 Node: Results buffer15992 Ref: usage id816090 Ref: 2416090 Ref: usage results-buffer16090 Ref: 2316090 Node: Navigation16737 Ref: usage navigation16818 Ref: 2516818 Ref: usage command-compilation-next-error16910 Ref: 2616910 Ref: usage command-compilation-previous-error16997 Ref: 2716997 Ref: usage command-next-error-no-select17092 Ref: 2817092 Ref: usage command-previous-error-no-select17236 Ref: 2917236 Ref: usage command-rg-next-file17388 Ref: 2a17388 Ref: usage command-rg-prev-file17543 Ref: 2c17543 Ref: usage command-compilation-next-file17702 Ref: 2d17702 Ref: usage command-compilation-previous-file17789 Ref: 2e17789 Ref: usage command-compile-goto-error17879 Ref: 2f17879 Node: Refine search18120 Ref: usage refine-search18234 Ref: 3018234 Ref: usage command-rg-rerun-change-dir18451 Ref: 3118451 Ref: usage command-rg-rerun-change-files18542 Ref: 3218542 Ref: usage command-rg-rerun-change-literal18638 Ref: 3318638 Ref: usage command-rg-rerun-change-regexp18766 Ref: 3418766 Ref: usage command-rg-recompile19205 Ref: 3519205 Ref: usage command-rg-rerun-toggle-case19306 Ref: 3619306 Ref: usage command-rg-rerun-toggle-ignore19459 Ref: 3719459 Ref: usage command-rg-menu19731 Ref: 3919731 Node: Full command line search19834 Ref: usage full-command-line-search19956 Ref: 1119956 Ref: usage id919956 Ref: 3a19956 Node: History navigation20480 Ref: usage history-navigation20609 Ref: 3b20609 Ref: usage id1020609 Ref: 3c20609 Ref: usage command-rg-back-history20855 Ref: 3d20855 Ref: usage command-rg-forward-history20931 Ref: 3e20931 Node: Edit and apply wgrep21177 Ref: usage edit-and-apply21273 Ref: 3f21273 Ref: usage edit-and-apply-wgrep21273 Ref: 4021273 Ref: usage command-wgrep-change-to-wgrep-mode21495 Ref: 4121495 Node: Search management21741 Ref: usage id1221847 Ref: 4221847 Ref: usage search-management21847 Ref: 4321847 Ref: usage command-rg-save-search22088 Ref: 4422088 Ref: usage command-rg-save-search-as-name22531 Ref: 4522531 Ref: usage command-rg-list-searches23272 Ref: 4723272 Ref: usage command-rg-kill-saved-searches23427 Ref: 4823427 Node: Multi line search23928 Ref: usage id1324011 Ref: 4924011 Ref: usage multi-line-search24011 Ref: 4a24011 Node: Configuration24698 Ref: configuration doc24782 Ref: 4c24782 Ref: configuration configuration24782 Ref: 4d24782 Node: Customization24964 Ref: configuration customization25039 Ref: 4e25039 Ref: configuration id125039 Ref: 4f25039 Ref: configuration option-rg-executable25230 Ref: 5025230 Ref: configuration option-rg-executable-per-connection25791 Ref: 5125791 Ref: configuration option-rg-custom-type-aliases26162 Ref: 1f26162 Ref: configuration option-rg-prioritized-type-aliases26824 Ref: 5226824 Ref: configuration option-rg-default-alias-fallback27204 Ref: 1827204 Ref: configuration option-rg-command-line-flags27455 Ref: 4b27455 Ref: configuration option-rg-group-result27678 Ref: 2b27678 Ref: configuration option-rg-show-columns28020 Ref: 5328020 Ref: configuration option-rg-ignore-case28127 Ref: c28127 Ref: configuration option-rg-hide-command28767 Ref: 5428767 Ref: configuration option-rg-keymap-prefix29021 Ref: 2129021 Ref: configuration option-rg-use-transient-menu29253 Ref: 5529253 Ref: configuration option-rg-show-header29459 Ref: 5629459 Ref: configuration option-rg-buffer-name29667 Ref: 4629667 Ref: configuration option-rg-ignore-ripgreprc30380 Ref: 730380 Node: Position numbers alignment31015 Ref: configuration id231089 Ref: 5731089 Ref: configuration position-numbers-alignment31089 Ref: 5831089 Ref: configuration option-rg-align-position-numbers31696 Ref: 5931696 Ref: configuration option-rg-align-line-number-field-length31853 Ref: 5a31853 Ref: configuration option-rg-align-column-number-field-length31963 Ref: 5b31963 Ref: configuration option-rg-align-line-column-separator32077 Ref: 5c32077 Ref: configuration option-rg-align-position-content-separator32256 Ref: 5d32256 Node: Faces32457 Ref: configuration faces32564 Ref: 5e32564 Node: Results buffer<2>32767 Ref: configuration results-buffer32844 Ref: 5f32844 Ref: configuration option-rg-match-face32887 Ref: 6032887 Ref: configuration option-rg-error-face32978 Ref: 6132978 Ref: configuration option-rg-context-face33094 Ref: 6233094 Ref: configuration option-rg-info-face33249 Ref: 6333249 Ref: configuration option-rg-warning-face33411 Ref: 6433411 Ref: configuration option-rg-filename-face33533 Ref: 6533533 Ref: configuration option-rg-file-tag-face33640 Ref: 6633640 Ref: configuration option-rg-line-number-face33760 Ref: 6733760 Ref: configuration option-rg-column-number-face33860 Ref: 6833860 Ref: configuration option-rg-match-position-face33966 Ref: 6933966 Node: Header line34185 Ref: configuration header-line34262 Ref: 6a34262 Ref: configuration header-line-config34262 Ref: 6b34262 Ref: configuration option-rg-toggle-on-face34299 Ref: 6c34299 Ref: configuration option-rg-toggle-off-face34410 Ref: 6d34410 Ref: configuration option-rg-literal-face34520 Ref: 6e34520 Ref: configuration option-rg-regexp-face34644 Ref: 6f34644 Node: Configuration functions34773 Ref: configuration configuration-functions34872 Ref: 7034872 Ref: configuration id334872 Ref: 7134872 Ref: configuration function-rg-enable-default-bindings34929 Ref: 7234929 Ref: configuration function-rg-enable-menu35275 Ref: 7335275 Ref: configuration function-rg-use-old-defaults35523 Ref: 7435523 Node: Hooks35816 Ref: configuration hooks35930 Ref: 7535930 Ref: configuration id435930 Ref: 7635930 Ref: configuration option-rg-finish-functions35951 Ref: 7735951 Ref: configuration option-rg-filter-hook36186 Ref: 7836186 Ref: configuration option-rg-mode-hook36429 Ref: 7936429 Node: Configuration macros36521 Ref: configuration configuration-macros36630 Ref: 7a36630 Ref: configuration id536630 Ref: 7b36630 Ref: configuration function-rg-define-toggle36681 Ref: 3836681 Ref: configuration function-rg-define-search37590 Ref: 2237590 Node: Use with evil-mode40589 Ref: configuration use-with-evil-mode40713 Ref: 7c40713 Node: Customizing the menu41687 Ref: configuration customizing-the-menu41782 Ref: 7d41782 Ref: configuration id641782 Ref: 7e41782 Ref: configuration function-rg-menu-transient-insert42818 Ref: 7f42818 Node: Contribute43441 Ref: contribute doc43527 Ref: 8043527 Ref: contribute contribute43527 Ref: 8143527 Node: Pull requests43833 Ref: contribute id143906 Ref: 8243906 Ref: contribute pull-requests43906 Ref: 8343906 Node: Docker44712 Ref: contribute docker44799 Ref: 8444799 Ref: contribute id244799 Ref: 8544799 Node: Tests45769 Ref: contribute id345856 Ref: 8645856 Ref: contribute tests45856 Ref: 8745856 Node: Setup46226 Ref: contribute setup46287 Ref: 8846287 Ref: contribute tests-setup46287 Ref: 8946287 Node: Running46463 Ref: contribute running46524 Ref: 8a46524 Ref: contribute tests-running46524 Ref: 8b46524 Node: Documentation46805 Ref: contribute documentation46877 Ref: 8c46877 Ref: contribute id446877 Ref: 8d46877 Node: Setup<2>47597 Ref: contribute documentation-setup47670 Ref: 8e47670 Ref: contribute id547670 Ref: 8f47670 Node: Building47910 Ref: contribute building47983 Ref: 9047983 Ref: contribute documentation-building47983 Ref: 9147983 Node: License48288 Ref: index id148366 Ref: 9248366 Ref: index license48366 Ref: 9348366 Node: GNU General Public License48898 Ref: gpl doc48966 Ref: 9548966 Ref: gpl gnu-general-public-license48966 Ref: 9648966 Ref: gpl gpl49029 Ref: 9449029 Node: Index86942  End Tag Table  Local Variables: coding: utf-8 End: ================================================ FILE: test/data/bar.baz ================================================ hello Hello hellO ================================================ FILE: test/data/bar.el ================================================ hello Hello hellO ================================================ FILE: test/data/context.el ================================================ context1 context2 context3 context4 amid context context5 context6 context7 context8 amid context context9 context10 context11 context12 amid context context13 context14 context15 context16 ================================================ FILE: test/data/dirlocals/.dir-locals.el ================================================ ((nil . ((rg-buffer-name . "from dir locals")))) ================================================ FILE: test/data/foo.baz ================================================ hello Hello hellO ================================================ FILE: test/data/foo.el ================================================ hello Hello hellO ================================================ FILE: test/data/limenu.el ================================================ imenu ================================================ FILE: test/package-bootstrap.el ================================================ ;;; package-bootstrap.el --- rg.el: Helper for package install test ;; Copyright (C) 2017 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;; This bootstraps package.el with a temporary package dir in ;; /tmp/${PKG_FULL_NAME}-elpa to test package installation in a clean ;; environment. ;;; Code: (require 'package) (defvar pkg_name (getenv "PKG_FULL_NAME")) (setq package-user-dir (concat "/tmp/" pkg_name "-elpa")) (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t) (package-initialize) (package-refresh-contents) ================================================ FILE: test/rg-header.el-test.el ================================================ (require 'cl-lib) (defvar rg-unit/long-search-pattern "everything everywhere all at once") ;;; Mocks. (cl-defmacro rg-unit/mock-truncation-predicate ((&key max predicate) &rest body) "Execute BODY with truncation predicate mocked by PREDICATE. MAX is the value `rg-header-max-search-string-length' will be set to." (declare (indent defun)) `(cl-letf* ((rg-header-max-search-string-length ,max) ((symbol-function 'rg-header-truncates-p) #',predicate)) ,@body)) (cl-defmacro rg-unit/mock-rg-cur-search-pattern ((&key do-return) &rest body) "Execute BODY with `rg-cur-search-pattern' mocked. Instead DO-RETURN will be returned when the function is called." (declare (indent defun)) `(cl-letf* (((symbol-function 'rg-cur-search-pattern) (lambda (&rest _) ,do-return))) ,@body)) ;;; Tests. (ert-deftest rg-unit/header-truncation-predicate () "Test `rg-header-truncates-p'." ;; Not set. (let ((rg-header-max-search-string-length nil)) (should-not (rg-header-truncates-p rg-unit/long-search-pattern))) ;; Erroneous value. (let ((rg-header-max-search-string-length 'none)) (should-not (rg-header-truncates-p rg-unit/long-search-pattern))) ;; The length of the string passed. (let ((rg-header-max-search-string-length (length rg-unit/long-search-pattern))) (should-not (rg-header-truncates-p rg-unit/long-search-pattern))) ;; One below string length. (let ((rg-header-max-search-string-length (1- (length rg-unit/long-search-pattern)))) (should (rg-header-truncates-p rg-unit/long-search-pattern))) ;; At 0. (let ((rg-header-max-search-string-length 0)) (should (rg-header-truncates-p rg-unit/long-search-pattern))) ;; Below 0. (let ((rg-header-max-search-string-length -1)) (should (rg-header-truncates-p rg-unit/long-search-pattern)))) (ert-deftest rg-unit/search-pattern-truncation-in-header () "Tests `rg-header-truncate-search-pattern'." ;; When predicate is true. (let ((ellipsis-len (length (rg-truncate-string-ellipsis)))) (rg-unit/mock-truncation-predicate (:max 11 :predicate always) (should (string= (concat (substring "everything" 0 (- 11 ellipsis-len)) (rg-truncate-string-ellipsis)) (rg-header-truncate-search-pattern rg-unit/long-search-pattern)))) (rg-unit/mock-truncation-predicate (:max 5 :predicate always) (should (string= (concat (substring "ever" 0 (- 5 ellipsis-len)) (rg-truncate-string-ellipsis)) (rg-header-truncate-search-pattern rg-unit/long-search-pattern)))) (rg-unit/mock-truncation-predicate (:max (length rg-unit/long-search-pattern) :predicate always) (should (string= "everything everywhere all at once" (rg-header-truncate-search-pattern rg-unit/long-search-pattern)))) ;; When predicate is false. (rg-unit/mock-truncation-predicate (:max 11 :predicate ignore) (should (string= "everything everywhere all at once" (rg-header-truncate-search-pattern rg-unit/long-search-pattern)))) (rg-unit/mock-truncation-predicate (:predicate ignore) (should (string= "everything everywhere all at once" (rg-header-truncate-search-pattern rg-unit/long-search-pattern)))))) (ert-deftest rg-unit/search-help-for-header () "Tests `rg-header-search-help'." (rg-unit/mock-rg-cur-search-pattern (:do-return "this is the full string") (rg-unit/mock-truncation-predicate (:predicate always) (should (string= "Change search string: this is the full string" (rg-header-search-help)))) (rg-unit/mock-truncation-predicate (:predicate ignore) (should (string= "Change search string" (rg-header-search-help)))))) ================================================ FILE: test/rg-history.el-test.el ================================================ ;;; rg-history.el-test.el --- Tests for rg-history.el -*- lexical-binding: t; -*- ;; Copyright (C) 2018 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;; Tests for search history navigation. ;;; Code: ;; Unit tests (ert-deftest rg-unit/history-navigation () "Testing the history data structure for navigation." (let ((instance (rg-history-create))) (should (equal (rg-history-back instance) nil)) (rg-history-push 'search1 instance) (rg-history-push 'search2 instance) (rg-history-push 'search3 instance) (should (equal (rg-history-forward instance) nil)) (should (equal (rg-history-back instance) 'search2)) (should (equal (rg-history-back instance) 'search1)) (should (equal (rg-history-back instance) nil)) (should (equal (rg-history-forward instance) 'search2)) (should (equal (rg-history-forward instance) 'search3)) (should (equal (rg-history-forward instance) nil)))) (ert-deftest rg-unit/history-new-search () "Testing the history data structure behavior when inserting new search after moving in history." (let ((instance (rg-history-create))) (should (equal (rg-history-back instance) nil)) (rg-history-push 'search1 instance) (rg-history-push 'search2 instance) (rg-history-push 'search3 instance) (should (equal (rg-history-forward instance) nil)) (should (equal (rg-history-back instance) 'search2)) (rg-history-push 'search4 instance) (should (equal (rg-history-forward instance) nil)) (should (equal (rg-history-back instance) 'search2)) (should (equal (rg-history-back instance) 'search1)) (should (equal (rg-history-back instance) nil)))) ;; Integration tests (ert-deftest rg-integration/history () "Verify that simple history navigation works." :tags '(need-rg) (rg-run-and-wait #'rg-run "foo" "all" (concat default-directory "test/data")) (rg-run-and-wait #'rg-run "bar" "all" (concat default-directory "test/data")) (rg-run-and-wait #'rg-run "baz" "all" (concat default-directory "test/data")) (with-current-buffer (rg-buffer-name) (rg-run-and-wait #'rg-back-history) (should (equal (rg-search-pattern rg-cur-search) "bar")) (rg-run-and-wait #'rg-back-history) (should (equal (rg-search-pattern rg-cur-search) "foo")) (rg-run-and-wait #'rg-forward-history) (should (equal (rg-search-pattern rg-cur-search) "bar")) (rg-run-and-wait #'rg-forward-history) (should (equal (rg-search-pattern rg-cur-search) "baz")))) (provide 'rg-history.el-test) ;;; rg-history.el-test.el ends here ================================================ FILE: test/rg-isearch.el-test.el ================================================ ;;; rg-isearch.el-test.el --- rg-isearch.el tests -*- lexical-binding: t; -*- ;; Copyright (C) 2020 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: (ert-deftest rg-unit/isearch-current-file () "Test `rg-isearch-current-file'." (cl-letf* ((called-pattern nil) (called-literal nil) (called-files nil) (called-dir nil) ((symbol-function #'rg-run) (lambda (pattern files dir &optional literal &rest _) (setq called-pattern pattern) (setq called-literal literal) (setq called-files files) (setq called-dir dir)))) (find-file "test/data/bar.el") (let ((isearch-string "test")) (rg-isearch-current-file) (should (equal called-pattern "test")) (should (eq called-literal t)) (should (equal called-files "bar.el")) (should (equal (expand-file-name called-dir) (expand-file-name default-directory)))))) (ert-deftest rg-unit/isearch-current-dir () "Test `rg-isearch-current-dir'." (cl-letf* ((called-pattern nil) (called-literal nil) (called-files nil) (called-dir nil) ((symbol-function #'rg-run) (lambda (pattern files dir &optional literal &rest _) (setq called-pattern pattern) (setq called-literal literal) (setq called-files files) (setq called-dir dir)))) (find-file "test/data/bar.el") (let ((isearch-string "test")) (rg-isearch-current-dir) (should (equal called-pattern "test")) (should (eq called-literal t)) (should (equal called-files "elisp")) (should (equal (expand-file-name called-dir) (expand-file-name default-directory)))))) (ert-deftest rg-unit/isearch-project () "Test `rg-isearch-project'." (cl-letf* ((called-pattern nil) (called-literal nil) (called-files nil) (called-dir nil) (project-dir (expand-file-name default-directory)) ((symbol-function #'rg-run) (lambda (pattern files dir &optional literal &rest _) (setq called-pattern pattern) (setq called-literal literal) (setq called-files files) (setq called-dir dir)))) (find-file "test/data/bar.el") (let ((isearch-string "test")) (rg-isearch-project) (should (equal called-pattern "test")) (should (eq called-literal t)) (should (equal called-files "elisp")) (should (equal (expand-file-name called-dir) project-dir))))) (provide 'rg-isearch.el-test) ;;; rg-isearch.el-test.el ends here ================================================ FILE: test/rg-menu.el-test.el ================================================ ;;; rg-menu.el-test.el --- Tests for rg-menu.el -*- lexical-binding: t; -*- ;; Copyright (C) 2018 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/davja/ ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: ;; Unit tests (ert-deftest rg-unit-test/enable-menu () "Test `rg-enable-menu'." (rg-with-temp-global-keymap (rg-enable-menu) (should (eq 'rg-menu (lookup-key (current-global-map) "\C-cs")))) (rg-with-temp-global-keymap (rg-enable-menu "\M-s") (should (eq 'rg-menu (lookup-key (current-global-map) "\M-s")))) (let ((rg-keymap-prefix "\M-s")) ;; Default prefix (rg-with-temp-global-keymap (rg-enable-menu) (should (eq 'rg-menu (lookup-key (current-global-map) "\M-s")))) ;; Function supplied prefix (rg-with-temp-global-keymap (rg-enable-menu "\C-ct") (should (eq 'rg-menu (lookup-key (current-global-map) "\C-ct"))))) (let ((rg-mode-map (make-sparse-keymap))) (rg-enable-menu) (should (eq 'rg-menu (lookup-key rg-mode-map "m"))))) (ert-deftest rg-unit/menu-transient-wrappers () "Test the transient wrappers." (cl-letf* ((transient-flags '("a" "b" "c")) (func-flags '("d" "e")) (flags-result nil) (rg-command-line-flags-function (lambda (_) '("f"))) ((symbol-function #'test-rerun) #'ignore) ((symbol-function #'test-search) (lambda () (interactive) (setf flags-result (funcall rg-command-line-flags-function func-flags)))) ((symbol-function #'transient-args) (lambda (prefix) transient-flags)) (rg-cur-search (rg-search-create :pattern "test" :files "all" :dir "/tmp" :literal t :flags nil))) (rg-menu-wrap-transient-search test-search) (should (commandp #'test-search--transient)) (rg-menu-wrap-transient-rerun test-rerun) (should (commandp #'test-rerun--transient)) (test-search--transient) (should-not (seq-difference flags-result '("a" "b" "c" "d" "e" "f"))) (test-rerun--transient) (should (equal (rg-search-flags rg-cur-search) transient-flags)))) (ert-deftest rg-unit/menu-rerun-custom-flags () "Test that rerun from transient menu preserves custom flags." (rg-define-search rg-test-search-with-flags :flags '("--hidden" "--no-ignore")) (let ((captured-flags nil)) (cl-letf* (((symbol-function #'rg-run) (lambda (pattern files dir &optional literal confirm flags) (with-current-buffer (get-buffer-create "*rg*") (rg-mode) (setq rg-cur-search (rg-search-create :pattern pattern :files files :dir dir :literal literal :flags flags)) (setq compilation-arguments (list "a" 'rg-mode 'rg-buffer-name nil))))) ((symbol-function #'transient-args) (lambda (_) '("--multiline" "--hidden")))) (rg-test-search-with-flags "test" "all" "/tmp") (with-current-buffer "*rg*" (rg-rerun--transient) (setq captured-flags (rg-search-flags rg-cur-search))) ;; This is for certain from the :flags key (should (member "--no-ignore" captured-flags)) ;; This is for certain from the transient-args (should (member "--multiline" captured-flags)) ;; This could come from either (should (member "--hidden" captured-flags)) ;; Ensure duplicates were removed (should (= (length captured-flags) 3))))) (ert-deftest rg-unit/menu-transient-insert () "Test adding new items to the menu" (transient-define-prefix rg-menu () [ "Switches" ] [ "Options" ] [[ "Search" (3 "s" "search" search--transient)] [ "Rerun" (3 "r" "rerun" rerun--transient)]]) ;; Make sure we are not using some other rg-menu (should-not (ignore-errors (transient-get-suffix 'rg-menu '(2 2)))) (should (transient-get-suffix 'rg-menu 'search--transient)) (should (transient-get-suffix 'rg-menu 'rerun--transient)) ;; Add to existing sub group (rg-menu-transient-insert "Search" "n" "new search" 'new--transient) (should (eq (plist-get (cdr (transient-get-suffix 'rg-menu '(2 0 1))) :command) 'new--transient)) ;; Create a new sub group and add to that (rg-menu-transient-insert "Custom" "c" "custom search" 'custom--transient) (should (eq (plist-get (cdr (transient-get-suffix 'rg-menu '(2 2 0))) :command) 'custom--transient))) (ert-deftest rg-unit/menu-define-search () "Test adding new items to the menu" (transient-define-prefix rg-menu () [ "Switches" ] [ "Options" ] [[ "Search" (3 "s" "search" search--transient)] [ "Rerun" (3 "r" "rerun" rerun--transient)]]) ;; Make sure we are not using some other rg-menu (should-not (ignore-errors (transient-get-suffix 'rg-menu '(2 2)))) (rg-define-search menu-search :menu ("Macro" "m" "macro search")) (let ((subgroup (seq-elt (transient-get-suffix 'rg-menu '(2 2)) 1)) (entry (cdr (transient-get-suffix 'rg-menu '(2 2 0))))) (should (equal (plist-get subgroup :description) "Macro")) (should (eq (plist-get entry :command) 'menu-search--transient)) (should (equal (plist-get entry :description) "macro search")) (should (equal (plist-get entry :key) "m")))) (ert-deftest rg-unit/menu-search-initial-value () "Test extraction of flags into transient initial value." (let ((rg-cur-search (rg-search-create :pattern "test" :files "all" :dir "/tmp" :literal t :flags '("a" "b")))) (with-temp-buffer (should (equal (rg-menu-search-initial-value) nil)) (rg-mode) (should-not (seq-difference (rg-menu-search-initial-value) '("a" "b")))))) (provide 'rg-menu.el-test) ;;; rg-menu.el-test.el ends here ================================================ FILE: test/rg.el-test.el ================================================ ;;; rg.el-test.el --- rg.el: Tests -*- lexical-binding: t; -*- ;; Copyright (C) 2017 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: ;; Unit tests (ert-deftest rg-unit-test/case-expand-template () "Test that `rg-apply-case-flag' handles case settings correctly." (let (rg-initial-toggle-flags) (let ((case-fold-search t)) (rg-apply-case-flag "foo") (should (member "-i" rg-initial-toggle-flags)) (rg-apply-case-flag "fOo") (should-not (member "-i" rg-initial-toggle-flags))) (let ((case-fold-search nil)) (rg-apply-case-flag "foo") (should-not (member "-i" rg-initial-toggle-flags)) (rg-apply-case-flag "fOo") (should-not (member "-i" rg-initial-toggle-flags))) (let ((rg-ignore-case 'smart)) (rg-apply-case-flag "foo") (should (member "-i" rg-initial-toggle-flags)) (rg-apply-case-flag "fOo") (should-not (member "-i" rg-initial-toggle-flags))) (let ((rg-ignore-case 'force)) (rg-apply-case-flag "foo") (should (member "-i" rg-initial-toggle-flags)) (rg-apply-case-flag "fOo") (should (member "-i" rg-initial-toggle-flags))) (let ((rg-ignore-case nil)) (rg-apply-case-flag "foo") (should-not (member "-i" rg-initial-toggle-flags)) (rg-apply-case-flag "fOo") (should-not (member "-i" rg-initial-toggle-flags))))) (ert-deftest rg-unit-test/custom-command-line-flags () "Test that `rg-command-line-flags' is added to the template." (let ((rg-executable "rg") (rg-custom-type-aliases nil) (rg-command-line-flags '("--foo" "--bar"))) (should (s-matches? (rg-regexp-anywhere-but-last "--foo --bar") (rg-build-command "" "" nil nil))))) (ert-deftest rg-unit-test/custom-command-line-function () "Test that when `rg-command-line-flags' is a function, the returned list of flags are added to the template." (let ((rg-command "rg") (rg-custom-type-aliases nil) (rg-command-line-flags (lambda () '("--foo" "--bar")))) (should (s-matches? (rg-regexp-anywhere-but-last "--foo --bar") (rg-build-command "" "" nil nil))))) (ert-deftest rg-unit-test/rg-buffer-name-string () "Test that function `rg-buffer-name' will return correct buffer name if variable `rg-buffer-name' is string." (let ((rg-buffer-name "rg results")) (should (string= "*rg results*" (rg-buffer-name))))) (ert-deftest rg-unit-test/rg-buffer-name-function () "Test that function `rg-buffer-name' will return correct buffer name if variable `rg-buffer-name' is function." (let ((rg-buffer-name (lambda () "" "rg results"))) (should (string= "*rg results*" (rg-buffer-name))))) (ert-deftest rg-unit-test/rg-buffer-name-dirlocals () "Test that `rg-buffer-name' is set from dir-locals.el." ;; This test covers two cases: ;; - that dir-locals from search dir is applied at all ;; - that dir-locals from search dir is applied if search is ;; started from file buffers :tags '(need-rg) (let ((safe-local-variable-values '((rg-buffer-name . "from dir locals")))) (find-file (concat default-directory "test/data/foo.baz")) (rg-run "foo" "*.baz" (concat default-directory "dirlocals")) (should (get-buffer "*from dir locals*")))) (ert-deftest rg-unit-test/save-search-as-name () "Verify exit messages." :tags '(need-rg) (let ((rg-buffer-name "rg results")) (rg-run "foo" "*.baz" (concat default-directory "test/data")) (with-current-buffer (rg-buffer-name) (rg-save-search-as-name "bar") (should (string= "*rg results bar*" (buffer-name)))))) (ert-deftest rg-unit-test/toggle-command-flag () "Test `rg-list-toggle'." (let ((testflag "--foo") flaglist) (setq flaglist (rg-list-toggle testflag flaglist)) (should (member testflag flaglist)) (setq flaglist (rg-list-toggle testflag flaglist)) (should-not (member testflag flaglist)))) (ert-deftest rg-unit-test/rerun-change-regexp-literal () "Test result of `rg-rerun-change-regexp' and `rg-rerun-change-literal'." (let ((rg-cur-search (rg-search-create :pattern "pattern" :files "elisp" :dir "/tmp/test"))) (cl-letf (((symbol-function #'rg-rerun) #'ignore) ((symbol-function #'rg-read-pattern) (lambda (&rest _) "new-pattern"))) (rg-rerun-change-regexp) (should (equal (rg-search-create :pattern "new-pattern" :files "elisp" :dir "/tmp/test" :literal nil) rg-cur-search)) (rg-rerun-change-literal) (should (equal (rg-search-create :pattern "new-pattern" :files "elisp" :dir "/tmp/test" :literal t) rg-cur-search)) (rg-rerun-change-regexp) (should (eq (rg-search-literal rg-cur-search) nil))))) (ert-deftest rg-unit-test/read-pattern-correct-read-func () "Test that `rg-read-pattern' choose the correct read function depending on emacs version." (let (called prompt-result) (cl-letf (((symbol-function #'read-string) (lambda (pr default &rest _) (setq called 'read-string) (setq prompt-result pr))) ((symbol-function #'read-regexp) (lambda (pr &rest _) (setq called 'read-regexp) (setq prompt-result pr)))) (rg-read-pattern nil "foo") (should (eq called 'read-regexp)) (should (equal prompt-result "Regexp search for"))))) (ert-deftest rg-unit-test/rerun-change-files () "Test result of `rg-rerun-change-files'." (let ((rg-cur-search (rg-search-new "regexp" "elisp" "/tmp/test"))) (cl-letf (((symbol-function #'rg-rerun) #'ignore) ((symbol-function #'completing-read) (lambda (&rest _) "cpp"))) (rg-rerun-change-files) (should (equal (rg-search-new "regexp" "cpp" "/tmp/test") rg-cur-search))))) (ert-deftest rg-unit-test/rerun-change-dir () "Test result of `rg-rerun-change-dir'." (let ((rg-cur-search (rg-search-new "regexp" "elisp" "/tmp/test"))) (cl-letf (((symbol-function #'rg-rerun) #'ignore) ((symbol-function #'read-directory-name) (lambda (&rest _) "/tmp/new"))) (rg-rerun-change-dir) (should (equal (rg-search-new "regexp" "elisp" "/tmp/new") rg-cur-search))))) (ert-deftest rg-unit-test/custom-toggle () "Test `rg-define-toggle' macro." (let ((rg-cur-search (rg-search-create :pattern "regexp" :files "elisp" :dir "/tmp/test"))) (cl-letf (((symbol-function #'rg-rerun) #'ignore)) (rg-define-toggle "--foo") (should (functionp 'rg-custom-toggle-flag-foo)) (should-not (member "--foo" rg-initial-toggle-flags)) (rg-define-toggle "--bar" nil t) (should (functionp 'rg-custom-toggle-flag-bar)) (should (member "--bar" rg-initial-toggle-flags)) (simulate-rg-run (rg-search-pattern rg-cur-search) (rg-search-files rg-cur-search) (rg-search-dir rg-cur-search)) (rg-custom-toggle-flag-foo) (should (member "--foo" (rg-search-flags rg-cur-search))) (should (member "--bar" (rg-search-flags rg-cur-search))) (rg-custom-toggle-flag-foo) (should-not (member "--foo" (rg-search-flags rg-cur-search))) (should (member "--bar" (rg-search-flags rg-cur-search))) (rg-custom-toggle-flag-bar) (should-not (member "--foo" (rg-search-flags rg-cur-search))) (should-not (member "--bar" (rg-search-flags rg-cur-search))) (rg-custom-toggle-flag-bar) (should-not (member "--foo" rg-initial-toggle-flags)) (should (member "--bar" rg-initial-toggle-flags))))) (ert-deftest rg-unit-test/custom-toggle-key-binding () "Test `rg-define-toggle' macro key bindings." (let ((rg-cur-search (rg-search-create :pattern "regexp" :files "elisp" :dir "/tmp/test"))) (cl-letf (((symbol-function #'recompile) #'ignore)) (rg-define-toggle "--baz" "b") (should (functionp 'rg-custom-toggle-flag-baz)) (should (eq 'rg-custom-toggle-flag-baz (lookup-key rg-mode-map "b")))))) (ert-deftest rg-unit-test/custom-toggle-double-definition () "Test multiple clashing definitions of same flag and bindings in `rg-define-toggle' macro." (let ((rg-cur-search (rg-search-create :pattern "regexp" :files "elisp" :dir "/tmp/test"))) (cl-letf (((symbol-function #'recompile) #'ignore)) (rg-define-toggle "--qux" nil t) (should (functionp 'rg-custom-toggle-flag-qux)) (should-not (eq 'rg-custom-toggle-flag-qux (lookup-key rg-mode-map "q"))) (should (member "--qux" rg-initial-toggle-flags)) (rg-define-toggle "--qux" "q") (should (functionp 'rg-custom-toggle-flag-qux)) (should (eq 'rg-custom-toggle-flag-qux (lookup-key rg-mode-map "q"))) (should-not (member "--qux" rg-initial-toggle-flags)) (rg-define-toggle "--qux" "z") (should (functionp 'rg-custom-toggle-flag-qux)) (should (eq 'rg-custom-toggle-flag-qux (lookup-key rg-mode-map "z"))) (rg-define-toggle "--quux" "q") (should (eq 'rg-custom-toggle-flag-quux (lookup-key rg-mode-map "q"))) (should-not (eq 'rg-custom-toggle-flag-qux (lookup-key rg-mode-map "q")))))) (ert-deftest rg-unit-test/build-command-files () "Test that`rg-build-command' convert files argument to correct type alias." (let ((rg-executable "rg") (rg-custom-type-aliases nil) full-command) (setq full-command (rg-build-command "foo" "cpp" nil nil)) (should (s-matches? "rg.*? +--type=cpp.*? +foo" full-command)) (setq full-command (rg-build-command "foo" "everything" nil nil)) (should-not (s-matches? "rg.*? +--type=.*? +foo" full-command)) (setq full-command (rg-build-command "foo" "bar" nil nil)) (should (s-matches? (concat "rg.*? +--type-add=" (regexp-quote (shell-quote-argument "custom:bar")) " +--type=\"?custom.*? +foo") full-command)))) (ert-deftest rg-unit-test/build-command-type () "Test `rg-build-template' template creation." (let* ((rg-executable "rg") (rg-custom-type-aliases nil) (notype-command (rg-build-command "query" "everything" nil nil)) (builtin-type-command (rg-build-command "query" "elisp" nil nil)) (custom-type-command (rg-build-command "query" "glob" nil nil)) (type-pattern (rg-regexp-anywhere-but-last "--type=[^ ]+")) (type-add-pattern (rg-regexp-anywhere-but-last (concat "--type-add=" (s-replace "----" "[^ ]+" (regexp-quote (shell-quote-argument "custom:----"))))))) (should (s-matches? (rg-regexp-anywhere "-e query") notype-command)) (should-not (s-matches? type-pattern notype-command)) (should-not (s-matches? type-add-pattern notype-command)) (should (s-matches? type-pattern builtin-type-command)) (should-not (s-matches? type-add-pattern builtin-type-command)) (should (s-matches? type-add-pattern custom-type-command)) (should (s-matches? (rg-regexp-anywhere-but-last "--type=custom") custom-type-command)))) (ert-deftest rg-unit-test/build-command-explicit-dir () "Test that `rg-build-command' use explicit current dir on some platforms." (let ((rg-executable "rg")) (should-not (string-match "\\.$" (rg-build-command "query" "glob" nil nil))) (let ((system-type 'darwin)) (should (string-match "\\.$" (rg-build-command "query" "glob" nil nil)))) ;; Need w32-shell-dos-semantics when faking system-type on Linux (cl-letf (((symbol-function (intern "w32-shell-dos-semantics")) (lambda ())) (system-type 'windows-nt)) (should (string-match "\\.$" (rg-build-command "query" "glob" nil nil)))))) (ert-deftest rg-unit-test/default-alias () "Test that `rg-default-alias' detects the current file and selects matching alias." (find-file "test/data/foo.el") (should (equal (car (rg-default-alias)) "elisp")) (find-file "test/data/foo.baz") (should (equal (car (rg-default-alias)) rg-default-alias-fallback)) (let ((rg-custom-type-aliases '(("test" . "*.baz")))) (find-file "test/data/foo.baz") (should (equal (car (rg-default-alias)) "test")))) (ert-deftest rg-unit-test/navigate-file-message () "Test that `rg-navigate-file-message' find font-lock-face matches correctly." (let (pos limit) (with-temp-buffer (insert "noproperty" (propertize "match1" 'rg-file-message t) (propertize "someotherproperty" 'rg-file-message nil) "noproperty2" (propertize "othermatch" 'rg-file-message t)) (setq pos (rg-navigate-file-message (point-min) (point-max) 1)) (goto-char pos) (should (looking-at "match1")) (setq pos (rg-navigate-file-message pos (point-max) 1)) (goto-char pos) (should (looking-at "othermatch")) (setq pos (rg-navigate-file-message pos (point-min) -1)) (goto-char pos) (should (looking-at "match1")) (setq pos (rg-navigate-file-message pos (point-max) 1)) (goto-char pos) (should (looking-at "othermatch")) (setq limit (+ pos 4)) (setq pos (rg-navigate-file-message pos limit 1)) (should (eq pos limit))))) (ert-deftest rg-unit/next-prev-file () "Test that `rg-next-file' and `rg-prev-file' dispatch calls as it should." (let (called arg) (cl-letf (((symbol-function #'rg-navigate-file-group) (lambda (n) (setq called 'rg-navigate-file-group arg n))) ((symbol-function #'compilation-next-file) (lambda (n) (setq called 'compilation-next-file arg n))) ((symbol-function #'compilation-previous-file) (lambda (n) (setq called 'compilation-previous-file arg n)))) (let ((rg-group-result t)) (rg-next-file 1) (should (eq called 'rg-navigate-file-group)) (should (eq arg 1)) (rg-prev-file 1) (should (eq called 'rg-navigate-file-group)) (should (eq arg (- 1)))) (let ((rg-group-result nil)) (rg-next-file 1) (should (eq called 'compilation-next-file)) (should (eq arg 1)) (rg-prev-file 1) (should (eq called 'compilation-previous-file)) (should (eq arg 1)))))) (ert-deftest rg-unit/match-grouped-filename () "Test that `rg-match-grouped-filename' finds correct match and restores state." (let (saved-pos) (with-temp-buffer (insert "some random text\n" "File: matched/text/file.foo\n" "4:1: Some matched text") (goto-char (point-min)) (re-search-forward "Some match") (setq saved-pos (point)) (should (equal "matched/text/file.foo" (car (rg-match-grouped-filename)))) (should (equal "Some match" (match-string 0))) (should (eq saved-pos (point)))))) (ert-deftest rg-unit-test/global-keymap () "Test global keymap." (let ((rg-use-transient-menu nil)) ;; Default prefix (rg-with-temp-global-keymap (rg-enable-default-bindings) (should (eq rg-global-map (lookup-key (current-global-map) "\C-cs")))) ;; Function supplied prefix (rg-with-temp-global-keymap (rg-enable-default-bindings "\M-s") (should (eq rg-global-map (lookup-key (current-global-map) "\M-s")))) ;; Customized prefix (let ((rg-keymap-prefix "\M-s")) ;; Default prefix (rg-with-temp-global-keymap (rg-enable-default-bindings) (should (eq rg-global-map (lookup-key (current-global-map) "\M-s")))) ;; Function supplied prefix (rg-with-temp-global-keymap (rg-enable-default-bindings "\C-ct") (should (eq rg-global-map (lookup-key (current-global-map) "\C-ct"))))) ;; Make sure rg-global-map comes into play (rg-with-temp-global-keymap (rg-enable-default-bindings) (should (eq 'rg (lookup-key (current-global-map) "\C-csr"))) ;; Modify global map (define-key rg-global-map "0" 'foo) (should (eq 'foo (lookup-key (current-global-map) "\C-cs0")))))) (ert-deftest rg-unit/literal-search () "Test `rg-literal'." (cl-letf* ((called-literal nil) ((symbol-function #'rg-run) (lambda (_pattern _files _dir &optional literal &rest _) (setf called-literal literal)))) (rg-literal "foo" "elisp" "/tmp/test") (should-not (eq called-literal nil)))) (ert-deftest rg-unit/regexp-search () "Test `rg'." (cl-letf* ((called-literal nil) ((symbol-function #'rg-run) (lambda (_pattern _files _dir &optional literal &rest _) (setq called-literal literal)))) (rg "foo" "elisp" "/tmp/test") (should (eq called-literal nil)))) (ert-deftest rg-unit/set-search-defaults () "Test rg-define-search helper defun rg-set-search-defaults." (let ((defaults (rg-set-search-defaults nil))) (should (eq (plist-get defaults :confirm) 'never)) (should (eq (plist-get defaults :format) 'regexp)) (should (eq (plist-get defaults :query) 'ask)) (should (eq (plist-get defaults :files) 'ask)) (should (eq (plist-get defaults :dir) 'ask)))) (ert-deftest rg-unit/search-parse-local-bindings-confirm () "Test rg-define-search helper defun rg-search-parse-local-bindings. Test `:confirm' directive." (let ((prefix (rg-search-parse-local-bindings '(:confirm prefix))) (never (rg-search-parse-local-bindings '(:confirm never))) (always (rg-search-parse-local-bindings '(:confirm always)))) (should (null (cadr (assq 'confirm never)))) (should (cadr (assq 'confirm always))) (should (eq (cadr (cadr (assq 'confirm prefix))) 'current-prefix-arg)))) (ert-deftest rg-unit/search-parse-local-bindings-query () "Test rg-define-search helper defun rg-search-parse-local-bindings. Test `:query' directive." (let ((ask (rg-search-parse-local-bindings '(:query ask))) (form (rg-search-parse-local-bindings '(:query (form)))) (point (rg-search-parse-local-bindings '(:query point)))) (should (null (assq 'query ask))) (should (equal (cadr (assq 'query form)) '(form))) (should (equal (cadr (assq 'query point)) '(or (rg-tag-default) (rg-read-pattern literal)))))) (ert-deftest rg-unit/search-parse-local-bindings-dir () "Test rg-define-search helper defun rg-search-parse-local-bindings. Test `:dir' directive." (let ((ask (rg-search-parse-local-bindings '(:dir ask))) (project (rg-search-parse-local-bindings '(:dir project))) (current (rg-search-parse-local-bindings '(:dir current))) (form (rg-search-parse-local-bindings '(:dir (form))))) (should (null (assq 'dir ask))) (should (equal (cadr (assq 'dir project)) '(rg-project-root buffer-file-name))) (should (equal (cadr (assq 'dir current)) 'default-directory)) (should (equal (cadr (assq 'dir form)) '(form))))) (ert-deftest rg-unit/search-parse-local-bindings-files () "Test rg-define-search helper defun rg-search-parse-local-bindings. Test `:files' directive." (let ((ask (rg-search-parse-local-bindings '(:files ask))) (current (rg-search-parse-local-bindings '(:files current))) (form (rg-search-parse-local-bindings '(:files (form))))) (should (null (assq 'files ask))) (should (equal (cadr (assq 'files current)) '(car (rg-default-alias)))) (should (equal (cadr (assq 'files form)) '(form))))) (ert-deftest rg-unit/search-parse-local-bindings-flags () "Test rg-define-search helper defun rg-search-parse-global-bindings. Test `:flags' directive." (let ((ask (rg-search-parse-local-bindings '(:flags ask))) (form (rg-search-parse-local-bindings '(:flags '("--flag"))))) (should (cadr (assq 'flags ask))) (should (equal (cadr (assq 'flags form)) (quote (funcall rg-command-line-flags-function '("--flag"))))))) (ert-deftest rg-unit/search-parse-interactive-args () "Test rg-define-search helper defun rg-search-parse-interactive-args." (let ((empty (rg-search-parse-interactive-args nil)) (query (rg-search-parse-interactive-args '(:query ask))) (files (rg-search-parse-interactive-args '(:files ask))) (dir (rg-search-parse-interactive-args '(:dir ask))) (flags (rg-search-parse-interactive-args '(:flags ask)))) (should (null empty)) (should (equal (cadr (assq 'query query)) 'rg-read-pattern)) (should (equal (cadr (assq 'files files)) 'rg-read-files )) (should (equal (cadr (assq 'dir dir)) 'read-directory-name)) (should (equal (caar (cddr (assq 'flags flags))) 'read-string)))) (ert-deftest rg-unit/builtin-aliases-empty-strings () "Test that empty strings in builtin aliases are filtered out." (cl-letf (((symbol-function #'rg-invoke-rg-type-list) (lambda () "\n\n foo: *.foo,*.fo\n\n bar: *.bar,*.ba\n"))) (equal (rg-list-builtin-type-aliases) '(("foo" . "*.foo *.fo") ("bar" . "*.bar *.ba"))))) (ert-deftest rg-unit/prepend-space () "Test rg-prepend-space." (should (equal (rg-prepend-space "foo" (length "foo")) "foo")) (should (equal (rg-prepend-space "foo" (1+ (length "foo"))) " foo")) (should (equal (rg-prepend-space "foo" (1- (length "foo"))) "foo"))) ;; Integration tests (ert-deftest rg-integration-test/search-alias-builtin () "Test that rg builtin aliases works." :tags '(need-rg) (let ((rg-group-result nil) (rg-ignore-case 'force)) (rg-run "hello" "elisp" (concat default-directory "test/data")) (rg-with-current-result (let ((bufstr (buffer-substring-no-properties (point-min) (point-max)))) (should (= 3 (s-count-matches "foo.el.*hello" bufstr))) (should (= 3 (s-count-matches "bar.el.*hello" bufstr))) (should (= 0 (s-count-matches "foo.baz.*hello" bufstr))) (should (= 0 (s-count-matches "bar.baz.*hello" bufstr))))))) (ert-deftest rg-integration-test/search-alias-custom () "Test that aliases defined in `rg-custom-type-aliases' work if explicitly selected." :tags '(need-rg) (let ((rg-group-result nil) (rg-ignore-case 'force) (rg-custom-type-aliases '(("test" . "*.baz")))) (rg-run "hello" "test" (concat default-directory "test/data")) (rg-with-current-result (let ((bufstr (buffer-substring-no-properties (point-min) (point-max)))) (should (= 0 (s-count-matches "foo.el.*hello" bufstr))) (should (= 0 (s-count-matches "bar.el.*hello" bufstr))) (should (= 3 (s-count-matches "foo.baz.*hello" bufstr))) (should (= 3 (s-count-matches "bar.baz.*hello" bufstr))))))) (ert-deftest rg-integration-test/search-alias-all-custom () "Test that aliases defined in `rg-custom-type-ailiases' work if implicitly selected via '--type=all'." :tags '(need-rg) (let ((rg-group-result nil) (rg-ignore-case 'force) (rg-custom-type-aliases '(("test" . "*.baz")))) (rg-run "hello" "all" (concat default-directory "test/data")) (rg-with-current-result (let ((bufstr (buffer-substring-no-properties (point-min) (point-max)))) (should (= 3 (s-count-matches "foo.el.*hello" bufstr))) (should (= 3 (s-count-matches "bar.el.*hello" bufstr))) (should (= 3 (s-count-matches "foo.baz.*hello" bufstr))) (should (= 3 (s-count-matches "bar.baz.*hello" bufstr))))))) (ert-deftest rg-integration-test/search-alias-custom-lambda () "Test that aliases defined via lambdas in `rg-custom-type-aliases' work." :tags '(need-rg) (let ((rg-group-result nil) (rg-ignore-case 'force) (rg-custom-type-aliases '((lambda () '("test" . "*.baz"))))) (rg-run "hello" "test" (concat default-directory "test/data")) (rg-with-current-result (let ((bufstr (buffer-substring-no-properties (point-min) (point-max)))) (should (= 0 (s-count-matches "foo.el.*hello" bufstr))) (should (= 0 (s-count-matches "bar.el.*hello" bufstr))) (should (= 3 (s-count-matches "foo.baz.*hello" bufstr))) (should (= 3 (s-count-matches "bar.baz.*hello" bufstr))))))) (ert-deftest rg-integration-test/search-no-alias() "Test that custom file pattern that is not an alias works." :tags '(need-rg) (let ((rg-group-result nil) (rg-ignore-case 'force)) (rg-run "hello" "*.baz" (concat default-directory "test/data")) (rg-with-current-result (let ((bufstr (buffer-substring-no-properties (point-min) (point-max)))) (should (= 0 (s-count-matches "foo.el.*hello" bufstr))) (should (= 0 (s-count-matches "bar.el.*hello" bufstr))) (should (= 3 (s-count-matches "foo.baz.*hello" bufstr))) (should (= 3 (s-count-matches "bar.baz.*hello" bufstr))))))) (ert-deftest rg-integration-test/rg-get-type-aliases() "Test that rg-get-type-aliases combines the aliases and priorities correctly." :tags '(need-rg) (let* ((rg-custom-type-aliases '()) (builtin (rg-list-builtin-type-aliases))) ;; No custom type aliases and no prioritized aliases gives the ;; ripgrep internal aliases. (should (rg-set-equal-p (rg-get-type-aliases 'skip-internal) builtin)) (setq rg-builtin-type-aliases-cache nil) (let* ((rg-prioritized-type-aliases '("cpp" "puppet" "elisp")) (result (rg-get-type-aliases 'skip-internal))) ;; Using prioritized aliases doesn't modify the set of aliases (should (rg-set-equal-p result builtin)) ;; The three first aliases should be the prioritized ones. (should (equal (mapcar #'car (seq-take result 3)) rg-prioritized-type-aliases)) (setq rg-builtin-type-aliases-cache nil) (let* ((rg-custom-type-aliases '(("foo" . "*.foo") ("bar" . "*.bar"))) (result (rg-get-type-aliases 'skip-internal))) ;; Custom aliases are included. (should (rg-set-equal-p result (append rg-custom-type-aliases builtin))) ;; The two first are the custom aliases (should (rg-set-equal-p (mapcar #'car (seq-take result 2)) (mapcar #'car (seq-take rg-custom-type-aliases 2)))) ;; The three following are the prioritized aliases. (should (rg-set-equal-p (mapcar #'car (seq-take (seq-drop result 2) 3)) rg-prioritized-type-aliases)))))) (ert-deftest rg-integration-test/rg-order-of-prioritized-type-aliases() "Test that rg-get-type-aliases honors the order of the `rg-prioritized-type-aliases' var." :tags '(need-rg) (let* ((rg-custom-type-aliases '()) (rg-prioritized-type-aliases '("cpp" "puppet" "elisp")) (builtin (rg-list-builtin-type-aliases)) (result (rg-get-type-aliases 'skip-internal))) (should (equal (mapcar #'car (seq-take result 3)) rg-prioritized-type-aliases)) (setq rg-prioritized-type-aliases '("puppet" "cpp" "elisp")) (setq result (rg-get-type-aliases 'skip-internal)) (should (equal (mapcar #'car (seq-take result 3)) rg-prioritized-type-aliases)))) (ert-deftest rg-integration-test/search-history () "Test that `rg-history' gets updated." :tags '(need-rg) (let ((rg-history nil)) (rg-run "hello" "all" "/tmp/test") (rg-with-current-result (should (member (car compilation-arguments) rg-history))))) (ert-deftest rg-integration-test/search-uppercase-regexp () "Test that uppercase search triggers case sensitive search." :tags '(need-rg) (let ((rg-group-result nil) (rg-ignore-case 'smart)) (rg-run "Hello" "all" (concat default-directory "test/data")) (rg-with-current-result (let ((bufstr (buffer-substring-no-properties (point-min) (point-max)))) (should (= 1 (s-count-matches "foo.el.*hello" bufstr))) (should (= 1 (s-count-matches "bar.el.*hello" bufstr))))))) (ert-deftest rg-integration-test/search-case-sensitive-regexp () "Test explicit case sensitive search." :tags '(need-rg) (let ((rg-group-result nil) (rg-ignore-case 'nil)) (rg-run "hello" "all" (concat default-directory "test/data"))) (rg-with-current-result (let ((bufstr (buffer-substring-no-properties (point-min) (point-max)))) (should (= 1 (s-count-matches "foo.el.*hello" bufstr))) (should (= 1 (s-count-matches "bar.el.*hello" bufstr)))))) (ert-deftest rg-integration-test/project-root () "Test that all paths in `rt-project-root' gives valid results. This test abuse the internal priority of `rg-project-root', by first checking the root and then successively disable the internally used method. " (rg-check-git-project-root) (with-eval-after-load 'projectile (fmakunbound 'projectile-project-root)) ;; ffip (rg-check-git-project-root) (with-eval-after-load 'find-file-in-project (fmakunbound 'ffip-project-root)) ;; project.el (rg-check-git-project-root) (with-eval-after-load 'project (fmakunbound 'project-current)) ;; vc-backend (rg-check-git-project-root) ;; default (should (equal (expand-file-name (rg-project-root "/tmp/foo.el")) "/tmp/"))) (ert-deftest rg-integration-test/imenu-populated () "Test that the imenu entries are correct." :tags '(need-rg) (rg-run "imenu" "elisp" (concat default-directory "test/data")) (with-current-buffer (rg-buffer-name) (rg-wait-for-search-result) (let ((ientries (funcall imenu-create-index-function))) (should (equal (length ientries) 1)) (should (equal '("limenu.el") (mapcar #'car ientries))))) (rg-run "hello" "*.baz" (concat default-directory "test/data")) (with-current-buffer (rg-buffer-name) (rg-wait-for-search-result) (let ((ientries (funcall imenu-create-index-function))) (should (equal (length ientries) 2)) (should (equal '("bar.baz" "foo.baz") (seq-sort #'string-lessp (mapcar #'car ientries))))))) (ert-deftest rg-integration/command-hiding-hide () "Test command hiding when `rg-hide-command` is non nil." :tags '(need-rg) (rg-test-with-command-start "hello" (should (get-text-property (point) 'display)) (rg-toggle-command-hiding) (should-not (get-text-property (point) 'display)))) (ert-deftest rg-integration/command-hiding-nohide () "Test command hiding when `rg-hide-command` is nil." :tags '(need-rg) (let ((rg-hide-command nil)) (rg-test-with-command-start "hello" (should-not (get-text-property (point) 'display)) (rg-toggle-command-hiding) (should (get-text-property (point) 'display))))) (ert-deftest rg-integration/nogroup-show-columns () "Test that column numbers are shown in no group mode if enabled." :tags '(need-rg) (let ((rg-group-result nil) (rg-show-columns t)) (rg-test-with-first-error "hello" (should (looking-at ".*:[0-9]:[0-9]"))))) (ert-deftest rg-integration/nogroup-hide-columns () "Test that column numbers are hidden in no group mode if disabled." :tags '(need-rg) (let ((rg-group-result nil) (rg-show-columns nil)) (rg-test-with-first-error "hello" (should (looking-at ".*:[0-9]:[^0-9]"))))) (ert-deftest rg-integration/positions-line-only () "Test line position format without alignment." :tags '(need-rg) (let ((rg-align-position-numbers nil)) (rg-test-with-first-error "hello" (should (looking-at "[0-9]:"))))) (ert-deftest rg-integration/positions-line-column () "Test line and column position format without alignment." :tags '(need-rg) (let ((rg-align-position-numbers nil) (rg-show-columns t)) (rg-test-with-first-error "hello" (should (looking-at "[0-9]+:[0-9]+:"))))) (ert-deftest rg-integration/positions-align-line () "Test line position format with alignment." :tags '(need-rg) (let ((rg-align-position-numbers t)) (rg-test-with-first-error "hello" (should (looking-at (format " \\{0,3\\}[0-9]\\{1,4\\}%s" rg-align-position-content-separator))) (should (equal (length (match-string 0)) (1+ rg-align-line-number-field-length)))) (let ((rg-align-position-content-separator "#")) (rg-test-with-first-error "hello" (should (looking-at (format " \\{0,4\\}[0-9]\\{1,5\\}%s" rg-align-position-content-separator))))))) (ert-deftest rg-integration/positions-align-line-column () "Test line and column position format with alignment." :tags '(need-rg) (let ((rg-show-columns t) (rg-align-position-numbers t)) (rg-test-with-first-error "hello" (should (looking-at (format " \\{0,3\\}[0-9]\\{1,4\\}%s \\{0,2\\}[0-9]\\{1,3\\}%s" rg-align-line-column-separator rg-align-position-content-separator))) (should (equal (length (match-string 0)) (+ rg-align-line-number-field-length rg-align-column-number-field-length 2)))) (let ((rg-align-position-content-separator ";") (rg-align-line-column-separator "&")) (rg-test-with-first-error "hello" (should (looking-at (format " \\{0,3\\}[0-9]\\{1,4\\}%s \\{0,2\\}[0-9]\\{1,3\\}%s" rg-align-line-column-separator rg-align-position-content-separator))) (should (equal (length (match-string 0)) (+ rg-align-line-number-field-length rg-align-column-number-field-length 2))))))) (ert-deftest rg-integration/positions-align-context-line () "Test line position format with alignment." :tags '(need-rg) (let ((rg-align-position-numbers t) (ctx-line-rx " \\{0,4\\}[1-9]-") (match-line-rx (format " \\{0,3\\}[0-9]\\{1,4\\}%s" rg-align-position-content-separator)) ctx-match line-match) (rg-test-with-first-error (rg-run "amid" "elisp" (concat default-directory "test/data") nil nil '("--context=3")) (forward-line 1) (should (looking-at ctx-line-rx)) (setq ctx-match (match-string 0)) (forward-line -1) (should (looking-at match-line-rx)) (setq line-match (match-string 0)) (should (equal (length ctx-match) (length line-match))) (forward-line -1) (should (looking-at ctx-line-rx)) (setq ctx-match (match-string 0)) (should (equal (length ctx-match) (length line-match)))))) (ert-deftest rg-integration/positions-align-context-line-column () "Test line position format with alignment." :tags '(need-rg) (let ((rg-show-columns t) (rg-align-position-numbers t) (contex-line-rx " \\{0,9\\}[1-9]-") (match-line-rx (format " \\{0,3\\}[0-9]\\{1,4\\}%s \\{0,2\\}[0-9]\\{1,3\\}%s" rg-align-line-column-separator rg-align-position-content-separator)) ctx-match line-match) (rg-test-with-first-error (rg-run "amid" "elisp" (concat default-directory "test/data") nil nil '("--context=3")) (forward-line 1) (should (looking-at contex-line-rx)) (setq ctx-match (match-string 0)) (forward-line -1) (should (looking-at match-line-rx)) (setq line-match (match-string 0)) (should (equal (length ctx-match) (length line-match))) (forward-line -1) (should (looking-at contex-line-rx)) (setq ctx-match (match-string 0)) (should (equal (length ctx-match) (length line-match)))))) (ert-deftest rg-integration/navigate-file-group-in-grouped-result () "Test file navigation in grouped result." :tags '(need-rg) (let ((rg-group-result t) (files '("bar.el" "foo.el")) pos) (rg-run "hello" "elisp" (concat default-directory "test/data") nil nil (list "--sort=path")) (rg-with-current-result (goto-char (point-min)) (rg-navigate-file-group 1) (should (looking-at (concat "File: " (car files)))) (rg-navigate-file-group 1) (should (looking-at (concat "File: " (cadr files)))) (compilation-next-error 1) (setq pos (point)) ;; Move exactly to first match (rg-navigate-file-group -2) (should (looking-at (concat "File: " (car files)))) (goto-char pos) ;; Move past first match should move to first match (rg-navigate-file-group -3) (should (looking-at (concat "File: " (car files)))) ;; rg-navigate-file-group should indicate when it didn't move at all (should-not (rg-navigate-file-group -1))))) (ert-deftest rg-integration/next-prev-file () "Test file navigation in grouped result." :tags '(need-rg) (let ((rg-group-result t) (files '("bar.el" "foo.el")) pos) (rg-run "hello" "elisp" (concat default-directory "test/data") nil nil (list "--sort=path")) (rg-with-current-result (goto-char (point-min)) (rg-next-file 1) (forward-line -1) (should (looking-at-p (concat "File: " (car files)))) (rg-next-file 1) (forward-line -1) (should (looking-at-p (concat "File: " (cadr files)))) ;; prev from header (rg-prev-file 1) (forward-line -1) (should (looking-at-p (concat "File: " (car files)))) ;; prev from match (rg-next-file 1) (rg-prev-file 1) (forward-line -1) (should (looking-at-p (concat "File: " (car files)))) ;; prev from last line (goto-char (- (point-max) 5)) (rg-prev-file 1) (forward-line -1) (should (looking-at-p (concat "File: " (cadr files)))) ;; prev from empty separator line (forward-line 4) (should (looking-at-p "^$")) (rg-prev-file 1) (forward-line -1) (should (looking-at-p (concat "File: " (cadr files))))))) (ert-deftest rg-integration/navigate-file-group-in-ungrouped-result () "Test file navigation in ungrouped result." :tags '(need-rg) (let ((rg-group-result nil)) (rg-run "hello" "elisp" (concat default-directory "test/data")) (rg-with-current-result (goto-char (point-min)) (rg-navigate-file-group 1) (should (eq (point) (point-min)))))) (defun rg-test-highlight-match (grouped) "Helper for highlight testing. If GROUPED is is non nil grouped result are used." (let ((rg-group-result grouped) pos) (rg-run "hello" "elisp" (concat default-directory "test/data")) (rg-with-current-result (setq pos (rg-single-font-lock-match 'rg-match-face (point-min) (point-max) 1)) (should-not (eq (point-max) pos))))) (ert-deftest rg-integration/highlight-match-group () "Test that highlighting of matches works." :tags '(need-rg) (rg-test-highlight-match t) (rg-test-highlight-match nil)) (ert-deftest rg-integration/group-result-variable () "Test that grouped result is triggered if `rg-group-result' is non nil and ungrouped otherwise." :tags '(need-rg) (should-not (rg-file-message-exist-in-result nil)) (should (rg-file-message-exist-in-result t))) (ert-deftest rg-integration/rg-recompile () "Make sure that `rg-recompile' preserves search parameters." :tags '(need-rg) (let ((parent-dir (concat (expand-file-name default-directory) "test/"))) (rg-run "hello" "elisp" (concat parent-dir "data")) (rg-with-current-result (cl-letf (((symbol-function #'rg-read-pattern) #'ignore)) (setf (rg-search-files rg-cur-search) "all") (setf (rg-search-pattern rg-cur-search) "Hello") (setf (rg-search-dir rg-cur-search) parent-dir) (setf (rg-search-flags rg-cur-search) '("--text")) (rg-rerun)) (should (rg-wait-for-search-result)) (should (equal (list "Hello" "all" parent-dir) (list (rg-search-pattern rg-cur-search) (rg-search-files rg-cur-search) (rg-search-dir rg-cur-search)))) (should (equal '("--text") (rg-search-flags rg-cur-search))) (rg-recompile) (should (rg-wait-for-search-result)) (should (equal (list "Hello" "all" parent-dir) (list (rg-search-pattern rg-cur-search) (rg-search-files rg-cur-search) (rg-search-dir rg-cur-search)))) (should (equal '("--text") (rg-search-flags rg-cur-search)))))) (ert-deftest rg-integration/display-exit-message () "Verify exit messages." :tags '(need-rg) (rg-run "foo" "*.baz" (concat default-directory "test/data")) (with-current-buffer (rg-buffer-name) (rg-wait-for-search-result) (s-matches-p "no matches found" (buffer-substring-no-properties (point-min) (point-max)))) (rg-run "hello" "*.baz" (concat default-directory "test/data")) (rg-with-current-result (should (equal rg-hit-count 6)) (s-matches-p "(6 matches found)" (buffer-substring-no-properties (point-min) (point-max))))) (ert-deftest rg-integration/list-searches () "Test `rg-list-searches'." :tags '(need-rg) (rg-run "hello" "all" (concat default-directory "test/data")) (rg-with-current-result (rg-list-searches) (with-current-buffer rg-search-list-buffer-name (ibuffer-forward-line) (search-forward "hello")) (kill-buffer rg-search-list-buffer-name))) (ert-deftest rg-integration/run-confirm-unchanged-command () "Test confirm and full command search" :tags '(need-rg) (cl-letf* ((rg-history nil) (called-prompt nil) (called-history nil) ((symbol-function #'read-from-minibuffer) (lambda (prompt command _ign1 _ign2 history) (setq called-prompt prompt) (setq called-history history) command))) (rg-run "hello" "all" "tmp/test" nil 'confirm) (rg-with-current-result (should (eq called-history 'rg-history)) (should (equal called-prompt "Confirm: ")) (should-not (null rg-cur-search)) ;; We use the stub about which does not update the history (should (null rg-history))))) (ert-deftest rg-integration/run-confirm-changed-command () "Test confirm and full command search" :tags '(need-rg) (cl-letf* ((rg-history nil) (changed-command nil) (original-command nil) ((symbol-function #'read-from-minibuffer) (lambda (_ign1 command _ign2 _ign3 _ign4) (setq changed-command (concat command "\\ world")) (setq original-command command) changed-command))) (rg-run "hello" "all" "tmp/test" nil 'confirm) (rg-with-current-result (should (equal changed-command (concat original-command "\\ world"))) (should (rg-search-full-command rg-cur-search)) ;; We use the stub above which does not update the history (should (null rg-history))))) (ert-deftest rg-integration/dwim-search () "Test `rg-dwim'." (cl-letf* ((called-pattern nil) (called-files nil) (called-dir nil) (called-literal nil) (project-dir (expand-file-name default-directory)) ((symbol-function #'rg-run) (lambda (pattern files dir &optional literal &rest _) (setq called-pattern pattern) (setq called-files files) (setq called-dir dir) (setq called-literal literal)))) (find-file "test/data/foo.el") (rg-dwim) (should (equal called-pattern "hello")) (should (equal called-files "elisp")) (should (equal (expand-file-name called-dir) project-dir)) (should-not (eq called-literal nil)) (rg-dwim '(4)) (should (equal (expand-file-name called-dir) (expand-file-name default-directory))) (rg-dwim '(16)) (should (equal called-files "foo.el")))) (ert-deftest rg-integration/project-search () "Test `rg-project'." (cl-letf* ((called-pattern nil) (called-files nil) (called-dir nil) (called-literal nil) (project-dir (expand-file-name default-directory)) ((symbol-function #'rg-run) (lambda (pattern files dir &optional literal &rest _) (setq called-pattern pattern) (setq called-files files) (setq called-dir dir) (setq called-literal literal)))) (find-file "test/data/foo.baz") (rg-project "hello" "elisp") (should (equal called-pattern "hello")) (should (equal called-files "elisp")) (should (equal (expand-file-name called-dir) project-dir)) (should (eq called-literal nil)))) (defun rg-only-rg-regexps-p () (and (seq-every-p (lambda (elem) (eq elem t)) (mapcar (lambda (regexp) (s-starts-with-p "rg-" (symbol-name regexp))) compilation-error-regexp-alist)) (seq-every-p (lambda (elem) (eq elem t)) (mapcar (lambda (pair) (s-starts-with-p "rg-" (symbol-name (car pair)))) compilation-error-regexp-alist-alist)))) (ert-deftest rg-integration/compilation-error-regexp () "Test that compilation-error-regexp-alist is only using rg regexps." (with-temp-buffer (rg-mode) (should (rg-only-rg-regexps-p))) (with-temp-buffer (setq compilation-error-regexp-alist '(foo-bar)) (rg-mode) (should (rg-only-rg-regexps-p))) (with-temp-buffer (add-hook 'compilation-mode-hook (lambda () (setq compilation-error-regexp-alist '(foo-bar)))) (rg-mode) (should (rg-only-rg-regexps-p)))) (ert-deftest rg-integration/rg-executable-per-connection () "Disabling setting should disable connection local variable." (skip-unless (version<= "27" emacs-version)) (rg-with-executable-find-mock (let ((rg-executable-per-connection nil) (connection-local-profile-alist '()) (default-directory (format "/sudo:%s@localhost:%s" (user-login-name) default-directory))) (with-temp-buffer (should (equal (rg-executable) "local-rg")))))) (ert-deftest rg-integration/rg-executable-local-notfound () "Test error if local exeutable not found." (rg-with-executable-find-mock (let ((rg-executable nil) (default-directory "/tmp")) (with-temp-buffer (should-error (rg-executable)))))) (ert-deftest rg-integration/rg-executable-local () "Local buffer should get local executable." (rg-with-executable-find-mock (let ((connection-local-profile-alist '())) (with-temp-buffer (should (equal (rg-executable) "local-rg")))))) (ert-deftest rg-integration/rg-executable-remote-notfound () "Test error if remote exeutable not found." (skip-unless (version<= "27" emacs-version)) (cl-letf (((symbol-function #'executable-find) (lambda (command &rest) nil)) (connection-local-profile-alist '()) (default-directory (format "/sudo:%s@localhost:%s" (user-login-name) default-directory))) (with-temp-buffer (should-error (rg-executable))))) (ert-deftest rg-integration/rg-executable-through-rg-run () "Correct executable should be trigggered through main entry points." (skip-unless (version<= "27" emacs-version)) (rg-with-executable-find-mock (cl-letf* ((connection-local-profile-alist '()) (result nil) ((symbol-function #'compilation-start) (lambda (command &rest _) (setq result command) (let ((buffer (get-buffer-create "*rg-temp*"))) (with-current-buffer buffer (rg-mode)) buffer)))) (with-temp-buffer (rg-run "pattern" "all" "/tmp") (should (string-prefix-p "local-rg" result))) (with-temp-buffer (rg-run "pattern" "all" (format "/sudo:%s@localhost:%s" (user-login-name) default-directory)) (should (string-prefix-p "remote-rg-localhost" result)))))) (ert-deftest rg-integration/rg-executable-remote-new-buffers () "Once set the remote executable should be applied to new buffers for the same host." (skip-unless (version<= "27" emacs-version)) (rg-with-executable-find-mock (let ((default-directory (format "/sudo:%s@localhost:%s" (user-login-name) default-directory)) (connection-local-profile-alist '())) (with-temp-buffer (should (equal (rg-executable) "remote-rg-localhost"))) (with-temp-buffer (should (equal (rg-executable) "remote-rg-localhost")))))) (ert-deftest rg-integration/rg-executable-remote-open-buffers () "Once set the remote executable should be applied to already open buffers for the same host." (skip-unless (version<= "27" emacs-version)) (rg-with-executable-find-mock (let ((connection-local-profile-alist '()) (default-directory (format "/sudo:%s@localhost:%s" (user-login-name) default-directory))) (with-temp-buffer (with-temp-buffer (should (equal (rg-executable) "remote-rg-localhost"))) (should (equal (rg-executable) "remote-rg-localhost")))))) (ert-deftest rg-integration/rg-executable-remote-multiple-hosts () "Different hosts should get get remote specific binary." (skip-unless (version<= "27" emacs-version)) (rg-with-executable-find-mock (let ((connection-local-profile-alist '())) (let ((default-directory (format "/sudo:%s@localhost:%s" (user-login-name) default-directory))) (with-temp-buffer (should (equal (rg-executable) "remote-rg-localhost")))) (let ((default-directory (format "/sudo:%s@127.0.0.1:%s" (user-login-name) default-directory))) (with-temp-buffer (should (equal (rg-executable) "remote-rg-127.0.0.1"))))))) (ert-deftest rg-integration/rg-executable-remote-and-local () "Remote host get remote executable and local host get local executable." (skip-unless (version<= "27" emacs-version)) (rg-with-executable-find-mock (let ((connection-local-profile-alist '())) (let ((default-directory (format "/sudo:%s@localhost:%s" (user-login-name) default-directory))) (with-temp-buffer (should (equal (rg-executable) "remote-rg-localhost")) (let ((default-directory "/tmp")) (should (equal (rg-executable) "local-rg")))))))) (ert-deftest rg-integration/rg-executable-results-buffer () "Correct executable should be trigggered from results buffer." (skip-unless (version<= "27" emacs-version)) (rg-with-executable-find-mock (let ((connection-local-profile-alist '())) (with-temp-buffer (rg-run "pattern" "all" "/tmp") (rg-with-current-result (should (string-prefix-p "local-rg" (car compilation-arguments))) (setf (rg-search-dir rg-cur-search) (format "/sudo:%s@localhost:%s" (user-login-name) default-directory)) (rg-rerun) (rg-wait-for-search-result) (should (string-prefix-p "remote-rg-localhost" (car compilation-arguments))) (setf (rg-search-dir rg-cur-search) "/tmp") (rg-rerun) (rg-wait-for-search-result) (should (string-prefix-p "local-rg" (car compilation-arguments)))))))) (ert-deftest rg-integration/default-directory-search-from-results () "Verify default-directory when doing a search from results buffer." :tags '(need-rg) (let ((search-dir1 (expand-file-name default-directory)) (search-dir2 (file-name-directory (expand-file-name (concat default-directory "test/data"))))) (rg-run "foo" "*.baz" search-dir1) (with-current-buffer (rg-buffer-name) (should (equal default-directory search-dir1)) (rg-wait-for-search-result) (rg-run "foo" "*.baz" search-dir2) (should (equal default-directory search-dir2)) (rg-wait-for-search-result)))) (provide 'rg.el-test) ;;; rg.el-test.el ends here ================================================ FILE: test/style-check.el ================================================ ;;; style-check.el --- rg.el: Check emacs lisp style of package files ;; Copyright (C) 2017 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: (require 'flycheck) (require 'package-lint) (load "test-helper") (defun rg-message ()) (defun get-compilation-buffer () (let (buffer) (dolist (buf (buffer-list) buffer) (when (eq (with-current-buffer buf major-mode) 'compilation-mode) (setq buffer buf))))) (defun run-flycheck (checker) (flycheck-compile checker) (let ((buffer (get-compilation-buffer))) (unless buffer (error "No compilation buffer")) (with-current-buffer buffer (rg-wait-for-search-result) (goto-char (point-min)) (let (beg error-lines) (while (ignore-errors (compilation-next-error 1)) (setq beg (point)) (end-of-line) (push (buffer-substring-no-properties beg (point)) error-lines)) (when error-lines (rg-message "%s" (mapconcat 'identity (reverse error-lines) "\n")) (kill-emacs 2)))))) (defun run-emacs-lisp-flycheck-and-exit () (cl-letf (((symbol-function #'rg-message) (symbol-function #'message)) ((symbol-function #'message) #'ignore) (flycheck-emacs-lisp-load-path 'inherit)) (dolist (file argv) (dolist (checker '(emacs-lisp-checkdoc emacs-lisp)) (with-temp-buffer (insert-file-contents file t) (emacs-lisp-mode) (run-flycheck checker))))) (kill-emacs 0)) (defun run-package-lint-and-exit () (setq package-user-dir "/tmp/rg-elpa") (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t) (package-refresh-contents) (package-lint-batch-and-exit)) ;;; style-check.el ends here ================================================ FILE: test/test-helper.el ================================================ ;;; test-helper.el --- rg.el: Helper for tests ;; Copyright (C) 2017 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: (when (require 'undercover nil t) (undercover "*.el" (:report-format 'lcov) (:send-report nil))) (require 'cl-lib) (require 'ert) (require 'rg) (require 'rg-history) (require 'rg-isearch) (require 'rg-menu) (require 's) (require 'seq) (require 'tramp) (require 'wgrep-rg) (require 'mule-util) (defun rg-regexp-anywhere (needle) (s-replace "%%%%" needle "\\( \\|^\\)%%%%\\( \\|$\\)")) (defun rg-regexp-last (needle) (s-replace "%%%%" needle "\\( \\|^\\)%%%%$")) (defun rg-regexp-anywhere-but-last (needle) (s-replace "%%%%" needle "\\( \\|^\\)%%%% ")) (defun rg-run-and-wait (fn &rest args) "Run FN with ARGS and then wait for search to be done." (apply fn args) (with-current-buffer (rg-buffer-name) (rg-wait-for-search-result))) (defun rg-wait-for-search-result () "Wait for the rg process to finish searching and return non nil if the search was successful. Timeout is 10 s." (let (search-finished) (add-hook 'compilation-finish-functions (lambda (buffer msg) (setq search-finished msg)) t t) (with-timeout (10 nil) (while (not search-finished) (accept-process-output nil 0.1))) (when search-finished (equal (string-trim search-finished) "finished")))) (defun rg-check-git-project-root () "Check that project root of rg.el-test.el file is main dir of repository." (should (equal (expand-file-name (rg-project-root (concat default-directory "/test/rg.el-test.el"))) (expand-file-name default-directory)))) (defmacro rg-with-current-result (&rest body) "Evaluate BODY in current result buffer when search has finished." (declare (indent 0) (debug t)) `(with-current-buffer (rg-buffer-name) (font-lock-ensure) (rg-wait-for-search-result) (let ((result (progn ,@body))) (kill-buffer) result))) (defmacro rg-with-temp-global-keymap (&rest body) "Evaluate BODY with a temporary keymap as global map. Restore original global keymap afterwards." (declare (indent 0) (debug t)) (let ((saved-global-map (cl-gensym)) (temp-global-map (cl-gensym))) `(let ((,saved-global-map (current-global-map)) (,temp-global-map (make-sparse-keymap))) (use-global-map ,temp-global-map) ,@body (use-global-map ,saved-global-map)))) (defmacro rg-test-with-fontified-buffer (search &rest body) "Run SEARCH and make sure buffer is fontified when executing BODY. SEARCH can either be a search string or a form invocating `rg-run'." (declare (indent 1) (debug t)) (let ((invocation (if (stringp search) `(rg-run ,search "elisp" (concat default-directory "test/data")) search))) (cl-assert (consp invocation)) `(progn ,invocation (rg-with-current-result ;; font-lock-mode is disabled by default in batch mode so ;; request explicit fontification (font-lock-ensure) ,@body)))) (defmacro rg-test-with-command-start (search &rest body) "Run search and put point to beginning of rg command when running BODY." (declare (indent 0) (debug t)) (let ((command-start (cl-gensym))) `(rg-test-with-fontified-buffer ,search (let ((,command-start (next-single-property-change (point-min) 'rg-command-hidden-part))) (should ,command-start) (should-not (eq ,command-start (point-max))) (goto-char ,command-start) ,@body)))) (defmacro rg-test-with-first-error (search &rest body) "Run search and put point at start of first error line when running BODY." (declare (indent 0) (debug t)) `(rg-test-with-fontified-buffer ,search (compilation-next-error 1) (should-not (eq (point) (point-max))) (beginning-of-line) ,@body)) (defmacro rg-with-executable-find-mock (&rest body) "Mock `executable-find' in BODY." (declare (indent 0) (debug t)) `(cl-letf* (((symbol-function #'executable-find) (lambda (command &optional remote) (let ((tramp-name (tramp-dissect-file-name default-directory))) (concat "remote-rg-" (tramp-file-name-host-port tramp-name))))) (rg-executable "local-rg")) ,@body)) (defun simulate-rg-run (pattern files dir) (setq-default rg-cur-search (rg-search-create :pattern pattern :files files :flags rg-initial-toggle-flags))) (defun rg-file-message-exist-in-result (grouped) "Search and return non nil if 'rg-file-tag-face exist in buffer. GROUPED control if `rg-group-result' is used." (let((rg-group-result grouped) pos) (rg-run "hello" "elisp" (concat default-directory "test/data")) (rg-with-current-result (not (eq (point-max) (next-single-property-change (point-min) 'rg-file-message nil (point-max))))))) (defun rg-single-font-lock-match (face pos limit direction) "Return position of next match of 'font-lock-face property that equals FACE. POS is the start position of the search and LIMIT is the limit of the search. If FACE is not found within LIMIT, LIMIT is returned. If DIRECTION is positive search forward in the buffer, otherwise search backward." (let ((single-property-change-func (if (> direction 0) 'next-single-property-change 'previous-single-property-change))) (while (progn (setq pos (funcall single-property-change-func pos 'font-lock-face nil limit)) (and (not (equal pos limit)) (not (let ((properties (get-text-property pos 'font-lock-face))) (if (listp properties) (member face properties) (eq face properties)))))))) pos) (defun rg-set-equal-p (set1 set2) "Compare if SET1 and SET2 contain the same elements." (not (cl-set-exclusive-or set1 set2 :test #'equal))) (defun rg-truncate-string-ellipsis () (if (fboundp 'truncate-string-ellipsis) (truncate-string-ellipsis) truncate-string-ellipsis)) ;;; test-helper.el ends here ================================================ FILE: test/wgrep-rg.el-test.el ================================================ ;;; wgrep-rg.el-test.el --- wgrep-rg.el tests -*- lexical-binding: t; -*- ;; Copyright (C) 2018 David Landell ;; ;; Author: David Landell ;; URL: https://github.com/dajva/rg.el ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 3 ;; of the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ;; 02110-1301, USA. ;;; Commentary: ;;; Code: ;; Integration tests (ert-deftest rg-integration-test/wgrep-nogroup () "wgrep test with grouped result." :tags '(need-rg) (let ((rg-show-columns nil) (rg-group-result nil) (rg-align-position-numbers nil)) (rg-run-wgrep-tests) (rg-run-wgrep-context-tests))) (ert-deftest rg-integration-test/wgrep-nogroup-columns () "wgrep test with grouped result." :tags '(need-rg) (let ((rg-show-columns t) (rg-group-result nil) (rg-align-position-numbers nil)) (rg-run-wgrep-tests) (rg-run-wgrep-context-tests))) (ert-deftest rg-integration-test/wgrep-group () "wgrep test with grouped result." :tags '(need-rg) (let ((rg-show-columns nil) (rg-group-result t) (rg-align-position-numbers nil)) (rg-run-wgrep-tests) (rg-run-wgrep-context-tests))) (ert-deftest rg-integration-test/wgrep-group-columns () "wgrep test with grouped result." :tags '(need-rg) (let ((rg-show-columns t) (rg-group-result t) (rg-align-position-numbers nil)) (rg-run-wgrep-tests) (rg-run-wgrep-context-tests))) (ert-deftest rg-integration-test/wgrep-group-align () "wgrep test with aligned and grouped result." :tags '(need-rg) (let ((rg-show-columns nil) (rg-group-result t) (rg-align-position-numbers t)) (rg-run-wgrep-tests) (rg-run-wgrep-context-tests))) (ert-deftest rg-integration-test/wgrep-group-align-columns () "wgrep test with aligned and grouped result including column numbers." :tags '(need-rg) (let ((rg-show-columns t) (rg-group-result t) (rg-align-position-numbers t)) (rg-run-wgrep-tests) (rg-run-wgrep-context-tests))) (ert-deftest rg-integration-test/wgrep-group-align-columns-custom-separators () "wgrep test with aligned and grouped result including column numbers." :tags '(need-rg) (let ((rg-show-columns t) (rg-group-result t) (rg-align-position-numbers t) (rg-align-line-column-separator " ") (rg-align-position-content-separator "#")) (rg-run-wgrep-tests) (rg-run-wgrep-context-tests))) ;; Helper functions (defun rg-check-wgrep-header () (goto-char (point-min)) (should (get-text-property (point) 'wgrep-header)) ;; Jump over "rg-started" line that could match error regexp. (goto-char (next-single-property-change (point-min) 'rg-command-hidden-part)) (should (get-text-property (point) 'wgrep-header)) (end-of-line) (should (get-text-property (point) 'wgrep-header)) (rg-next-file 1) (should-not (get-text-property (point) 'wgrep-header))) (defun rg-check-wgrep-footer () (goto-char (1- (point-max))) (should (get-text-property (point) 'wgrep-footer)) ;; "rg finished" line sometimes match error regexp so skip that. (forward-line -1) (compilation-previous-error 1) (should-not (get-text-property (point) 'wgrep-footer)) (re-search-forward "^$") (should (get-text-property (point) 'wgrep-footer))) (defun rg-check-wgrep-props-at-pos (pos) (let ((wgrep-expected-props '(wgrep-line-filename wgrep-line-number)) (all-props (text-properties-at pos))) (dolist (expected wgrep-expected-props) (should (member expected all-props))))) (defun rg-check-no-wgrep-prop-at-pos (pos) (let ((wgrep-expected-props '(wgrep-line-filename wgrep-line-number)) (all-props (text-properties-at pos))) (dolist (expected wgrep-expected-props) (should-not (member expected all-props))))) (defun rg-check-wgrep-current-line () (rg-check-wgrep-props-at-pos (point)) (should (get-text-property (point) 'read-only)) (end-of-line) (backward-char) (rg-check-no-wgrep-prop-at-pos (point)) (should-not (get-text-property (point) 'read-only))) (defun rg-move-to-context-line () (goto-char (point-min)) ;; Jump over "rg-started" line that could match error regexp. (goto-char (next-single-property-change (point-min) 'rg-command-hidden-part)) (compilation-next-file 1) (forward-line -1)) (defun rg-run-wgrep-context-tests () (rg-test-with-fontified-buffer (rg-run "amid" "elisp" (concat default-directory "test/data") nil nil '("--context=3")) (wgrep-change-to-wgrep-mode) (rg-check-wgrep-current-buffer) (rg-move-to-context-line) (rg-check-wgrep-current-line))) (defun rg-run-wgrep-tests() (rg-test-with-fontified-buffer "hello" (wgrep-change-to-wgrep-mode) (rg-check-wgrep-current-buffer))) (defun rg-check-wgrep-current-buffer () (rg-check-wgrep-header) (rg-check-wgrep-footer) (goto-char (point-min)) ;; Jump over "rg-started" line that could match error regexp. (goto-char (next-single-property-change (point-min) 'rg-command-hidden-part)) (compilation-next-file 1) (rg-check-wgrep-current-line) (goto-char (point-max)) ;; "rg finished" line sometimes match error regexp so skip that. (forward-line -1) (compilation-previous-error 1) (rg-check-wgrep-current-line)) (provide 'wgrep-rg.el-test) ;;; wgrep-rg.el-test.el ends here ================================================ FILE: wgrep-rg.el ================================================ ;;; wgrep-rg.el --- Writable rg buffer and apply the changes to files -*- lexical-binding: t; -*- ;; Author: Masahiro Hayashi ;; Rewritten by: Dale Sedivec ;; Maintainer: David Landell ;; Keywords: grep edit extensions ;; URL: http://github.com/dajva/rg.el ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation; either version 3, or (at ;; your option) any later version. ;; This program is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;;; Commentary: ;; wgrep-rg allows you to edit a rg buffer and apply those changes to ;; the file buffer. ;;; Install: ;; 1. Install rg.el ;; ;; https://github.com/dajva/rg.el ;; 2. Install wgrep.el ;; 3. Put this file into load-path'ed directory, and byte compile it if ;; desired. And put the following expression into your ~/.emacs. ;; ;; (autoload 'wgrep-rg-setup "wgrep-rg") ;; (add-hook 'rg-mode-hook 'wgrep-rg-setup) ;;; Usage: ;; See wgrep.el ;;; Code: (require 'wgrep) ;; Forward declarations (declare-function rg-file-line-column-pattern-group "rg-result.el") (declare-function rg-file-line-pattern-group "rg-result.el") (defvar rg-mode-hook) (defvar wgrep-rg-grouped-result-file-regexp "^File:[[:space:]]+\\(.*\\)$" "Regular expression for the start of results for a file in grouped results. \"Grouped results\" are what you get from rg.el when `rg-group-result' is true or when you call rg with --heading.") (defvar wgrep-rg-ungrouped-result-regexp "^\\(.+?\\)\\(?:-\\|:\\)\\([1-9][0-9]*\\)\\(?:-\\|:\\)\\(?:[1-9][0-9]*:\\)*" "Regular expression for an ungrouped result. You get \"ungrouped results\" when `rg-group-result' is false or when you manage to call rg with --no-heading.") (defun wgrep-rg-prepare-header/footer () "Add wgrep related text properties for header and footer. This is needed in order for wgrep to ignore thos areas when parsing the content." (save-excursion (goto-char (point-min)) ;; Look for the first useful result line. (let ((result-line-regexp (concat wgrep-rg-grouped-result-file-regexp "\\|" wgrep-rg-ungrouped-result-regexp))) (while (progn (if (not (re-search-forward result-line-regexp nil t)) ;; No results in this buffer, let's mark the whole thing as ;; header. (progn (add-text-properties (point-min) (point-max) '(read-only t wgrep-header t)) nil) (save-excursion (beginning-of-line) ;; The ungrouped result regexp also match the "rg started" ;; line. If that happens continue searching. (if (looking-at-p "rg started.*") 'continue (add-text-properties (point-min) (point) '(read-only t wgrep-header t)) nil))))) (goto-char (point-max)) (re-search-backward "^rg finished .*$" nil t) ;; Move to the start of the line after the last result, and ;; mark everything from that line forward as wgrep-footer. (when (zerop (forward-line -1)) (add-text-properties (point) (point-max) '(read-only t wgrep-footer t)))))) (defun wgrep-rg-parse-command-results () "Parse the rg results for wgrep usage. This makes non content of the buffer readonly and marks specific areas with wgrep text properties to allow for wgrep to do its job." ;; Note that this function is called with the buffer narrowed to ;; exclude the header and the footer. (We're going to assert that ;; fact here, because we use (bobp) result a bit further down to ;; decide that we're not reading grouped results; see below.) (unless (bobp) (error "Expected to be called with point at beginning of buffer")) (save-excursion ;; First look for grouped results (`rg-group-result' is/was ;; probably true). (while (re-search-forward wgrep-rg-grouped-result-file-regexp nil t) ;; Ignore the line that introduces matches from a file, so that ;; wgrep doesn't let you edit it. (add-text-properties (match-beginning 0) (match-end 0) '(wgrep-ignore t)) (let ((file-name (match-string-no-properties 1))) ;; Note that I think wgrep uses this property to quickly find ;; the file it's interested in when searching during some ;; operation(s). We stick it on the file name in the results ;; group header. (add-text-properties (match-beginning 1) (match-end 1) (list (wgrep-construct-filename-property file-name) file-name)) ;; Matches are like: 999{separator}55{separator}line content here ;; Context lines are like: 999-line content here (while (and (zerop (forward-line 1)) ; last line (not (looking-at-p "^$"))) ; Empty line between files (cond ((or (looking-at (rg-file-line-column-pattern-group)) (looking-at (rg-file-line-pattern-group)) ;; Context lines (looking-at "^ *\\([1-9][0-9]*\\)-")) (add-text-properties (match-beginning 0) (match-end 0) (list 'wgrep-line-filename file-name 'wgrep-line-number (string-to-number (match-string 1))))) ;; Ignore context separator. ((looking-at "^--$") (add-text-properties (match-beginning 0) (match-end 0) '(wgrep-ignore t))))))) (when (bobp) ;; Search above never moved point, so match non-grouped results ;; (`rg-group-result' is/was probably false). (let (last-file-name) ;; Matches are like: /foo/bar:999:55:line content here ;; Context lines are like: /foo/bar-999-line content here (while (re-search-forward (concat wgrep-rg-ungrouped-result-regexp "\\|\\(^--$\\)") nil t) (if (match-beginning 3) ;; Ignore context separator. (add-text-properties (match-beginning 0) (match-end 0) '(wgrep-ignore t)) (let ((file-name (match-string-no-properties 1)) (line-number (string-to-number (match-string 2)))) (unless (equal file-name last-file-name) ;; This line is a result from a different file than ;; the last match (or else this is the first match in ;; the results). Write the special file name property ;; for wgrep. (let ((file-name-prop (wgrep-construct-filename-property file-name))) (add-text-properties (match-beginning 1) (match-end 1) (list file-name-prop file-name))) (setq last-file-name file-name)) (add-text-properties (match-beginning 0) (match-end 0) (list 'wgrep-line-filename file-name 'wgrep-line-number line-number))))))))) ;;;###autoload (defun wgrep-rg-setup () "Setup wgrep rg support." (set (make-local-variable 'wgrep-header&footer-parser) 'wgrep-rg-prepare-header/footer) (set (make-local-variable 'wgrep-results-parser) 'wgrep-rg-parse-command-results) (wgrep-setup-internal)) (defun wgrep-rg-warn-ag-setup () "Print warning message if old ag setup is used." (when (memq 'wgrep-ag-setup rg-mode-hook) (message "Warning: wgrep-ag is no longer supported by this package. Please remove wgrep-ag-setup from rg-mode-hook."))) ;;;###autoload (add-hook 'rg-mode-hook 'wgrep-rg-setup) ;; For `unload-feature' (defun wgrep-rg-unload-function () "Allow for unloading wgrep rg support." (remove-hook 'rg-mode-hook 'wgrep-rg-setup)) (provide 'wgrep-rg) ;;; wgrep-rg.el ends here