├── .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 | [](https://melpa.org/#/helm-rg)
5 |
6 | 
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 |
--------------------------------------------------------------------------------