├── .dir-locals.el ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── emacs-helm-rg.png ├── gen-commentary ├── .gitignore ├── create-markdown.coffee ├── package-lock.json ├── package.json └── update-commentary.el ├── helm-rg.el └── tests ├── LICENSE ├── checkdoc-batch.el ├── helm-rg-test.el └── install-packages.el /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((nil 5 | (require-final-newline . t) 6 | (indent-tabs-mode . nil) 7 | (checkdoc-force-docstrings-flag . nil) 8 | (fill-column . 100) 9 | (highlight-80+-columns . 100)) 10 | (emacs-lisp-mode 11 | (sentence-end-double-space . nil))) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: emacs-lisp 2 | sudo: false 3 | 4 | before_install: 5 | - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh 6 | - evm install $EVM_EMACS --use --skip 7 | 8 | env: 9 | - EVM_EMACS=emacs-25.1-travis 10 | - EVM_EMACS=emacs-25.2-travis 11 | - EVM_EMACS=emacs-25.3-travis 12 | - EVM_EMACS=emacs-26.1-travis 13 | 14 | script: 15 | - emacs --version 16 | - make install-packages test-noninteractive 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EMACS:=emacs 2 | 3 | # TODO: do interactive testing here too (start up an interactive emacs)! 4 | # TODO: once that is done, invoke EVM here to test against multiple emacs versions! 5 | 6 | # TODO: make a `define` for this shared test structure! 7 | test-noninteractive: compile-all checkdoc 8 | $(EMACS) -Q --batch \ 9 | --eval '(package-initialize)' \ 10 | -l helm-rg.elc -l tests/helm-rg-test.elc \ 11 | -l ert -l rx \ 12 | --eval "(ert (rx bos \"test-helm-rg\"))" \ 13 | --eval '(kill-emacs 0)' 14 | @echo "All tests passed!" 15 | 16 | test-interactive: compile-all checkdoc 17 | $(EMACS) -Q \ 18 | --eval '(package-initialize)' \ 19 | -l helm-rg.elc -l tests/helm-rg-test.elc \ 20 | -l ert -l rx \ 21 | --eval "(ert (rx bos \"test-helm-rg\"))" \ 22 | --eval '(kill-emacs 0)' 23 | @echo "All tests passed!" 24 | 25 | # TODO: what is this needed for? 26 | install-packages: 27 | $(EMACS) -Q --batch -l tests/install-packages.el 28 | 29 | error_output:=error-output.log 30 | 31 | # Output to a file, and if any errors we care about are detected, print the whole output as well. 32 | checkdoc: 33 | find . -maxdepth 1 -type f -name 'helm-rg-*.el' \ 34 | | xargs $(EMACS) -Q --batch -l tests/checkdoc-batch.el 2>&1 \ 35 | | tee $(error_output) \ 36 | | grep -vF 'Some lines are over 80 columns wide' \ 37 | | grep -vF 'Arguments occur in the doc string out of order' \ 38 | | grep -E '^.*\.el:[1-9]+|exited with status 255' \ 39 | && (cat $(error_output) >&2 ; exit 1) \ 40 | || exit 0 41 | 42 | compile-all: 43 | $(EMACS) -Q --batch \ 44 | --eval '(package-initialize)' \ 45 | --eval '(setq byte-compile-error-on-warn t)' \ 46 | -l helm-rg.el \ 47 | -f batch-byte-compile helm-rg.el tests/helm-rg-test.el 48 | 49 | clean: 50 | find . -type f -name '*.elc' -exec rm '{}' '+' 51 | rm -f $(error_output) 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | helm-rg 2 | ======= 3 | 4 | [![MELPA](https://melpa.org/packages/helm-rg-badge.svg)](https://melpa.org/#/helm-rg) 5 | 6 | ![`helm-rg` example usage](./emacs-helm-rg.png) 7 | 8 | Search massive codebases extremely fast, using [`ripgrep`](https://github.com/BurntSushi/ripgrep) and [`helm`](https://github.com/emacs-helm/helm). Inspired by [`helm-ag`](https://github.com/syohex/emacs-helm-ag) and [`f3`](https://github.com/cosmicexplorer/f3). 9 | 10 | Also check out [rg.el](https://github.com/dajva/rg.el), which I haven't used much but seems pretty cool. 11 | 12 | # Usage 13 | 14 | *See the [`ripgrep` whirlwind tour](https://github.com/BurntSushi/ripgrep#whirlwind-tour) for further information on invoking `ripgrep`.* 15 | 16 | - Invoke the interactive function `helm-rg` to start a search with `ripgrep` in the current directory. 17 | - `helm` is used to browse the results and update the output as you type. 18 | - Each line has the file path, the line number, and the column number of the start of the match, and each part is highlighted differently. 19 | - Use TAB to invoke the helm persistent action, which previews the result and highlights the matched text in the preview. 20 | - Use RET to visit the file containing the result, move point to the start of the match, and recenter. 21 | - The result's buffer is displayed with `helm-rg-display-buffer-normal-method` (which defaults to `switch-to-buffer`). 22 | - Use a prefix argument (C-u RET) to open the buffer with `helm-rg-display-buffer-alternate-method` (which defaults to `pop-to-buffer`). 23 | - The text entered into the minibuffer is interpreted into a [PCRE](https://pcre.org) regexp to pass to `ripgrep`. 24 | - `helm-rg`'s pattern syntax is basically PCRE, but single spaces basically act as a more powerful conjunction operator. 25 | - For example, the pattern `a b` in the minibuffer is transformed into `a.*b|b.*a`. 26 | - The single space can be used to find lines with any permutation of the regexps on either side of the space. 27 | - Two spaces in a row will search for a literal single space. 28 | - `ripgrep`'s `--smart-case` option is used so that case-sensitive search is only on if any of the characters in the pattern are capitalized. 29 | - For example, `ab` (conceptually) searches `[Aa][bB]`, but `Ab` in the minibuffer will only search for the pattern `Ab` with `ripgrep`, because it has at least one uppercase letter. 30 | - Use M-d to select a new directory to search from. 31 | - Use M-g to input a glob pattern to filter files by, e.g. `*.py`. 32 | - The glob pattern defaults to the value of `helm-rg-default-glob-string`, which is an empty string (matches every file) unless you customize it. 33 | - Pressing M-g again shows the same minibuffer prompt for the glob pattern, with the string that was previously input. 34 | - Use and to go up and down by files in the results. 35 | - and simply go up and down by match result, and there may be many matches for your pattern in a single file, even multiple on a single line (which `ripgrep` reports as multiple separate results). 36 | - The and keys will move up or down until it lands on a result from a different file than it started on. 37 | - When moving by file, `helm-rg` will cycle around the results list, but it will print a harmless error message instead of looping infinitely if all results are from the same file. 38 | - Use the interactive autoloaded function `helm-rg-display-help` to see the ripgrep command's usage info. 39 | 40 | # TODO 41 | 42 | *items checked completed here are ready to be added to the docs above* 43 | 44 | - [x] make a keybinding to drop into an "edit mode" and edit file content inline in results like [`helm-ag`](https://github.com/syohex/emacs-helm-ag) 45 | - *currently called "bounce mode"* in the alpha stage 46 | - [x] needs to dedup results from the same line 47 | - [x] should also merge the colorations 48 | - [x] this might be easier without using the `--vimgrep` flag (!!!) 49 | - [x] can insert markers on either side of each line to find the text added or removed 50 | - [x] can change the filename by editing the file line 51 | - [x] needs to reset all the file data for each entry if the file name is being changed!!! 52 | - [x] can expand the windows of text beyond single lines at a time 53 | - using `helm-rg--expand-match-context` and/or `helm-rg--spread-match-context` 54 | - [x] and pop into another buffer for a quick view if you want 55 | - can use `helm-rg--visit-current-file-for-bounce` 56 | - [ ] can expand up and down from file header lines to add lines from the top or bottom of the file! 57 | - [ ] can use newlines in inserted text 58 | - not for file names -- newlines are still removed there 59 | - would need to use text properties to move by match results then, for everything that uses `helm-rg--apply-matches-with-file-for-bounce` basically 60 | - [x] visiting the file should go to the appropriate line of the file! 61 | - [ ] should flash a highlight of the matched text when visiting the file! 62 | - [x] color all results in the file in the async action! 63 | - [x] don't recolor when switching to a different result in the same file! 64 | - [x] don't color matches whenever file path matches `helm-rg-shallow-highlight-files-regexp` 65 | - [ ] use `ripgrep` file types instead of flattening globbing out into `helm-rg-default-glob-string` 66 | - user defines file types in a `defcustom`, and can interactively toggle the accepted file types 67 | - user can also set the default set of file types 68 | - as a dir-local variable!! 69 | - [ ] add testing 70 | - [ ] should be testing all of our interactive functions 71 | - in all configurations (for all permutations of `defcustom` values) 72 | - [ ] also everything that's called by helm 73 | - does helm have any frameworks to make integration testing easier? 74 | - [ ] publish `update-commentary.el` and the associated machinery 75 | - as an npm package, MELPA package, pandoc writer, *???* 76 | - [ ] make a keybinding for running `helm-rg` on dired marked files 77 | - then you could do an `f3` search, bounce to dired, then immediately `helm-rg` on just the file paths from the `f3` search, *which would be sick* 78 | - [ ] does ripgrep have any options to traverse the fs in (any type of) sorted order? 79 | - if so we'll definitely want a `defcustom` on that asap 80 | 81 | # License 82 | 83 | [GPL 3.0+](./LICENSE) 84 | -------------------------------------------------------------------------------- /emacs-helm-rg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmicexplorer/helm-rg/ee0a3c09da0c843715344919400ab0a0190cc9dc/emacs-helm-rg.png -------------------------------------------------------------------------------- /gen-commentary/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /gen-commentary/create-markdown.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | fs = require 'fs' 4 | wordwrap = require 'wordwrap' 5 | 6 | processReadme = (readme) -> 7 | processed = readme 8 | .replace(///^[^=]+=+\n///, '') 9 | .replace(/(.*?)<\/kbd>/g, (all, g1) -> "\x00#{g1}'") 10 | .replace(///\[!\[([^\]]+)\]\([^\)]+\)\]\(([^\)]+)\)///g, 11 | (all, g1, g2) -> "#{g1}: #{g2}") 12 | .replace(///\[([^\]]+)\]\(([^\)]+)\)///g, (all, g1, g2) -> "#{g1} (#{g2})") 13 | .replace(///`([^`]+)`///g, (all, g1) -> "`#{g1}'") 14 | .replace(/^ +/mg, (all) -> all.replace(/ /g, '=')) 15 | wordwrap(77, {mode: 'soft'})(processed) 16 | .replace(///^=+///mg, (all) -> all.replace(///=///g, ' ')) 17 | .replace(///^#+(.*)$///mg, (all, g1) -> ";= \n;;#{g1}:") 18 | .replace(///^$///mg, ';=') 19 | .replace(///^([^;])///mg, (all, g1) -> ";; #{g1}") 20 | .replace(///^;=///mg, '') 21 | .replace(/\x00/g, '`') 22 | 23 | link = "https://github.com/cosmicexplorer/helm-rg" 24 | header = ";; The below is generated from a README at\n;; #{link}.\n" 25 | 26 | readme = fs.readFileSync("#{__dirname}/../README.md").toString() 27 | helmRgEl = fs.readFileSync("#{__dirname}/../helm-rg.el").toString() 28 | 29 | output = helmRgEl.replace(/(;;; Commentary:)\n(;; End Commentary)/g, (all, g1, g2) -> 30 | "#{g1}\n\n#{header}#{processReadme(readme)}\n#{g2}") 31 | 32 | fs.writeFileSync "#{__dirname}/../helm-rg.el", output 33 | -------------------------------------------------------------------------------- /gen-commentary/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "wordwrap": { 6 | "version": "1.0.0", 7 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 8 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gen-commentary/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "repository": "https://github.com/cosmicexplorer/f3", 3 | "license": "GPL-3.0+", 4 | "dependencies": { 5 | "wordwrap": "^1.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /gen-commentary/update-commentary.el: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | :;exec emacs -batch -l "$0" -- "$@" 3 | 4 | ;;; execute this file from within its containing directory to sync helm-rg.el 5 | ;;; with the project readme 6 | 7 | (defconst search-regexp 8 | (format "\\(%s\\)\\(?:%s\\)\\(%s\\)" 9 | ";;; Commentary:" 10 | "[[:ascii:]]*?" 11 | ";; End Commentary")) 12 | (defconst target-file "../helm-rg.el") 13 | 14 | (with-current-buffer (find-file target-file) 15 | (re-search-forward search-regexp) 16 | (replace-match "\\1\n\\2") 17 | (save-buffer) 18 | (kill-buffer)) 19 | 20 | (let ((res (shell-command-to-string "./create-markdown.coffee"))) 21 | (unless (string= res "") 22 | (message "%s" res))) 23 | 24 | ;; Local Variables: 25 | ;; mode: emacs-lisp 26 | ;; End: 27 | -------------------------------------------------------------------------------- /helm-rg.el: -------------------------------------------------------------------------------- 1 | ;;; helm-rg.el --- a helm interface to ripgrep -*- lexical-binding: t -*- 2 | 3 | ;; Author: Danny McClanahan 4 | ;; Version: 0.1 5 | ;; URL: https://github.com/cosmicexplorer/helm-rg 6 | ;; Package-Requires: ((emacs "25") (cl-lib "0.5") (dash "2.13.0") (helm "2.8.8")) 7 | ;; Keywords: find, file, files, helm, fast, rg, ripgrep, grep, search, match 8 | 9 | ;; This file is not part of GNU Emacs. 10 | 11 | ;; This file is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation; either version 3, or (at your option) 14 | ;; any later version. 15 | 16 | ;; This file is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | 25 | ;;; Commentary: 26 | 27 | ;; The below is generated from a README at 28 | ;; https://github.com/cosmicexplorer/helm-rg. 29 | 30 | ;; MELPA: https://melpa.org/#/helm-rg 31 | 32 | ;; !`helm-rg' example usage (./emacs-helm-rg.png) 33 | 34 | ;; Search massive codebases extremely fast, using `ripgrep' 35 | ;; (https://github.com/BurntSushi/ripgrep) and `helm' 36 | ;; (https://github.com/emacs-helm/helm). Inspired by `helm-ag' 37 | ;; (https://github.com/syohex/emacs-helm-ag) and `f3' 38 | ;; (https://github.com/cosmicexplorer/f3). 39 | 40 | ;; Also check out rg.el (https://github.com/dajva/rg.el), which I haven't used 41 | ;; much but seems pretty cool. 42 | 43 | 44 | ;; Usage: 45 | 46 | ;; *See the `ripgrep' whirlwind tour 47 | ;; (https://github.com/BurntSushi/ripgrep#whirlwind-tour) for further 48 | ;; information on invoking `ripgrep'.* 49 | 50 | ;; - Invoke the interactive function `helm-rg' to start a search with `ripgrep' 51 | ;; in the current directory. 52 | ;; - `helm' is used to browse the results and update the output as you 53 | ;; type. 54 | ;; - Each line has the file path, the line number, and the column number of 55 | ;; the start of the match, and each part is highlighted differently. 56 | ;; - Use `TAB' to invoke the helm persistent action, which previews the 57 | ;; result and highlights the matched text in the preview. 58 | ;; - Use `RET' to visit the file containing the result, move point to the 59 | ;; start of the match, and recenter. 60 | ;; - The result's buffer is displayed with 61 | ;; `helm-rg-display-buffer-normal-method' (which defaults to 62 | ;; `switch-to-buffer'). 63 | ;; - Use a prefix argument (`C-u RET') to open the buffer with 64 | ;; `helm-rg-display-buffer-alternate-method' (which defaults to 65 | ;; `pop-to-buffer'). 66 | ;; - The text entered into the minibuffer is interpreted into a PCRE 67 | ;; (https://pcre.org) regexp to pass to `ripgrep'. 68 | ;; - `helm-rg''s pattern syntax is basically PCRE, but single spaces 69 | ;; basically act as a more powerful conjunction operator. 70 | ;; - For example, the pattern `a b' in the minibuffer is transformed 71 | ;; into `a.*b|b.*a'. 72 | ;; - The single space can be used to find lines with any 73 | ;; permutation of the regexps on either side of the space. 74 | ;; - Two spaces in a row will search for a literal single space. 75 | ;; - `ripgrep''s `--smart-case' option is used so that case-sensitive 76 | ;; search is only on if any of the characters in the pattern are capitalized. 77 | ;; - For example, `ab' (conceptually) searches `[Aa][bB]', but `Ab' 78 | ;; in the minibuffer will only search for the pattern `Ab' with `ripgrep', 79 | ;; because it has at least one uppercase letter. 80 | ;; - Use `M-d' to select a new directory to search from. 81 | ;; - Use `M-g' to input a glob pattern to filter files by, e.g. `*.py'. 82 | ;; - The glob pattern defaults to the value of 83 | ;; `helm-rg-default-glob-string', which is an empty string (matches every file) 84 | ;; unless you customize it. 85 | ;; - Pressing `M-g' again shows the same minibuffer prompt for the glob 86 | ;; pattern, with the string that was previously input. 87 | ;; - Use `' and `' to go up and down by files in the results. 88 | ;; - `' and `' simply go up and down by match result, and there 89 | ;; may be many matches for your pattern in a single file, even multiple on a 90 | ;; single line (which `ripgrep' reports as multiple separate results). 91 | ;; - The `' and `' keys will move up or down until it lands on 92 | ;; a result from a different file than it started on. 93 | ;; - When moving by file, `helm-rg' will cycle around the results list, 94 | ;; but it will print a harmless error message instead of looping infinitely if 95 | ;; all results are from the same file. 96 | ;; - Use the interactive autoloaded function `helm-rg-display-help' to see the 97 | ;; ripgrep command's usage info. 98 | 99 | 100 | ;; TODO: 101 | 102 | ;; *items checked completed here are ready to be added to the docs above* 103 | 104 | ;; - [x] make a keybinding to drop into an "edit mode" and edit file content 105 | ;; inline in results like `helm-ag' (https://github.com/syohex/emacs-helm-ag) 106 | ;; - *currently called "bounce mode"* in the alpha stage 107 | ;; - [x] needs to dedup results from the same line 108 | ;; - [x] should also merge the colorations 109 | ;; - [x] this might be easier without using the `--vimgrep' flag (!!!) 110 | ;; - [x] can insert markers on either side of each line to find the text 111 | ;; added or removed 112 | ;; - [x] can change the filename by editing the file line 113 | ;; - [x] needs to reset all the file data for each entry if the file 114 | ;; name is being changed!!! 115 | ;; - [x] can expand the windows of text beyond single lines at a time 116 | ;; - using `helm-rg--expand-match-context' and/or 117 | ;; `helm-rg--spread-match-context' 118 | ;; - [x] and pop into another buffer for a quick view if you want 119 | ;; - can use `helm-rg--visit-current-file-for-bounce' 120 | ;; - [ ] can expand up and down from file header lines to add lines 121 | ;; from the top or bottom of the file! 122 | ;; - [ ] can use newlines in inserted text 123 | ;; - not for file names -- newlines are still removed there 124 | ;; - would need to use text properties to move by match results 125 | ;; then, for everything that uses `helm-rg--apply-matches-with-file-for-bounce' 126 | ;; basically 127 | ;; - [x] visiting the file should go to the appropriate line of the file! 128 | ;; - [x] color all results in the file in the async action! 129 | ;; - [x] don't recolor when switching to a different result in the same 130 | ;; file! 131 | ;; - [x] don't color matches whenever file path matches 132 | ;; `helm-rg-shallow-highlight-files-regexp' 133 | ;; - [ ] use `ripgrep' file types instead of flattening globbing out into 134 | ;; `helm-rg-default-glob-string' 135 | ;; - user defines file types in a `defcustom', and can interactively toggle 136 | ;; the accepted file types 137 | ;; - user can also set the default set of file types 138 | ;; - as a dir-local variable!! 139 | ;; - [ ] add testing 140 | ;; - [ ] should be testing all of our interactive functions 141 | ;; - in all configurations (for all permutations of `defcustom' values) 142 | ;; - [ ] also everything that's called by helm 143 | ;; - does helm have any frameworks to make integration testing easier? 144 | ;; - [ ] publish `update-commentary.el' and the associated machinery 145 | ;; - as an npm package, MELPA package, pandoc writer, *???* 146 | ;; - [ ] make a keybinding for running `helm-rg' on dired marked files 147 | ;; - then you could do an `f3' search, bounce to dired, then immediately 148 | ;; `helm-rg' on just the file paths from the `f3' search, *which would be 149 | ;; sick* 150 | 151 | 152 | ;; License: 153 | 154 | ;; GPL 3.0+ (./LICENSE) 155 | 156 | ;; End Commentary 157 | 158 | 159 | ;;; Code: 160 | 161 | (require 'ansi-color) 162 | (require 'cl-lib) 163 | (require 'dash) 164 | (require 'font-lock) 165 | (require 'helm) 166 | (require 'helm-files) 167 | (require 'helm-grep) 168 | (require 'helm-lib) 169 | (require 'pcase) 170 | (require 'rx) 171 | (require 'subr-x) 172 | 173 | 174 | ;; Customization Helpers 175 | (defun helm-rg--always-safe-local (_) 176 | "Use as a :safe predicate in a `defcustom' form to accept any local override." 177 | t) 178 | 179 | (defun helm-rg--gen-defcustom-form-from-alist (name alist doc args) 180 | ;; TODO: get all the pcase macros at the very top of the file! 181 | (let ((alist-resolved (pcase-exhaustive alist 182 | ((and (pred symbolp) x) (symbol-value x)) 183 | ((and (pred listp) x) x)))) 184 | `(defcustom ,name ',(car (helm-rg--alist-keys alist-resolved)) 185 | ,doc 186 | :type `(radio ,@(--map `(const ,it) (helm-rg--alist-keys ',alist-resolved))) 187 | :group 'helm-rg 188 | ,@args))) 189 | 190 | (defmacro helm-rg--defcustom-from-alist (name alist doc &rest args) 191 | "Create a `defcustom' named NAME which can take the keys of ALIST as values. 192 | 193 | The DOC and ARGS are passed on to the generated `defcustom' form. The default value for the 194 | `defcustom' is the `car' of the first element of ALIST. ALIST must be the unquoted name of a 195 | variable containing an alist." 196 | (declare (indent 2)) 197 | (helm-rg--gen-defcustom-form-from-alist name alist doc args)) 198 | 199 | 200 | ;; CL deftypes 201 | (cl-deftype helm-rg-existing-file () 202 | `(and string 203 | (satisfies file-exists-p))) 204 | 205 | (cl-deftype helm-rg-existing-directory () 206 | `(and helm-rg-existing-file 207 | (satisfies file-directory-p))) 208 | 209 | 210 | ;; Interesting macros 211 | (cl-defmacro helm-rg--with-gensyms ((&rest syms) &rest body) 212 | (declare (indent 1)) 213 | `(let ,(--map `(,it (cl-gensym)) syms) 214 | ,@body)) 215 | 216 | (defmacro helm-rg--_ (expr) 217 | "Replace all instances of `_' in EXPR with an anonymous argument. 218 | 219 | Return a lambda accepting that argument." 220 | (declare (debug (sexp body))) 221 | (helm-rg--with-gensyms (arg) 222 | `(lambda (,arg) 223 | ,(cl-subst arg '_ expr :test #'eq)))) 224 | 225 | (cl-defun helm-rg--join-conditions (conditions &key (joiner 'or)) 226 | "If CONDITIONS has one element, return it, otherwise wrap them with JOINER. 227 | 228 | This is used because `pcase' doesn't accept conditions with a single element (e.g. `(or 3)')." 229 | (pcase-exhaustive conditions 230 | (`nil (error "The list of conditions may not be nil (with joiner '%S')" joiner)) 231 | (`(,single-sexp) single-sexp) 232 | (x `(,joiner ,@x)))) 233 | 234 | (pcase-defmacro helm-rg-cl-typep (&rest types) 235 | "Matches when the subject is any of TYPES, using `cl-typep'." 236 | (helm-rg--with-gensyms (val) 237 | `(and ,val 238 | ,(helm-rg--join-conditions 239 | (--map `(guard (cl-typep ,val ',it)) types))))) 240 | 241 | (pcase-defmacro helm-rg-deref-sym (sym) 242 | "???" 243 | (list 'quote (eval sym))) 244 | 245 | (defconst helm-rg--keyword-symbol-rx-expr `(: bos ":")) 246 | 247 | (cl-deftype helm-rg-non-keyword-symbol () 248 | `(and symbol 249 | (not keyword))) 250 | 251 | (defun helm-rg--make-non-keyword-sym-from-keyword-sym (kw-sym) 252 | (cl-check-type kw-sym keyword) 253 | (->> kw-sym 254 | (symbol-name) 255 | (replace-regexp-in-string (rx-to-string helm-rg--keyword-symbol-rx-expr) "") 256 | (intern))) 257 | 258 | (defun helm-rg--make-keyword-from-non-keyword-sym (non-kw-sym) 259 | (cl-check-type non-kw-sym helm-rg-non-keyword-symbol) 260 | (->> non-kw-sym 261 | (symbol-name) 262 | (format ":%s") 263 | (intern))) 264 | 265 | (defun helm-rg--parse-plist-spec (plist-spec) 266 | (pcase-exhaustive plist-spec 267 | (`(,(and (helm-rg-cl-typep keyword) kw-sym) 268 | ,value) 269 | `(,kw-sym ,value)) 270 | ((and (helm-rg-cl-typep helm-rg-non-keyword-symbol) 271 | sym) 272 | `(,(helm-rg--make-keyword-from-non-keyword-sym sym) 273 | ,sym)))) 274 | 275 | (defmacro helm-rg-construct-plist (&rest plist-specs) 276 | (->> plist-specs 277 | (-map #'helm-rg--parse-plist-spec) 278 | (apply #'append '(list)))) 279 | 280 | (defun helm-rg--parse-&optional-spec (optional-spec) 281 | (pcase-exhaustive optional-spec 282 | (`(,upat ,initform ,svar) 283 | (helm-rg-construct-plist upat initform svar)) 284 | (`(,upat ,initform) 285 | (helm-rg-construct-plist upat initform)) 286 | ((or `(,upat) upat) 287 | (helm-rg-construct-plist upat)))) 288 | 289 | (defun helm-rg--read-&optional-specs (parsed-optional-spec-list) 290 | (pcase-exhaustive parsed-optional-spec-list 291 | (`(,cur . ,rest) 292 | `(or (and `nil 293 | ,@(->> (cons cur rest) 294 | (--map (cl-destructuring-bind (&key upat initform svar) it 295 | `(,@(and svar `((let ,svar nil))) 296 | (let ,upat ,initform)))) 297 | (funcall #'append) 298 | (-flatten-n 1))) 299 | ,(cl-destructuring-bind (&key upat _initform svar) cur 300 | (helm-rg--join-conditions 301 | ;; FIXME: put the below comment in the docstrings for optional and keyword pcase 302 | ;; macros! 303 | ;; NB: SVAR is bound before INITFORM is evaluated, which means you can refer to SVAR 304 | ;; within INITFORM (and more importantly, within UPAT)! 305 | `(,@(and svar `((let ,svar t))) 306 | ,(->> (and rest 307 | (->> rest 308 | (helm-rg--read-&optional-specs) 309 | (list '\,))) 310 | (cons (list '\, upat)) 311 | (list '\`))) 312 | :joiner 'and)))))) 313 | 314 | (pcase-defmacro helm-rg-&optional (&rest all-optional-specs) 315 | (->> all-optional-specs 316 | (-map #'helm-rg--parse-&optional-spec) 317 | (helm-rg--read-&optional-specs))) 318 | 319 | (defun helm-rg--parse-&key-spec (key-spec) 320 | (pcase-exhaustive key-spec 321 | ((and (or :exhaustive :required) special-sym) 322 | special-sym) 323 | (`(,(or `(,(and (helm-rg-cl-typep keyword) 324 | kw-sym) 325 | ,upat) 326 | (and (or `(,upat) upat) 327 | (let kw-sym (helm-rg--make-keyword-from-non-keyword-sym upat)))) 328 | . ,(or 329 | (and :required 330 | (let required t) 331 | (let initform nil) 332 | (let svar nil)) 333 | (and (helm-rg-&optional initform svar) 334 | (let required nil)))) 335 | (helm-rg-construct-plist kw-sym upat required initform svar)) 336 | ((and (helm-rg-cl-typep helm-rg-non-keyword-symbol) 337 | upat) 338 | (helm-rg-construct-plist 339 | (:kw-sym (helm-rg--make-keyword-from-non-keyword-sym upat)) 340 | upat 341 | (:required nil) 342 | (:initform nil) 343 | (:svar nil))))) 344 | 345 | (defun helm-rg--flipped-plist-member (prop plist) 346 | (plist-member plist prop)) 347 | 348 | (defun helm-rg--plist-parse-pairs (plist) 349 | (cl-loop 350 | with prev-keyword = nil 351 | for el in plist 352 | for is-keyword-posn = t then (not is-keyword-posn) 353 | when is-keyword-posn 354 | do (progn 355 | (cl-check-type el keyword) 356 | (setq prev-keyword el)) 357 | else 358 | collect (list prev-keyword el) 359 | into pairs 360 | finally return (progn 361 | (cl-assert (not is-keyword-posn) t 362 | (format "Invalid plist %S ends on keyword '%S'" 363 | plist prev-keyword)) 364 | pairs))) 365 | 366 | (defun helm-rg--plist-keys (plist) 367 | (->> plist 368 | (helm-rg--plist-parse-pairs) 369 | (-map #'car))) 370 | 371 | (defun helm-rg--force-required-parsed-&key-spec (spec) 372 | (cl-destructuring-bind (&key kw-sym upat required initform svar) spec 373 | ;; TODO: better error messaging here! 374 | (cl-assert (not initform)) 375 | (cl-assert (not svar)) 376 | (cl-assert (not required)) 377 | (helm-rg-construct-plist kw-sym upat (:required t) (:initform nil) (:svar nil)))) 378 | 379 | (cl-defun helm-rg--find-first-duplicate (seq &key (test #'eq)) 380 | (cl-loop 381 | with tbl = (make-hash-table :test test) 382 | for el in seq 383 | when (gethash el tbl) 384 | return el 385 | else do (puthash el t tbl) 386 | finally return nil)) 387 | 388 | (cl-defun helm-rg--read-&key-specs (parsed-key-spec-list &key exhaustive) 389 | (let* ((all-keys (->> parsed-key-spec-list 390 | (--keep (pcase-exhaustive it 391 | (:required nil) 392 | (x (plist-get x :kw-sym)))))) 393 | (first-duplicate-key (helm-rg--find-first-duplicate all-keys))) 394 | (when first-duplicate-key 395 | (error "Keyword '%S' provided more than once for keyword set %S" 396 | first-duplicate-key all-keys)) 397 | (let ((pcase-expr 398 | (pcase-exhaustive parsed-key-spec-list 399 | (`(:required . ,rest) 400 | (--> rest 401 | (-map #'helm-rg--force-required-parsed-&key-spec it) 402 | (helm-rg--read-&key-specs it))) 403 | (`(,cur . ,rest) 404 | (helm-rg--join-conditions 405 | `(,(helm-rg--join-conditions 406 | (cl-destructuring-bind 407 | (&key kw-sym upat required initform svar) cur 408 | `((app (helm-rg--flipped-plist-member ,kw-sym) 409 | ,(helm-rg--join-conditions 410 | `(,@(unless required 411 | `((and `nil 412 | ,@(and svar `((let ,svar nil))) 413 | (let ,upat ,initform)))) 414 | ,(helm-rg--join-conditions 415 | `(,@(and svar `((let ,svar t))) 416 | ;; `plist-member' gives us the rest of the list too -- discard 417 | ;; by matching it to `_'. 418 | ,(->> (list kw-sym (list '\, upat) '\, '_) 419 | (list '\`))) 420 | :joiner 'and)) 421 | :joiner 'or)))) 422 | :joiner 'and) 423 | ,@(and rest (list (helm-rg--read-&key-specs rest)))) 424 | :joiner 'and))))) 425 | (if exhaustive 426 | (helm-rg--with-gensyms (exp-plist-keys) 427 | `(and 428 | ;; NB: we do not attempt to parse the `pcase' subject as a plist (done with 429 | ;; `helm-rg--plist-keys') unless `:exhaustive' is provided (we just use `plist-get') 430 | ;; -- this is intentional. 431 | (and (app (helm-rg--plist-keys) ,exp-plist-keys) 432 | (guard (not (-difference ,exp-plist-keys ',all-keys)))) 433 | ,pcase-expr)) 434 | pcase-expr)))) 435 | 436 | (pcase-defmacro helm-rg-&key (&rest all-key-specs) 437 | ;;; TODO: add alist matching -- this should be trivial, just allowing 438 | ;;; non-keyword syms in the argument spec. 439 | (pcase all-key-specs 440 | (`(:exhaustive . ,rest) 441 | (--> rest 442 | (-map #'helm-rg--parse-&key-spec it) 443 | (helm-rg--read-&key-specs it :exhaustive t))) 444 | (specs (->> specs 445 | (-map #'helm-rg--parse-&key-spec) 446 | (helm-rg--read-&key-specs))))) 447 | 448 | (pcase-defmacro helm-rg-&key-complete (&rest all-key-specs) 449 | "`helm-rg-&key', but there must be no other keys, and all the keys in ALL-KEY-SPECS must exist." 450 | `(helm-rg-&key :exhaustive :required ,@all-key-specs)) 451 | 452 | (defun helm-rg--parse-format-spec (format-spec) 453 | "Convert a list FORMAT-SPEC into some result for `helm-rg--make-formatter'." 454 | (pcase-exhaustive format-spec 455 | ((and (helm-rg-cl-typep string) x) 456 | (helm-rg-construct-plist 457 | (:fmt x) (:expr nil) (:argument nil))) 458 | ((and (helm-rg-cl-typep helm-rg-non-keyword-symbol) sym) 459 | (helm-rg-construct-plist (:fmt "%s") (:expr sym) (:argument nil))) 460 | ((and (helm-rg-cl-typep keyword) 461 | (app (helm-rg--make-non-keyword-sym-from-keyword-sym) 462 | non-kw-sym)) 463 | (helm-rg-construct-plist (:fmt "%s") (:expr non-kw-sym) (:argument non-kw-sym))) 464 | (`(,(or (and (helm-rg-cl-typep keyword) 465 | (app (helm-rg--make-non-keyword-sym-from-keyword-sym) 466 | argument) 467 | (let expr argument)) 468 | (and expr (let argument nil))) 469 | . ,(helm-rg-&key (fmt "%s"))) 470 | (helm-rg-construct-plist fmt expr argument)))) 471 | 472 | (defun helm-rg--read-format-specs (format-spec-list) 473 | (cl-loop 474 | with fmts = nil 475 | with exprs = nil 476 | with arguments = nil 477 | for parsed-spec in (-map #'helm-rg--parse-format-spec format-spec-list) 478 | ;; TODO: turn this into an unzip-plists method/macro or something! 479 | do (cl-destructuring-bind (&key fmt expr argument) parsed-spec 480 | (push fmt fmts) 481 | (when expr (push expr exprs)) 482 | (when argument (push argument arguments))) 483 | finally return (helm-rg-construct-plist 484 | (:fmts (reverse fmts)) 485 | (:exprs (reverse exprs)) 486 | (:arguments (-> arguments (-uniq) (reverse)))))) 487 | 488 | (cl-defmacro helm-rg-format ((format-specs &rest kwargs) &key (sep " ")) 489 | (cl-destructuring-bind (&key fmts exprs arguments) 490 | (helm-rg--read-format-specs format-specs) 491 | (cond 492 | (arguments 493 | `(cl-destructuring-bind (&key ,@arguments) ',kwargs 494 | ;; TODO: a "once-only" macro that's just sugar for gensyms 495 | (format (mapconcat #'identity (list ,@fmts) ,sep) ,@exprs))) 496 | (kwargs 497 | (error "No arguments were declared, but keyword arguments %S were provided" kwargs)) 498 | (t 499 | `(format (mapconcat #'identity (list ,@fmts) ,sep) ,@exprs))))) 500 | 501 | (cl-defmacro helm-rg-make-formatter (format-specs &key (sep " ")) 502 | (cl-destructuring-bind (&key fmts exprs arguments) 503 | (helm-rg--read-format-specs format-specs) 504 | (unless arguments 505 | (error "No arguments were declared in the specs %S" format-specs)) 506 | ;; TODO: make a macro that can create a lambda with visible keyword arguments (a "cl-lambda" 507 | ;; type thing) 508 | (helm-rg--with-gensyms (args) 509 | `(lambda (&rest ,args) 510 | (cl-destructuring-bind (&key ,@arguments) ,args 511 | (format (mapconcat #'identity (list ,@fmts) ,sep) ,@exprs)))))) 512 | 513 | (defun helm-rg--validate-rx-kwarg (keyword-sym-for-binding) 514 | (pcase-exhaustive keyword-sym-for-binding 515 | ((and (helm-rg-cl-typep keyword) 516 | (app (helm-rg--make-non-keyword-sym-from-keyword-sym) 517 | non-kw-sym)) 518 | non-kw-sym) 519 | ((and (helm-rg-cl-typep symbol) 520 | non-kw-sym 521 | (app (helm-rg--make-keyword-from-non-keyword-sym) 522 | kw-sym)) 523 | (error (helm-rg-format 524 | (("symbol" (non-kw-sym :fmt "%S") 525 | "must be a keyword arg" (kw-sym :fmt "(e.g. %S).")))))))) 526 | 527 | (defun helm-rg--apply-tree-fun (mapper tree) 528 | "Apply MAPPER to the nodes of TREE using `-tree-map-nodes'. 529 | 530 | This method applies MAPPER, saves the result, and if the result is non-nil, returns the result 531 | instead of the node of MAPPER, otherwise it continues to recurse down the nodes of TREE." 532 | (let (intermediate-value-holder) 533 | (-tree-map-nodes 534 | (helm-rg--_ (setq intermediate-value-holder (funcall mapper _))) 535 | (helm-rg--_ intermediate-value-holder) 536 | tree))) 537 | 538 | (defmacro helm-rg--pcase-tree (tree &rest pcase-exprs) 539 | "Apply a `pcase' to the nodes of TREE with `helm-rg--apply-tree-fun'. 540 | 541 | PCASE-EXPRS are the cases provided to `pcase'. If the `pcase' cases do not 542 | match the node (returns nil), it continues to recurse down the tree -- 543 | otherwise, the return value replaces the node of the tree." 544 | (declare (indent 1)) 545 | `(helm-rg--apply-tree-fun 546 | (helm-rg--_ (pcase _ ,@pcase-exprs)) 547 | ,tree)) 548 | 549 | (defconst helm-rg--named-group-symbol 'named-group) 550 | (defconst helm-rg--eval-expr-symbol 'eval) 551 | (defconst helm-rg--duplicate-var-eval-form-error-str 552 | "'%S' variable name used a second time in evaluation of form '%S'. 553 | previous vars were: %S") 554 | (defconst helm-rg--duplicate-var-literal-form-error-str 555 | "'%S' variable named used a second time in declaration of regexp group '%S'. 556 | previous vars were: %S") 557 | (cl-defun helm-rg--transform-rx-sexp (sexp &key (group-num-init 1)) 558 | (let ((all-bind-vars-mappings nil)) 559 | (--> (helm-rg--pcase-tree sexp 560 | ;; `(eval ,eval-expr) => evaluate the expression! 561 | ;; NB: this occurs at macro-expansion time, like the equivalent `rx' 562 | ;; pcase macro, which is before any surrounding let-bindings occur!) 563 | (`(,(helm-rg-deref-sym helm-rg--eval-expr-symbol) ,eval-expr) 564 | (cl-destructuring-bind (&key transformed bind-vars) 565 | (helm-rg--transform-rx-sexp (eval eval-expr t) :group-num-init group-num-init) 566 | (cl-loop 567 | for quoted-var in bind-vars 568 | do (progn 569 | (cl-incf group-num-init) 570 | (when (cl-find quoted-var all-bind-vars-mappings) 571 | (error helm-rg--duplicate-var-eval-form-error-str 572 | quoted-var eval-expr all-bind-vars-mappings)) 573 | (push quoted-var all-bind-vars-mappings))) 574 | transformed)) 575 | ;; `(named-group :var-name . ,rx-forms) => create an explicitly-numbered regexp group 576 | ;; and, if the resulting regexp matches, bind the match string for that numbered group to 577 | ;; var-name (without the initial ":", which is required)! 578 | (`(,(helm-rg-deref-sym helm-rg--named-group-symbol) 579 | ,(app (helm-rg--validate-rx-kwarg) binding-var) 580 | . ,rx-forms) 581 | ;; We have bound to this variable -- save the current group number and push this 582 | ;; variable onto the list of binding variables. 583 | (let ((cur-group-num group-num-init)) 584 | (push binding-var all-bind-vars-mappings) 585 | (cl-incf group-num-init) 586 | (cl-loop 587 | for sub-rx in rx-forms 588 | collect (cl-destructuring-bind (&key transformed bind-vars) 589 | (helm-rg--transform-rx-sexp sub-rx :group-num-init group-num-init) 590 | (cl-loop 591 | for quoted-var in bind-vars 592 | do (progn 593 | (cl-incf group-num-init) 594 | (when (cl-find quoted-var all-bind-vars-mappings) 595 | (error 596 | helm-rg--duplicate-var-literal-form-error-str 597 | quoted-var sub-rx all-bind-vars-mappings)) 598 | (push quoted-var all-bind-vars-mappings))) 599 | transformed) 600 | into all-transformed-exprs 601 | finally return `(group-n ,cur-group-num ,@all-transformed-exprs))))) 602 | (list :transformed it :bind-vars (reverse all-bind-vars-mappings))))) 603 | 604 | (defmacro helm-rg-pcase-cl-defmacro (&rest args) 605 | "`pcase-defmacro', but the --pcase-macroexpander function is a `cl-defun'. 606 | \n(fn NAME ARGS [DOC] &rest BODY...)" 607 | (declare (indent 2) (debug defun) (doc-string 3)) 608 | (->> `(pcase-defmacro ,@args) 609 | (macroexpand-1) 610 | (cl-subst 'cl-defun 'defun))) 611 | 612 | (helm-rg-pcase-cl-defmacro helm-rg-rx (rx-sexp) 613 | ;; FIXME: have some way to get the indices of each bound var (for things like 614 | ;; `match-data') 615 | (pcase-exhaustive (helm-rg--transform-rx-sexp rx-sexp) 616 | ((helm-rg-&key-complete transformed bind-vars) 617 | (helm-rg--with-gensyms (str-sym) 618 | `(and ,str-sym 619 | ,(helm-rg--join-conditions 620 | ;; We would just delegate to `rx--pcase-macroexpander', but requiring subr errors 621 | ;; out, extremely mysteriously. 622 | `((pred (string-match (rx-to-string ',transformed))) 623 | ,@(cl-loop for symbol-to-bind in bind-vars 624 | for match-index upfrom 1 625 | collect `(let ,symbol-to-bind (match-string ,match-index ,str-sym)))) 626 | :joiner 'and)))))) 627 | 628 | (defun helm-rg--prefix-symbol-with-underscore (sym) 629 | (->> sym 630 | (symbol-name) 631 | (format "_%s") 632 | (intern))) 633 | 634 | (defmacro helm-rg-mark-unused (vars &rest body) 635 | (declare (indent 1)) 636 | `(let (,@(--map `(,(helm-rg--prefix-symbol-with-underscore it) ,it) vars)) 637 | ,@body)) 638 | 639 | 640 | ;; Public error types 641 | (define-error 'helm-rg-error "Error invoking `helm-rg'") 642 | 643 | 644 | ;; Customization 645 | (defgroup helm-rg nil 646 | "Group for `helm-rg' customizations." 647 | :group 'helm-grep) 648 | 649 | (defcustom helm-rg-ripgrep-executable (executable-find "rg") 650 | "The location of the ripgrep binary executable." 651 | :type 'string 652 | :group 'helm-rg) 653 | 654 | (defcustom helm-rg-default-glob-string "" 655 | "The glob pattern used for the '-g' argument to ripgrep. 656 | Set to the empty string to match every file." 657 | :type 'string 658 | :safe #'helm-rg--always-safe-local 659 | :group 'helm-rg) 660 | 661 | (defcustom helm-rg-default-extra-args nil 662 | "Extra arguments passed to ripgrep on the command line. 663 | Note that default filename globbing and case sensitivity can be set with their own defcustoms, and 664 | can be modified while invoking `helm-rg' -- see the help for that method. If the extra arguments are 665 | ones you use commonly, consider submitting a pull request to 666 | https://github.com/cosmicexplorer/helm-rg with a specific `defcustom' and keybinding for that 667 | particular ripgrep option and set of options." 668 | :type '(repeat string) 669 | :safe #'helm-rg--always-safe-local 670 | :group 'helm-rg) 671 | 672 | (defcustom helm-rg-default-directory 'default 673 | "Specification for starting directory to invoke ripgrep in. 674 | Used in `helm-rg--interpret-starting-dir'. Possible values: 675 | 676 | 'default => Use `default-directory'. 677 | 'git-root => Use \"git rev-parse --show-toplevel\" (see 678 | `helm-rg-git-executable'). 679 | => Use the directory at path ." 680 | :type '(choice symbol string) 681 | :safe #'helm-rg--always-safe-local 682 | :group 'helm-rg) 683 | 684 | (defcustom helm-rg-git-executable (executable-find "git") 685 | "Location of git executable." 686 | :type 'string 687 | :group 'helm-rg) 688 | 689 | (defcustom helm-rg-thing-at-point 'symbol 690 | "Type of object at point to initialize the `helm-rg' minibuffer input with." 691 | :type 'symbol 692 | :safe #'helm-rg--always-safe-local 693 | :group 'helm-rg) 694 | 695 | (defcustom helm-rg-input-min-search-chars 2 696 | "Ripgrep will not be invoked unless the input is at least this many chars. 697 | 698 | See `helm-rg--make-process' and `helm-rg--make-dummy-process' if interested." 699 | ;; FIXME: this should be a *positive* integer! 700 | :type 'integer 701 | :safe #'helm-rg--always-safe-local 702 | :group 'helm-rg) 703 | 704 | (defcustom helm-rg-display-buffer-normal-method #'switch-to-buffer 705 | "A function accepting a single argument BUF and displaying the buffer. 706 | 707 | The default function to invoke to display a visited buffer in some window in 708 | `helm-rg'." 709 | :type 'function 710 | :group 'helm-rg) 711 | 712 | (defcustom helm-rg-display-buffer-alternate-method #'pop-to-buffer 713 | "A function accepting a single argument BUF and displaying the buffer. 714 | 715 | The function will be invoked if a prefix argument is used when visiting a result 716 | in `helm-rg'." 717 | :type 'function 718 | :group 'helm-rg) 719 | 720 | (defcustom helm-rg-shallow-highlight-files-regexp nil 721 | "Regexp describing file paths to only partially highlight, for performance reasons. 722 | 723 | By default, `helm-rg' will create overlays to highlight all the matches from ripgrep in a file when 724 | previewing a result. This is done each time a match is selected, even for buffers already 725 | previewed. Creating these overlays can be slow for files with lots of matches in some search. If 726 | this variable is set to an elisp regexp and some file path matches it, `helm-rg' will only highlight 727 | the current line of the file and the matches in that line when previewing that file." 728 | :type 'regexp 729 | :safe #'helm-rg--always-safe-local 730 | :group 'helm-rg) 731 | 732 | (defcustom helm-rg-prepend-file-name-line-at-top-of-matches t 733 | "Whether to put the file path as a separate line in `helm-rg' output above the file's matches. 734 | 735 | The file can be visited as if it was a match on the first line of the file (without any matched 736 | text). 737 | 738 | FIXME: if this is nil and `helm-rg-include-file-on-every-match-line' is t, you get a stream of just 739 | line numbers and content, without any file names. We should unify these two boolean options somehow 740 | to get all three allowable states." 741 | :type 'boolean 742 | :group 'helm-rg) 743 | 744 | (defcustom helm-rg-include-file-on-every-match-line nil 745 | "Whether to include the file path on every line of `helm-rg' output. 746 | 747 | This is purely an interface change, and does not affect anything else." 748 | :type 'boolean 749 | :group 'helm-rg) 750 | 751 | (defcustom helm-rg--default-expand-match-lines-for-bounce 3 752 | "???" 753 | ;; FIXME: this should be a *positive* integer! 754 | :type 'integer 755 | :group 'helm-rg) 756 | 757 | 758 | ;; Faces 759 | (defface helm-rg-preview-line-highlight 760 | '((t (:background "green" :foreground "black"))) 761 | "Face for the line of text matched by the ripgrep process." 762 | :group 'helm-rg) 763 | 764 | (defface helm-rg-base-rg-cmd-face 765 | '((t (:foreground "gray" :weight normal))) 766 | "Face for the ripgrep executable in the ripgrep invocation." 767 | :group 'helm-rg) 768 | 769 | (defface helm-rg-extra-arg-face 770 | '((t (:foreground "yellow" :weight normal))) 771 | "Face for any arguments added to the command line through `helm-rg--extra-args'." 772 | :group 'helm-rg) 773 | 774 | (defface helm-rg-inactive-arg-face 775 | '((t (:foreground "gray" :weight normal))) 776 | "Face for non-essential arguments in the ripgrep invocation." 777 | :group 'helm-rg) 778 | 779 | (defface helm-rg-active-arg-face 780 | '((t (:foreground "green"))) 781 | "Face for arguments in the ripgrep invocation which affect the results." 782 | :group 'helm-rg) 783 | 784 | (defface helm-rg-directory-cmd-face 785 | '((t (:foreground "brown" :background "black" :weight normal))) 786 | "Face for any directories provided as paths to the ripgrep invocation.") 787 | 788 | (defface helm-rg-error-message 789 | '((t (:foreground "red"))) 790 | "Face for error text displayed in the `helm-buffer' for `helm-rg'." 791 | :group 'helm-rg) 792 | 793 | (defface helm-rg-title-face 794 | '((t (:foreground "purple" :background "black" :weight bold))) 795 | "Face for the title of the ripgrep async helm source." 796 | :group 'helm-rg) 797 | 798 | (defface helm-rg-directory-header-face 799 | '((t (:foreground "white" :background "black" :weight bold))) 800 | "Face for the current directory in the header of the `helm-buffer' for `helm-rg'." 801 | :group 'helm-rg) 802 | 803 | (defface helm-rg-file-match-face 804 | '((t (:foreground "#0ff" :underline t))) 805 | "Face for the file name when displaying matches in the `helm-buffer' for `helm-rg'." 806 | :group 'helm-rg) 807 | 808 | (defface helm-rg-colon-separator-ripgrep-output-face 809 | '((t (:foreground "white"))) 810 | "Face for the separator between file, line, and match text in ripgrep output." 811 | :group 'helm-rg) 812 | 813 | (defface helm-rg-line-number-match-face 814 | '((t (:foreground "orange" :underline t))) 815 | "Face for line numbers when displaying matches in the `helm-buffer' for `helm-rg'." 816 | :group 'helm-rg) 817 | 818 | (defface helm-rg-match-text-face 819 | '((t (:foreground "white" :background "purple"))) 820 | "Face for displaying matches in the `helm-buffer' and in file previews for `helm-rg'." 821 | :group 'helm-rg) 822 | 823 | 824 | ;; Constants 825 | (defconst helm-rg--color-format-argument-alist 826 | '((red :cmd-line "red" :text-property "red3")) 827 | "Alist mapping symbols to color descriptions. 828 | 829 | This alist mapps (a symbol named after a color) -> (strings to describe that symbol on the ripgrep 830 | command line and in an Emacs text property). This allows `helm-rg' to identify matched text using 831 | ripgrep's highlighted output directly instead of doing it ourselves, by telling ripgrep to highlight 832 | matches a specific color, then searching for that specific color as a text property in the output.") 833 | 834 | (defconst helm-rg--style-format-argument-alist 835 | '((bold :cmd-line "bold" :text-property bold)) 836 | "Very similar to `helm-rg--color-format-argument-alist', but for non-color styling.") 837 | 838 | (defconst helm-rg--case-sensitive-argument-alist 839 | '((smart-case "--smart-case") 840 | (case-sensitive "--case-sensitive") 841 | (case-insensitive "--ignore-case")) 842 | "Alist of methods of treating case-sensitivity when invoking ripgrep. 843 | 844 | The value is the ripgrep command line argument which enforces the specified type of 845 | case-sensitivity.") 846 | 847 | (defconst helm-rg--ripgrep-argv-format-alist 848 | `((helm-rg-ripgrep-executable :face helm-rg-base-rg-cmd-face) 849 | ((->> helm-rg--case-sensitive-argument-alist 850 | (helm-rg--alist-get-exhaustive helm-rg--case-sensitivity)) 851 | :face helm-rg-active-arg-face) 852 | ("--color=ansi" :face helm-rg-inactive-arg-face) 853 | ((helm-rg--construct-match-color-format-arguments) 854 | :face helm-rg-inactive-arg-face) 855 | ((unless (helm-rg--empty-glob-p helm-rg--glob-string) 856 | (list "-g" helm-rg--glob-string)) 857 | :face helm-rg-active-arg-face) 858 | (helm-rg--extra-args :face helm-rg-extra-arg-face) 859 | (it 860 | :face font-lock-string-face) 861 | ((helm-rg--process-paths-to-search helm-rg--paths-to-search) 862 | :face helm-rg-directory-cmd-face)) 863 | "Alist mapping (sexp -> face) describing how to generate and propertize the argv for ripgrep.") 864 | 865 | (defconst helm-rg--helm-buffer-name "*helm-rg*") 866 | (defconst helm-rg--process-name "*helm-rg--rg*") 867 | (defconst helm-rg--process-buffer-name "*helm-rg--rg-output*") 868 | 869 | (defconst helm-rg--error-process-name "*helm-rg--error-process*") 870 | (defconst helm-rg--error-buffer-name "*helm-rg--errors*") 871 | 872 | (defconst helm-rg--ripgrep-help-buffer-name "helm-rg-usage-help") 873 | 874 | (defconst helm-rg--bounce-buffer-name "helm-rg-bounce-buf") 875 | 876 | (defconst helm-rg--output-new-file-line-rx-expr 877 | `(named-group 878 | :whole-line 879 | (: bos 880 | (named-group :file-path (+? (not (any 0)))) 881 | eos)) 882 | "Regexp for ripgrep output which marks the start of results for a new file. 883 | 884 | See `helm-rg--process-transition' for usage.") 885 | 886 | (defconst helm-rg--numbered-text-line-rx-expr 887 | `(named-group 888 | :whole-line 889 | (: bos 890 | (named-group :line-num-str (+ digit)) 891 | ":" 892 | (named-group :content (*? anything)) 893 | eos)) 894 | "Regexp for ripgrep output which marks a matched line, with the line number and content. 895 | 896 | See `helm-rg--process-transition' for usage.") 897 | 898 | (defconst helm-rg--persistent-action-display-buffer-method #'switch-to-buffer 899 | "A function accepting a single argument BUF and displaying the buffer. 900 | 901 | Let-bound to `helm-rg--display-buffer-method' in `helm-rg--async-persistent-action'.") 902 | 903 | (defconst helm-rg--loop-input-pattern-regexp 904 | (rx 905 | (: 906 | (* (char ? )) 907 | ;; group 1 = single entire element 908 | (group 909 | (+ 910 | (| 911 | (not (in ? )) 912 | (= 2 ? )))))) 913 | "Regexp applied iteratively to split the input interpreted by `helm-rg'.") 914 | 915 | (defconst helm-rg--all-whitespace-regexp 916 | (rx (: bos (zero-or-more space) eos))) 917 | 918 | (defconst helm-rg--jump-location-text-property 'helm-rg-jump-to 919 | "Name of a text property attached to the colorized ripgrep output. 920 | 921 | This text property contains location and match info. See `helm-rg--process-transition' for usage.") 922 | 923 | (defconst helm-rg--helm-header-property-name 'helm-header 924 | "Property used for the \"header\" of the `helm-buffer' displayed in `helm-rg'. 925 | 926 | This header is generated by helm, and is separate from the process output.") 927 | 928 | 929 | ;; Variables 930 | (defvar helm-rg--append-persistent-buffers nil 931 | "Whether to record buffers opened during an `helm-rg' session.") 932 | 933 | (defvar helm-rg--cur-persistent-bufs nil 934 | "List of buffers opened temporarily during an `helm-rg' session.") 935 | 936 | (defvar helm-rg--matches-in-current-file-overlays nil 937 | "List of overlays used to highlight matches in `helm-rg'.") 938 | 939 | (defvar helm-rg--current-line-overlay nil 940 | "Overlay for highlighting the selected matching line in a file in `helm-rg'.") 941 | 942 | (defvar helm-rg--current-dir nil 943 | "Working directory for the current `helm-rg' session.") 944 | 945 | (defvar helm-rg--last-dir nil 946 | "Last used working directory for resume.") 947 | 948 | (defvar helm-rg--glob-string nil 949 | "Glob string used for the current `helm-rg' session.") 950 | 951 | (defvar helm-rg--glob-string-history nil 952 | "History variable for the selection of `helm-rg--glob-string'.") 953 | 954 | (defvar helm-rg--extra-args nil 955 | "Arguments not associated with other `helm-rg' options, added to the ripgrep command line.") 956 | 957 | (defvar helm-rg--extra-args-history nil 958 | "History variable for the selection of `helm-rg--extra-args'.") 959 | 960 | (defvar helm-rg--input-history nil 961 | "History variable for the pattern input to the ripgrep process.") 962 | 963 | (defvar helm-rg--display-buffer-method nil 964 | "The method to use to display a buffer visiting a result. 965 | Should accept one argument BUF, the buffer to display.") 966 | 967 | (defvar helm-rg--paths-to-search nil 968 | ;; FIXME: we have multiple `defvar's which just mirror `defcustoms' (and can then be toggled while 969 | ;; searching) -- we should almost definitely have a macro to declare/access these kinds of 970 | ;; variables uniformly. 971 | "List of paths to use in the ripgrep command. 972 | All paths are interpreted relative to the directory ripgrep is invoked from. 973 | When nil, searches from the directory ripgrep is invoked from. 974 | See the documentation for `helm-rg-default-directory'.") 975 | 976 | (defvar helm-rg--case-sensitivity nil 977 | "Key of `helm-rg--case-sensitive-argument-alist' to use in a `helm-rg' session.") 978 | 979 | (defvar helm-rg--previously-highlighted-buffer nil 980 | "Previous buffer visited in between async actions of a `helm-rg' session. 981 | 982 | Used to cache the overlays drawn for matches within a file when visiting matches in the same file 983 | using `helm-rg--async-persistent-action'.") 984 | 985 | (defvar helm-rg--last-argv nil 986 | "Argument list for the most recent ripgrep invocation. 987 | 988 | Used for the command line header in `helm-rg--bounce-mode'.") 989 | 990 | 991 | ;; Buffer-local Variables 992 | (defvar-local helm-rg--process-output-parse-state 993 | (list :cur-file nil) 994 | "Contains state which is updated as the ripgrep output is processed. 995 | 996 | This is buffer-local because it is specific to a single process invocation and is manipulated in 997 | that process's buffer. See `helm-rg--parse-process-output' for usage.") 998 | 999 | (defvar-local helm-rg--beginning-of-bounce-content-mark nil 1000 | "Contains a marker pointing to the beginning of the match results in a `helm-rg--bounce' buffer.") 1001 | 1002 | (defvar-local helm-rg--do-font-locking nil 1003 | "If t, colorize the file text as it would be in an editor. 1004 | 1005 | This may be expensive for larger files, so it is turned off if 1006 | `helm-rg-shallow-highlight-files-regexp' is a regexp matching the file's path.") 1007 | 1008 | 1009 | ;; Utilities 1010 | (defun helm-rg--alist-get-exhaustive (key alist) 1011 | "Get KEY from ALIST, or throw an error." 1012 | (or (alist-get key alist) 1013 | (error "Key '%s' was not found in alist '%S' during an exhaustiveness check" 1014 | key alist))) 1015 | 1016 | (defun helm-rg--alist-keys (alist) 1017 | "Get all keys of ALIST." 1018 | (cl-mapcar #'car alist)) 1019 | 1020 | (defmacro helm-rg--get-optional-typed (type-name obj &rest body) 1021 | "If OBJ is non-nil, check its type against TYPE-NAME, then bind it to `it' and execute BODY." 1022 | (declare (indent 2)) 1023 | `(let ((it ,obj)) 1024 | (when it 1025 | (cl-check-type it ,type-name) 1026 | ,@body))) 1027 | 1028 | (defmacro helm-rg--into-temp-buffer (to-insert &rest body) 1029 | "Execute BODY at the beginning of a `with-temp-buffer' containing TO-INSERT." 1030 | (declare (indent 1)) 1031 | `(with-temp-buffer 1032 | (insert ,to-insert) 1033 | (goto-char (point-min)) 1034 | ,@body)) 1035 | 1036 | (defmacro helm-rg--with-named-temp-buffer (name &rest body) 1037 | "Execute BODY after binding the result of a `with-temp-buffer' to NAME. 1038 | 1039 | BODY is executed in the original buffer, not the new temp buffer." 1040 | (declare (indent 1)) 1041 | (let ((cur-buf (cl-gensym "helm-rg--with-named-temp-buffer"))) 1042 | `(let ((,cur-buf (current-buffer))) 1043 | (with-temp-buffer 1044 | (let ((,name (current-buffer))) 1045 | (with-current-buffer ,cur-buf 1046 | ,@body)))))) 1047 | 1048 | 1049 | ;; Logic 1050 | (defun helm-rg--make-dummy-process (input err-msg) 1051 | "Make a process that immediately exits to display just a title. 1052 | 1053 | Provide INPUT to represent the `helm-pattern', and ERR-MSG as the reasoning for failing to display 1054 | any results." 1055 | (let* ((dummy-proc (make-process 1056 | :name helm-rg--process-name 1057 | :buffer helm-rg--process-buffer-name 1058 | :command '("echo") 1059 | :noquery t)) 1060 | (input-repr 1061 | (cond 1062 | ((string= input "") 1063 | "") 1064 | ((string-match-p helm-rg--all-whitespace-regexp input) 1065 | "") 1066 | (t input))) 1067 | (helm-src-name 1068 | (format "%s %s: %s" 1069 | (helm-rg--make-face 'helm-rg-error-message "no results for input") 1070 | (helm-rg--make-face 'font-lock-string-face input-repr) 1071 | (helm-rg--make-face 'helm-rg-error-message err-msg)))) 1072 | (helm-attrset 'name helm-src-name) 1073 | dummy-proc)) 1074 | 1075 | (defun helm-rg--validate-or-make-dummy-process (input) 1076 | (cond 1077 | ((< (length input) helm-rg-input-min-search-chars) 1078 | (helm-rg--make-dummy-process 1079 | input 1080 | (format "must be at least %d characters" helm-rg-input-min-search-chars))) 1081 | (t t))) 1082 | 1083 | (defun helm-rg--join (sep seq) 1084 | (mapconcat #'identity seq sep)) 1085 | 1086 | (defun helm-rg--props (props str) 1087 | (apply #'propertize (append (list str) props))) 1088 | 1089 | (defun helm-rg--make-face (face str) 1090 | (helm-rg--props `(face ,face) str)) 1091 | 1092 | (defun helm-rg--process-paths-to-search (paths) 1093 | (cl-check-type helm-rg--current-dir helm-rg-existing-directory) 1094 | (cl-loop 1095 | for path in paths 1096 | for expanded = (expand-file-name path helm-rg--current-dir) 1097 | unless (file-exists-p expanded) 1098 | do (error (concat "Error: expanded path '%s' does not exist. " 1099 | "The cwd was '%s', and the paths provided were %S.") 1100 | expanded 1101 | helm-rg--current-dir 1102 | paths) 1103 | ;; TODO: a `pcase-defmacro' or `pcase' wrapper which checks that all possible cases of a 1104 | ;; `helm-rg--defcustom-from-alist' are enumerated at compile time! 1105 | ;; TODO: `helm-resume' currently fails on resume in the 'relative case. 1106 | collect (pcase-exhaustive helm-rg-file-paths-in-matches-behavior 1107 | (`relative (file-relative-name expanded helm-rg--current-dir)) 1108 | (`absolute expanded)))) 1109 | 1110 | (defun helm-rg--empty-glob-p (glob-str) 1111 | (or (null glob-str) 1112 | (string-blank-p glob-str))) 1113 | 1114 | (defun helm-rg--construct-argv (pattern) 1115 | "Create an argument list from the `helm-pattern' PATTERN for the ripgrep command. 1116 | 1117 | This argument list is propertized for display in the `helm-buffer' header when using `helm-rg', and 1118 | is used directly to invoke ripgrep. It uses `defcustom' values, and `defvar' values bound in other 1119 | functions." 1120 | ;; TODO: document these pcase deconstructions in the docstring for 1121 | ;; `helm-rg--ripgrep-argv-format-alist'! 1122 | (cl-loop 1123 | for el in helm-rg--ripgrep-argv-format-alist 1124 | append (pcase-exhaustive el 1125 | (`(,(or (and `it (let expr pattern)) expr) :face ,face-sym) 1126 | (pcase-exhaustive (eval expr) 1127 | ((and (pred listp) args) 1128 | (--map (helm-rg--make-face face-sym it) args)) 1129 | (arg 1130 | (list (helm-rg--make-face face-sym arg)))))))) 1131 | 1132 | (defun helm-rg--make-process-from-argv (argv) 1133 | (let* ((real-proc (make-process 1134 | :name helm-rg--process-name 1135 | :buffer helm-rg--process-buffer-name 1136 | :command argv 1137 | :noquery t)) 1138 | (helm-src-name 1139 | (format "argv: %s" (helm-rg--join " " argv)))) 1140 | (helm-attrset 'name helm-src-name) 1141 | (set-process-query-on-exit-flag real-proc nil) 1142 | real-proc)) 1143 | 1144 | (defun helm-rg--make-process () 1145 | "Invoke ripgrep in `helm-rg--current-dir' with `helm-pattern'. 1146 | Make a dummy process if the input is empty with a clear message to the user." 1147 | (let* ((default-directory helm-rg--current-dir) 1148 | (input helm-pattern)) 1149 | (pcase-exhaustive (helm-rg--validate-or-make-dummy-process input) 1150 | ((and (pred processp) x) 1151 | (setq helm-rg--last-argv nil) 1152 | x) 1153 | (`t 1154 | (let* ((rg-regexp (helm-rg--helm-pattern-to-ripgrep-regexp input)) 1155 | (argv (helm-rg--construct-argv rg-regexp)) 1156 | (real-proc (helm-rg--make-process-from-argv argv))) 1157 | (setq helm-rg--last-argv argv) 1158 | real-proc))))) 1159 | 1160 | (defun helm-rg--make-overlay-with-face (beg end face) 1161 | "Generate an overlay in region BEG to END with face FACE." 1162 | (let ((olay (make-overlay beg end))) 1163 | (overlay-put olay 'face face) 1164 | olay)) 1165 | 1166 | (defun helm-rg--delete-match-overlays () 1167 | "Delete all cached overlays in `helm-rg--matches-in-current-file-overlays', and clear it." 1168 | (mapc #'delete-overlay helm-rg--matches-in-current-file-overlays) 1169 | (setq helm-rg--matches-in-current-file-overlays nil)) 1170 | 1171 | (defun helm-rg--delete-line-overlay () 1172 | "Delete the cached overlay `helm-rg--current-line-overlay', if it exists, and clear it." 1173 | (helm-rg--get-optional-typed overlay helm-rg--current-line-overlay 1174 | (delete-overlay it)) 1175 | (setq helm-rg--current-line-overlay nil)) 1176 | 1177 | (defun helm-rg--collect-lines-matches-current-file (orig-line-parsed) 1178 | "Collect all of the matched text regions from ripgrep's highlighted output from ORIG-LINE-PARSED." 1179 | ;; If we are on a file's line, stay where we are, otherwise back up to the closest file line above 1180 | ;; the current line (this is the file that "owns" the entry). 1181 | (cl-destructuring-bind (&key 1182 | ((:file orig-file)) 1183 | ((:line-num _orig-line-num)) 1184 | ((:match-results _orig-match-results))) 1185 | orig-line-parsed 1186 | ;; Collect all the results on all matching lines of the file. 1187 | (with-helm-window 1188 | (helm-rg--file-backward t) 1189 | (let ((all-match-results nil)) 1190 | ;; Process the first line (`helm-rg--iterate-results' will advance 1191 | ;; past the initial element). 1192 | (cl-destructuring-bind (&key _file line-num match-results) (helm-rg--current-jump-location) 1193 | (when (and line-num match-results) 1194 | (push (list :match-line-num line-num 1195 | :line-match-results match-results) 1196 | all-match-results))) 1197 | (helm-rg--iterate-results 1198 | 'forward 1199 | :success-fn (lambda (cur-line-parsed) 1200 | (cl-destructuring-bind (&key file line-num match-results) 1201 | cur-line-parsed 1202 | (cl-check-type orig-file string) 1203 | (cl-check-type file string) 1204 | (if (not (string= orig-file file)) 1205 | ;; We have reached the results from a different file, so done. 1206 | t 1207 | (progn 1208 | ;; In filename lines, these are nil. 1209 | (when (and line-num match-results) 1210 | (push (list :match-line-num line-num 1211 | :line-match-results match-results) 1212 | all-match-results)) 1213 | ;; We loop forever if there's only one file in 1214 | ;; the results unless we return this as success. 1215 | (helm-end-of-source-p))))) 1216 | :failure-fn (lambda (cur-line-parsed) 1217 | (helm-rg--different-file-line orig-line-parsed cur-line-parsed))) 1218 | (helm-rg--iterate-results 1219 | 'backward 1220 | :success-fn (lambda (cur-line-parsed) 1221 | (helm-rg--on-same-entry orig-line-parsed cur-line-parsed)) 1222 | :failure-fn #'ignore) 1223 | (reverse all-match-results))))) 1224 | 1225 | (defun helm-rg--convert-lines-matches-to-overlays (line-match-results) 1226 | (beginning-of-line) 1227 | (--map (cl-destructuring-bind (&key beg end) it 1228 | (helm-rg--make-overlay-with-face 1229 | (+ (point) beg) (+ (point) end) 1230 | 'helm-rg-match-text-face)) 1231 | line-match-results)) 1232 | 1233 | (defun helm-rg--make-match-overlays-for-result (cur-file-matches) 1234 | (save-excursion 1235 | (goto-char (point-min)) 1236 | (cl-loop 1237 | with cur-line = 1 1238 | for line-match-set in cur-file-matches 1239 | append (cl-destructuring-bind (&key match-line-num line-match-results) 1240 | line-match-set 1241 | (let ((lines-diff (- match-line-num cur-line))) 1242 | (cl-assert (>= lines-diff 0)) 1243 | (forward-line lines-diff) 1244 | (cl-incf cur-line lines-diff) 1245 | (cl-assert (not (eobp))) 1246 | (helm-rg--convert-lines-matches-to-overlays line-match-results)))))) 1247 | 1248 | (defun helm-rg--async-action (parsed-output &optional highlight-matches) 1249 | "Visit the file at the line and column according to PARSED-OUTPUT. 1250 | 1251 | The match is highlighted in its buffer if HIGHLIGHT-MATCHES is non-nil." 1252 | (let ((default-directory helm-rg--current-dir) 1253 | (helm-rg--display-buffer-method 1254 | (or helm-rg--display-buffer-method 1255 | ;; If a prefix arg is given for the async action or persistent action, use the 1256 | ;; alternate buffer display method (which by default is `pop-to-buffer'). 1257 | (if helm-current-prefix-arg helm-rg-display-buffer-alternate-method 1258 | helm-rg-display-buffer-normal-method)))) 1259 | ;; We always want to delete the line overlay if it exists, no matter what. 1260 | (helm-rg--delete-line-overlay) 1261 | (cl-destructuring-bind (&key file line-num match-results) parsed-output 1262 | (let* ((file-abs-path (expand-file-name file)) 1263 | (buffer-to-display 1264 | (or (when-let ((visiting-buf (find-buffer-visiting file-abs-path))) 1265 | ;; TODO: prompt to save the buffer if modified? something? 1266 | visiting-buf) 1267 | (let ((new-buf (find-file-noselect file-abs-path))) 1268 | (when helm-rg--append-persistent-buffers 1269 | (push new-buf helm-rg--cur-persistent-bufs)) 1270 | new-buf))) 1271 | (cur-file-matches 1272 | ;; Clear the old matches and make new ones, if this is a different file than the last 1273 | ;; one we visited in this session. 1274 | (cond 1275 | ;; We don't highlight any matches, probably because we are the async action and just 1276 | ;; want to jump to a file location. 1277 | ((not highlight-matches) 1278 | nil) 1279 | ;; If the file path matches `helm-rg-shallow-highlight-files-regexp', just 1280 | ;; highlight the matches for the current line, if any. We need to do this again, even 1281 | ;; if it is the same file, because the single line number to draw may change. 1282 | ((and (stringp helm-rg-shallow-highlight-files-regexp) 1283 | (string-match-p helm-rg-shallow-highlight-files-regexp file-abs-path)) 1284 | ;; Delete the overlay for the previous line. 1285 | (helm-rg--delete-match-overlays) 1286 | (list (list :match-line-num line-num 1287 | :line-match-results match-results))) 1288 | ;; This is the same buffer as last time, so do nothing. 1289 | ((eq helm-rg--previously-highlighted-buffer buffer-to-display) 1290 | nil) 1291 | (t 1292 | ;; This is a different buffer, so record that. 1293 | (setq helm-rg--previously-highlighted-buffer buffer-to-display) 1294 | ;; Clear the old lines (from the previous buffer) and make new ones. 1295 | (helm-rg--delete-match-overlays) 1296 | (helm-rg--collect-lines-matches-current-file parsed-output))))) 1297 | ;; Display the buffer visiting the file with the matches. 1298 | (funcall helm-rg--display-buffer-method buffer-to-display) 1299 | ;; Make overlays highlighting all the matches (unless we are in the same file as 1300 | ;; before, or highlight-matches is nil). 1301 | (when cur-file-matches 1302 | (setq helm-rg--matches-in-current-file-overlays 1303 | (helm-rg--make-match-overlays-for-result cur-file-matches))) 1304 | ;; Advance in the file to the given line. 1305 | (goto-char (point-min)) 1306 | (helm-rg--get-optional-typed natnum line-num 1307 | (forward-line (1- it))) 1308 | ;; Make a line overlay, if requested. 1309 | (when highlight-matches 1310 | (let ((line-olay 1311 | (helm-rg--make-overlay-with-face (line-beginning-position) (line-end-position) 1312 | 'helm-rg-preview-line-highlight))) 1313 | (setq helm-rg--current-line-overlay line-olay))) 1314 | ;; Move to the first match in the line (all lines have >= 1 match because ripgrep only 1315 | ;; outputs matching lines). 1316 | (let ((first-match-beginning (plist-get (car match-results) :beg))) 1317 | (helm-rg--get-optional-typed natnum first-match-beginning 1318 | (forward-char it))) 1319 | (recenter))))) 1320 | 1321 | (defun helm-rg--async-persistent-action (parsed-output) 1322 | "Visit the file at the line and column specified by PARSED-OUTPUT. 1323 | 1324 | Call `helm-rg--async-action', but push the buffer corresponding to PARSED-OUTPUT to 1325 | `helm-rg--matches-in-current-file-overlays', if there was no buffer visiting it already." 1326 | (let ((helm-rg--append-persistent-buffers t) 1327 | (helm-rg--display-buffer-method helm-rg--persistent-action-display-buffer-method)) 1328 | (helm-rg--async-action parsed-output t))) 1329 | 1330 | (defun helm-rg--kill-proc-if-live (proc-name) 1331 | "Delete the process named PROC-NAME, if it is alive." 1332 | (let ((proc (get-process proc-name))) 1333 | (when (process-live-p proc) 1334 | (delete-process proc)))) 1335 | 1336 | (defun helm-rg--kill-bufs-if-live (&rest bufs) 1337 | "Kill any live buffers in BUFS." 1338 | (mapc 1339 | (lambda (buf) 1340 | (when (buffer-live-p (get-buffer buf)) 1341 | (kill-buffer buf))) 1342 | bufs)) 1343 | 1344 | (defun helm-rg--unwind-cleanup () 1345 | "Reset all the temporary state in `defvar's in this package." 1346 | (helm-rg--delete-match-overlays) 1347 | (helm-rg--delete-line-overlay) 1348 | (cl-loop 1349 | for opened-buf in helm-rg--cur-persistent-bufs 1350 | unless (eq (current-buffer) opened-buf) 1351 | do (kill-buffer opened-buf) 1352 | finally (setq helm-rg--cur-persistent-bufs nil)) 1353 | (helm-rg--kill-proc-if-live helm-rg--process-name) 1354 | ;; Don't delete `helm-rg--helm-buffer-name' to support using e.g. `helm-resume'. 1355 | ;; TODO: add testing for this use case. 1356 | (helm-rg--kill-bufs-if-live helm-rg--process-buffer-name 1357 | helm-rg--error-buffer-name) 1358 | (setq helm-rg--glob-string nil 1359 | helm-rg--extra-args nil 1360 | helm-rg--paths-to-search nil 1361 | helm-rg--case-sensitivity nil 1362 | helm-rg--previously-highlighted-buffer nil 1363 | helm-rg--last-argv nil)) 1364 | 1365 | (defun helm-rg--do-helm-rg (rg-pattern) 1366 | "Invoke ripgrep to search for RG-PATTERN, using `helm'." 1367 | (helm :sources '(helm-rg-process-source) 1368 | :buffer helm-rg--helm-buffer-name 1369 | :input rg-pattern 1370 | :prompt "rg pattern: ")) 1371 | 1372 | (defun helm-rg--get-thing-at-pt () 1373 | "Get the object surrounding point, or the empty string." 1374 | (helm-aif (thing-at-point helm-rg-thing-at-point) 1375 | (substring-no-properties it) 1376 | "")) 1377 | 1378 | (defun helm-rg--header-name (src-name) 1379 | (format "%s %s @ %s" 1380 | (helm-rg--make-face 'helm-rg-title-face "rg") 1381 | src-name 1382 | (helm-rg--make-face 'helm-rg-directory-header-face helm-rg--current-dir))) 1383 | 1384 | (defun helm-rg--current-jump-location (&optional object) 1385 | (get-text-property (line-beginning-position) helm-rg--jump-location-text-property object)) 1386 | 1387 | (defun helm-rg--get-jump-location-from-line (line) 1388 | "Get the value of `helm-rg--jump-location-text-property' at the start of LINE." 1389 | ;; When there is an empty pattern, the argument can be nil due to the way helm handles our dummy 1390 | ;; process. There may be a way to avoid having to do this check. 1391 | (when line 1392 | (get-text-property 0 helm-rg--jump-location-text-property line))) 1393 | 1394 | (defun helm-rg--display-to-real (_) 1395 | "Extract the information from the process filter stored in the current entry's text properties. 1396 | 1397 | Note that this doesn't use the argument at all. I don't think you can get the currently selected 1398 | line without the text properties scrubbed using helm without doing this." 1399 | (helm-rg--get-jump-location-from-line (helm-get-selection nil 'withprop))) 1400 | 1401 | (defun helm-rg--collect-matches (regexp) 1402 | (cl-loop while (re-search-forward regexp nil t) 1403 | collect (match-string 1))) 1404 | 1405 | (defun helm-rg--helm-pattern-to-ripgrep-regexp (pattern) 1406 | "Transform PATTERN (the `helm-input') into a Perl-compatible regular expression. 1407 | 1408 | TODO: add ert testing for this function!" 1409 | ;; For example: "a b c" => "a b.*c|c.*a b". 1410 | (->> 1411 | ;; Split the pattern into our definition of "components". Suppose PATTERN is "a b c". Then: 1412 | ;; "a b c" => '("a b" "c") 1413 | (helm-rg--into-temp-buffer pattern 1414 | (helm-rg--collect-matches helm-rg--loop-input-pattern-regexp)) 1415 | ;; Two spaces in a row becomes a single space in the output regexp. Each component is now a 1416 | ;; regexp. 1417 | ;; '("a b" "c") => '("a b" "c") 1418 | (--map (replace-regexp-in-string (rx (= 2 ? )) " " it)) 1419 | ;; All permutations of all component regexps. 1420 | ;; '("a b" "c") => '(("a b" "c") ("c" "a b")) 1421 | (-permutations) 1422 | ;; Each permutation is converted into a regexp which matches a line containing each regexp in 1423 | ;; the permutation in order, each separated by 0 or more non-newline characters. 1424 | ;; '(("a b" "c") ("c" "a b")) => '("a b.*c" "c.*a b") 1425 | (--map (helm-rg--join ".*" it)) 1426 | ;; Return a regexp which matches any of the resulting regexps. 1427 | ;; '("a b.*c" "c.*a b") => "a b.*c|c.*a b" 1428 | (helm-rg--join "|"))) 1429 | 1430 | (defun helm-rg--advance-forward () 1431 | "Move forward a line in the results, cycling if necessary." 1432 | (interactive) 1433 | (let ((helm-move-to-line-cycle-in-source t)) 1434 | (if (helm-end-of-source-p) 1435 | (helm-beginning-of-buffer) 1436 | (helm-next-line)))) 1437 | 1438 | (defun helm-rg--advance-backward () 1439 | "Move backward a line in the results, cycling if necessary." 1440 | (interactive) 1441 | (let ((helm-move-to-line-cycle-in-source t)) 1442 | (if (helm-beginning-of-source-p) 1443 | (helm-end-of-buffer) 1444 | (helm-previous-line)))) 1445 | 1446 | (define-error 'helm-rg--helm-buffer-iteration-error 1447 | "Iterating over ripgrep match results in the helm buffer failed." 1448 | 'helm-rg-error) 1449 | 1450 | (cl-defun helm-rg--iterate-results (direction &key success-fn failure-fn) 1451 | (with-helm-buffer 1452 | (let ((move-fn 1453 | (pcase-exhaustive direction 1454 | (`forward #'helm-rg--advance-forward) 1455 | (`backward #'helm-rg--advance-backward)))) 1456 | (call-interactively move-fn) 1457 | (cl-loop 1458 | for cur-line-parsed = (helm-rg--current-jump-location) 1459 | until (funcall success-fn cur-line-parsed) 1460 | if (funcall failure-fn cur-line-parsed) 1461 | return (signal 'helm-rg--helm-buffer-iteration-error "could not cycle to the next entry") 1462 | else do (call-interactively move-fn))))) 1463 | 1464 | (defun helm-rg--current-line-contents () 1465 | "`helm-current-line-contents' doesn't keep text properties." 1466 | (buffer-substring (point-at-bol) (point-at-eol))) 1467 | 1468 | (cl-defun helm-rg--nullable-states-different (a b &key (cmp #'eq)) 1469 | "Compare A and B respecting nullability using CMP. 1470 | 1471 | When CMP is `string=', the following results: 1472 | (A=nil, B=nil) => nil 1473 | (A=\"a\", B=nil) => t 1474 | (A=nil, B=\"a\") => t 1475 | (A=\"a\", B=\"a\") => nil 1476 | (A=\"a\", B=\"b\") => t 1477 | 1478 | TODO: throw the above into an ert test!" 1479 | (if a 1480 | (not (and b (funcall cmp a b))) 1481 | b)) 1482 | 1483 | (defun helm-rg--on-same-entry (orig-line-parsed cur-line-parsed) 1484 | (cl-destructuring-bind (&key ((:file orig-file)) ((:line-num orig-line-num)) ((:match-results _))) 1485 | orig-line-parsed 1486 | (cl-check-type orig-file string) 1487 | (cl-destructuring-bind (&key ((:file cur-file)) ((:line-num cur-line-num)) ((:match-results _))) 1488 | cur-line-parsed 1489 | (cl-check-type cur-file string) 1490 | (and (string= orig-file cur-file) 1491 | (not (helm-rg--nullable-states-different orig-line-num cur-line-num :cmp #'=)))))) 1492 | 1493 | (defun helm-rg--different-file-line (orig-line-parsed cur-line-parsed) 1494 | (cl-destructuring-bind (&key ((:file orig-file)) ((:line-num _)) ((:match-results _))) 1495 | orig-line-parsed 1496 | (cl-check-type orig-file string) 1497 | (cl-destructuring-bind (&key ((:file cur-file)) ((:line-num _)) ((:match-results _))) 1498 | cur-line-parsed 1499 | (cl-check-type cur-file string) 1500 | (not (string= orig-file cur-file))))) 1501 | 1502 | (defun helm-rg--move-file (direction) 1503 | "Move through matching lines from ripgrep in the given DIRECTION. 1504 | 1505 | This will loop around the results when advancing past the beginning or end of the results." 1506 | (with-helm-buffer 1507 | (let* ((orig-line-parsed (helm-rg--current-jump-location))) 1508 | (helm-rg--iterate-results 1509 | direction 1510 | :success-fn (lambda (cur-line-parsed) 1511 | (helm-rg--different-file-line orig-line-parsed cur-line-parsed)) 1512 | :failure-fn (lambda (cur-line-parsed) 1513 | (helm-rg--on-same-entry orig-line-parsed cur-line-parsed)))))) 1514 | 1515 | (defun helm-rg--file-forward () 1516 | "Move forward to the beginning of the next file in the output, cycling if necessary." 1517 | (interactive) 1518 | (condition-case _err 1519 | (helm-rg--move-file 'forward) 1520 | (helm-rg--helm-buffer-iteration-error 1521 | (with-helm-window (helm-end-of-buffer))))) 1522 | 1523 | (defun helm-rg--do-file-backward-dwim (stay-if-at-top-of-file) 1524 | (with-helm-window 1525 | (let ((orig-line-parsed (helm-rg--current-jump-location))) 1526 | (helm-rg--advance-backward) 1527 | (let* ((before-line-parsed (helm-rg--current-jump-location)) 1528 | (at-top-of-file-p (helm-rg--different-file-line orig-line-parsed before-line-parsed))) 1529 | (unless (and at-top-of-file-p (not stay-if-at-top-of-file)) 1530 | (helm-rg--advance-forward)) 1531 | (helm-rg--move-file 'backward)) 1532 | ;; `helm-rg--move-file' gets us one before the line we actually want when going backwards. 1533 | (helm-rg--advance-forward)))) 1534 | 1535 | (defun helm-rg--file-backward (stay-if-at-top-of-file) 1536 | "Move backward to the beginning of the previous file in the output, cycling if necessary. 1537 | 1538 | STAY-IF-AT-TOP-OF-FILE determines whether to move to the previous file if point is at the top of a 1539 | file in the output." 1540 | (interactive (list nil)) 1541 | (condition-case _err 1542 | (helm-rg--do-file-backward-dwim stay-if-at-top-of-file) 1543 | (helm-rg--helm-buffer-iteration-error 1544 | (with-helm-window (helm-beginning-of-buffer))))) 1545 | 1546 | (defconst helm--whitespace-trim-rx-expr 1547 | '(| (: bos (+ whitespace)) (: (+ whitespace) eos))) 1548 | 1549 | (defun helm-rg--trim-whitespace (str) 1550 | (-> (rx-to-string helm--whitespace-trim-rx-expr) 1551 | (replace-regexp-in-string "" str))) 1552 | 1553 | (defun helm-rg--process-output (exe &rest args) 1554 | "Get output from a process EXE with string arguments ARGS. 1555 | 1556 | Merges stdout and stderr, and trims whitespace from the result." 1557 | (with-temp-buffer 1558 | (let ((proc (make-process 1559 | :name "temp-proc" 1560 | :buffer (current-buffer) 1561 | :command `(,exe ,@args) 1562 | :sentinel #'ignore))) 1563 | (while (accept-process-output proc nil nil t))) 1564 | (helm-rg--trim-whitespace (buffer-string)))) 1565 | 1566 | (defun helm-rg--check-directory-path (path) 1567 | (if (and path (file-directory-p path)) path 1568 | (error "Path '%S' was not a directory" path))) 1569 | 1570 | (defun helm-rg--make-help-buffer (help-buf-name) 1571 | ;; FIXME: this could be more useful -- but also, is it going to matter to anyone but the 1572 | ;; developer? 1573 | (with-current-buffer (get-buffer-create help-buf-name) 1574 | (read-only-mode -1) 1575 | (erase-buffer) 1576 | (fundamental-mode) 1577 | (insert (helm-rg--process-output helm-rg-ripgrep-executable "--help")) 1578 | (goto-char (point-min)) 1579 | (read-only-mode 1) 1580 | (current-buffer))) 1581 | 1582 | (defun helm-rg--lookup-default-alist (alist elt) 1583 | (if elt 1584 | (helm-rg--alist-get-exhaustive elt alist) 1585 | (cdar alist))) 1586 | 1587 | (defun helm-rg--lookup-color (&optional color) 1588 | (helm-rg--lookup-default-alist helm-rg--color-format-argument-alist color)) 1589 | 1590 | (defun helm-rg--lookup-style (&optional style) 1591 | (helm-rg--lookup-default-alist helm-rg--style-format-argument-alist style)) 1592 | 1593 | (defun helm-rg--construct-match-color-format-arguments () 1594 | (list 1595 | (format "--colors=match:fg:%s" 1596 | (plist-get (helm-rg--lookup-color) :cmd-line)) 1597 | (format "--colors=match:style:%s" 1598 | (plist-get (helm-rg--lookup-style) :cmd-line)))) 1599 | 1600 | (defun helm-rg--construct-match-text-properties () 1601 | (cl-destructuring-bind (&key ((:text-property style-text-property)) ((:cmd-line _))) 1602 | (helm-rg--lookup-style) 1603 | (cl-destructuring-bind (&key ((:text-property color-text-property)) ((:cmd-line _))) 1604 | (helm-rg--lookup-color) 1605 | `(,style-text-property 1606 | (foreground-color . ,color-text-property))))) 1607 | 1608 | (defun helm-rg--is-match (position object) 1609 | (let ((text-props-for-position (get-text-property position 'font-lock-face object)) 1610 | (text-props-for-match (helm-rg--construct-match-text-properties))) 1611 | (equal text-props-for-position text-props-for-match))) 1612 | 1613 | (defun helm-rg--first-match-start-ripgrep-output (position match-line &optional find-end) 1614 | (cl-loop 1615 | with line-char-index = position 1616 | for is-match-p = (helm-rg--is-match line-char-index match-line) 1617 | until (if find-end (not is-match-p) is-match-p) 1618 | for next-chg = (next-single-property-change line-char-index 'font-lock-face match-line) 1619 | if next-chg do (setq line-char-index next-chg) 1620 | else return (if find-end 1621 | ;; char at end of line is end of match 1622 | (length match-line) 1623 | nil) 1624 | finally return line-char-index)) 1625 | 1626 | (defun helm-rg--parse-propertize-match-regions-from-match-line (match-line) 1627 | (cl-loop 1628 | with line-char-index = 0 1629 | for match-beg = (helm-rg--first-match-start-ripgrep-output line-char-index match-line) 1630 | unless match-beg 1631 | return (list :propertized-line (concat cur-match-str 1632 | (substring match-line match-end)) 1633 | :match-regions match-regions) 1634 | concat (substring match-line match-end match-beg) 1635 | into cur-match-str 1636 | for match-end = (helm-rg--first-match-start-ripgrep-output match-beg match-line t) 1637 | collect (list :beg match-beg :end match-end) 1638 | into match-regions 1639 | do (setq line-char-index match-end) 1640 | concat (--> match-line 1641 | (substring it match-beg match-end) 1642 | (helm-rg--make-face 'helm-rg-match-text-face it)) 1643 | into cur-match-str)) 1644 | 1645 | (cl-defun helm-rg--join-output-line (&key cur-file line-num-str propertized-line) 1646 | (helm-rg--join (->> ":" 1647 | (helm-rg--make-face 'helm-rg-colon-separator-ripgrep-output-face)) 1648 | `(,@(and cur-file (list cur-file)) 1649 | ,(->> line-num-str 1650 | (helm-rg--make-face 'helm-rg-line-number-match-face)) 1651 | ,propertized-line))) 1652 | 1653 | (defun helm-rg--process-transition (cur-file line) 1654 | (pcase-exhaustive line 1655 | ;; When we see an empty line, we clear all the state. 1656 | ((helm-rg-rx (: bos eos)) 1657 | (list :file-path nil)) 1658 | ;; When we see a line with a number and text, we must be collecting match lines from a 1659 | ;; particular file right now. Parse the line and add "jump" information as text properties. 1660 | ((and (helm-rg-rx (eval helm-rg--numbered-text-line-rx-expr)) 1661 | (let (helm-rg-&key-complete propertized-line match-regions) 1662 | (helm-rg--parse-propertize-match-regions-from-match-line content))) 1663 | (cl-check-type cur-file string) 1664 | (helm-rg-mark-unused (content whole-line) 1665 | (let* ((prefixed-line (helm-rg--join-output-line 1666 | :cur-file (and helm-rg-include-file-on-every-match-line cur-file) 1667 | :line-num-str line-num-str 1668 | :propertized-line propertized-line)) 1669 | (line-num (string-to-number line-num-str)) 1670 | (jump-to (list :file cur-file 1671 | :line-num line-num 1672 | :match-results match-regions)) 1673 | (output-line 1674 | (propertize prefixed-line helm-rg--jump-location-text-property jump-to))) 1675 | (list :file-path cur-file 1676 | :line-content output-line)))) 1677 | ;; If we see a line with just a filename, we must have just finished the results from another 1678 | ;; file. We update the state to the file parsed from this line, but we may not insert anything 1679 | ;; into the output depending on the user's customizations. 1680 | ((helm-rg-rx (eval helm-rg--output-new-file-line-rx-expr)) 1681 | ;; FIXME: why does this fail? 1682 | ;; (cl-check-type cur-file null) 1683 | (let* ((whole-line-effaced (helm-rg--make-face 'helm-rg-file-match-face whole-line)) 1684 | (file-path-effaced (helm-rg--make-face 'helm-rg-file-match-face file-path)) 1685 | (jump-to (list :file file-path-effaced)) 1686 | (output-line 1687 | (propertize whole-line-effaced helm-rg--jump-location-text-property jump-to))) 1688 | (append 1689 | (list :file-path file-path-effaced) 1690 | (and helm-rg-prepend-file-name-line-at-top-of-matches 1691 | (list :line-content output-line))))))) 1692 | 1693 | (defun helm-rg--maybe-get-line (content) 1694 | (helm-rg--into-temp-buffer content 1695 | (if (re-search-forward (rx (: (group (*? anything)) "\n")) nil t) 1696 | (list :line (match-string 1) 1697 | :rest (buffer-substring (point) (point-max))) 1698 | (list :line nil 1699 | :rest (buffer-string))))) 1700 | 1701 | (defun helm-rg--parse-process-output (input-line) 1702 | ;; TODO: document this function! 1703 | (let* ((colored-line (ansi-color-apply input-line)) 1704 | (string-result 1705 | (cl-destructuring-bind (&key cur-file) helm-rg--process-output-parse-state 1706 | (-if-let* ((parsed (helm-rg--process-transition cur-file colored-line))) 1707 | (cl-destructuring-bind (&key file-path line-content) parsed 1708 | (setq-local helm-rg--process-output-parse-state (list :cur-file file-path)) 1709 | ;; Exits here. 1710 | (or line-content "")) 1711 | (error "Line '%s' could not be parsed! state was: '%S'" 1712 | colored-line helm-rg--process-output-parse-state))))) 1713 | string-result)) 1714 | 1715 | 1716 | ;; Bounce-mode specific functions (temporary, experimental) 1717 | (defun helm-rg--freeze-header-for-bounce (argv) 1718 | (cl-assert (get-text-property (point-min) helm-rg--helm-header-property-name)) 1719 | ;; We want to keep the helm header with the argv for reference, but we don't want it to affect 1720 | ;; any of the editing, so we make it read-only. 1721 | (let ((helm-header-end 1722 | (next-single-property-change (point-min) helm-rg--helm-header-property-name)) 1723 | (inhibit-read-only t)) 1724 | (delete-region (point-min) (1+ helm-header-end)) 1725 | (insert (format "%s\n" (helm-rg--join " " argv))) 1726 | (let ((new-argv-end (point))) 1727 | ;; This means insertion after the header (the first char of the buffer text) won't take on 1728 | ;; the header's face. 1729 | (put-text-property (point-min) new-argv-end 'rear-nonsticky '(face read-only)) 1730 | ;; This stops insertion before the header as well (the beginning of the buffer). 1731 | (put-text-property (point-min) new-argv-end 'front-sticky '(face read-only)) 1732 | ;; Finally set everything to read-only. 1733 | (put-text-property (point-min) new-argv-end 'read-only t)))) 1734 | 1735 | (defun helm-rg--maybe-insert-file-heading-for-bounce (cur-jump-loc) 1736 | ;; TODO: insert the file line if it's not there (if 1737 | ;; `helm-rg-prepend-file-name-line-at-top-of-matches' is nil)! 1738 | ;; (i.e. check to make sure this function works) 1739 | (let ((inhibit-read-only t) 1740 | (pt (point))) 1741 | ;; NB: the file line is NOT readonly!!! It can be used to modify the file names. 1742 | (pcase-exhaustive cur-jump-loc 1743 | ((helm-rg-&key line-num :required file) 1744 | (cl-check-type file string) 1745 | ;; FIXME: add some divider above each file line!!! 1746 | (if (not line-num) 1747 | ;; We already have an appropriate file heading. We assume all file entries are a single 1748 | ;; line at this point, because the user has not started editing the buffer yet. 1749 | (helm-rg--down-for-bounce) 1750 | ;; We need to insert the file's line. 1751 | ;; NB: we cut off the location entry to only the file, because we are 1752 | ;; making a file header line. 1753 | ;; TODO: make file header line creation into a factory method 1754 | (let* ((file-entry-loc (list :file file)) 1755 | (propertized-file-entry-line 1756 | (propertize file helm-rg--jump-location-text-property file-entry-loc))) 1757 | (insert (format "%s\n" propertized-file-entry-line)))))) 1758 | 1759 | ;; TODO: ??? 1760 | (put-text-property pt (point) 'front-sticky `(face ,helm-rg--jump-location-text-property)))) 1761 | 1762 | (defun helm-rg--propertize-line-number-prefix-range (beg end) 1763 | ;; Inserting text at the beginning is not allowed, except for the newline before this 1764 | ;; entry. 1765 | (put-text-property beg end 'front-sticky '(read-only)) 1766 | ;; Inserting text after this entry is allowed, and we don't want it to take the face of this 1767 | ;; text. 1768 | (put-text-property beg end 'rear-nonsticky '(face read-only)) 1769 | ;; Apply the read-only property. 1770 | ;; FIXME: can we remove this (1-) here? Why is it here? 1771 | (put-text-property (1- beg) end 'read-only t)) 1772 | 1773 | (defun helm-rg--format-match-line-for-bounce (jump-loc) 1774 | (let ((inhibit-read-only t)) 1775 | (pcase-exhaustive jump-loc 1776 | ((helm-rg-&key :required file line-num) 1777 | ;; TODO: remove the file from the match line if it's there (if 1778 | ;; `helm-rg-include-file-on-every-match-line' is non-nil)! 1779 | ;; (i.e. just check to make sure this line works) 1780 | (when (looking-at (rx-to-string `(: bol ,file ":"))) 1781 | (replace-match "")) 1782 | ;; TODO: fix cl-destructuring-bind, and merge with pcase and regexp matching (allowing named 1783 | ;; matches)! 1784 | ;; We are looking at a line number. 1785 | (cl-assert (looking-at (rx-to-string `(: bol (group-n 1 ,(number-to-string line-num)) ":")))) 1786 | ;; Make the propertized line number text read-only. 1787 | (let* ((matched-number-str (match-string 1)) 1788 | (matched-num (string-to-number matched-number-str))) 1789 | ;; TODO: is this check necessary? 1790 | (cl-assert (= matched-num line-num))) 1791 | (helm-rg--propertize-line-number-prefix-range (match-beginning 0) (match-end 0))))) 1792 | ;; We can use `forward-line' here, because we are building the bounce buffer (so there are no 1793 | ;; multiline entries). 1794 | (forward-line 1)) 1795 | 1796 | (defun helm-rg--propertize-match-line-from-file-for-bounce (line-to-propertize jump-loc) 1797 | ;; Copy the input string, because we will be mutating it. 1798 | (let ((resulting-line (cl-copy-seq line-to-propertize))) 1799 | (pcase-exhaustive jump-loc 1800 | ((helm-rg-&key :required match-results) 1801 | ;; Apply face to matches within the text to insert. 1802 | (cl-loop for match in match-results 1803 | do (cl-destructuring-bind (&key beg end) match 1804 | (put-text-property beg end 'face 'helm-rg-match-text-face 1805 | resulting-line))) 1806 | ;; Apply the jump location to the text to insert. 1807 | (put-text-property 0 (length resulting-line) helm-rg--jump-location-text-property jump-loc 1808 | resulting-line) 1809 | resulting-line)))) 1810 | 1811 | (defun helm-rg--line-from-corresponding-file-for-bounce (scratch-buf) 1812 | "Get the corresponding line in the file's buffer SCRATCH-BUF, and return it. 1813 | 1814 | SCRATCH-BUF has already been advanced to the appropriate line." 1815 | (with-current-buffer scratch-buf 1816 | (let ((beg (line-beginning-position)) 1817 | (end (line-end-position))) 1818 | (when helm-rg--do-font-locking 1819 | (font-lock-ensure beg end)) 1820 | (buffer-substring beg end)))) 1821 | 1822 | (defun helm-rg--rewrite-propertized-match-line-from-file-for-bounce (scratch-buf jump-loc) 1823 | (let* ((cur-line-in-file-to-propertize 1824 | (helm-rg--line-from-corresponding-file-for-bounce scratch-buf)) 1825 | (line-to-insert 1826 | (helm-rg--propertize-match-line-from-file-for-bounce 1827 | cur-line-in-file-to-propertize jump-loc))) 1828 | (delete-region (point) (line-end-position)) 1829 | (insert line-to-insert))) 1830 | 1831 | (cl-defun helm-rg--insert-new-match-line-for-bounce (&key file line-to-insert line-contents) 1832 | (let* ((output-line (helm-rg--join-output-line 1833 | :line-num-str (number-to-string line-to-insert) 1834 | :propertized-line line-contents)) 1835 | (inhibit-read-only t)) 1836 | (insert (format "%s\n" output-line)) 1837 | (helm-rg--up-for-bounce) 1838 | (helm-rg--propertize-line-number-prefix-range 1839 | (line-beginning-position) (point)) 1840 | (let ((new-entry-props (list :file file 1841 | :line-num line-to-insert 1842 | :match-results nil))) 1843 | (put-text-property (line-beginning-position) (line-end-position) 1844 | helm-rg--jump-location-text-property new-entry-props) 1845 | new-entry-props))) 1846 | 1847 | (defun helm-rg--expand-match-lines-for-bounce (before after match-loc scratch-buf) 1848 | (cl-destructuring-bind (&key ((:file orig-file)) 1849 | ((:line-num orig-line-num)) 1850 | ((:match-results _orig-match-results))) 1851 | match-loc 1852 | ;; Insert any "before" lines. 1853 | (save-excursion 1854 | (cl-loop 1855 | for line-to-insert from (1- orig-line-num) downto (- orig-line-num before) 1856 | do (helm-rg--up-for-bounce) 1857 | for cur-loc = (helm-rg--current-jump-location) 1858 | do (let ((cur-file (plist-get cur-loc :file)) 1859 | (cur-line-num (plist-get cur-loc :line-num))) 1860 | (cl-assert (string= cur-file orig-file)) 1861 | ;; If it is not equal, and the lines are sorted, then the line we wish to insert must be 1862 | ;; >= any line above, at all times (induction). 1863 | ;; Checking for line-num means this will always insert after a file header (this file's 1864 | ;; header). 1865 | (with-current-buffer scratch-buf 1866 | (forward-line -1)) 1867 | ;; Unless we are already on the correct line. 1868 | (unless (and cur-line-num (= cur-line-num line-to-insert)) 1869 | (let ((cur-line-in-file 1870 | (helm-rg--line-from-corresponding-file-for-bounce scratch-buf))) 1871 | ;; Our line is greater than this one. Insert ours after this line. 1872 | (helm-rg--down-for-bounce) 1873 | (helm-rg--insert-new-match-line-for-bounce 1874 | :file orig-file 1875 | :line-to-insert line-to-insert 1876 | :line-contents cur-line-in-file)))))) 1877 | (with-current-buffer scratch-buf 1878 | (forward-line before)) 1879 | ;; Insert any "after" lines. We need a save-excursion because we need to start at the middle 1880 | ;; here. 1881 | (cl-loop 1882 | for line-to-insert from (1+ orig-line-num) upto (+ orig-line-num after) 1883 | do (helm-rg--down-for-bounce) 1884 | for cur-loc = (helm-rg--current-jump-location) 1885 | do (let ((cur-line-num (plist-get cur-loc :line-num))) 1886 | (with-current-buffer scratch-buf 1887 | (forward-line 1)) 1888 | ;; Checking for line-num means this will always insert before a file header (the next 1889 | ;; file's header). 1890 | (unless (and cur-line-num (= cur-line-num line-to-insert)) 1891 | (let ((cur-line-in-file 1892 | (helm-rg--line-from-corresponding-file-for-bounce scratch-buf))) 1893 | ;; Our line is less than this one -- insert it above (no motion). 1894 | (helm-rg--insert-new-match-line-for-bounce 1895 | :file orig-file 1896 | :line-to-insert line-to-insert 1897 | :line-contents cur-line-in-file))))))) 1898 | 1899 | (defun helm-rg--expand-match-context-for-bounce (before after) 1900 | ;; TODO: allow doing this expansion on a file header line to expand from the top or bottom of a 1901 | ;; file! 1902 | (cl-check-type before natnum) 1903 | (cl-check-type after natnum) 1904 | (save-excursion 1905 | (let ((cur-match-entry (helm-rg--current-jump-location))) 1906 | (pcase-exhaustive cur-match-entry 1907 | ((helm-rg-&key line-num :required file) 1908 | (if (not line-num) 1909 | ;; We are on a file header line. 1910 | (message "the current line is a file: %s and currently cannot be expanded from." file) 1911 | (helm-rg--apply-matches-with-file-for-bounce 1912 | :match-line-visitor (lambda (scratch-buf match-loc) 1913 | (helm-rg--expand-match-lines-for-bounce 1914 | before after match-loc scratch-buf)) 1915 | ;; TODO: make the filter kwargs into a single object, or a single function. 1916 | :filter-to-file file 1917 | :filter-to-match cur-match-entry))))))) 1918 | 1919 | (defun helm-rg--save-match-line-content-to-file-for-bounce 1920 | (scratch-buf jump-loc maybe-new-file-name) 1921 | ;; We are at the beginning of the match text. 1922 | (let ((match-text 1923 | ;; Insert the text without any of our coloration. 1924 | (buffer-substring-no-properties (point) (line-end-position)))) 1925 | ;; This buffer has already been moved to the appropriate line. 1926 | (with-current-buffer scratch-buf 1927 | (delete-region (line-beginning-position) (line-end-position)) 1928 | (insert match-text)) 1929 | ;; If we have changed the file name, we need to rewrite the jump location for this line. 1930 | (pcase-exhaustive jump-loc 1931 | ((helm-rg-&key :required file) 1932 | (unless (string= file maybe-new-file-name) 1933 | (let ((new-props 1934 | (helm-rg--copy-jump-location-and-override 1935 | jump-loc (list :file maybe-new-file-name))) 1936 | (inhibit-read-only t)) 1937 | (put-text-property (line-beginning-position) (line-end-position) 1938 | helm-rg--jump-location-text-property new-props))))))) 1939 | 1940 | (cl-defun helm-rg--iterate-match-entries-for-bounce (&key file-visitor match-visitor end-of-file-fn) 1941 | (goto-char helm-rg--beginning-of-bounce-content-mark) 1942 | (cl-loop 1943 | while (not (eobp)) 1944 | for file-header-loc = (helm-rg--current-jump-location) 1945 | for cur-file = (plist-get file-header-loc :file) 1946 | do (funcall file-visitor file-header-loc) 1947 | do (cl-loop 1948 | for match-loc = (helm-rg--current-jump-location) 1949 | for match-file = (plist-get match-loc :file) 1950 | while (string= cur-file match-file) 1951 | do (funcall match-visitor match-loc)) 1952 | if end-of-file-fn 1953 | do (funcall end-of-file-fn file-header-loc))) 1954 | 1955 | (defun helm-rg--process-line-numbered-matches-for-bounce () 1956 | (helm-rg--iterate-match-entries-for-bounce 1957 | :file-visitor (lambda (file-header-loc) 1958 | (helm-rg--maybe-insert-file-heading-for-bounce file-header-loc)) 1959 | :match-visitor (lambda (match-loc) 1960 | (helm-rg--format-match-line-for-bounce match-loc)))) 1961 | 1962 | (defun helm-rg--insert-colorized-file-contents-for-bounce (scratch-buf file-header-loc) 1963 | (cl-destructuring-bind (&key file) file-header-loc 1964 | (with-current-buffer scratch-buf 1965 | (insert-file-contents file t nil nil t) 1966 | ;; Don't apply e.g. syntax highlighting if e.g. this file is very large (according to 1967 | ;; `helm-rg-shallow-highlight-files-regexp'). 1968 | (unless (and helm-rg-shallow-highlight-files-regexp 1969 | (string-match-p helm-rg-shallow-highlight-files-regexp file)) 1970 | (normal-mode) 1971 | (font-lock-mode 1) 1972 | (setq-local helm-rg--do-font-locking t)) 1973 | (goto-char (point-min))))) 1974 | 1975 | (defun helm-rg--file-equals (file1 file2) 1976 | (string= file1 file2)) 1977 | 1978 | (defun helm-rg--match-entry-equals (entry1 entry2) 1979 | (equal entry1 entry2)) 1980 | 1981 | (defun helm-rg--make-line-number-prefix-regexp-for-bounce (line-num) 1982 | (rx-to-string `(: bol ,(number-to-string line-num) ":"))) 1983 | 1984 | (define-error 'helm-rg--bounce-mode-iteration-error 1985 | "Iterating over files in bounce mode failed." 1986 | 'helm-rg-error) 1987 | 1988 | (cl-defun helm-rg--apply-matches-with-file-for-bounce 1989 | (&key file-header-line-visitor match-line-visitor finalize-file-buffer-fn 1990 | filter-to-file filter-to-match) 1991 | (cl-check-type match-line-visitor function) 1992 | (let ((did-find-matching-entry-p nil) 1993 | is-matching-file-p 1994 | cur-line) 1995 | (helm-rg--with-named-temp-buffer scratch-buf 1996 | (helm-rg--iterate-match-entries-for-bounce 1997 | :file-visitor (lambda (file-header-loc) 1998 | (cl-destructuring-bind (&key file) file-header-loc 1999 | (setq is-matching-file-p 2000 | (if filter-to-file 2001 | (helm-rg--file-equals file filter-to-file) 2002 | t))) 2003 | (setq cur-line 1) 2004 | (helm-rg--insert-colorized-file-contents-for-bounce 2005 | scratch-buf file-header-loc) 2006 | (when (and file-header-line-visitor is-matching-file-p) 2007 | (funcall file-header-line-visitor file-header-loc)) 2008 | (helm-rg--down-for-bounce)) 2009 | :match-visitor (lambda (match-loc) 2010 | (pcase-exhaustive match-loc 2011 | ((helm-rg-&key :required line-num) 2012 | (re-search-forward 2013 | (helm-rg--make-line-number-prefix-regexp-for-bounce line-num)) 2014 | (let ((line-diff (- line-num cur-line))) 2015 | (cl-assert (or (and (= cur-line 1) 2016 | (= line-num 1)) 2017 | (> line-diff 0))) 2018 | (with-current-buffer scratch-buf 2019 | (forward-line line-diff)) 2020 | (when (and is-matching-file-p 2021 | (if filter-to-match 2022 | (helm-rg--match-entry-equals filter-to-match match-loc) 2023 | t)) 2024 | (setq did-find-matching-entry-p t) 2025 | (funcall match-line-visitor scratch-buf match-loc)) 2026 | ;; Update the line number in the scratch buffer to the one from this 2027 | ;; match line. 2028 | (setq cur-line line-num) 2029 | (helm-rg--down-for-bounce))))) 2030 | :end-of-file-fn (when finalize-file-buffer-fn 2031 | (lambda (file-header-loc) 2032 | (when is-matching-file-p 2033 | (funcall finalize-file-buffer-fn file-header-loc scratch-buf)))))) 2034 | ;; FIXME: fix the error message here 2035 | (unless did-find-matching-entry-p 2036 | (signal 'helm-rg--bounce-mode-iteration-error 2037 | (format "no entries matched the filter objects: %S, %S" 2038 | filter-to-file filter-to-match))))) 2039 | 2040 | (defun helm-rg--copy-jump-location-and-override (old-loc new-loc) 2041 | (cl-destructuring-bind (&key file line-num match-results) old-loc 2042 | (cl-destructuring-bind (&key ((:file new-file)) 2043 | ((:line-num new-line-num)) 2044 | ((:match-results new-match-results))) 2045 | new-loc 2046 | `(:file ,(or new-file file) 2047 | ,@(helm-rg--get-optional-typed natnum (or new-line-num line-num) 2048 | `(:line-num ,it)) 2049 | ,@(helm-rg--get-optional-typed list (or new-match-results match-results) 2050 | `(:match-results ,it)))))) 2051 | 2052 | (defun helm-rg--rewrite-file-header-line-for-bounce (file-header-loc) 2053 | (cl-destructuring-bind (&key file) file-header-loc 2054 | (let ((inhibit-read-only t)) 2055 | (delete-region (point) (line-end-position)) 2056 | (insert file) 2057 | (put-text-property (line-beginning-position) (line-end-position) 2058 | helm-rg--jump-location-text-property file-header-loc)))) 2059 | 2060 | (defun helm-rg--reread-entries-from-file-for-bounce (just-this-file-p) 2061 | (let ((filter-to-file-name (when just-this-file-p 2062 | (pcase-exhaustive (helm-rg--current-jump-location) 2063 | ((helm-rg-&key :required file) 2064 | file))))) 2065 | (helm-rg--apply-matches-with-file-for-bounce 2066 | :file-header-line-visitor #'helm-rg--rewrite-file-header-line-for-bounce 2067 | :match-line-visitor #'helm-rg--rewrite-propertized-match-line-from-file-for-bounce 2068 | :filter-to-file filter-to-file-name))) 2069 | 2070 | (defun helm-rg--validate-file-name-change-and-propertize-for-bounce (_orig-file-name) 2071 | ;; TODO: do some validation (???) 2072 | (let* ((new-file-name-maybe (buffer-substring (point) (line-end-position))) 2073 | (inhibit-read-only t)) 2074 | ;; Rewrite the :file jump location text property with the new file name. 2075 | (put-text-property (point) (line-end-position) 2076 | helm-rg--jump-location-text-property (list :file new-file-name-maybe)) 2077 | new-file-name-maybe)) 2078 | 2079 | (defun helm-rg--up-for-bounce () 2080 | (forward-line -1) 2081 | (beginning-of-line)) 2082 | 2083 | (defun helm-rg--down-for-bounce () 2084 | (forward-line 1) 2085 | (beginning-of-line)) 2086 | 2087 | (defun helm-rg--do-file-rename-for-bounce (scratch-buf orig-file new-file) 2088 | (with-current-buffer scratch-buf 2089 | (let ((prev-scratch-buf-name (buffer-name))) 2090 | (write-file new-file t) 2091 | (erase-buffer) 2092 | (set-visited-file-name nil t) 2093 | (rename-buffer prev-scratch-buf-name))) 2094 | ;; if any buffer visiting, switch to the new file! 2095 | (cl-loop for buf in (helm-file-buffers orig-file) 2096 | do (with-current-buffer buf 2097 | (set-visited-file-name new-file t t) 2098 | ;; Confirm reverting the buffer. 2099 | (revert-buffer nil nil t))) 2100 | ;; Move the original file into the trash. 2101 | (move-file-to-trash orig-file)) 2102 | 2103 | (defun helm-rg--save-entries-to-file-for-bounce (just-this-file-p) 2104 | (let ((filter-to-file-name (when just-this-file-p 2105 | (pcase-exhaustive (helm-rg--current-jump-location) 2106 | ((helm-rg-&key :required file) 2107 | file)))) 2108 | ;; The content of the file header -- if it is different, we rename the file. 2109 | maybe-new-file-name) 2110 | (helm-rg--apply-matches-with-file-for-bounce 2111 | :file-header-line-visitor 2112 | (lambda (file-header-loc) 2113 | (pcase-exhaustive file-header-loc 2114 | ((helm-rg-&key :required file) 2115 | (setq maybe-new-file-name 2116 | (helm-rg--validate-file-name-change-and-propertize-for-bounce file))))) 2117 | :match-line-visitor (lambda (scratch-buf jump-loc) 2118 | (helm-rg--save-match-line-content-to-file-for-bounce 2119 | scratch-buf jump-loc maybe-new-file-name)) 2120 | :finalize-file-buffer-fn (lambda (file-header-loc scratch-buf) 2121 | (cl-destructuring-bind (&key ((:file orig-file))) file-header-loc 2122 | (if (string= orig-file maybe-new-file-name) 2123 | (with-current-buffer scratch-buf 2124 | ;; Commit our edits to the various lines of this file to 2125 | ;; disk. 2126 | (save-buffer)) 2127 | (helm-rg--do-file-rename-for-bounce 2128 | scratch-buf orig-file maybe-new-file-name)))) 2129 | :filter-to-file filter-to-file-name))) 2130 | 2131 | (defun helm-rg--make-buffer-for-bounce () 2132 | ;; Make a new buffer instead of assuming you'll only want one session at a time. This will become 2133 | ;; especially useful when live editing is introduced. 2134 | (let ((new-buf (--> helm-rg--bounce-buffer-name 2135 | (format "%s: '%s' @ %s" it helm-pattern helm-rg--current-dir) 2136 | (generate-new-buffer it)))) 2137 | (with-helm-buffer 2138 | (copy-to-buffer new-buf (point-min) (point-max))) 2139 | (with-current-buffer new-buf 2140 | ;; TODO: add test to ensure we are in the same directory! 2141 | (cd helm-rg--current-dir) 2142 | (helm-rg--bounce-mode) 2143 | ;; Fix up, then advance past the end of the header. 2144 | (helm-rg--freeze-header-for-bounce helm-rg--last-argv) 2145 | (setq-local helm-rg--beginning-of-bounce-content-mark 2146 | (-> (make-marker) (set-marker (point)))) 2147 | (save-excursion 2148 | (helm-rg--process-line-numbered-matches-for-bounce) 2149 | ;; TODO: remove the final newline somehow, but don't break the ability to add newlines! 2150 | (cl-assert (and (eobp) (bolp) (eolp)))) 2151 | (set-buffer-modified-p nil)) 2152 | new-buf)) 2153 | 2154 | (defun helm-rg--bounce () 2155 | "Enter into `helm-rg--bounce-mode' in a new buffer from the results for `helm-rg'." 2156 | (interactive) 2157 | (let ((new-buf (helm-rg--make-buffer-for-bounce))) 2158 | (helm-rg--run-after-exit 2159 | (funcall helm-rg-display-buffer-normal-method new-buf)))) 2160 | 2161 | (defun helm-rg--bounce-refresh () 2162 | "Revert all the contents in the bounce mode buffer to what they were in the file." 2163 | (interactive) 2164 | ;; TODO: fix prompts 2165 | (if (and (buffer-modified-p) 2166 | (not (y-or-n-p "Changes found. lose changes and overwrite anyway? "))) 2167 | (message "%s" "No changes were made") 2168 | (message "%s" "Reading file contents... ") 2169 | (save-excursion 2170 | (helm-rg--reread-entries-from-file-for-bounce nil)) 2171 | (set-buffer-modified-p nil))) 2172 | 2173 | (defun helm-rg--bounce-refresh-current-file () 2174 | "Revert just the contents of the current file in the bounce mode buffer." 2175 | (interactive) 2176 | ;; TODO: add messaging! 2177 | ;; FIXME: add some indicator of whether the current file contents have been modified, not just 2178 | ;; everything in the bounce buffer! 2179 | (save-excursion 2180 | (helm-rg--reread-entries-from-file-for-bounce t))) 2181 | 2182 | (defun helm-rg--bounce-dump () 2183 | "Save the contents of all the files in the bounce mode buffer." 2184 | (interactive) 2185 | (if (not (buffer-modified-p)) 2186 | (message "%s" "no changes to save!") 2187 | (message "%s" "saving file contents...") 2188 | (save-excursion 2189 | (helm-rg--save-entries-to-file-for-bounce nil)) 2190 | (set-buffer-modified-p nil))) 2191 | 2192 | (defun helm-rg--bounce-dump-current-file () 2193 | "Save just the content of the current file in the bounce mode buffer." 2194 | (interactive) 2195 | ;; TODO: add messaging! 2196 | (save-excursion 2197 | (helm-rg--save-entries-to-file-for-bounce t))) 2198 | 2199 | (defun helm-rg--spread-match-context (signed-amount) 2200 | "Read the contents of the current line and SIGNED-AMOUNT lines above or below from the file." 2201 | (interactive "p") 2202 | ;; TODO: add useful messaging! 2203 | (cond 2204 | ((zerop signed-amount)) 2205 | ((> signed-amount 0) 2206 | (helm-rg--expand-match-context-for-bounce 0 signed-amount)) 2207 | (t 2208 | (cl-assert (< signed-amount 0)) 2209 | (helm-rg--expand-match-context-for-bounce (abs signed-amount) 0)))) 2210 | 2211 | (defun helm-rg--expand-match-context (unsigned-amount) 2212 | "Read the contents of the current line and UNSIGNED-AMOUNT lines above and below from the file." 2213 | (interactive (list (if (numberp current-prefix-arg) (abs current-prefix-arg) 2214 | helm-rg--default-expand-match-lines-for-bounce))) 2215 | ;; TODO: add useful messaging! 2216 | (helm-rg--expand-match-context-for-bounce unsigned-amount unsigned-amount)) 2217 | 2218 | (defun helm-rg--visit-current-file-for-bounce () 2219 | "Jump to the current line of the current file where point is at in bounce mode." 2220 | (interactive) 2221 | ;; TODO: add useful messaging! 2222 | ;; TODO: visit the right line number too!!! (if on a match line) 2223 | (save-excursion 2224 | (pcase-exhaustive (helm-rg--current-jump-location) 2225 | ((helm-rg-&key line-num :required file) 2226 | (let ((buf-for-file (find-file-noselect file))) 2227 | ;; We could have a separate defcustom for this, but I think that's a setting nobody will 2228 | ;; want to tweak, and if they do, they can override it very easily by making an interactive 2229 | ;; method and let-binding `helm-rg-display-buffer-alternate-method' before calling this 2230 | ;; one. 2231 | (funcall helm-rg-display-buffer-alternate-method buf-for-file) 2232 | (when line-num 2233 | (goto-char (point-min)) 2234 | (forward-line (1- line-num))) 2235 | (recenter)))))) 2236 | 2237 | 2238 | ;; Toggles and settings 2239 | (defmacro helm-rg--run-after-exit (&rest body) 2240 | "Wrap BODY in `helm-run-after-exit'." 2241 | `(helm-run-after-exit (lambda () ,@body))) 2242 | 2243 | (defmacro helm-rg--set-setting-and-restart (bind-var expr) 2244 | "Save `helm-rg' variables, then set the given BIND-VAR to EXPR, then restart search." 2245 | (declare (indent 1)) 2246 | `(let* ((pat helm-pattern) 2247 | (start-dir helm-rg--current-dir)) 2248 | (helm-rg--run-after-exit 2249 | (let ((helm-rg--current-dir start-dir) 2250 | (,bind-var ,expr)) 2251 | (helm-rg--do-helm-rg pat))))) 2252 | 2253 | (defun helm-rg--set-glob (glob-str) 2254 | "Set the glob string used to invoke ripgrep, then search again." 2255 | ;; NB: The single argument GLOB-STR is for testability' -- we need to call `read-string' within the 2256 | ;; body of `helm-rg--set-setting-and-restart' to avoid a recursive edit. 2257 | (interactive (list nil)) 2258 | (helm-rg--set-setting-and-restart helm-rg--glob-string 2259 | (or glob-str 2260 | (read-string 2261 | "rg glob: " helm-rg--glob-string 'helm-rg--glob-string-history)))) 2262 | 2263 | (defun helm-rg--set-extra-args (arg-str) 2264 | "Set any extra arguments to ripgrep, then search again. 2265 | 2266 | NOTE: this method is only able to parse double quotes -- single-quoted strings with spaces in them 2267 | will be split!" 2268 | ;; TODO: find a package or implement real shell quoting for this! 2269 | (interactive (list nil)) 2270 | (helm-rg--set-setting-and-restart helm-rg--extra-args 2271 | (split-string-and-unquote 2272 | (or arg-str 2273 | (read-string 2274 | "rg extra args: " helm-rg--extra-args 'helm-rg--extra-args-history))))) 2275 | 2276 | (defun helm-rg--set-dir () 2277 | "Set the directory in which to invoke ripgrep and search again." 2278 | (interactive) 2279 | (let ((pat helm-pattern)) 2280 | (helm-rg--run-after-exit 2281 | (let ((helm-rg--current-dir 2282 | (read-directory-name "rg directory: " helm-rg--current-dir nil t))) 2283 | (helm-rg--do-helm-rg pat))))) 2284 | 2285 | (defun helm-rg--is-executable-file (path) 2286 | (and path 2287 | (file-executable-p path) 2288 | (not (file-directory-p path)))) 2289 | 2290 | (defun helm-rg--get-git-root () 2291 | (if (helm-rg--is-executable-file helm-rg-git-executable) 2292 | (helm-rg--process-output helm-rg-git-executable 2293 | "rev-parse" "--show-toplevel") 2294 | (error "The defvar helm-rg-git-executable is not an executable file (was: %S)" 2295 | helm-rg-git-executable))) 2296 | 2297 | (defun helm-rg--interpret-starting-dir (default-directory-spec) 2298 | (pcase-exhaustive default-directory-spec 2299 | ('default default-directory) 2300 | ('git-root (helm-rg--get-git-root)) 2301 | ;; TODO: add a test for this function for all values of the directory spec (see #5)! 2302 | ((pred stringp) (helm-rg--check-directory-path default-directory-spec)))) 2303 | 2304 | (defun helm-rg--set-case-sensitivity () 2305 | "Set the value of `helm-rg--case-sensitivity' and re-run `helm-rg'." 2306 | (interactive) 2307 | (let ((pat helm-pattern) 2308 | (start-dir helm-rg--current-dir)) 2309 | (helm-rg--run-after-exit 2310 | ;; TODO: see if all of this rebinding of the defvars is necessary, and if it must occur then 2311 | ;; make it part of the `helm-rg--run-after-exit' macro. 2312 | (let* ((helm-rg--current-dir start-dir) 2313 | (all-sensitivity-keys 2314 | (helm-rg--alist-keys helm-rg--case-sensitive-argument-alist)) 2315 | (sensitivity-selection 2316 | (completing-read "Choose case sensitivity: " all-sensitivity-keys nil t)) 2317 | (helm-rg--case-sensitivity (intern sensitivity-selection))) 2318 | (helm-rg--do-helm-rg pat))))) 2319 | 2320 | (defun helm-rg--resume (orig-fun arg) 2321 | (let ((helm-rg--current-dir helm-rg--last-dir)) 2322 | (funcall orig-fun arg))) 2323 | 2324 | (advice-add 'helm-resume :around #'helm-rg--resume) 2325 | 2326 | 2327 | ;; Keymaps 2328 | (defconst helm-rg-map 2329 | (let ((map (make-sparse-keymap))) 2330 | (set-keymap-parent map helm-map) 2331 | ;; TODO: basically all of these functions need to be tested. 2332 | (define-key map (kbd "M-b") #'helm-rg--bounce) 2333 | (define-key map (kbd "M-g") #'helm-rg--set-glob) 2334 | (define-key map (kbd "M-m") #'helm-rg--set-extra-args) 2335 | (define-key map (kbd "M-d") #'helm-rg--set-dir) 2336 | (define-key map (kbd "M-c") #'helm-rg--set-case-sensitivity) 2337 | (define-key map (kbd "") #'helm-rg--file-forward) 2338 | (define-key map (kbd "") #'helm-rg--file-backward) 2339 | map) 2340 | "Keymap for `helm-rg'.") 2341 | 2342 | (defconst helm-rg--bounce-mode-map 2343 | (let ((map (make-sparse-keymap))) 2344 | (define-key map (kbd "C-x C-r") #'helm-rg--bounce-refresh) 2345 | (define-key map (kbd "C-c C-r") #'helm-rg--bounce-refresh-current-file) 2346 | (define-key map (kbd "C-x C-s") #'helm-rg--bounce-dump) 2347 | (define-key map (kbd "C-c C-s") #'helm-rg--bounce-dump-current-file) 2348 | (define-key map (kbd "C-c f") #'helm-rg--visit-current-file-for-bounce) 2349 | (define-key map (kbd "C-c e") #'helm-rg--expand-match-context) 2350 | (define-key map (kbd "C-c C-e") #'helm-rg--spread-match-context) 2351 | map) 2352 | "Keymap for `helm-rg--bounce-mode'.") 2353 | 2354 | 2355 | ;; Helm sources 2356 | (defconst helm-rg-process-source 2357 | (helm-make-source "ripgrep" 'helm-source-async 2358 | ;; FIXME: we don't want the header to be hydrated by helm, it's huge and blue and 2359 | ;; unnecessary. Do it ourselves, then we don't have to delete the header in 2360 | ;; `helm-rg--freeze-header-for-bounce'. 2361 | :nohighlight t 2362 | :nomark t 2363 | :header-name #'helm-rg--header-name 2364 | :keymap 'helm-rg-map 2365 | :history 'helm-rg--input-history 2366 | :help-message "FIXME: useful help message!!!" 2367 | ;; TODO: basically all of these functions need to be tested. 2368 | :candidates-process #'helm-rg--make-process 2369 | :action (helm-make-actions "Visit" #'helm-rg--async-action) 2370 | :filter-one-by-one #'helm-rg--parse-process-output 2371 | :display-to-real #'helm-rg--display-to-real 2372 | ;; TODO: add a `defcustom' for this. 2373 | ;; :candidate-number-limit 200 2374 | ;; It doesn't seem there is any obvious way to get the original input if using 2375 | ;; :pattern-transformer. 2376 | :persistent-action #'helm-rg--async-persistent-action 2377 | :persistent-help "Visit result buffer and highlight matches" 2378 | :requires-pattern nil 2379 | :group 'helm-rg) 2380 | "Helm async source to search files in a directory using ripgrep.") 2381 | 2382 | 2383 | ;; Major modes 2384 | (define-derived-mode helm-rg--bounce-mode fundamental-mode "BOUNCE" 2385 | "TODO: document this! 2386 | 2387 | \\{helm-rg--bounce-mode-map}" 2388 | ;; TODO: consider whether other kwargs of this macro would be useful! 2389 | :group 'helm-rg 2390 | (font-lock-mode 1)) 2391 | 2392 | 2393 | ;; Meta-programmed defcustom forms 2394 | (helm-rg--defcustom-from-alist helm-rg-default-case-sensitivity 2395 | helm-rg--case-sensitive-argument-alist 2396 | "Case sensitivity to use in ripgrep searches. 2397 | 2398 | This is the default value for `helm-rg--case-sensitivity', which can be modified with 2399 | `helm-rg--set-case-sensitivity' during a `helm-rg' session. 2400 | 2401 | This must be an element of `helm-rg--case-sensitive-argument-alist'.") 2402 | 2403 | (helm-rg--defcustom-from-alist helm-rg-file-paths-in-matches-behavior 2404 | ((relative) (absolute)) 2405 | "Whether to print each file's absolute path in matches on every line of `helm-rg' output. 2406 | 2407 | This is currently necessary to be compatible with `helm-resume'.") 2408 | 2409 | 2410 | ;; Autoloaded functions 2411 | ;;;###autoload 2412 | (defun helm-rg (rg-pattern &optional pfx paths) 2413 | "Search for the PCRE regexp RG-PATTERN extremely quickly with ripgrep. 2414 | 2415 | When invoked interactively with a prefix argument, or when PFX is non-nil, 2416 | set the cwd for the ripgrep process to `default-directory'. Otherwise use the 2417 | cwd as described by `helm-rg-default-directory'. 2418 | 2419 | If PATHS is non-nil, ripgrep will search only those paths, relative to the 2420 | process's cwd. Otherwise, the process's cwd will be searched. 2421 | 2422 | Note that ripgrep respects glob patterns from .gitignore, .rgignore, and .ignore 2423 | files, excluding files matching those patterns. This composes with the glob 2424 | defined by `helm-rg-default-glob-string', which only finds files matching the 2425 | glob, and can be overridden with `helm-rg--set-glob', which is defined in 2426 | `helm-rg-map'. 2427 | 2428 | There are many more `defcustom' forms, which are visible by searching for \"defcustom\" in the 2429 | `helm-rg' source (which can be located using `find-function'). These `defcustom' forms set defaults 2430 | for options which can be modified while invoking `helm-rg' using the keybindings listed below. 2431 | 2432 | The ripgrep command's help output can be printed into its own buffer for 2433 | reference with the interactive command `helm-rg-display-help'. 2434 | 2435 | \\{helm-rg-map}" 2436 | (interactive (list (helm-rg--get-thing-at-pt) current-prefix-arg nil)) 2437 | (let* ((helm-rg--current-dir 2438 | (or helm-rg--current-dir 2439 | (and pfx default-directory) 2440 | (helm-rg--interpret-starting-dir helm-rg-default-directory))) 2441 | ;; TODO: make some declarative way to ensure these variables are all initialized and 2442 | ;; destroyed (an alist `defconst' should do the trick)! 2443 | (helm-rg--glob-string 2444 | (or helm-rg--glob-string 2445 | helm-rg-default-glob-string)) 2446 | (helm-rg--extra-args 2447 | (or helm-rg--extra-args 2448 | helm-rg-default-extra-args)) 2449 | (helm-rg--paths-to-search 2450 | (or helm-rg--paths-to-search 2451 | paths)) 2452 | (helm-rg--case-sensitivity 2453 | (or helm-rg--case-sensitivity 2454 | helm-rg-default-case-sensitivity))) 2455 | ;; FIXME: make all the `defvar's into buffer-local variables (or give them local counterparts)? 2456 | ;; the idea is that `helm-resume' can be applied and work with the async action -- currently it 2457 | ;; tries to find a buffer which we killed in the cleanup here when we do the async action 2458 | ;; (i think) 2459 | (setq helm-rg--last-dir helm-rg--current-dir) 2460 | (unwind-protect (helm-rg--do-helm-rg rg-pattern) 2461 | (helm-rg--unwind-cleanup)))) 2462 | 2463 | ;;;###autoload 2464 | (defun helm-rg-display-help (&optional pfx) 2465 | "Display a buffer with the ripgrep command's usage help. 2466 | 2467 | The help buffer will be reused if it was already created. A prefix argument when 2468 | invoked interactively, or a non-nil value for PFX, will display the help buffer 2469 | in the current window. Otherwise, if the help buffer is already being displayed 2470 | in some window, select that window, or else display the help buffer with 2471 | `pop-to-buffer'." 2472 | (interactive "P") 2473 | (let ((filled-out-help-buf 2474 | (or (get-buffer helm-rg--ripgrep-help-buffer-name) 2475 | (helm-rg--make-help-buffer helm-rg--ripgrep-help-buffer-name)))) 2476 | (if pfx (switch-to-buffer filled-out-help-buf) 2477 | (-if-let* ((buf-win (get-buffer-window filled-out-help-buf t))) 2478 | (select-window buf-win) 2479 | (pop-to-buffer filled-out-help-buf))))) 2480 | 2481 | ;;;###autoload 2482 | (defun helm-rg-from-isearch () 2483 | "Invoke `helm-rg' from isearch." 2484 | (interactive) 2485 | (let ((input (if isearch-regexp isearch-string (regexp-quote isearch-string)))) 2486 | (isearch-exit) 2487 | (helm-rg input))) 2488 | 2489 | (provide 'helm-rg) 2490 | ;;; helm-rg.el ends here 2491 | -------------------------------------------------------------------------------- /tests/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /tests/checkdoc-batch.el: -------------------------------------------------------------------------------- 1 | (require 'checkdoc) 2 | (require 'cl-lib) 3 | 4 | (defun helm-rg-test--checkdoc-file (file) 5 | "Check FILE for document, comment, error style, and rogue spaces. 6 | Taken from Emacs 25 source." 7 | (with-current-buffer (find-file-noselect file) 8 | (let ((checkdoc-diagnostic-buffer "*warn*")) 9 | (checkdoc-current-buffer t)))) 10 | 11 | (setq checkdoc-force-docstrings-flag nil) 12 | (setq sentence-end-double-space nil) 13 | 14 | (let ((argv command-line-args)) 15 | (pop argv) ; This is the emacs executable. 16 | (cl-assert (string-equal "-l" (pop argv))) 17 | (cl-assert (string-equal "tests/checkdoc-batch.el" (pop argv))) 18 | (cl-loop for file in argv 19 | do (helm-rg-test--checkdoc-file file))) 20 | -------------------------------------------------------------------------------- /tests/helm-rg-test.el: -------------------------------------------------------------------------------- 1 | ;;; helm-rg-test.el --- Tests for helm-rg -*- lexical-binding: t -*- 2 | 3 | ;;; Commentary: 4 | 5 | ;; 6 | 7 | ;;; Code: 8 | 9 | (require 'helm-rg) 10 | 11 | (require 'cl-lib) 12 | (require 'ert) 13 | 14 | (defconst helm-rg-test--cwd 15 | (-> (or load-file-name buffer-file-name) (file-name-directory) (expand-file-name))) 16 | 17 | (defconst helm-rg-test--time-step 1 18 | "Floating-point number of seconds to wait for steps in between interactive helm actions.") 19 | 20 | (defmacro helm-rg-test--delayed-do (&rest body) 21 | "Evaluate BODY after some time to simulate user input of some sort." 22 | `(run-at-time helm-rg-test--time-step nil (lambda () ,@body))) 23 | 24 | (defmacro helm-rg-test--define-interactive-test (name doc &rest body) 25 | "Define an `ert-deftest' wrapped to execute correctly when invoked from the command line." 26 | (declare (doc-string 1) (indent 1)) 27 | `(ert-deftest ,name () ,doc 28 | (progn 29 | (helm-rg-test--mark-interactive) 30 | ;; We have to set the debugger to ignore because `ert' will abort the test after a keyboard 31 | ;; quit otherwise. 32 | (let ((debugger #'ignore) 33 | ;; We don't want dir-local variables (like the ones defined in this repo!) to be 34 | ;; activated in testing, so we set this to a file name which (hopefully!) doesn't 35 | ;; exist. 36 | (dir-locals-file "????????????????")) 37 | ,@body)))) 38 | 39 | (defmacro helm-rg-test--in-test-dir (&rest body) 40 | "Evaluate BODY with `default-directory' at `helm-rg-test--cwd'." 41 | `(let ((default-directory helm-rg-test--cwd) 42 | ;; TODO: we have to add this line for `test-helm-rg/helm-rg-from-isearch' to work -- why? 43 | (helm-rg--current-dir helm-rg-test--cwd)) 44 | ,@body)) 45 | 46 | (defun helm-rg-test--invoke-in-test-dir (pattern) 47 | "Invoke `helm-rg' in the tests directory with some input PATTERN." 48 | (helm-rg-test--in-test-dir 49 | ;; NB: We could just add `helm-rg-test--cwd' to the PATHS here, but it feels like that might 50 | ;; miss the more common scenario of just running `helm-rg' in a directory. 51 | (helm-rg pattern t))) 52 | 53 | ;;; TODO: this seems better done with a macro, but it's not clear how to reach into the 54 | ;;; `ert-deftest' forms to get to the top of the BODY forms. 55 | (defun helm-rg-test--mark-interactive () 56 | (when noninteractive 57 | (ert-skip "This test can unfortunately only be run interactively."))) 58 | 59 | (defun helm-rg-test--find-gpl () 60 | "A function to find a string in a known file to test that actions can find the right file." 61 | (let ((helm-rg-default-glob-string "LICENSE")) 62 | (helm-rg-test--invoke-in-test-dir "GPL"))) 63 | 64 | (defun helm-rg-test--assert-current-line (line) 65 | "Assert the contents of the current line, either in the `helm-rg' buffer, or in a matched file." 66 | (let ((cur-line (buffer-substring (point-at-bol) (point-at-eol)))) 67 | (should (equal cur-line line)))) 68 | 69 | (helm-rg-test--define-interactive-test test-helm-rg/helm-resume 70 | "Test that the right file is visited when resuming a `helm-rg' session with `helm-resume'." 71 | (helm-rg-test--delayed-do 72 | (helm-keyboard-quit)) 73 | (with-helm-quittable 74 | (helm-rg-test--find-gpl)) 75 | (let ((helm-rg-buf helm-last-buffer)) 76 | (sit-for helm-rg-test--time-step) 77 | (helm-rg-test--delayed-do 78 | (helm-exit-minibuffer)) 79 | (helm-resume helm-rg-buf) 80 | ;; The phrase "GPL" is expected to show up when searching the LICENSE file in the test 81 | ;; directory, and the `helm-exit-minibuffer' should select the file and visit it. 82 | (should (equal (buffer-file-name) (concat helm-rg-test--cwd "LICENSE"))))) 83 | 84 | (helm-rg-test--define-interactive-test test-helm-rg/helm-rg-from-isearch 85 | "Test that we can invoke `helm-rg' from an `isearch' invocation with `helm-rg-from-isearch'." 86 | (let ((match-line-in-file 87 | " Developers that use the GNU GPL protect your rights with two steps:")) 88 | (find-file (concat helm-rg-test--cwd "LICENSE")) 89 | (goto-char (point-min)) 90 | ;; (helm-rg-test--delayed-do 91 | ;; (isearch-exit)) 92 | (isearch-forward nil t) 93 | (isearch-process-search-string "GPL" "wow") 94 | (helm-rg-test--assert-current-line match-line-in-file) 95 | (helm-rg-test--delayed-do 96 | (progn 97 | (should helm-alive-p) 98 | ;; We are searching for the string "GPL" in `helm-rg' now. 99 | (helm-rg-test--assert-current-line "GPL") 100 | (helm-exit-minibuffer) 101 | (helm-rg-test--assert-current-line match-line-in-file))) 102 | (helm-rg-test--in-test-dir 103 | (helm-rg-from-isearch)))) 104 | -------------------------------------------------------------------------------- /tests/install-packages.el: -------------------------------------------------------------------------------- 1 | (require 'cl-lib) 2 | 3 | (setq package-archives 4 | '(("gnu" . "https://elpa.gnu.org/packages/") 5 | ;; FIXME: This fails if set to https on the emacs-26.1-travis evm image??? 6 | ("melpa" . "http://melpa.milkbox.net/packages/"))) 7 | 8 | (defconst dependent-packages '(dash helm)) 9 | 10 | (package-initialize) 11 | (package-refresh-contents) 12 | 13 | (cl-loop for pkg in dependent-packages 14 | do (package-install pkg)) 15 | --------------------------------------------------------------------------------