diff options
| author | Jonas Bernoulli <jonas@bernoul.li> | 2018-06-21 12:20:13 -0500 |
|---|---|---|
| committer | Jonas Bernoulli <jonas@bernoul.li> | 2019-02-14 21:50:55 +0100 |
| commit | 33f538a0bb83c8d4abc8f4c2db0dfbb9b09c4f92 (patch) | |
| tree | 1d7afa205e484dbac95be4e0f30f729f09110c16 | |
Release version 0.1.0v0.1.0
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | LICENSE | 676 | ||||
| -rw-r--r-- | Makefile | 52 | ||||
| -rw-r--r-- | README.md | 50 | ||||
| -rw-r--r-- | default.mk | 25 | ||||
| -rw-r--r-- | docs/Makefile | 96 | ||||
| -rw-r--r-- | docs/transient.org | 1862 | ||||
| -rw-r--r-- | docs/transient.texi | 2197 | ||||
| -rw-r--r-- | lisp/Makefile | 45 | ||||
| -rw-r--r-- | lisp/transient.el | 2583 |
10 files changed, 7591 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36ebf81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/config.mk +/docs/*.html +/docs/*.pdf +/docs/dir +/docs/transient/ @@ -0,0 +1,676 @@ + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1d04e13 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +-include config.mk +include default.mk + +.PHONY: lisp docs clean + +all: lisp docs + +help: + $(info make all - generate lisp and manual) + $(info make docs - generate most manual formats) + $(info make lisp - generate byte-code and autoloads) + $(info make texi - generate texi manual (see comments)) + $(info make info - generate info manual) + $(info make html - generate html manual file) + $(info make html-dir - generate html manual directory) + $(info make pdf - generate pdf manual) + $(info make publish - publish snapshot manuals) + $(info make release - publish release manuals) + $(info make clean - remove most generated files) + @printf "\n" + +lisp: + @$(MAKE) -C lisp lisp + +docs: + @$(MAKE) -C docs docs + +texi: + @$(MAKE) -C docs texi + +info: + @$(MAKE) -C docs info + +html: + @$(MAKE) -C docs html + +html-dir: + @$(MAKE) -C docs html-dir + +pdf: + @$(MAKE) -C docs pdf + +publish: + @$(MAKE) -C docs publish + +release: + @$(MAKE) -C docs release + +clean: + @printf "Cleaning...\n" + @$(MAKE) -C lisp clean + @$(MAKE) -C docs clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..0052a5e --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +Transient commands +================== + +Taking inspiration from prefix keys and prefix arguments, Transient +implements a similar abstraction involving a prefix command, infix +arguments and suffix commands. We could call this abstraction a +"transient command", but because it always involves at least two +commands (a prefix and a suffix) we prefer to call it just a +"transient". + +> Transient keymaps are a feature provided by Emacs. Transients as +> implemented by this package involve the use of transient keymaps. +> +> Emacs provides a feature that it calls "prefix commands". When we +> talk about "prefix commands" in Transient's documentation, then we +> mean our own kind of "prefix commands", unless specified otherwise. +> To avoid ambiguity we sometimes use the terms "transient prefix +> command" for our kind and "regular prefix command" for Emacs' kind. + +When the user calls a transient prefix command, then a transient +(temporary) keymap is activated, which binds the transient's infix and +suffix commands, and functions that control the transient state are +added to `pre-command-hook` and `post-command-hook`. The available +suffix and infix commands and their state are shown in the echo area +until the transient is exited by invoking a suffix command. + +Calling an infix command causes its value to be changed. How that is +done depends on the type of the infix command. The simplest case is +an infix command that represents a command-line argument that does not +take a value. Invoking such an infix command causes the switch to be +toggled on or off. More complex infix commands may read a value from +the user, using the minibuffer. + +Calling a suffix command usually causes the transient to be exited; +the transient keymaps and hook functions are removed, the echo area no +longer shows information about the (no longer bound) suffix commands, +the values of some public global variables are set, while some +internal global variables are unset, and finally the command is +actually called. Suffix commands can also be configured to not exit +the transient. + +A suffix command can, but does not have to, use the infix arguments in +much the same way it can choose to use or ignore the prefix arguments. +For a suffix command that was invoked from a transient the variable +`current-transient-suffixes` and the function `transient-args` serve about +the same purpose as the variables `prefix-arg` and `current-prefix-arg` do +for any command that was called after the prefix arguments have been +set using a command such as `universal-argument`. + + diff --git a/default.mk b/default.mk new file mode 100644 index 0000000..f92298f --- /dev/null +++ b/default.mk @@ -0,0 +1,25 @@ +PKG = transient + +ELS = $(PKG).el +ELS += $(PKG)-demo.el +ELCS = $(ELS:.el=.elc) + +DEPS = dash +DEPS += hydra # for lv.el + +EMACS ?= emacs +EMACS_ARGS ?= + +LOAD_PATH ?= $(addprefix -L ../../,$(DEPS)) +LOAD_PATH += -L . + +ifndef ORG_LOAD_PATH +ORG_LOAD_PATH = -L ../../dash +ORG_LOAD_PATH += -L ../../org/lisp +ORG_LOAD_PATH += -L ../../org/contrib/lisp +ORG_LOAD_PATH += -L ../../ox-texinfo+ +endif + +INSTALL_INFO ?= $(shell command -v ginstall-info || printf install-info) +MAKEINFO ?= makeinfo +MANUAL_HTML_ARGS ?= --css-ref /assets/page.css diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..eb5084f --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,96 @@ +-include ../config.mk +include ../default.mk + +docs: info html html-dir pdf + +info: $(PKG).info dir +html: $(PKG).html +pdf: $(PKG).pdf + +ORG_ARGS = --batch -Q $(ORG_LOAD_PATH) -l ox-extra -l ox-texinfo+.el +ORG_EVAL = --eval "(ox-extras-activate '(ignore-headlines))" +ORG_EVAL += --eval "(setq indent-tabs-mode nil)" +ORG_EVAL += --eval "(setq org-src-preserve-indentation nil)" +ORG_EVAL += --funcall org-texinfo-export-to-texinfo + +# This target first bumps version strings in the Org source. The +# necessary tools might be missing so other targets do not depend +# on this target and it has to be run explicitly when appropriate. +# +# AMEND=t make texi Update manual to be amended to HEAD. +# VERSION=N make texi Update manual for release. +# +.PHONY: texi +texi: + @$(EMACS) $(ORG_ARGS) $(PKG).org $(ORG_EVAL) + @printf "\n" >> $(PKG).texi + @rm -f $(PKG).texi~ + +%.info: %.texi + @printf "Generating $@\n" + @$(MAKEINFO) --no-split $< -o $@ + +dir: $(PKG).info + @printf "Generating $@\n" + @printf "%s" $^ | xargs -n 1 $(INSTALL_INFO) --dir=$@ + +HTML_FIXUP_CSS = '/<link rel="stylesheet" type="text\/css" href="\/assets\/page.css">/a\ +<link class="s-css-s--style" rel="stylesheet" title="Default" href="/assets/themes/default.css">\ +\n<link class="s-css-s--style" rel="stylesheet alternate" title="Default high contrast" href="/assets/themes/default-high-contrast.css">\ +\n<link class="s-css-s--style" rel="stylesheet alternate" title="Solarized dark xterm" href="/assets/themes/solarized-dark-xterm.css">\ +\n<link class="s-css-s--style" rel="stylesheet alternate" title="Black on white" href="/assets/themes/black-on-white.css">\ +\n<script src="/assets/js/simple-css-switch.js"></script>' +HTML_FIXUP_ONLOAD = 's/<body lang="en">/<body lang="en" onload="simpleCssSwitch()">/' +HTML_FIXUP_MENU = '/<\/body>/i<div id="s-css-s--menu"><\/div>' + +%.html: %.texi + @printf "Generating $@\n" + @$(MAKEINFO) --html --no-split $(MANUAL_HTML_ARGS) $< + @sed -i -e $(HTML_FIXUP_CSS) -e $(HTML_FIXUP_ONLOAD) -e $(HTML_FIXUP_MENU) $@ + +html-dir: $(PKG).texi + @printf "Generating $(PKG)/*.html\n" + @$(MAKEINFO) --html $(MANUAL_HTML_ARGS) $< + @for f in $$(find $(PKG) -name '*.html') ; do \ + sed -i -e $(HTML_FIXUP_CSS) -e $(HTML_FIXUP_ONLOAD) -e $(HTML_FIXUP_MENU) $$f ; \ + done + +%.pdf: %.texi + @printf "Generating $@\n" + @texi2pdf --clean $< > /dev/null + +DOMAIN ?= magit.vc +PUBLISH_PATH ?= /manual/ +RELEASE_PATH ?= /manual/$(VERSION)/ + +S3_BUCKET ?= s3://$(DOMAIN) +PUBLISH_TARGET = $(S3_BUCKET)$(PUBLISH_PATH) +RELEASE_TARGET = $(S3_BUCKET)$(RELEASE_PATH) + +CFRONT_DIST ?= E2LUHBKU1FBV02 +CFRONT_PATHS = $(PKG).html $(PKG).pdf $(PKG)/* + +comma := , +empty := +space := $(empty) $(empty) + +publish: html html-dir pdf + @aws s3 cp $(PKG).html $(PUBLISH_TARGET) + @aws s3 cp $(PKG).pdf $(PUBLISH_TARGET) + @aws s3 sync $(PKG) $(PUBLISH_TARGET)$(PKG)/ + @printf "Generating CDN invalidation\n" + @aws cloudfront create-invalidation --distribution-id $(CFRONT_DIST) --paths \ + "$(subst $(space),$(comma),$(addprefix $(PUBLISH_PATH),$(CFRONT_PATHS)))" > /dev/null + +release: html html-dir pdf + @aws s3 cp $(PKG).html $(RELEASE_TARGET) + @aws s3 cp $(PKG).pdf $(RELEASE_TARGET) + @aws s3 sync $(PKG) $(RELEASE_TARGET)$(PKG)/ + @printf "Generating CDN invalidation\n" + @aws cloudfront create-invalidation --distribution-id $(CFRONT_DIST) --paths \ + "$(subst $(space),$(comma),$(addprefix $(RELEASE_PATH),$(CFRONT_PATHS)))" > /dev/null + +CLEAN = $(PKG).info dir $(PKG) $(PKG).html $(PKG).pdf + +clean: + @rm -rf $(CLEAN) diff --git a/docs/transient.org b/docs/transient.org new file mode 100644 index 0000000..065fc7a --- /dev/null +++ b/docs/transient.org @@ -0,0 +1,1862 @@ +#+TITLE: Transient User and Developer Manual +:PREAMBLE: +#+AUTHOR: Jonas Bernoulli +#+EMAIL: jonas@bernoul.li +#+DATE: 2018-2019 +#+LANGUAGE: en + +#+TEXINFO_DIR_CATEGORY: Emacs +#+TEXINFO_DIR_TITLE: Transient: (transient). +#+TEXINFO_DIR_DESC: Transient Commands +#+SUBTITLE: for version 0.1.0 + +#+TEXINFO_DEFFN: t +#+OPTIONS: H:4 num:4 toc:2 +#+BIND: ox-texinfo+-before-export-hook ox-texinfo+-update-version-strings + +Taking inspiration from prefix keys and prefix arguments, Transient +implements a similar abstraction involving a prefix command, infix +arguments and suffix commands. We could call this abstraction a +"transient command", but because it always involves at least two +commands (a prefix and a suffix) we prefer to call it just a +"transient". + +When the user calls a transient prefix command, then a transient +(temporary) keymap is activated, which binds the transient's infix +and suffix commands, and functions that control the transient state +are added to ~pre-command-hook~ and ~post-command-hook~. The available +suffix and infix commands and their state are shown in the echo area +until the transient is exited by invoking a suffix command. + +Calling an infix command causes its value to be changed, possibly by +reading a new value in the minibuffer. + +Calling a suffix command usually causes the transient to be exited +but suffix commands can also be configured to not exit the transient. + +#+TEXINFO: @noindent +This manual is for Transient version 0.1.0. + +#+BEGIN_QUOTE +Copyright (C) 2018-2019 Jonas Bernoulli <jonas@bernoul.li> + +You can redistribute this document and/or modify it under the terms +of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any +later version. + +This document is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. +#+END_QUOTE +:END: +* Introduction + +Taking inspiration from prefix keys and prefix arguments, Transient +implements a similar abstraction involving a prefix command, infix +arguments and suffix commands. We could call this abstraction a +"transient command", but because it always involves at least two +commands (a prefix and a suffix) we prefer to call it just a +"transient". + +#+BEGIN_QUOTE +Transient keymaps are a feature provided by Emacs. Transients as +implemented by this package involve the use of transient keymaps. + +Emacs provides a feature that it calls "prefix commands". When we +talk about "prefix commands" in this manual, then we mean our own kind +of "prefix commands", unless specified otherwise. To avoid ambiguity +we sometimes use the terms "transient prefix command" for our kind and +"regular prefix command" for Emacs' kind. +#+END_QUOTE + +When the user calls a transient prefix command, then a transient +(temporary) keymap is activated, which binds the transient's infix and +suffix commands, and functions that control the transient state are +added to ~pre-command-hook~ and ~post-command-hook~. The available suffix +and infix commands and their state are shown in the echo area until +the transient state is exited by invoking a suffix command. + +Calling an infix command causes its value to be changed. How that is +done depends on the type of the infix command. The simplest case is +an infix command that represents a command-line argument that does not +take a value. Invoking such an infix command causes the switch to be +toggled on or off. More complex infix commands may read a value from +the user, using the minibuffer. + +Calling a suffix command usually causes the transient to be exited; +the transient keymaps and hook functions are removed, the echo area no +longer shows information about the (no longer bound) suffix commands, +the values of some public global variables are set, while some +internal global variables are unset, and finally the command is +actually called. Suffix commands can also be configured to not exit +the transient. + +A suffix command can, but does not have to, use the infix arguments in +much the same way it can choose to use or ignore the prefix arguments. +For a suffix command that was invoked from a transient the variable +~current-transient-suffixes~ and the function ~transient-args~ serve about +the same purpose as the variables ~prefix-arg~ and ~current-prefix-arg~ do +for any command that was called after the prefix arguments have been +set using a command such as ~universal-argument~. + +The information shown in the echo area while a transient is active +looks a bit like this: + +#+BEGIN_EXAMPLE +,----------------------------------------- +|Arguments +| -f Force (--force) +| -a Annotate (--annotate) +| +|Create +| t tag +| r telease +`----------------------------------------- +#+END_EXAMPLE + +#+BEGIN_QUOTE +This is a simplified version of ~magit-tag~. Info manuals do not +support images or colored text, so the above "screenshot" lacks some +information; in practice you would be able to tell whether the +arguments ~--force~ and ~--annotate~ are enabled or not based on their +color. +#+END_QUOTE + +Transient can be used to implement simple "command dispatchers". The +main benefit then is that the user can see all the available commands +in the echo area. That is useful by itself because it frees the user +from having to remember all the keys that are valid after a certain +prefix key or command. Magit's ~magit-dispatch~ command is an example +of using Transient to merely implement a command dispatcher. + +In addition to that, Transient also allows users to interactively pass +arguments to commands. These arguments can be much more complex than +what is reasonable when using prefix arguments. There is a limit to +how many aspects of a command can be controlled using prefix +arguments. Furthermore what a certain prefix argument means for +different commands can be completely different, and users have to read +documentation to learn and then commit to memory what a certain prefix +argument means to a certain command. + +Transient suffix commands on the other hand can accept dozens of +different arguments without the user having to remember anything. +When using Transient, then one can call a command with arguments that +are just as complex as when calling the same function non-interactively +using code. + +Invoking a transient command with arguments is similar to invoking a +command in a shell with command-line completion and history enabled. +One benefit of the Transient interface is that it remembers history +not only on a global level ("this command was invoked using these +arguments and previously it was invoked using those other arguments"), +but also remembers the values of individual arguments independently. +see [[*Using History]]. + +After a transient prefix command is invoked ~C-h <key>~ can be used to +show the documentation for the infix or suffix command that ~<key>~ is +bound to (see [[*Getting Help for Suffix Commands]]) and infixes and +suffixes can be removed from the transient using ~C-x l <key>~. Infixes +and suffixes that are disabled by default can be enabled the same way. +See [[*Enabling and Disabling Suffixes]]. + +Transient ships with support for a few different types of specialized +infix commands. A command that sets a command line option for example +has different needs than a command that merely toggles a boolean flag. +Additionally Transient provides abstractions for defining new types, +which the author of Transient did not anticipate (or didn't get around +to implement yet). + +* Usage +** Invoking Transients + +A transient prefix command is invoked like any other command by +pressing the key that is bound to that command. The main difference +to other commands is that a transient prefix commands activates a +transient keymap, which temporarily binds the transients infix and +suffix commands. Bindings from other keymaps may, or may not, be +disabled while the transient state is in effect. + +There are two kinds of commands that are available after invoking a +transient prefix command; infix and suffix commands. Infix commands +set some value (which is then shown in the echo area), without leaving +the transient. Suffix commands on the other hand usually quit the +transient and they may use the values set by the infix commands, +i.e. the infix *arguments*. + +Instead of setting arguments to be used by a suffix command, infix +commands may also set some value by side-effect. + +** Aborting and Resuming Transients + +To quit the transient without invoking a suffix command press ~C-g~. + +Key bindings in transient keymaps may be longer than a single event. +After pressing a valid prefix key, all commands whose bindings do not +begin with that prefix key are temporarily unavailable and grayed out. +To abort the prefix key press ~C-g~ (which in this case only quits the +prefix key, but not the complete transient). + +A transient prefix command can be bound as a suffix of another +transient. Invoking such a suffix replaces the current transient +state with a new transient state, i.e. the available bindings change +and the information displayed in the echo area is updated accordingly. +Pressing ~C-g~ while a nested transient is active only quits the +innermost transient, causing a return to the previous transient. + +~C-q~ and ~C-z~ on the other hand always exits all transients. If you use +the latter, then you can later resume the stack of transients using +~M-x transient-resume~. + +- Key: C-g, transient-quit-seq +- Key: C-g, transient-quit-one + + This key quits the currently active incomplete key sequence, if any, + or else the current transient. When quitting the current transient, + then it returns to the previous transient, if any. + +- Key: C-q, transient-quit-all + + This command quits the currently active incomplete key sequence, if + any, and all transients, including the active transient and all + suspended transients, if any. + +- Key: C-z, transient-suspend + + Like ~transient-quit-all~, this command quits an incomplete key + sequence, if any, and all transients. Additionally it saves the + stack of transients so that it can easily be resumed (which is + particularly useful if you quickly need to do "something else" and + the stack is deeper than a single transient and/or you have already + changed the values of some infix arguments). + + Note that only a single stack of transients can be saved at a time. + If another stack is already saved, then saving a new stack discards + the previous stack. + +- Key: M-x transient-resume, transient-resume + + This command resumes the previously suspended stack of transients, + if any. + +** Common Suffix Commands +*** _ :ignore: + +A few shared suffix commands are available in all transients. These +suffix commands are not shown in the echo area by default. + +Most of these commands are bound to ~C-x <key>~ and after pressing ~C-x~ a +section featuring all common commands is temporarily show in the echo +area. After invoking one of these commands that section disappears +again. Note however that one of these commands is described as "Show +common permanently"; invoke that if you want the common commands to +always be shown for all transients. + +- Key: C-x t, transient-toggle-common + + This command toggles whether the generic commands that are common to + all transients are always displayed or only after typing the + incomplete prefix key sequence ~C-x~. This only affects the current + Emacs session. + +- User Option: transient-show-common-commands + + This option controls whether shared suffix commands are shown + alongside the transient-specific infix and suffix commands. By + default the shared commands are not shown to avoid overwhelming + the user with to many options. + + While a transient is active, pressing ~C-x~ always shows the common + command. The value of this option can be changed for the current + Emacs session by typing ~C-x t~ while a transient is active. + +The other common commands are describe in either the previous node or +in one of the following nodes. + +*** Notes on Common Key Bindings +:PROPERTIES: +:NONODE: t +:END: + +You may have noticed that the bindings for some of the common commands +do *not* have the prefix ~C-x~ and that furthermore some of these commands +are grayed out while others are not. That unfortunately is a bit +confusing if the section of common commands is not shown permanently, +making the following explanation necessary. + +The purpose of usually hiding that section but showing it after the +user pressed the respective prefix key is to conserve space and not +overwhelm users with too much noise, while allowing the user to +quickly list common bindings on demand. + +That however should not keep us from using the best possible key +bindings. The bindings that do use a prefix do so to avoid wasting +too many non-prefix bindings, keeping them available for use in +individual transients. The bindings that do not use a prefix and that +are *not* grayed out are very important bindings that are *always* +available, even when invoking the "common command key prefix" or *any +other* transient-specific prefix. The non-prefix keys that *are* grayed +out however, are not available when any incomplete prefix key sequence +is active. They do not use the "common command key prefix" because it +is likely that users want to invoke them several times in a row and +e.g. ~M-p M-p M-p~ is much more convenient than ~C-x M-p C-x M-p C-x M-p~. + +You may also have noticed that the "Set" command is bound to ~C-x s~, +while Magit-Popup used to bind ~C-c C-c~ instead. I have seen several +users praise the latter binding (sic), so I did not change it +willy-nilly. The reason that I changed it is that using different +prefix keys for different common commands, would have made the +temporary display of the common commands even more confusing, +i.e. after pressing ~C-c~ all the ~C-x ...~ bindings would be grayed out. + +Using a single prefix for common commands key means that all other +potential prefix keys can be used for transient-specific commands +*without* the section of common commands also popping up. ~C-c~ in +particular is a prefix that I want (and already do) use for Magit, and +also using that for a common command would prevent me from doing so. + +** Saving Values + +After setting the infix arguments in a transient, the user can save +those arguments for future invocations. + +Most transients will start out with the saved arguments when they are +invoked. There are a few exceptions though. Some transients are +designed so that the value that they use is stored externally as the +buffer-local value of some variable. Invoking such a transient again +uses the buffer-local value. [fn:1] + +If the user does not save the value and just exits using a regular +suffix command, then the value is merely saved to the transient's +history. That value won't be used when the transient is next invoked +but it is easily accessible (see [[*Using History]]). + +- Key: C-x s, transient-set + + This command saves the value of the active transient for this Emacs + session. + +- Key: C-x C-s, transient-save + + Save the value of the active transient persistently across Emacs + sessions. + +- User Option: transient-values-file + + This file is used to persist the values of transients between Emacs + sessions. + +[fn:1] ~magit-diff~ and ~magit-log~ are two prominent examples, and their +handling of buffer-local values is actually a bit more complicated +than outlined above and even customizable. This is something I am +rethinking, but I don't want to rush any changes.) + +** Using History + +Every time the user invokes a suffix command the transient's current +value is saved to its history. This values can be cycled through the +same way one can cycle through the history of commands that read +user-input in the minibuffer. + +- Key: M-p, transient-history-prev + + This command switches to the previous value used for the active + transient. + +- Key: M-n, transient-history-next + + This command switches to the next value used for the active + transient. + +In addition to the transient-wide history, Transient of course +supports per-infix history. When an infix reads user-input using the +minibuffer, then the user can use the regular minibuffer history +commands to cycle through previously used values. Usually the same +keys as those mentioned above are bound to those commands. + +Authors of transients should arrange for different infix commands that +read the same kind of value to also use the same history key (see +[[*Suffix Slots]]). + +Both kinds of history are saved to a file when Emacs is exited. + +- User Option: transient-history-file + + This file is used to persist the history of transients and their + infixes between Emacs sessions. + +- User Option: transient-history-limit + + This option controls how many history elements are kept at the time + the history in saved in ~transient-history-file~. + +** Getting Help for Suffix Commands + +Transients can have many suffixes and infixes that the user might not +be familiar with. To make it trivial to get help for these, Transient +provides access to the documentation directly from the active +transient. + +- Key: C-h, transient-help + + This command enters help mode. When help mode is active, then + typing ~<key>~ shows information about the suffix command that ~<key>~ + normally is bound to (instead of invoking it). Pressing ~C-h~ a + second time shows information about the /prefix/ command. + + After typing ~<key>~ the stack of transient states is suspended and + information about the suffix command is shown instead. Typing ~q~ in + the help buffer buries that buffer and resumes the transient state. + +What sort of documentation is shown depends on how the transient was +defined. For infix commands that represent command-line arguments +this ideally shows the appropriate manpage. ~transient-help~ then tries +to jump to the correct location within that. Info manuals are also +supported. The fallback is to show the commands doc-string, for +non-infix suffixes this is usually appropriate. + +** Enabling and Disabling Suffixes + +The user base of a package that uses transients can be very diverse. +This is certainly the case for Magit; some users have been using it and +Git for a decade, while others are just getting started now. + +For that reason a mechanism is that authors can use to classify a +transient's infixes and suffixes along the essentials...everything +spectrum. We use the term "levels" to describe that mechanism. + +Each suffix command is placed on a level and each transient has a +level (called transient-level), which controls which suffix commands +are available. Integers between 1 and 7 (inclusive) are valid levels. +For suffixes, 0 is also valid; it means that the suffix is not +displayed at any level. + +The levels of individual transient and/or their individual suffixes +can be changed interactively, by invoking the transient and then +pressing ~C-x l~ to enter the "edit" mode, see below. + +The default level for both transients and their suffixes is 4. The +~transient-default-level~ option only controls the default for +transients. The default suffix level is always 4. The authors of +transients should place certain suffixes on a higher level, if they +expect that it won't be of use to most users, and they should place +very important suffixes on a lower level, so that they remain +available even if the user lowers the transient level. + +(Magit currently places nearly all suffixes on level 4 and lower +levels are not used at all yet. So for the time being you should not +set a lower default level and using a higher level might not give you +as many additional suffixes as you hoped.) + +- User Option: transient-default-level + + This option controls which suffix levels are made available by + default. It sets the transient-level for transients for which the + user has not set that individually. + +- User Option: transient-levels-file + + This file is used to persist the levels of transients and their + suffix between Emacs sessions. + +- Key: C-x l, transient-set-level + + This command enters edit mode. When edit mode is active, then all + infixes and suffixes that are currently usable are displayed along + with their levels. The colors of the levels indicate whether they + are enabled or not. The level of the transient is also displayed + along with some usage information. + + In edit mode, pressing the key that would usually invoke a certain + suffix does instead prompt the user for the level that that suffix + should be placed on. + + Help mode is available in edit mode. + + To change the transient level press ~C-x l~ again. + + To exit edit mode press ~C-g~. + + Note that edit mode does not display any suffixes that are not + currently usable. ~magit-rebase~ for example shows different suffixes + depending on whether a rebase is already in progress or not. The + predicates also apply in edit mode. + + Therefore, to control which suffixes are available given a certain + state, you have to make sure that that state is currently active. + +* Other Options + +- User Option: transient-show-popup + + This option controls whether the current transient's infix and + suffix commands are shown in the echo area. + + If ~t~ (the default), then the infix and suffix commands are shown as + soon as the transient is invoked. If ~nil~, only a one line summary + is shown until the user presses a key that forms an incomplete key + sequence. If a number, behave as for ~nil~ but also show the commands + after that many seconds of inactivity. + +- User Option: transient-highlight-mismatched-keys + + This option controls whether key bindings of infix commands that do + not match the respective command-line argument should be highlighted. + For other infix commands this option has no effect. + + When this option is non-nil, then the key binding for infix argument + are highlighted when only a long argument (e.g. ~--verbose~) is + specified but no shorthand (e.g ~-v~). In the rare case that a + shorthand is specified but the key binding does not match, then it + is highlighted differently. + + Highlighting mismatched key bindings is useful when learning the + arguments of the underlying command-line tool; you wouldn't want to + learn any short-hands that do not actually exist. + + The highlighting is done using one of the faces + ~transient-mismatched-key~ and ~transient-nonstandard-key~. + +- User Option: transient-substitute-key-function + + This function is used to modify key bindings. It the value of this + option is nil (the default), then no substitution is performed. + + This function is called with one argument, the prefix object, and + must return a key binding description, either the existing key + description it finds in the ~key~ slot, or key description that + replaces the prefix key. It could be used to make other + substitutions, but that is discouraged. + + For example, ~=~ is hard to reach using my custom keyboard layout, + so I substitute ~(~ for that, which is easy to reach using a layout + optimized for lisp. + + #+BEGIN_SRC emacs-lisp + (setq transient-substitute-key-function + (lambda (obj) + (let ((key (oref obj key))) + (if (string-match "\\`\\(=\\)[a-zA-Z]" key) + (replace-match "(" t t key 1) + key)))) + #+END_SRC + +- User Option: transient-detect-key-conflicts + + This option controls whether key binding conflicts should be + detected at the time the transient is invoked. If so, then this + results in an error, which prevents the transient from being used. + Because of that, conflicts are ignored by default. + + Conflicts cannot be determined earlier, i.e. when the transient is + being defined and when new suffixes are being added, because at that + time there can be false-positives. It is actually valid for + multiple suffixes to share a common key binding, provided the + predicates of those suffixes prevent that more than one of them is + enabled at a time. + +* Modifying Existing Transients + +To an extend transients can be customized interactively, see [[*Enabling +and Disabling Suffixes]]. This section explains how existing transients +can be further modified non-interactively. + +The following functions share a few arguments: + +- PREFIX is a transient prefix command, a symbol. +- SUFFIX is a transient infix or suffix specification in the same form + as expected by ~define-transient-command~. See [[*Suffix Specifications]]. +- LOC is a command, a key vector or a key description (a string as + returned by ~key-description~). + +These functions operate on the information stored in the +~transient--layout~ property of the PREFIX symbol. Suffix entries in +that tree are not objects but have the form ~(LEVEL CLASS PLIST)~, where +plist should set at least ~:key~, ~:description~ and ~:command~. + +- Function: transient-insert-suffix prefix loc suffix + + This function inserts SUFFIX into PREFIX before LOC. + +- Function: transient-append-suffix prefix loc suffix + + This function inserts SUFFIX into PREFIX after LOC. + +- Function: transient-replace-suffix prefix loc suffix + + This function replaces the suffix at LOC in PREFIX with SUFFIX. + +- Function: transient-remove-suffix prefix loc + + This function removes the suffix at LOC in PREFIX. + +- Function: transient-get-suffix prefix loc + + This function returns the suffix at LOC in PREFIX. The returned + value has the form mentioned above. + +- Function: transient-suffix-put prefix loc prop value + + This function edits the suffix at LOC in PREFIX, by setting the + PROP of its plist to VALUE. + +Most of these functions do not signal an error if they cannot perform +the requested modification. The functions that insert new suffixes +show a warning if LOC cannot be found in PREFIX, without signaling an +error. The reason for doing it like this is that establishing a key +binding (and that is what we essentially are trying to do here) should +not prevent the rest of the configuration to fail also. Among these +functions only ~transient-get-suffix~ and ~transient-suffix-put~ may +signal an error. + +* Defining New Commands +** Defining Transients + +A transient consists of a prefix command and at least one suffix +command, though usually a transient has several infix and suffix +commands. The below macro defines the transient prefix command *and* it +binds the transient's infix and suffix commands. In other works, it +defines the complete transient, not just the transient prefix command +that is used to invoke that transient. + +- Macro: define-transient-command name arglist [docstring] [keyword value]... group... [body...] + + This macro defines NAME as a transient prefix command and binds the + transient's infix and suffix commands. + + ARGLIST are the arguments that the prefix command takes. + DOCSTRING is the documentation string and is optional. + + These arguments can optionally be followed by keyword-value pairs. + Each key has to be a keyword symbol, either ~:class~ or a keyword + argument supported by the constructor of that class. The + ~transient-prefix~ class is used if the class is not specified + explicitly. + + GROUPs add key bindings for infix and suffix commands and specify + how these bindings are presented in the echo area. At least one + GROUP has to be specified. See [[*Binding Suffix and Infix Commands]]. + + The BODY is optional. If it is omitted, then ARGLIST is ignored and + the function definition becomes: + + #+BEGIN_SRC emacs-lisp + (lambda () + (interactive) + (transient-setup 'NAME)) + #+END_SRC + + If BODY is specified, then it must begin with an ~interactive~ form + that matches ARGLIST, and it must call ~transient-setup~. It may + however call that function only when some condition is satisfied. + + All transients have a (possibly ~nil~) value, which is exported when + suffix commands are called, so that they can consume that value. + For some transients it might be necessary to have a sort of + secondary value, called a "scope". Such a scope would usually be + set in the command's ~interactive~ form and has to be passed to the + setup function: + + #+BEGIN_SRC emacs-lisp + (transient-setup 'NAME nil nil :scope SCOPE) + #+END_SRC + + For example, the scope of the ~magit-branch-configure~ transient is + the branch whose variables are being configured. + +** Binding Suffix and Infix Commands +*** _ :ignore: + +The macro ~define-transient-command~ is used to define a transient. +This defines the actual transient prefix command (see [[*Defining +Transients]]) and adds the transient's infix and suffix bindings, as +described below. + +Users and third-party packages can add additional bindings using +functions such as ~transient-insert-suffix~ (See [[*Modifying Existing +Transients]]). These functions take a "suffix specification" as one of +their arguments, which has the same form as the specifications used in +~define-transient-command~. + +*** Group Specifications + +The suffix and infix commands of a transient are organized in groups. +The grouping controls how the descriptions of the suffixes are +outlined visually but also makes it possible to set certain properties +for a set of suffixes. + +Several group classes exist, some of which organize suffixes in +subgroups. In most cases the class does not have to be specified +explicitly, but see [[*Group Classes]]. + +Groups are specified in the call to ~define-transient-command~, using +vectors. Because groups are represented using vectors, we cannot use +square brackets to indicate an optional element and instead use curly +brackets to do the latter. + +Group specifications then have this form: + +#+BEGIN_SRC emacs-lisp + [{LEVEL} {DESCRIPTION} {KEYWORD VALUE}... ELEMENT...] +#+END_SRC + +The LEVEL is optional and defaults to 4. See [[*Enabling and Disabling +Suffixes]]. + +The DESCRIPTION is optional. If present it is used as the heading of +the group. + +The KEYWORD-VALUE pairs are optional. Each keyword has to be a +keyword symbol, either ~:class~ or a keyword argument supported by the +constructor of that class. + +- One of these keywords, ~:description~, is equivalent to specifying + DESCRIPTION at the very beginning of the vector. The recommendation + is to use ~:description~ if some other keyword is also used, for + consistency, or DESCRIPTION otherwise, because it looks better. + +- Likewise ~:level~ is equivalent to LEVEL. + +- Other important keywords include the ~:if...~ keywords. These + keywords control whether the group is available in a certain + situation. + + For example, one group of the ~magit-rebase~ transient uses ~:if + magit-rebase-in-progress-p~, which contains the suffixes that are + useful while rebase is already in progress; and another that uses + ~:if-not magit-rebase-in-progress-p~, which contains the suffixes that + initiate a rebase. + + These predicates can also be used on individual suffixes and are + only documented once, see [[*Predicate Slots]]. + +- Finally the value of ~:hide~, if non-nil, is a predicate that control + whether the group is hidden by default. The key bindings for + suffixes of a hidden group should all use the same prefix key. + Pressing that prefix key should temporarily show the group and its + suffixes, which assumes that a predicate like this is used: + + #+BEGIN_SRC emacs-lisp + (lambda () + (eq (car transient--redisplay-key) + ?\C-c)) ; the prefix key shared by all bindings + #+END_SRC + +The ELEMENTs are either all subgroups (vectors), or all suffixes +(lists) and strings. (At least currently no group type exists that +would allow mixing subgroups with commands at the same level, though +in principal there is nothing that prevents that.) + +If the ELEMENTs are not subgroups, then they can be a mixture of lists +that specify commands and strings. Strings are inserted verbatim. +The empty string can be used to insert gaps between suffixes, which is +particularly useful if the suffixes are outlined as a table. + +The form of suffix specifications is documented in the next node. + +*** Suffix Specifications + +A transient's suffix and infix commands are bound when the transient +prefix command is defined using ~define-transient-command~, see +[[*Defining Transients]]. The commands are organized into groups, see +[[*Group Specifications]]. Here we describe the form used to bind an +individual suffix command. + +The same form is also used when later binding additional commands +using functions such as ~transient-insert-suffix~, see [[*Modifying +Existing Transients]]. + +Suffix specifications have this form: + +#+BEGIN_SRC emacs-lisp + ([LEVEL] [KEY] [DESCRIPTION] COMMAND|ARGUMENT [KEYWORD VALUE]...) +#+END_SRC + +LEVEL, KEY and DESCRIPTION can also be specified using the KEYWORDs +~:level~, ~:key~ and ~:description~. If the object that is associated with +COMMAND sets these properties, then they do not have to be specified +here. You can however specify them here anyway, possibly overriding +the objects value just for the binding inside this transient. + +- LEVEL is the suffix level, an integer between 1 and 7. See + [[*Enabling and Disabling Suffixes]]. + +- KEY is the key binding, either a vector or key description string. + +- DESCRIPTION is the description, either a string or a function that + return a string. The function should to be a lambda expression to + avoid ambiguity. In some cases a symbol that is bound as a function + would also work but to be safe you should use ~:description~ in that + case. + +The next element is either a command or an argument. This is the only +argument that is mandatory in all cases. + +- COMMAND is a symbol that is bound as a function, which has to be a + command. Any command will do; it does not need to have an object + associated with it (as would be the case if ~define-suffix-command~ + or ~define-infix-command~ were used to define it). + + As mentioned above the object that is associated with a command can + be used to set the default for certain values that otherwise have to + be set in the suffix specification. Therefore if there is no object, + then you have to make sure to specify the KEY and the DESCRIPTION. + +- The mandatory argument can also be an command-line argument, a + string. In that case an anonymous command is defined and bound. + + Instead of a string, this can also be a list of two strings, in + which case the first string is used as the short argument (which can + also be specified using ~:shortarg~) and the second the long argument + (which can also be specified using ~:argument~). + + Only the long argument is displayed in the echo area. See + ~transient-detect-key-conflicts~ for how the short argument may be + used. + + Unless the class is specified explicitly, the appropriate class is + guessed based on the long argument. If the argument ends with "=" + (e.g. "--format=") then ~transient-option~ is used, otherwise + ~transient-switch~. + +Finally details can be specified using optional KEYWORD-VALUE pairs. +Each keyword has to be a keyword symbol, either ~:class~ or a keyword +argument supported by the constructor of that class. See [[*Suffix +Slots]]. + +** Defining Suffix and Infix Commands + +- Macro: define-suffix-command name arglist [docstring] [keyword value]... body... + + This macro defines NAME as a transient suffix command. + + ARGLIST are the arguments that the command takes. + DOCSTRING is the documentation string and is optional. + + These arguments can optionally be followed by keyword-value pairs. + Each keyword has to be a keyword symbol, either ~:class~ or a keyword + argument supported by the constructor of that class. The + ~transient-suffix~ class is used if the class is not specified + explicitly. + + The BODY must begin with an ~interactive~ form that matches ARGLIST. + Use the function ~transient-args~ or the low-level variable + ~current-transient-suffixes~ if the former does not give you all the + required details. This should, but does not necessarily have to be, + done inside the ~interactive~ form; just like for ~prefix-arg~ and + ~current-prefix-arg~. + +- Macro: define-infix-command name arglist [docstring] [keyword value]... + + This macro defines NAME as a transient infix command. + + ARGLIST is always ignored (but mandatory never-the-less) and + reserved for future use. DOCSTRING is the documentation string and + is optional. + + The keyword-value pairs are mandatory. All transient infix commands + are ~equal~ to each other (but not ~eq~), so it is meaningless to define + an infix command without also setting at least ~:class~ and one other + keyword (which it is depends on the used class, usually ~:argument~ or + ~:variable~). + + Each keyword has to be a keyword symbol, either ~:class~ or a keyword + argument supported by the constructor of that class. The + ~transient-switch~ class is used if the class is not specified + explicitly. + + The function definitions is always: + + #+BEGIN_SRC emacs-lisp + (lambda (obj value) + (interactive + (let ((obj (transient-suffix-object))) + (list obj (transient-infix-read obj)))) + (transient-infix-set obj value) + (transient--show)) + #+END_SRC + + ~transient-infix-read~ and ~transient-infix-set~ are generic functions. + Different infix commands behave differently because the concrete + methods are different for different infix command classes. In rare + cases the above command function might not be suitable, even if you + define your own infix command class. In that case you have to use + ~transient-suffix-command~ to define the infix command and use ~t~ as + the value of the ~:transient~ keyword. + +- Macro: define-infix-argument name arglist [docstring] [keyword value]... + + This macro defines NAME as a transient infix command. + + It is an alias for ~define-infix-command~. Only use this alias + to define an infix command that actually sets an infix argument. + To define a infix command that, for example, sets a variable use + ~define-infix-command~ instead. + +** Using Infix Arguments + +The function and the variables described below allow suffix commands +to access the value of the transient from which they were invoked; +which is the value of its infix arguments. These variables are set +when the user invokes a suffix command that exits the transient, but +before actually calling the command. + +When returning to the command-loop after calling the suffix command, +the arguments are reset to ~nil~ (which causes the function to return +~nil~ too). + +Like for Emacs' prefix arguments it is advisable, but not mandatory, +to access the infix arguments inside the command's ~interactive~ form. +The preferred way of doing that is to call the ~transient-args~ +function, which for infix arguments serves about the same purpose as +~prefix-arg~ serves for prefix arguments. + +- Function: transient-args &optional prefix separate + + This function returns the value of the transient from which the + current suffix was called. If the current suffix command was not + called from a transient, then it returns ~nil~. + + If optional PREFIX is non-~nil~, then it should be a symbol, a + transient prefix command. In that case the value of the transient + is only returned if the suffix was invoked from *that* transient. + Otherwise ~nil~ is returned. This function is also used internally, + in which PREFIX can also be a ~transient-prefix~ object. + + If optional SEPARATE is non-~nil~, then the arguments are separated + into two groups. If SEPARATE is ~t~, they are separated into atoms + and conses (~nil~ isn't a valid value, so it doesn't matter that that + is both an atom and a cons). + + SEPARATE can also be a predicate function, in which case the first + element is a list of the values for which it returns non-~nil~ and the + second element is a list of the values for which it returns ~nil~. + + For transients that are used to pass arguments to a subprocess (such + as ~git~), ~stringp~ is a useful value for SEPARATE, it separates + non-positional arguments from positional arguments. The value of + Magit's file argument (~"--"~) for example looks like this: ~("--" + file...)~." + +- Variable: current-transient-suffixes + + The suffixes of the transient from which this suffix command was + invoked. This is a list of objects. Usually it is sufficient to + instead use the function ~transient-args~, which returns a list of + values. In complex cases it might be necessary to use this variable + instead, i.e. if you need access to information beside the value. + +- Variable: current-transient-prefix + + The transient from which this suffix command was invoked. The + returned value is a ~transient-prefix~ object, which holds information + associated with the transient prefix command. + +- Variable: current-transient-command + + The transient from which this suffix command was invoked. The + returned value is a symbol, the transient prefix command. + +** Transient State +*** _ :ignore: +Invoking a transient prefix command "activates" the respective +transient, i.e. it puts a transient keymap into effect, which binds +the transient's infix and suffix commands. + +The default behavior while a transient is active is as follows: + +- Invoking an infix command does not affect the transient state; the + transient remains active. + +- Invoking a (non-infix) suffix command "deactivates" the transient + state by removing the transient keymap and performing some + additional cleanup. + +- Invoking a command that is bound in a keymap other than the + transient keymap is disallowed and trying to do so results in a + warning. This does not "deactivate" the transient. + +But these are just the defaults. Whether a certain command +deactivates or "exits" the transient is configurable. There is more +than one way in which a command can be "transient" or "non-transient"; +the exact behavior is implemented by calling a so-called "pre-command" +function. Whether non-suffix commands are allowed to be called is +configurable per transient. + +- The transient-ness of suffix commands (including infix commands) is + controlled by the value of their ~transient~ slot, which can be set + either when defining the command or when adding a binding to a + transient while defining the respective transient prefix command. + + Valid values are booleans and the pre-commands described below. + + - ~t~ is equivalent to ~transient--do-stay~. + - ~nil~ is equivalent to ~transient--do-exit~. + - If ~transient~ is unbound (and that is actually the default for + non-infix suffixes) then the value of the prefix's + ~transient-suffix~ slot is used instead. The default value of that + slot is ~nil~, so the suffix's ~transient~ slot being unbound is + essentially equivalent to it being ~nil~. + +- A suffix command can be a prefix command itself, i.e. a + "sub-prefix". While a sub-prefix is active we nearly always want + ~C-g~ to take the user back to the "super-prefix". However in rare + cases this may not be desirable, and that makes the following + complication necessary: + + For ~transient-suffix~ objects the ~transient~ slot is unbound. We can + ignore that for the most part because, as stated above, ~nil~ and the + slot being unbound are equivalent, and means "do exit". That isn't + actually true for suffixes that are sub-prefixes though. For such + suffixes unbound means "do exit but allow going back", which is the + default, while ~nil~ means "do exit permanently", which requires that + slot to be explicitly set to that value. + +- The transient-ness of certain built-in suffix commands is specified + using ~transient-predicate-map~. This is a special keymap, which + binds commands to pre-commands (as opposed to keys to commands) and + takes precedence over the ~transient~ slot. + +The available pre-command functions are documented below. They are +called by ~transient--pre-command~, a function on ~pre-command-hook~ and +the value that they return determines whether the transient is exited. +To do so the value of one of the constants ~transient--exit~ or +~transient--stay~ is used (that way we don't have to remember if ~t~ means +"exit" or "stay"). + +Additionally these functions may change the value of ~this-command~ +(which explains why they have to be called using ~pre-command-hook~), +call ~transient-export~, ~transient--stack-zap~ or ~transient--stack-push~; +and set the values of ~transient--exitp~, ~transient--helpp~ or +~transient--editp~. + +*** Pre-commands for Infixes +:PROPERTIES: +:NONODE: t +:END: + +The default for infixes is ~transient--do-stay~. This is also the only +function that makes sense for infixes. + +- Function: transient--do-stay + + Call the command without exporting variables and stay transient. + +*** Pre-commands for Suffixes +:PROPERTIES: +:NONODE: t +:END: + +The default for suffixes is ~transient--do-exit~. + +- Function: transient--do-exit + + Call the command after exporting variables and exit the transient. + +- Function: transient--do-call + + Call the command after exporting variables and stay transient. + +- Function: transient--do-replace + + Call the transient prefix command, replacing the active transient. + + This is used for suffix that are prefixes themselves, i.e. for + sub-prefixes. + +*** Pre-commands for Non-Suffixes +:PROPERTIES: +:NONODE: t +:END: + +The default for non-suffixes, i.e commands that are bound in other +keymaps beside the transient keymap, is ~transient--do-warn~. Silently +ignoring the user-error is also an option, though probably not a good +one. + +If you want to let the user invoke non-suffix commands, then use +~transient--do-stay~ as the value of the prefix's ~transient-non-suffix~ +slot. + +- Function: transient--do-warn + + Call ~transient-undefined~ and stay transient. + +- Function: transient--do-noop + + Call ~transient-noop~ and stay transient. + +*** Special Pre-Commands +:PROPERTIES: +:NONODE: t +:END: + +- Function: transient--do-quit-one + + If active, quit help or edit mode, else exit the active transient. + + This is used when the user pressed ~C-g~. + +- Function: transient--do-quit-all + + Exit all transients without saving the transient stack. + + This is used when the user pressed ~C-q~. + +- Function: transient--do-suspend + + Suspend the active transient, saving the transient stack. + + This is used when the user pressed ~C-z~. + +* Classes and Methods +** _ :ignore: + +Transient uses classes and generic functions to make it possible to +define new types of suffix commands that are similar to existing +types, but behave differently in some aspects. It does the same for +groups and prefix commands, though at least for prefix commands that +*currently* appears to be less important. + +Every prefix, infix and suffix command is associated with an object, +which holds information that controls certain aspects of its behavior. +This happens in two ways. + +- Associating a command with a certain class gives the command a type. + This makes it possible to use generic functions to do certain things + that have to be done differently depending on what type of command + it acts on. + + That in turn makes it possible for third-parties to add new types + without having to convince the maintainer of Transient that that new + type is important enough to justify adding a special case to a dozen + or so functions. + +- Associating a command with an object makes it possible to easily + store information that is specific to that particular command. + + Two commands may have the same type, but obviously their key + bindings and descriptions still have to be different, for example. + + The values of some slots are functions. The ~reader~ slot for example + holds a function that is used to read a new value for an infix + command. The values of such slots are regular functions. + + Generic functions are used when a function should do something + different based on the type of the command, i.e. when all commands + of a certain type should behave the same way but different from the + behavior for other types. Object slots that hold a regular function + as value are used when the task that they perform is likely to + differ even between different commands of the same type. + +** Group Classes + +The type of a group can be specified using the ~:class~ property at the +beginning of the class specification, e.g. ~[:class transient-columns +...]~ in a call to ~define-transient-command~. + +- The abstract ~transient-child~ class is the base class of both + ~transient-group~ (and therefore all groups) as well as of + ~transient-suffix~ (and therefore all suffix and infix commands). + + This class exists because the elements (aka "children") of certain + groups can be other groups instead of suffix and infix commands. + +- The abstract ~transient-group~ class is the superclass of all other + group classes. + +- The ~transient-column~ class is the simplest group. + + This is the default "flat" group. If the class is not specified + explicitly and the first element is not a vector (i.e. not a group), + then this class is used. + + This class displays each element on a separate line. + +- The ~transient-row~ class displays all elements on a single line. + +- The ~transient-columns~ class displays commands organized in columns. + + Direct elements have to be groups whose elements have to be commands + or strings. Each subgroup represents a column. This class takes + care of inserting the subgroups' elements. + + This is the default "nested" group. If the class is not specified + explicitly and the first element is a vector (i.e. a group), then + this class is used. + +- The ~transient-subgroups~ class wraps other groups. + + Direct elements have to be groups whose elements have to be commands + or strings. This group inserts an empty line between subgroups. + The subgroups themselves are responsible for displaying their + elements. + +** Group Methods + +- Function: transient--insert-group group + + This generic function formats the group and its elements and inserts + the result into the current buffer, which is a temporary buffer. + The contents of that buffer are later inserted into the echo area. + + Functions that are called by this function may need to operate in + the buffer from which the transient was called. To do so they can + temporally make the ~transient--source-buffer~ the current buffer. + +** Prefix Classes + +Currently the ~transient-prefix~ class is being used for all prefix +command and there is only a single generic functions that can be +specialized based on the class of a prefix command. + +- Function: transient--history-init obj + + This generic function is called while setting up the transient and + is responsible for initializing the ~history~ slot. This is the + transient-wide history; many individual infixes also have a history + of their own. + + The default (and currently only) method extracts the value from the + global variable ~transient-history~. + +A transient prefix command's object is stored in the ~transient--prefix~ +property of the command symbol. While a transient is active, a clone +of that object is stored in the variable ~transient--prefix~. A clone +is used because some changes that are made to the active transient's +object should not affect later invocations. + +** Suffix Classes + +- All suffix and infix classes derive from ~transient-suffix~, which in + turn derives from ~transient-child~, from which ~transient-group~ also + derives (see [[*Group Classes]]). + +- All infix classes derived from the abstract ~transient-infix~ class, + which in turn derives from the ~transient-suffix~ class. + + Infixes are a special type of suffixes. The primary difference is + that infixes always use the ~transient--do-stay~ pre-command, while + non-infix suffixes use a variety of pre-commands (see [[*Transient + State]]). Doing that is most easily achieved by using this class, + though theoretically it would be possible to define an infix class + that does not do so. If you do that then you get to implement many + methods. + + Also infixes and non-infix suffixes are usually defined using + different macros (see [[*Defining Suffix and Infix Commands]]). + +- Classes used for infix commands that represent arguments should + derived from the abstract ~transient-argument~ class. + +- The ~transient-switch~ class (or a derived class) is used for infix + arguments that represent command-line switches (arguments that do + not take a value). + +- The ~transient-option~ class (or a derived class) is used for infix + arguments that represent command-line options (arguments that do + not take a value). + +- The ~transient-switches~ class can be used for a set of mutually + exclusive command-line switches. + +- The ~transient-files~ class can be used for a "--" argument that + indicates that all remaining arguments are files. + +- Classes used for infix commands that represent variables should + derived from the abstract ~transient-variables~ class. + +Magit defines additional classes, which can serve as examples for the +fancy things you can do without modifying Transient. Some of these +classes will likely get generalized and added to Transient, for now +they are very much subject to change and not documented. + +** Suffix Methods +*** _ :ignore: + +To get information about the methods implementing these generic +functions use ~describe-function~. + +*** Suffix Value Methods + +- Function: transient-init-value obj + + This generic function sets the initial value of the object OBJ. + + This function is called for all suffix commands, but unless a + concrete method is implemented this falls through to the default + implementation, which is a noop. In other words this usually + only does something for infix commands, but note that this is + not implemented for the abstract class ~transient-infix~, so if + your class derives from that directly, then you must implement + a method. + +- Function: transient-infix-read obj + + This generic function determines the new value of the infix object + OBJ. + + This function merely determines the value; ~transient-infix-set~ is + used to actually store the new value in the object. + + For most infix classes this is done by reading a value from the + user using the reader specified by the ~reader~ slot (using the + ~transient-infix-value~ method described below). + + For some infix classes the value is changed without reading + anything in the minibuffer, i.e. the mere act of invoking the + infix command determines what the new value should be, based + on the previous value. + +- Function: transient-prompt obj + + This generic function returns the prompt to be used to read infix + object OBJ's value. + +- Function: transient-infix-set obj value + + This generic function sets the value of infix object OBJ to value. + +- Function: transient-infix-value obj + + This generic function returns the value of the suffix object OBJ. + + This function is called by ~transient-args~ (which see), meaning this + function is how the value of a transient is determined so that the + invoked suffix command can use it. + + Currently most values are strings, but that is not set in stone. + ~nil~ is not a value, it means "no value". + + Usually only infixes have a value, but see the method for + ~transient-suffix~. + +- Function: transient-init-scope obj + + This generic function sets the scope of the suffix object OBJ. + + The scope is actually a property of the transient prefix, not of + individual suffixes. However it is possible to invoke a suffix + command directly instead of from a transient. In that case, if + the suffix expects a scope, then it has to determine that itself + and store it in its ~scope~ slot. + + This function is called for all suffix commands, but unless a + concrete method is implemented this falls through to the default + implementation, which is a noop. + +*** Suffix Format Methods + +- Function: transient-format obj + + This generic function formats and returns OBJ for display. + + When this function is called, then the current buffer is some + temporary buffer. If you need the buffer from which the prefix + command was invoked to be current, then do so by temporarily + making ~transient--source-buffer~ current. + +- Function: transient-format-key obj + + This generic function formats OBJ's ~key~ for display and returns the + result. + +- Function: transient-format-description obj + + This generic function formats OBJ's ~description~ for display and + returns the result. + +- Function: transient-format-value obj + + This generic function formats OBJ's value for display and returns + the result. + +- Function: transient-show-help obj + + Show help for the prefix, infix or suffix command represented by + OBJ. + + For prefixes show the info manual, if that is specified using the + ~info-manual~ slot. Otherwise show the manpage if that is specified + using the ~man-page~ slot. Otherwise show the command's doc-string. + + For suffixes show the command's doc-string. + + For infixes show the manpage if that is specified. Otherwise show + the command's doc-string. + +** TODO Prefix Slots + +** Suffix Slots + +Here we document most of the slots that are only available for suffix +objects. Some slots are shared by suffix and group objects, they are +documented in [[*Predicate Slots]]. + +Also see [[*Suffix Classes]]. + +*** Slots of ~transient-suffix~ +:PROPERTIES: +:NONODE: t +:END: + +- ~key~ The key, a key vector or a key description string. + +- ~command~ The command, a symbol. + +- ~transient~ Whether to stay transient. See [[*Transient State]]. + +- ~format~ The format used to display the suffix in the echo area. Must + contain the following %-placeholders: + + - ~%k~ For the key. + - ~%d~ For the description. + - ~%v~ For the value. Non-infix suffixes don't have a value. + +- ~description~ The description, either a string or a function that is + called with no argument and returns a string. + +*** Slots of ~transient-infix~ +:PROPERTIES: +:NONODE: t +:END: + +Some of these slots are only meaningful for some of the subclasses. +They are defined here anyway to allow sharing certain methods. + +- ~argument~ The long argument, e.g. ~--verbose~. + +- ~shortarg~ The short argument, e.g. ~-v~. + +- ~multi-value~ For options, whether the option can have multiple + values. If non-nil, then default to use ~completing-read-multiple~. + +- ~allow-empty~ For options, whether the empty string is a valid value. + +- ~history-key~ The key used to store the history. This defaults to the + command name. This is useful when multiple infixes should share the + same history because their values are of the same kind. + +- ~reader~ The function used to read the value of an infix. Not used + for switches. The function takes three arguments, PROMPT, + INITIAL-INPUT and HISTORY, and must return a string. + +- ~prompt~ The prompt used when reading the value, either a string or a + function that takes the object as the only argument and which + returns a prompt string. + +- ~choices~ A list of valid values. How exactly that is used depends on + the class of the object. + +*** Slots of ~transient-variable~ +:PROPERTIES: +:NONODE: t +:END: + +- ~variable~ The variable. + +*** Slots of ~transient-switches~ +:PROPERTIES: +:NONODE: t +:END: + +- ~argument-format~ The display format. Must contain ~%s~, one of the + ~choices~ is substituted for that. E.g. ~--%s-order~. + +- ~argument-regexp~ The regexp used to match any one of the switches. + E.g. ~\\(--\\(topo\\|author-date\\|date\\)-order\\)~. + +** Predicate Slots + +Suffix and group objects share some predicate slots that control +whether a group or suffix should be available depending on some state. +Only one of these slots can be used at the same time. It is undefined +what happens if you use more than one. + +- ~if~ Enable if predicate returns non-nil. +- ~if-not~ Enable if predicate returns nil. +- ~if-non-nil~ Enable if variable's value is non-nil. +- ~if-nil~ Enable if variable's value is nil. +- ~if-mode~ Enable if major-mode matches value. +- ~if-not-mode~ Enable if major-mode does not match value. +- ~if-derived~ Enable if major-mode derives from value. +- ~if-not-derived~ Enable if major-mode does not derive from value. + +One more slot is shared between group and suffix classes, ~level~. Like +the slots documented above it is a predicate, but it is used for a +different purpose. The value has to be an integer between 1 +and 7. ~level~ controls whether it should be available depending on +whether the user wants that or not. See [[*Enabling and Disabling +Suffixes]]. + +* Related Abstractions and Packages +** Comparison With Prefix Keys and Prefix Arguments + +While transient commands were inspired by regular prefix keys and +prefix arguments, they are also quite different and much more complex. + +The following diagrams illustrate some of the differences. + +- ~(c)~ represents a return to the command loop. +- ~(+)~ represents the user's choice to press one key or another. +- ~{WORD}~ are possible behaviors. +- ~{NUMBER}~ is a footnote. + +*** Regular Prefix Commands +:PROPERTIES: +:NONODE: t +:END: + +See [[info:elisp#Prefix Keys]]. + +#+BEGIN_EXAMPLE + ,--> command1 --> (c) + | + (c)-(+)-> prefix command or key --+--> command2 --> (c) + | + `--> command3 --> (c) +#+END_EXAMPLE + +*** Regular Prefix Arguments +:PROPERTIES: +:NONODE: t +:END: + +See [[info:elisp#Prefix Command Arguments]]. + +#+BEGIN_EXAMPLE + ,----------------------------------, + | | + v | + (c)-(+)---> prefix argument command --(c)-(+)-> any command --> (c) + | ^ | + | | | + `-- sets or changes --, ,-- maybe used --' | + | | | + v | | + prefix argument state | + ^ | + | | + `-------- discards --------' +#+END_EXAMPLE + +*** Transients +:PROPERTIES: +:NONODE: t +:END: + +(∩`-´)⊃━☆゚.*・。゚ + +This diagram ignores the infix value and external state: + +#+BEGIN_EXAMPLE + (c) + | ,- {stay} ------<-,-<------------<-,-<---, + (+) | | | | + | | | | | + | | ,--> infix1 --| | | + | | | | | | + | | |--> infix2 --| | | + v v | | | | + prefix -(c)-(+)-> infix3 --' ^ | + | | | + |---------------> suffix1 -->--| | + | | | + |---------------> suffix2 ----{1}------> {exit} --> (c) + | | + |---------------> suffix3 -------------> {exit} --> (c) + | | + `--> any command --{2}-> {warn} -->--| + | | + |--> {noop} -->--| + | | + |--> {call} -->--' + | + `------------------> {exit} --> (c) +#+END_EXAMPLE + +This diagram takes the infix value into account to an extend, while +still ignoring external state: + +#+BEGIN_EXAMPLE + (c) + | ,- {stay} ------<-,-<------------<-,-<---, + (+) | | | | + | | | | | + | | ,--> infix1 --| | | + | | | | | | | + | | ,--> infix2 --| | | + v v | | | | | + prefix -(c)-(+)-> infix3 --' | | + | | ^ | + | | | | + |---------------> suffix1 -->--| | + | | ^ | | + | | | | | + |---------------> suffix2 ----{1}------> {exit} --> (c) + | | ^ | | + | | | | v + | | | | | + |---------------> suffix3 -------------> {exit} --> (c) + | | ^ | | + | sets | | v + | | maybe | | + | | used | | + | | | | | + | | infix --' | | + | `---> value | | + | ^ | | + | | | | + | hides | | + | | | | + | `--------------------------<---| + | | | + `--> any command --{2}-> {warn} -->--| | + | | | + |--> {noop} -->--| | + | | | + |--> {call} -->--' ^ + | | + `------------------> {exit} --> (c) +#+END_EXAMPLE + +This diagram provides more information about the infix value +and also takes external state into account. + +#+BEGIN_EXAMPLE + ,----sets--- "anything" + | + v + ,---------> external + | state + | | | + | initialized | ☉‿⚆ + sets from | + | | maybe + | ,----------' used + | | | + (c) | | v + | ,- {stay} --|---<-,-<------|-----<-,-<---, + (+) | | | | | | | + | | | v | | | | + | | ,--> infix1 --| | | | + | | | | | | | | | + | | | | v | | | | + | | ,--> infix2 --| | | | + | | | | ^ | | | | + v v | | | | | | | + prefix -(c)-(+)-> infix3 --' | | | + | | ^ | ^ | + | | | v | | + |---------------> suffix1 -->--| | + | | | ^ | | | + | | | | v | | + |---------------> suffix2 ----{1}------> {exit} --> (c) + | | | ^ | | | + | | | | | | v + | | | | v | | + |---------------> suffix3 -------------> {exit} --> (c) + | | | ^ | | + | sets | | | v + | | initalized maybe | | + | | from used | | + | | | | | | + | | `-- infix --' | | + | `---> value -----------------------------> persistent + | ^ ^ | | across + | | | | | invocations -, + | hides | | | | + | | `----------------------------------------------' + | | | | + | `--------------------------<---| + | | | + `--> any command --{2}-> {warn} -->--| | + | | | + |--> {noop} -->--| | + | | | + |--> {call} -->--' ^ + | | + `------------------> {exit} --> (c) +#+END_EXAMPLE + +- ~{1}~ Transients can be configured to be exited when a suffix command + is invoked. The default is to do so for all suffixes expect for + those that are common to all transients and which are used to + perform tasks such as providing help and saving the value of the + infix arguments for future invocations. The behavior can also be + specified for individual suffix commands individually and may even + depend on state. + +- ~{2}~ Transients can be configured to allow the user to invoke + non-suffix commands. The default is to not allow that and instead + warn the user. + +Despite already being rather complex, even the last diagram leaves out +many details. Most importantly it implies that the decision whether +to remain transient is made later than it actually is made (for the +most part a function on ~pre-command-hook~ is responsible). But such +implementation details are of little relevance to users and are +covered elsewhere. + +** Comparison With Other Packages +*** Magit-Popup +:PROPERTIES: +:NONODE: t +:END: + +Transient is the successor to Magit-Popup (see [[info:magit-popup]]). + +One major difference between these two implementations of the same +ideas is that while Transient uses transient keymaps and embraces the +command-loop, Magit-Popup implemented an inferior mechanism that does +not use transient keymaps and that instead of using the command-loop +implements a naive alternative based on ~read-char~. + +Magit-Popup does not use classes and generic functions and defining a +new command type is near impossible as it involves adding hard-coded +special-cases to many functions. Because of that only a single new +type was added, which was not already part of Magit-Popup's initial +release. + +A lot of things are hard-coded in Magit-Popup. One random example is +that the key bindings for switches must begin with "-" and those for +options must begin with "=". + +*** Hydra +:PROPERTIES: +:NONODE: t +:END: + +Hydra (see https://github.com/abo-abo/hydra) is another package that +provides features similar to those of Transient. + +Both packages use transient keymaps to make a set of commands +temporarily available and the ~lv~ library to show these commands in the +echo area. (The author of Hydra is also the author of ~lv~, which is +maintained in the same repository.) + +A Hydra "body" is equivalent to a Transient "prefix" and a Hydra +"head" is equivalent to a Transient "suffix". Hydra has no equivalent +of a Transient "infix". + +Both hydras and transients can be used as simple command dispatchers. +Used like this they are similar to regular prefix commands and prefix +keys, except that the available commands are shown in the echo area. + +(Another package that does this is ~which-key~. It does so automatically +for any incomplete key sequence. The advantage of that approach is +that no additional work is necessary; the disadvantage is that the +available commands are not organized semantically.) + +Both Hydra and Transient provide features that go beyond simple +command dispatchers: + +- Invoking a command from a hydra does not necessarily exit the hydra. + That makes it possible to invoke the same command again, but using a + shorter key sequence (i.e. the key that was used to enter the hydra + does not have to be pressed again). + + Transient supports that too, but for not this feature is not a focus + and the interface is a bit more complicated. A very basic example + using the current interface: + + #+BEGIN_SRC emacs-lisp + (define-transient-command outline-navigate () + :transient-suffix 'transient--do-stay + :transient-non-suffix 'transient--do-warn + [("p" "next visible heading" outline-previous-visible-heading) + ("n" "next visible heading" outline-next-visible-heading)]) + #+END_SRC + +- Transient support infix arguments; values that are set by infix + commands and then consumed by the invoked suffix command(s). + + To my knowledge, Hydra does not support that. + +Both packages make it possible to specify how exactly the available +commands are outlined: + +- With Hydra this is often done using an explicit format string, which + gives authors a lot of flexibility and makes it possible to do fancy + things. + + The downside of this is that it becomes harder for a user to add + additional commands to an existing hydra and to change key bindings. + +- Transient allows the author of a transient to organize the commands + into groups and the use of generic functions allows authors of + transients to control exactly how a certain command type is + displayed. + + However while Transient support giving sections a heading it does + not currently support giving the displayed information more + structure by, for example, using box-drawing characters. + + That could be implemented by defining a new group class, which lets + the author specify a format string. It should be possible to + implement that without modifying any existing code, but it does not + currently exist. + +* Keystroke Index +:PROPERTIES: +:APPENDIX: t +:INDEX: ky +:COOKIE_DATA: recursive +:END: +* Command Index +:PROPERTIES: +:APPENDIX: t +:INDEX: cp +:END: +* Function Index +:PROPERTIES: +:APPENDIX: t +:INDEX: fn +:END: +* Variable Index +:PROPERTIES: +:APPENDIX: t +:INDEX: vr +:END: + +* _ Copying +:PROPERTIES: +:COPYING: t +:END: + +#+BEGIN_QUOTE +Copyright (C) 2018-2019 Jonas Bernoulli <jonas@bernoul.li> + +You can redistribute this document and/or modify it under the terms +of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any +later version. + +This document is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. +#+END_QUOTE + +* _ :ignore: + +# LocalWords: ARGLIST ARGS DOCSTRING ELEMENTs EVAL GROUPs Infixes +# LocalWords: Infixes KEYWORDs LOC LocalWords MERCHANTABILITY Magit +# LocalWords: Magit's Magit-Popup Makefile OBJ OBJ's Pre arglist +# LocalWords: args boolean booleans customizable docstring eval +# LocalWords: featurep infixes init keymap keymaps loc magit manpage +# LocalWords: minibuffer ness nilly noop plist pre pre-command prev +# LocalWords: rebase src subclass subclasses subprocess superclass +# LocalWords: texinfo+ utils + +# IMPORTANT: Also update ORG_ARGS and ORG_EVAL in the Makefile. +# Local Variables: +# eval: (require 'magit-utils nil t) +# eval: (require 'org-man nil t) +# eval: (require 'ox-extra nil t) +# eval: (require 'ox-texinfo+ nil t) +# eval: (and (featurep 'ox-extra) (ox-extras-activate '(ignore-headlines))) +# indent-tabs-mode: nil +# org-src-preserve-indentation: nil +# End: diff --git a/docs/transient.texi b/docs/transient.texi new file mode 100644 index 0000000..a6f05c2 --- /dev/null +++ b/docs/transient.texi @@ -0,0 +1,2197 @@ +\input texinfo @c -*- texinfo -*- +@c %**start of header +@setfilename transient.info +@settitle Transient User and Developer Manual +@documentencoding UTF-8 +@documentlanguage en +@c %**end of header + +@copying +@quotation +Copyright (C) 2018-2019 Jonas Bernoulli <jonas@@bernoul.li> + +You can redistribute this document and/or modify it under the terms +of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any +later version. + +This document is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE@. See the GNU +General Public License for more details. + +@end quotation +@end copying + +@dircategory Emacs +@direntry +* Transient: (transient). Transient Commands. +@end direntry + +@finalout +@titlepage +@title Transient User and Developer Manual +@subtitle for version 0.1.0 +@author Jonas Bernoulli +@page +@vskip 0pt plus 1filll +@insertcopying +@end titlepage + +@contents + +@ifnottex +@node Top +@top Transient User and Developer Manual + +Taking inspiration from prefix keys and prefix arguments, Transient +implements a similar abstraction involving a prefix command, infix +arguments and suffix commands. We could call this abstraction a +"transient command", but because it always involves at least two +commands (a prefix and a suffix) we prefer to call it just a +"transient". + +When the user calls a transient prefix command, then a transient +(temporary) keymap is activated, which binds the transient's infix +and suffix commands, and functions that control the transient state +are added to @code{pre-command-hook} and @code{post-command-hook}. The available +suffix and infix commands and their state are shown in the echo area +until the transient is exited by invoking a suffix command. + +Calling an infix command causes its value to be changed, possibly by +reading a new value in the minibuffer. + +Calling a suffix command usually causes the transient to be exited +but suffix commands can also be configured to not exit the transient. + +@noindent +This manual is for Transient version 0.1.0. + +@quotation +Copyright (C) 2018-2019 Jonas Bernoulli <jonas@@bernoul.li> + +You can redistribute this document and/or modify it under the terms +of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any +later version. + +This document is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE@. See the GNU +General Public License for more details. + +@end quotation +@end ifnottex + +@menu +* Introduction:: +* Usage:: +* Other Options:: +* Modifying Existing Transients:: +* Defining New Commands:: +* Classes and Methods:: +* Related Abstractions and Packages:: +* Keystroke Index:: +* Command Index:: +* Function Index:: +* Variable Index:: + +@detailmenu +--- The Detailed Node Listing --- + +Usage + +* Invoking Transients:: +* Aborting and Resuming Transients:: +* Common Suffix Commands:: +* Saving Values:: +* Using History:: +* Getting Help for Suffix Commands:: +* Enabling and Disabling Suffixes:: + +Defining New Commands + +* Defining Transients:: +* Binding Suffix and Infix Commands:: +* Defining Suffix and Infix Commands:: +* Using Infix Arguments:: +* Transient State:: + +Binding Suffix and Infix Commands + +* Group Specifications:: +* Suffix Specifications:: + + +Classes and Methods + +* Group Classes:: +* Group Methods:: +* Prefix Classes:: +* Suffix Classes:: +* Suffix Methods:: +* Prefix Slots:: +* Suffix Slots:: +* Predicate Slots:: + +Suffix Methods + +* Suffix Value Methods:: +* Suffix Format Methods:: + + +Related Abstractions and Packages + +* Comparison With Prefix Keys and Prefix Arguments:: +* Comparison With Other Packages:: + +@end detailmenu +@end menu + +@node Introduction +@chapter Introduction + +Taking inspiration from prefix keys and prefix arguments, Transient +implements a similar abstraction involving a prefix command, infix +arguments and suffix commands. We could call this abstraction a +"transient command", but because it always involves at least two +commands (a prefix and a suffix) we prefer to call it just a +"transient". + +@quotation +Transient keymaps are a feature provided by Emacs. Transients as +implemented by this package involve the use of transient keymaps. + +Emacs provides a feature that it calls "prefix commands". When we +talk about "prefix commands" in this manual, then we mean our own kind +of "prefix commands", unless specified otherwise. To avoid ambiguity +we sometimes use the terms "transient prefix command" for our kind and +"regular prefix command" for Emacs' kind. + +@end quotation + +When the user calls a transient prefix command, then a transient +(temporary) keymap is activated, which binds the transient's infix and +suffix commands, and functions that control the transient state are +added to @code{pre-command-hook} and @code{post-command-hook}. The available suffix +and infix commands and their state are shown in the echo area until +the transient state is exited by invoking a suffix command. + +Calling an infix command causes its value to be changed. How that is +done depends on the type of the infix command. The simplest case is +an infix command that represents a command-line argument that does not +take a value. Invoking such an infix command causes the switch to be +toggled on or off. More complex infix commands may read a value from +the user, using the minibuffer. + +Calling a suffix command usually causes the transient to be exited; +the transient keymaps and hook functions are removed, the echo area no +longer shows information about the (no longer bound) suffix commands, +the values of some public global variables are set, while some +internal global variables are unset, and finally the command is +actually called. Suffix commands can also be configured to not exit +the transient. + +A suffix command can, but does not have to, use the infix arguments in +much the same way it can choose to use or ignore the prefix arguments. +For a suffix command that was invoked from a transient the variable +@code{current-transient-suffixes} and the function @code{transient-args} serve about +the same purpose as the variables @code{prefix-arg} and @code{current-prefix-arg} do +for any command that was called after the prefix arguments have been +set using a command such as @code{universal-argument}. + +The information shown in the echo area while a transient is active +looks a bit like this: + +@example +,----------------------------------------- +|Arguments +| -f Force (--force) +| -a Annotate (--annotate) +| +|Create +| t tag +| r telease +`----------------------------------------- +@end example + +@quotation +This is a simplified version of @code{magit-tag}. Info manuals do not +support images or colored text, so the above "screenshot" lacks some +information; in practice you would be able to tell whether the +arguments @code{--force} and @code{--annotate} are enabled or not based on their +color. + +@end quotation + +Transient can be used to implement simple "command dispatchers". The +main benefit then is that the user can see all the available commands +in the echo area. That is useful by itself because it frees the user +from having to remember all the keys that are valid after a certain +prefix key or command. Magit's @code{magit-dispatch} command is an example +of using Transient to merely implement a command dispatcher. + +In addition to that, Transient also allows users to interactively pass +arguments to commands. These arguments can be much more complex than +what is reasonable when using prefix arguments. There is a limit to +how many aspects of a command can be controlled using prefix +arguments. Furthermore what a certain prefix argument means for +different commands can be completely different, and users have to read +documentation to learn and then commit to memory what a certain prefix +argument means to a certain command. + +Transient suffix commands on the other hand can accept dozens of +different arguments without the user having to remember anything. +When using Transient, then one can call a command with arguments that +are just as complex as when calling the same function non-interactively +using code. + +Invoking a transient command with arguments is similar to invoking a +command in a shell with command-line completion and history enabled. +One benefit of the Transient interface is that it remembers history +not only on a global level ("this command was invoked using these +arguments and previously it was invoked using those other arguments"), +but also remembers the values of individual arguments independently. +see @ref{Using History}. + +After a transient prefix command is invoked @code{C-h <key>} can be used to +show the documentation for the infix or suffix command that @code{<key>} is +bound to (see @ref{Getting Help for Suffix Commands}) and infixes and +suffixes can be removed from the transient using @code{C-x l <key>}. Infixes +and suffixes that are disabled by default can be enabled the same way. +See @ref{Enabling and Disabling Suffixes}. + +Transient ships with support for a few different types of specialized +infix commands. A command that sets a command line option for example +has different needs than a command that merely toggles a boolean flag. +Additionally Transient provides abstractions for defining new types, +which the author of Transient did not anticipate (or didn't get around +to implement yet). + +@node Usage +@chapter Usage + +@menu +* Invoking Transients:: +* Aborting and Resuming Transients:: +* Common Suffix Commands:: +* Saving Values:: +* Using History:: +* Getting Help for Suffix Commands:: +* Enabling and Disabling Suffixes:: +@end menu + +@node Invoking Transients +@section Invoking Transients + +A transient prefix command is invoked like any other command by +pressing the key that is bound to that command. The main difference +to other commands is that a transient prefix commands activates a +transient keymap, which temporarily binds the transients infix and +suffix commands. Bindings from other keymaps may, or may not, be +disabled while the transient state is in effect. + +There are two kinds of commands that are available after invoking a +transient prefix command; infix and suffix commands. Infix commands +set some value (which is then shown in the echo area), without leaving +the transient. Suffix commands on the other hand usually quit the +transient and they may use the values set by the infix commands, +i.e. the infix @strong{arguments}. + +Instead of setting arguments to be used by a suffix command, infix +commands may also set some value by side-effect. + +@node Aborting and Resuming Transients +@section Aborting and Resuming Transients + +To quit the transient without invoking a suffix command press @code{C-g}. + +Key bindings in transient keymaps may be longer than a single event. +After pressing a valid prefix key, all commands whose bindings do not +begin with that prefix key are temporarily unavailable and grayed out. +To abort the prefix key press @code{C-g} (which in this case only quits the +prefix key, but not the complete transient). + +A transient prefix command can be bound as a suffix of another +transient. Invoking such a suffix replaces the current transient +state with a new transient state, i.e. the available bindings change +and the information displayed in the echo area is updated accordingly. +Pressing @code{C-g} while a nested transient is active only quits the +innermost transient, causing a return to the previous transient. + +@code{C-q} and @code{C-z} on the other hand always exits all transients. If you use +the latter, then you can later resume the stack of transients using +@code{M-x transient-resume}. + +@table @asis +@kindex C-g +@cindex transient-quit-seq +@item @kbd{C-g} @tie{}@tie{}@tie{}@tie{}(@code{transient-quit-seq}) +@kindex C-g +@cindex transient-quit-one +@item @kbd{C-g} @tie{}@tie{}@tie{}@tie{}(@code{transient-quit-one}) + +This key quits the currently active incomplete key sequence, if any, +or else the current transient. When quitting the current transient, +then it returns to the previous transient, if any. + +@kindex C-q +@cindex transient-quit-all +@item @kbd{C-q} @tie{}@tie{}@tie{}@tie{}(@code{transient-quit-all}) + +This command quits the currently active incomplete key sequence, if +any, and all transients, including the active transient and all +suspended transients, if any. + +@kindex C-z +@cindex transient-suspend +@item @kbd{C-z} @tie{}@tie{}@tie{}@tie{}(@code{transient-suspend}) + +Like @code{transient-quit-all}, this command quits an incomplete key +sequence, if any, and all transients. Additionally it saves the +stack of transients so that it can easily be resumed (which is +particularly useful if you quickly need to do "something else" and +the stack is deeper than a single transient and/or you have already +changed the values of some infix arguments). + +Note that only a single stack of transients can be saved at a time. +If another stack is already saved, then saving a new stack discards +the previous stack. + +@kindex M-x transient-resume +@cindex transient-resume +@item @kbd{M-x transient-resume} @tie{}@tie{}@tie{}@tie{}(@code{transient-resume}) + +This command resumes the previously suspended stack of transients, +if any. +@end table + +@node Common Suffix Commands +@section Common Suffix Commands + +A few shared suffix commands are available in all transients. These +suffix commands are not shown in the echo area by default. + +Most of these commands are bound to @code{C-x <key>} and after pressing @code{C-x} a +section featuring all common commands is temporarily show in the echo +area. After invoking one of these commands that section disappears +again. Note however that one of these commands is described as "Show +common permanently"; invoke that if you want the common commands to +always be shown for all transients. + +@table @asis +@kindex C-x t +@cindex transient-toggle-common +@item @kbd{C-x t} @tie{}@tie{}@tie{}@tie{}(@code{transient-toggle-common}) + +This command toggles whether the generic commands that are common to +all transients are always displayed or only after typing the +incomplete prefix key sequence @code{C-x}. This only affects the current +Emacs session. + +@end table + +@defopt transient-show-common-commands + +This option controls whether shared suffix commands are shown +alongside the transient-specific infix and suffix commands. By +default the shared commands are not shown to avoid overwhelming +the user with to many options. + +While a transient is active, pressing @code{C-x} always shows the common +command. The value of this option can be changed for the current +Emacs session by typing @code{C-x t} while a transient is active. +@end defopt + +The other common commands are describe in either the previous node or +in one of the following nodes. + +@subsection Notes on Common Key Bindings + +You may have noticed that the bindings for some of the common commands +do @strong{not} have the prefix @code{C-x} and that furthermore some of these commands +are grayed out while others are not. That unfortunately is a bit +confusing if the section of common commands is not shown permanently, +making the following explanation necessary. + +The purpose of usually hiding that section but showing it after the +user pressed the respective prefix key is to conserve space and not +overwhelm users with too much noise, while allowing the user to +quickly list common bindings on demand. + +That however should not keep us from using the best possible key +bindings. The bindings that do use a prefix do so to avoid wasting +too many non-prefix bindings, keeping them available for use in +individual transients. The bindings that do not use a prefix and that +are @strong{not} grayed out are very important bindings that are @strong{always} +available, even when invoking the "common command key prefix" or @strong{any +other} transient-specific prefix. The non-prefix keys that @strong{are} grayed +out however, are not available when any incomplete prefix key sequence +is active. They do not use the "common command key prefix" because it +is likely that users want to invoke them several times in a row and +e.g. @code{M-p M-p M-p} is much more convenient than @code{C-x M-p C-x M-p C-x M-p}. + +You may also have noticed that the "Set" command is bound to @code{C-x s}, +while Magit-Popup used to bind @code{C-c C-c} instead. I have seen several +users praise the latter binding (sic), so I did not change it +willy-nilly. The reason that I changed it is that using different +prefix keys for different common commands, would have made the +temporary display of the common commands even more confusing, +i.e. after pressing @code{C-c} all the @code{C-x ...} bindings would be grayed out. + +Using a single prefix for common commands key means that all other +potential prefix keys can be used for transient-specific commands +@strong{without} the section of common commands also popping up. @code{C-c} in +particular is a prefix that I want (and already do) use for Magit, and +also using that for a common command would prevent me from doing so. + +@node Saving Values +@section Saving Values + +After setting the infix arguments in a transient, the user can save +those arguments for future invocations. + +Most transients will start out with the saved arguments when they are +invoked. There are a few exceptions though. Some transients are +designed so that the value that they use is stored externally as the +buffer-local value of some variable. Invoking such a transient again +uses the buffer-local value. @footnote{@code{magit-diff} and @code{magit-log} are two prominent examples, and their +handling of buffer-local values is actually a bit more complicated +than outlined above and even customizable. This is something I am +rethinking, but I don't want to rush any changes.)} + +If the user does not save the value and just exits using a regular +suffix command, then the value is merely saved to the transient's +history. That value won't be used when the transient is next invoked +but it is easily accessible (see @ref{Using History}). + +@table @asis +@kindex C-x s +@cindex transient-set +@item @kbd{C-x s} @tie{}@tie{}@tie{}@tie{}(@code{transient-set}) + +This command saves the value of the active transient for this Emacs +session. + +@kindex C-x C-s +@cindex transient-save +@item @kbd{C-x C-s} @tie{}@tie{}@tie{}@tie{}(@code{transient-save}) + +Save the value of the active transient persistently across Emacs +sessions. + +@end table + +@defopt transient-values-file + +This file is used to persist the values of transients between Emacs +sessions. +@end defopt + +@node Using History +@section Using History + +Every time the user invokes a suffix command the transient's current +value is saved to its history. This values can be cycled through the +same way one can cycle through the history of commands that read +user-input in the minibuffer. + +@table @asis +@kindex M-p +@cindex transient-history-prev +@item @kbd{M-p} @tie{}@tie{}@tie{}@tie{}(@code{transient-history-prev}) + +This command switches to the previous value used for the active +transient. + +@kindex M-n +@cindex transient-history-next +@item @kbd{M-n} @tie{}@tie{}@tie{}@tie{}(@code{transient-history-next}) + +This command switches to the next value used for the active +transient. +@end table + +In addition to the transient-wide history, Transient of course +supports per-infix history. When an infix reads user-input using the +minibuffer, then the user can use the regular minibuffer history +commands to cycle through previously used values. Usually the same +keys as those mentioned above are bound to those commands. + +Authors of transients should arrange for different infix commands that +read the same kind of value to also use the same history key (see +@ref{Suffix Slots}). + +Both kinds of history are saved to a file when Emacs is exited. + +@defopt transient-history-file + +This file is used to persist the history of transients and their +infixes between Emacs sessions. +@end defopt + +@defopt transient-history-limit + +This option controls how many history elements are kept at the time +the history in saved in @code{transient-history-file}. +@end defopt + +@node Getting Help for Suffix Commands +@section Getting Help for Suffix Commands + +Transients can have many suffixes and infixes that the user might not +be familiar with. To make it trivial to get help for these, Transient +provides access to the documentation directly from the active +transient. + +@table @asis +@kindex C-h +@cindex transient-help +@item @kbd{C-h} @tie{}@tie{}@tie{}@tie{}(@code{transient-help}) + +This command enters help mode. When help mode is active, then +typing @code{<key>} shows information about the suffix command that @code{<key>} +normally is bound to (instead of invoking it). Pressing @code{C-h} a +second time shows information about the @emph{prefix} command. + +After typing @code{<key>} the stack of transient states is suspended and +information about the suffix command is shown instead. Typing @code{q} in +the help buffer buries that buffer and resumes the transient state. +@end table + +What sort of documentation is shown depends on how the transient was +defined. For infix commands that represent command-line arguments +this ideally shows the appropriate manpage. @code{transient-help} then tries +to jump to the correct location within that. Info manuals are also +supported. The fallback is to show the commands doc-string, for +non-infix suffixes this is usually appropriate. + +@node Enabling and Disabling Suffixes +@section Enabling and Disabling Suffixes + +The user base of a package that uses transients can be very diverse. +This is certainly the case for Magit; some users have been using it and +Git for a decade, while others are just getting started now. + +For that reason a mechanism is that authors can use to classify a +transient's infixes and suffixes along the essentials@dots{}everything +spectrum. We use the term "levels" to describe that mechanism. + +Each suffix command is placed on a level and each transient has a +level (called transient-level), which controls which suffix commands +are available. Integers between 1 and 7 (inclusive) are valid levels. +For suffixes, 0 is also valid; it means that the suffix is not +displayed at any level. + +The levels of individual transient and/or their individual suffixes +can be changed interactively, by invoking the transient and then +pressing @code{C-x l} to enter the "edit" mode, see below. + +The default level for both transients and their suffixes is 4. The +@code{transient-default-level} option only controls the default for +transients. The default suffix level is always 4. The authors of +transients should place certain suffixes on a higher level, if they +expect that it won't be of use to most users, and they should place +very important suffixes on a lower level, so that they remain +available even if the user lowers the transient level. + +(Magit currently places nearly all suffixes on level 4 and lower +levels are not used at all yet. So for the time being you should not +set a lower default level and using a higher level might not give you +as many additional suffixes as you hoped.) + +@defopt transient-default-level + +This option controls which suffix levels are made available by +default. It sets the transient-level for transients for which the +user has not set that individually. +@end defopt + +@defopt transient-levels-file + +This file is used to persist the levels of transients and their +suffix between Emacs sessions. +@end defopt + +@table @asis +@kindex C-x l +@cindex transient-set-level +@item @kbd{C-x l} @tie{}@tie{}@tie{}@tie{}(@code{transient-set-level}) + +This command enters edit mode. When edit mode is active, then all +infixes and suffixes that are currently usable are displayed along +with their levels. The colors of the levels indicate whether they +are enabled or not. The level of the transient is also displayed +along with some usage information. + +In edit mode, pressing the key that would usually invoke a certain +suffix does instead prompt the user for the level that that suffix +should be placed on. + +Help mode is available in edit mode. + +To change the transient level press @code{C-x l} again. + +To exit edit mode press @code{C-g}. + +Note that edit mode does not display any suffixes that are not +currently usable. @code{magit-rebase} for example shows different suffixes +depending on whether a rebase is already in progress or not. The +predicates also apply in edit mode. + +Therefore, to control which suffixes are available given a certain +state, you have to make sure that that state is currently active. +@end table + +@node Other Options +@chapter Other Options + +@defopt transient-show-popup + +This option controls whether the current transient's infix and +suffix commands are shown in the echo area. + +If @code{t} (the default), then the infix and suffix commands are shown as +soon as the transient is invoked. If @code{nil}, only a one line summary +is shown until the user presses a key that forms an incomplete key +sequence. If a number, behave as for @code{nil} but also show the commands +after that many seconds of inactivity. +@end defopt + +@defopt transient-highlight-mismatched-keys + +This option controls whether key bindings of infix commands that do +not match the respective command-line argument should be highlighted. +For other infix commands this option has no effect. + +When this option is non-nil, then the key binding for infix argument +are highlighted when only a long argument (e.g. @code{--verbose}) is +specified but no shorthand (e.g @code{-v}). In the rare case that a +shorthand is specified but the key binding does not match, then it +is highlighted differently. + +Highlighting mismatched key bindings is useful when learning the +arguments of the underlying command-line tool; you wouldn't want to +learn any short-hands that do not actually exist. + +The highlighting is done using one of the faces +@code{transient-mismatched-key} and @code{transient-nonstandard-key}. +@end defopt + +@defopt transient-substitute-key-function + +This function is used to modify key bindings. It the value of this +option is nil (the default), then no substitution is performed. + +This function is called with one argument, the prefix object, and +must return a key binding description, either the existing key +description it finds in the @code{key} slot, or key description that +replaces the prefix key. It could be used to make other +substitutions, but that is discouraged. + +For example, @code{=} is hard to reach using my custom keyboard layout, +so I substitute @code{(} for that, which is easy to reach using a layout +optimized for lisp. + +@lisp +(setq transient-substitute-key-function + (lambda (obj) + (let ((key (oref obj key))) + (if (string-match "\\`\\(=\\)[a-zA-Z]" key) + (replace-match "(" t t key 1) + key)))) +@end lisp +@end defopt + +@defopt transient-detect-key-conflicts + +This option controls whether key binding conflicts should be +detected at the time the transient is invoked. If so, then this +results in an error, which prevents the transient from being used. +Because of that, conflicts are ignored by default. + +Conflicts cannot be determined earlier, i.e. when the transient is +being defined and when new suffixes are being added, because at that +time there can be false-positives. It is actually valid for +multiple suffixes to share a common key binding, provided the +predicates of those suffixes prevent that more than one of them is +enabled at a time. +@end defopt + +@node Modifying Existing Transients +@chapter Modifying Existing Transients + +To an extend transients can be customized interactively, see @ref{Enabling and Disabling Suffixes}. This section explains how existing transients +can be further modified non-interactively. + +The following functions share a few arguments: + +@itemize +@item +PREFIX is a transient prefix command, a symbol. + +@item +SUFFIX is a transient infix or suffix specification in the same form +as expected by @code{define-transient-command}. See @ref{Suffix Specifications}. + +@item +LOC is a command, a key vector or a key description (a string as +returned by @code{key-description}). +@end itemize + +These functions operate on the information stored in the +@code{transient--layout} property of the PREFIX symbol. Suffix entries in +that tree are not objects but have the form @code{(LEVEL CLASS PLIST)}, where +plist should set at least @code{:key}, @code{:description} and @code{:command}. + +@defun transient-insert-suffix prefix loc suffix + +This function inserts SUFFIX into PREFIX before LOC@. +@end defun + +@defun transient-append-suffix prefix loc suffix + +This function inserts SUFFIX into PREFIX after LOC@. +@end defun + +@defun transient-replace-suffix prefix loc suffix + +This function replaces the suffix at LOC in PREFIX with SUFFIX@. +@end defun + +@defun transient-remove-suffix prefix loc + +This function removes the suffix at LOC in PREFIX@. +@end defun + +@defun transient-get-suffix prefix loc + +This function returns the suffix at LOC in PREFIX@. The returned +value has the form mentioned above. +@end defun + +@defun transient-suffix-put prefix loc prop value + +This function edits the suffix at LOC in PREFIX, by setting the +PROP of its plist to VALUE@. +@end defun + +Most of these functions do not signal an error if they cannot perform +the requested modification. The functions that insert new suffixes +show a warning if LOC cannot be found in PREFIX, without signaling an +error. The reason for doing it like this is that establishing a key +binding (and that is what we essentially are trying to do here) should +not prevent the rest of the configuration to fail also. Among these +functions only @code{transient-get-suffix} and @code{transient-suffix-put} may +signal an error. + +@node Defining New Commands +@chapter Defining New Commands + +@menu +* Defining Transients:: +* Binding Suffix and Infix Commands:: +* Defining Suffix and Infix Commands:: +* Using Infix Arguments:: +* Transient State:: +@end menu + +@node Defining Transients +@section Defining Transients + +A transient consists of a prefix command and at least one suffix +command, though usually a transient has several infix and suffix +commands. The below macro defines the transient prefix command @strong{and} it +binds the transient's infix and suffix commands. In other works, it +defines the complete transient, not just the transient prefix command +that is used to invoke that transient. + +@defmac define-transient-command name arglist [docstring] [keyword value]@dots{} group@dots{} [body@dots{}] + +This macro defines NAME as a transient prefix command and binds the +transient's infix and suffix commands. + +ARGLIST are the arguments that the prefix command takes. +DOCSTRING is the documentation string and is optional. + +These arguments can optionally be followed by keyword-value pairs. +Each key has to be a keyword symbol, either @code{:class} or a keyword +argument supported by the constructor of that class. The +@code{transient-prefix} class is used if the class is not specified +explicitly. + +GROUPs add key bindings for infix and suffix commands and specify +how these bindings are presented in the echo area. At least one +GROUP has to be specified. See @ref{Binding Suffix and Infix Commands}. + +The BODY is optional. If it is omitted, then ARGLIST is ignored and +the function definition becomes: + +@lisp +(lambda () + (interactive) + (transient-setup 'NAME)) +@end lisp + +If BODY is specified, then it must begin with an @code{interactive} form +that matches ARGLIST, and it must call @code{transient-setup}. It may +however call that function only when some condition is satisfied. + +All transients have a (possibly @code{nil}) value, which is exported when +suffix commands are called, so that they can consume that value. +For some transients it might be necessary to have a sort of +secondary value, called a "scope". Such a scope would usually be +set in the command's @code{interactive} form and has to be passed to the +setup function: + +@lisp +(transient-setup 'NAME nil nil :scope SCOPE) +@end lisp + +For example, the scope of the @code{magit-branch-configure} transient is +the branch whose variables are being configured. +@end defmac + +@node Binding Suffix and Infix Commands +@section Binding Suffix and Infix Commands + +The macro @code{define-transient-command} is used to define a transient. +This defines the actual transient prefix command (see @ref{Defining Transients}) and adds the transient's infix and suffix bindings, as +described below. + +Users and third-party packages can add additional bindings using +functions such as @code{transient-insert-suffix} (See @ref{Modifying Existing Transients}). These functions take a "suffix specification" as one of +their arguments, which has the same form as the specifications used in +@code{define-transient-command}. + +@menu +* Group Specifications:: +* Suffix Specifications:: +@end menu + +@node Group Specifications +@subsection Group Specifications + +The suffix and infix commands of a transient are organized in groups. +The grouping controls how the descriptions of the suffixes are +outlined visually but also makes it possible to set certain properties +for a set of suffixes. + +Several group classes exist, some of which organize suffixes in +subgroups. In most cases the class does not have to be specified +explicitly, but see @ref{Group Classes}. + +Groups are specified in the call to @code{define-transient-command}, using +vectors. Because groups are represented using vectors, we cannot use +square brackets to indicate an optional element and instead use curly +brackets to do the latter. + +Group specifications then have this form: + +@lisp +[@{LEVEL@} @{DESCRIPTION@} @{KEYWORD VALUE@}... ELEMENT...] +@end lisp + +The LEVEL is optional and defaults to 4. See @ref{Enabling and Disabling Suffixes}. + +The DESCRIPTION is optional. If present it is used as the heading of +the group. + +The KEYWORD-VALUE pairs are optional. Each keyword has to be a +keyword symbol, either @code{:class} or a keyword argument supported by the +constructor of that class. + +@itemize +@item +One of these keywords, @code{:description}, is equivalent to specifying +DESCRIPTION at the very beginning of the vector. The recommendation +is to use @code{:description} if some other keyword is also used, for +consistency, or DESCRIPTION otherwise, because it looks better. + + +@item +Likewise @code{:level} is equivalent to LEVEL@. + + +@item +Other important keywords include the @code{:if...} keywords. These +keywords control whether the group is available in a certain +situation. + +For example, one group of the @code{magit-rebase} transient uses @code{:if + magit-rebase-in-progress-p}, which contains the suffixes that are +useful while rebase is already in progress; and another that uses +@code{:if-not magit-rebase-in-progress-p}, which contains the suffixes that +initiate a rebase. + +These predicates can also be used on individual suffixes and are +only documented once, see @ref{Predicate Slots}. + + +@item +Finally the value of @code{:hide}, if non-nil, is a predicate that control +whether the group is hidden by default. The key bindings for +suffixes of a hidden group should all use the same prefix key. +Pressing that prefix key should temporarily show the group and its +suffixes, which assumes that a predicate like this is used: + +@lisp +(lambda () + (eq (car transient--redisplay-key) + ?\C-c)) ; the prefix key shared by all bindings +@end lisp +@end itemize + +The ELEMENTs are either all subgroups (vectors), or all suffixes +(lists) and strings. (At least currently no group type exists that +would allow mixing subgroups with commands at the same level, though +in principal there is nothing that prevents that.) + +If the ELEMENTs are not subgroups, then they can be a mixture of lists +that specify commands and strings. Strings are inserted verbatim. +The empty string can be used to insert gaps between suffixes, which is +particularly useful if the suffixes are outlined as a table. + +The form of suffix specifications is documented in the next node. + +@node Suffix Specifications +@subsection Suffix Specifications + +A transient's suffix and infix commands are bound when the transient +prefix command is defined using @code{define-transient-command}, see +@ref{Defining Transients}. The commands are organized into groups, see +@ref{Group Specifications}. Here we describe the form used to bind an +individual suffix command. + +The same form is also used when later binding additional commands +using functions such as @code{transient-insert-suffix}, see @ref{Modifying Existing Transients}. + +Suffix specifications have this form: + +@lisp +([LEVEL] [KEY] [DESCRIPTION] COMMAND|ARGUMENT [KEYWORD VALUE]...) +@end lisp + +LEVEL, KEY and DESCRIPTION can also be specified using the KEYWORDs +@code{:level}, @code{:key} and @code{:description}. If the object that is associated with +COMMAND sets these properties, then they do not have to be specified +here. You can however specify them here anyway, possibly overriding +the objects value just for the binding inside this transient. + +@itemize +@item +LEVEL is the suffix level, an integer between 1 and 7. See +@ref{Enabling and Disabling Suffixes}. + + +@item +KEY is the key binding, either a vector or key description string. + + +@item +DESCRIPTION is the description, either a string or a function that +return a string. The function should to be a lambda expression to +avoid ambiguity. In some cases a symbol that is bound as a function +would also work but to be safe you should use @code{:description} in that +case. +@end itemize + +The next element is either a command or an argument. This is the only +argument that is mandatory in all cases. + +@itemize +@item +COMMAND is a symbol that is bound as a function, which has to be a +command. Any command will do; it does not need to have an object +associated with it (as would be the case if @code{define-suffix-command} +or @code{define-infix-command} were used to define it). + +As mentioned above the object that is associated with a command can +be used to set the default for certain values that otherwise have to +be set in the suffix specification. Therefore if there is no object, +then you have to make sure to specify the KEY and the DESCRIPTION@. + + +@item +The mandatory argument can also be an command-line argument, a +string. In that case an anonymous command is defined and bound. + +Instead of a string, this can also be a list of two strings, in +which case the first string is used as the short argument (which can +also be specified using @code{:shortarg}) and the second the long argument +(which can also be specified using @code{:argument}). + +Only the long argument is displayed in the echo area. See +@code{transient-detect-key-conflicts} for how the short argument may be +used. + +Unless the class is specified explicitly, the appropriate class is +guessed based on the long argument. If the argument ends with "@samp{" + (e.g. "--format}") then @code{transient-option} is used, otherwise +@code{transient-switch}. +@end itemize + +Finally details can be specified using optional KEYWORD-VALUE pairs. +Each keyword has to be a keyword symbol, either @code{:class} or a keyword +argument supported by the constructor of that class. See @ref{Suffix Slots}. + +@node Defining Suffix and Infix Commands +@section Defining Suffix and Infix Commands + +@defmac define-suffix-command name arglist [docstring] [keyword value]@dots{} body@dots{} + +This macro defines NAME as a transient suffix command. + +ARGLIST are the arguments that the command takes. +DOCSTRING is the documentation string and is optional. + +These arguments can optionally be followed by keyword-value pairs. +Each keyword has to be a keyword symbol, either @code{:class} or a keyword +argument supported by the constructor of that class. The +@code{transient-suffix} class is used if the class is not specified +explicitly. + +The BODY must begin with an @code{interactive} form that matches ARGLIST@. +Use the function @code{transient-args} or the low-level variable +@code{current-transient-suffixes} if the former does not give you all the +required details. This should, but does not necessarily have to be, +done inside the @code{interactive} form; just like for @code{prefix-arg} and +@code{current-prefix-arg}. +@end defmac + +@defmac define-infix-command name arglist [docstring] [keyword value]@dots{} + +This macro defines NAME as a transient infix command. + +ARGLIST is always ignored (but mandatory never-the-less) and +reserved for future use. DOCSTRING is the documentation string and +is optional. + +The keyword-value pairs are mandatory. All transient infix commands +are @code{equal} to each other (but not @code{eq}), so it is meaningless to define +an infix command without also setting at least @code{:class} and one other +keyword (which it is depends on the used class, usually @code{:argument} or +@code{:variable}). + +Each keyword has to be a keyword symbol, either @code{:class} or a keyword +argument supported by the constructor of that class. The +@code{transient-switch} class is used if the class is not specified +explicitly. + +The function definitions is always: + +@lisp +(lambda (obj value) + (interactive + (let ((obj (transient-suffix-object))) + (list obj (transient-infix-read obj)))) + (transient-infix-set obj value) + (transient--show)) +@end lisp + +@code{transient-infix-read} and @code{transient-infix-set} are generic functions. +Different infix commands behave differently because the concrete +methods are different for different infix command classes. In rare +cases the above command function might not be suitable, even if you +define your own infix command class. In that case you have to use +@code{transient-suffix-command} to define the infix command and use @code{t} as +the value of the @code{:transient} keyword. +@end defmac + +@defmac define-infix-argument name arglist [docstring] [keyword value]@dots{} + +This macro defines NAME as a transient infix command. + +It is an alias for @code{define-infix-command}. Only use this alias +to define an infix command that actually sets an infix argument. +To define a infix command that, for example, sets a variable use +@code{define-infix-command} instead. +@end defmac + +@node Using Infix Arguments +@section Using Infix Arguments + +The function and the variables described below allow suffix commands +to access the value of the transient from which they were invoked; +which is the value of its infix arguments. These variables are set +when the user invokes a suffix command that exits the transient, but +before actually calling the command. + +When returning to the command-loop after calling the suffix command, +the arguments are reset to @code{nil} (which causes the function to return +@code{nil} too). + +Like for Emacs' prefix arguments it is advisable, but not mandatory, +to access the infix arguments inside the command's @code{interactive} form. +The preferred way of doing that is to call the @code{transient-args} +function, which for infix arguments serves about the same purpose as +@code{prefix-arg} serves for prefix arguments. + +@defun transient-args &optional prefix separate + +This function returns the value of the transient from which the +current suffix was called. If the current suffix command was not +called from a transient, then it returns @code{nil}. + +If optional PREFIX is non-@code{nil}, then it should be a symbol, a +transient prefix command. In that case the value of the transient +is only returned if the suffix was invoked from @strong{that} transient. +Otherwise @code{nil} is returned. This function is also used internally, +in which PREFIX can also be a @code{transient-prefix} object. + +If optional SEPARATE is non-@code{nil}, then the arguments are separated +into two groups. If SEPARATE is @code{t}, they are separated into atoms +and conses (@code{nil} isn't a valid value, so it doesn't matter that that +is both an atom and a cons). + +SEPARATE can also be a predicate function, in which case the first +element is a list of the values for which it returns non-@code{nil} and the +second element is a list of the values for which it returns @code{nil}. + +For transients that are used to pass arguments to a subprocess (such +as @code{git}), @code{stringp} is a useful value for SEPARATE, it separates +non-positional arguments from positional arguments. The value of +Magit's file argument (@code{"--"}) for example looks like this: @code{("--" + file...)}." +@end defun + +@defvar current-transient-suffixes + +The suffixes of the transient from which this suffix command was +invoked. This is a list of objects. Usually it is sufficient to +instead use the function @code{transient-args}, which returns a list of +values. In complex cases it might be necessary to use this variable +instead, i.e. if you need access to information beside the value. +@end defvar + +@defvar current-transient-prefix + +The transient from which this suffix command was invoked. The +returned value is a @code{transient-prefix} object, which holds information +associated with the transient prefix command. +@end defvar + +@defvar current-transient-command + +The transient from which this suffix command was invoked. The +returned value is a symbol, the transient prefix command. +@end defvar + +@node Transient State +@section Transient State + +Invoking a transient prefix command "activates" the respective +transient, i.e. it puts a transient keymap into effect, which binds +the transient's infix and suffix commands. + +The default behavior while a transient is active is as follows: + +@itemize +@item +Invoking an infix command does not affect the transient state; the +transient remains active. + + +@item +Invoking a (non-infix) suffix command "deactivates" the transient +state by removing the transient keymap and performing some +additional cleanup. + + +@item +Invoking a command that is bound in a keymap other than the +transient keymap is disallowed and trying to do so results in a +warning. This does not "deactivate" the transient. +@end itemize + +But these are just the defaults. Whether a certain command +deactivates or "exits" the transient is configurable. There is more +than one way in which a command can be "transient" or "non-transient"; +the exact behavior is implemented by calling a so-called "pre-command" +function. Whether non-suffix commands are allowed to be called is +configurable per transient. + +@itemize +@item +The transient-ness of suffix commands (including infix commands) is +controlled by the value of their @code{transient} slot, which can be set +either when defining the command or when adding a binding to a +transient while defining the respective transient prefix command. + +Valid values are booleans and the pre-commands described below. + +@itemize +@item +@code{t} is equivalent to @code{transient--do-stay}. + +@item +@code{nil} is equivalent to @code{transient--do-exit}. + +@item +If @code{transient} is unbound (and that is actually the default for +non-infix suffixes) then the value of the prefix's +@code{transient-suffix} slot is used instead. The default value of that +slot is @code{nil}, so the suffix's @code{transient} slot being unbound is +essentially equivalent to it being @code{nil}. +@end itemize + + +@item +A suffix command can be a prefix command itself, i.e. a +"sub-prefix". While a sub-prefix is active we nearly always want +@code{C-g} to take the user back to the "super-prefix". However in rare +cases this may not be desirable, and that makes the following +complication necessary: + +For @code{transient-suffix} objects the @code{transient} slot is unbound. We can +ignore that for the most part because, as stated above, @code{nil} and the +slot being unbound are equivalent, and means "do exit". That isn't +actually true for suffixes that are sub-prefixes though. For such +suffixes unbound means "do exit but allow going back", which is the +default, while @code{nil} means "do exit permanently", which requires that +slot to be explicitly set to that value. + + +@item +The transient-ness of certain built-in suffix commands is specified +using @code{transient-predicate-map}. This is a special keymap, which +binds commands to pre-commands (as opposed to keys to commands) and +takes precedence over the @code{transient} slot. +@end itemize + +The available pre-command functions are documented below. They are +called by @code{transient--pre-command}, a function on @code{pre-command-hook} and +the value that they return determines whether the transient is exited. +To do so the value of one of the constants @code{transient--exit} or +@code{transient--stay} is used (that way we don't have to remember if @code{t} means +"exit" or "stay"). + +Additionally these functions may change the value of @code{this-command} +(which explains why they have to be called using @code{pre-command-hook}), +call @code{transient-export}, @code{transient--stack-zap} or @code{transient--stack-push}; +and set the values of @code{transient--exitp}, @code{transient--helpp} or +@code{transient--editp}. + +@subsection Pre-commands for Infixes + +The default for infixes is @code{transient--do-stay}. This is also the only +function that makes sense for infixes. + +@defun transient--do-stay + +Call the command without exporting variables and stay transient. +@end defun + +@subsection Pre-commands for Suffixes + +The default for suffixes is @code{transient--do-exit}. + +@defun transient--do-exit + +Call the command after exporting variables and exit the transient. +@end defun + +@defun transient--do-call + +Call the command after exporting variables and stay transient. +@end defun + +@defun transient--do-replace + +Call the transient prefix command, replacing the active transient. + +This is used for suffix that are prefixes themselves, i.e. for +sub-prefixes. +@end defun + +@subsection Pre-commands for Non-Suffixes + +The default for non-suffixes, i.e commands that are bound in other +keymaps beside the transient keymap, is @code{transient--do-warn}. Silently +ignoring the user-error is also an option, though probably not a good +one. + +If you want to let the user invoke non-suffix commands, then use +@code{transient--do-stay} as the value of the prefix's @code{transient-non-suffix} +slot. + +@defun transient--do-warn + +Call @code{transient-undefined} and stay transient. +@end defun + +@defun transient--do-noop + +Call @code{transient-noop} and stay transient. +@end defun + +@subsection Special Pre-Commands + +@defun transient--do-quit-one + +If active, quit help or edit mode, else exit the active transient. + +This is used when the user pressed @code{C-g}. +@end defun + +@defun transient--do-quit-all + +Exit all transients without saving the transient stack. + +This is used when the user pressed @code{C-q}. +@end defun + +@defun transient--do-suspend + +Suspend the active transient, saving the transient stack. + +This is used when the user pressed @code{C-z}. +@end defun + +@node Classes and Methods +@chapter Classes and Methods + +Transient uses classes and generic functions to make it possible to +define new types of suffix commands that are similar to existing +types, but behave differently in some aspects. It does the same for +groups and prefix commands, though at least for prefix commands that +@strong{currently} appears to be less important. + +Every prefix, infix and suffix command is associated with an object, +which holds information that controls certain aspects of its behavior. +This happens in two ways. + +@itemize +@item +Associating a command with a certain class gives the command a type. +This makes it possible to use generic functions to do certain things +that have to be done differently depending on what type of command +it acts on. + +That in turn makes it possible for third-parties to add new types +without having to convince the maintainer of Transient that that new +type is important enough to justify adding a special case to a dozen +or so functions. + + +@item +Associating a command with an object makes it possible to easily +store information that is specific to that particular command. + +Two commands may have the same type, but obviously their key +bindings and descriptions still have to be different, for example. + +The values of some slots are functions. The @code{reader} slot for example +holds a function that is used to read a new value for an infix +command. The values of such slots are regular functions. + +Generic functions are used when a function should do something +different based on the type of the command, i.e. when all commands +of a certain type should behave the same way but different from the +behavior for other types. Object slots that hold a regular function +as value are used when the task that they perform is likely to +differ even between different commands of the same type. +@end itemize + +@menu +* Group Classes:: +* Group Methods:: +* Prefix Classes:: +* Suffix Classes:: +* Suffix Methods:: +* Prefix Slots:: +* Suffix Slots:: +* Predicate Slots:: +@end menu + +@node Group Classes +@section Group Classes + +The type of a group can be specified using the @code{:class} property at the +beginning of the class specification, e.g. @code{[:class transient-columns +...]} in a call to @code{define-transient-command}. + +@itemize +@item +The abstract @code{transient-child} class is the base class of both +@code{transient-group} (and therefore all groups) as well as of +@code{transient-suffix} (and therefore all suffix and infix commands). + +This class exists because the elements (aka "children") of certain +groups can be other groups instead of suffix and infix commands. + + +@item +The abstract @code{transient-group} class is the superclass of all other +group classes. + + +@item +The @code{transient-column} class is the simplest group. + +This is the default "flat" group. If the class is not specified +explicitly and the first element is not a vector (i.e. not a group), +then this class is used. + +This class displays each element on a separate line. + + +@item +The @code{transient-row} class displays all elements on a single line. + + +@item +The @code{transient-columns} class displays commands organized in columns. + +Direct elements have to be groups whose elements have to be commands +or strings. Each subgroup represents a column. This class takes +care of inserting the subgroups' elements. + +This is the default "nested" group. If the class is not specified +explicitly and the first element is a vector (i.e. a group), then +this class is used. + + +@item +The @code{transient-subgroups} class wraps other groups. + +Direct elements have to be groups whose elements have to be commands +or strings. This group inserts an empty line between subgroups. +The subgroups themselves are responsible for displaying their +elements. +@end itemize + +@node Group Methods +@section Group Methods + +@defun transient--insert-group group + +This generic function formats the group and its elements and inserts +the result into the current buffer, which is a temporary buffer. +The contents of that buffer are later inserted into the echo area. + +Functions that are called by this function may need to operate in +the buffer from which the transient was called. To do so they can +temporally make the @code{transient--source-buffer} the current buffer. +@end defun + +@node Prefix Classes +@section Prefix Classes + +Currently the @code{transient-prefix} class is being used for all prefix +command and there is only a single generic functions that can be +specialized based on the class of a prefix command. + +@defun transient--history-init obj + +This generic function is called while setting up the transient and +is responsible for initializing the @code{history} slot. This is the +transient-wide history; many individual infixes also have a history +of their own. + +The default (and currently only) method extracts the value from the +global variable @code{transient-history}. +@end defun + +A transient prefix command's object is stored in the @code{transient--prefix} +property of the command symbol. While a transient is active, a clone +of that object is stored in the variable @code{transient--prefix}. A clone +is used because some changes that are made to the active transient's +object should not affect later invocations. + +@node Suffix Classes +@section Suffix Classes + +@itemize +@item +All suffix and infix classes derive from @code{transient-suffix}, which in +turn derives from @code{transient-child}, from which @code{transient-group} also +derives (see @ref{Group Classes}). + + +@item +All infix classes derived from the abstract @code{transient-infix} class, +which in turn derives from the @code{transient-suffix} class. + +Infixes are a special type of suffixes. The primary difference is +that infixes always use the @code{transient--do-stay} pre-command, while +non-infix suffixes use a variety of pre-commands (see @ref{Transient State}). Doing that is most easily achieved by using this class, +though theoretically it would be possible to define an infix class +that does not do so. If you do that then you get to implement many +methods. + +Also infixes and non-infix suffixes are usually defined using +different macros (see @ref{Defining Suffix and Infix Commands}). + + +@item +Classes used for infix commands that represent arguments should +derived from the abstract @code{transient-argument} class. + + +@item +The @code{transient-switch} class (or a derived class) is used for infix +arguments that represent command-line switches (arguments that do +not take a value). + + +@item +The @code{transient-option} class (or a derived class) is used for infix +arguments that represent command-line options (arguments that do +not take a value). + + +@item +The @code{transient-switches} class can be used for a set of mutually +exclusive command-line switches. + + +@item +The @code{transient-files} class can be used for a "--" argument that +indicates that all remaining arguments are files. + + +@item +Classes used for infix commands that represent variables should +derived from the abstract @code{transient-variables} class. +@end itemize + +Magit defines additional classes, which can serve as examples for the +fancy things you can do without modifying Transient. Some of these +classes will likely get generalized and added to Transient, for now +they are very much subject to change and not documented. + +@node Suffix Methods +@section Suffix Methods + +To get information about the methods implementing these generic +functions use @code{describe-function}. + +@menu +* Suffix Value Methods:: +* Suffix Format Methods:: +@end menu + +@node Suffix Value Methods +@subsection Suffix Value Methods + +@defun transient-init-value obj + +This generic function sets the initial value of the object OBJ@. + +This function is called for all suffix commands, but unless a +concrete method is implemented this falls through to the default +implementation, which is a noop. In other words this usually +only does something for infix commands, but note that this is +not implemented for the abstract class @code{transient-infix}, so if +your class derives from that directly, then you must implement +a method. +@end defun + +@defun transient-infix-read obj + +This generic function determines the new value of the infix object +OBJ@. + +This function merely determines the value; @code{transient-infix-set} is +used to actually store the new value in the object. + +For most infix classes this is done by reading a value from the +user using the reader specified by the @code{reader} slot (using the +@code{transient-infix-value} method described below). + +For some infix classes the value is changed without reading +anything in the minibuffer, i.e. the mere act of invoking the +infix command determines what the new value should be, based +on the previous value. +@end defun + +@defun transient-prompt obj + +This generic function returns the prompt to be used to read infix +object OBJ's value. +@end defun + +@defun transient-infix-set obj value + +This generic function sets the value of infix object OBJ to value. +@end defun + +@defun transient-infix-value obj + +This generic function returns the value of the suffix object OBJ@. + +This function is called by @code{transient-args} (which see), meaning this +function is how the value of a transient is determined so that the +invoked suffix command can use it. + +Currently most values are strings, but that is not set in stone. +@code{nil} is not a value, it means "no value". + +Usually only infixes have a value, but see the method for +@code{transient-suffix}. +@end defun + +@defun transient-init-scope obj + +This generic function sets the scope of the suffix object OBJ@. + +The scope is actually a property of the transient prefix, not of +individual suffixes. However it is possible to invoke a suffix +command directly instead of from a transient. In that case, if +the suffix expects a scope, then it has to determine that itself +and store it in its @code{scope} slot. + +This function is called for all suffix commands, but unless a +concrete method is implemented this falls through to the default +implementation, which is a noop. +@end defun + +@node Suffix Format Methods +@subsection Suffix Format Methods + +@defun transient-format obj + +This generic function formats and returns OBJ for display. + +When this function is called, then the current buffer is some +temporary buffer. If you need the buffer from which the prefix +command was invoked to be current, then do so by temporarily +making @code{transient--source-buffer} current. +@end defun + +@defun transient-format-key obj + +This generic function formats OBJ's @code{key} for display and returns the +result. +@end defun + +@defun transient-format-description obj + +This generic function formats OBJ's @code{description} for display and +returns the result. +@end defun + +@defun transient-format-value obj + +This generic function formats OBJ's value for display and returns +the result. +@end defun + +@defun transient-show-help obj + +Show help for the prefix, infix or suffix command represented by +OBJ@. + +For prefixes show the info manual, if that is specified using the +@code{info-manual} slot. Otherwise show the manpage if that is specified +using the @code{man-page} slot. Otherwise show the command's doc-string. + +For suffixes show the command's doc-string. + +For infixes show the manpage if that is specified. Otherwise show +the command's doc-string. +@end defun + +@node Prefix Slots +@section @strong{TODO} Prefix Slots + +@node Suffix Slots +@section Suffix Slots + +Here we document most of the slots that are only available for suffix +objects. Some slots are shared by suffix and group objects, they are +documented in @ref{Predicate Slots}. + +Also see @ref{Suffix Classes}. + +@subsection Slots of @code{transient-suffix} + +@itemize +@item +@code{key} The key, a key vector or a key description string. + + +@item +@code{command} The command, a symbol. + + +@item +@code{transient} Whether to stay transient. See @ref{Transient State}. + + +@item +@code{format} The format used to display the suffix in the echo area. Must +contain the following %-placeholders: + +@itemize +@item +@code{%k} For the key. + +@item +@code{%d} For the description. + +@item +@code{%v} For the value. Non-infix suffixes don't have a value. +@end itemize + + +@item +@code{description} The description, either a string or a function that is +called with no argument and returns a string. +@end itemize + +@subsection Slots of @code{transient-infix} + +Some of these slots are only meaningful for some of the subclasses. +They are defined here anyway to allow sharing certain methods. + +@itemize +@item +@code{argument} The long argument, e.g. @code{--verbose}. + + +@item +@code{shortarg} The short argument, e.g. @code{-v}. + + +@item +@code{multi-value} For options, whether the option can have multiple +values. If non-nil, then default to use @code{completing-read-multiple}. + + +@item +@code{allow-empty} For options, whether the empty string is a valid value. + + +@item +@code{history-key} The key used to store the history. This defaults to the +command name. This is useful when multiple infixes should share the +same history because their values are of the same kind. + + +@item +@code{reader} The function used to read the value of an infix. Not used +for switches. The function takes three arguments, PROMPT, +INITIAL-INPUT and HISTORY, and must return a string. + + +@item +@code{prompt} The prompt used when reading the value, either a string or a +function that takes the object as the only argument and which +returns a prompt string. + + +@item +@code{choices} A list of valid values. How exactly that is used depends on +the class of the object. +@end itemize + +@subsection Slots of @code{transient-variable} + +@itemize +@item +@code{variable} The variable. +@end itemize + +@subsection Slots of @code{transient-switches} + +@itemize +@item +@code{argument-format} The display format. Must contain @code{%s}, one of the +@code{choices} is substituted for that. E.g. @code{--%s-order}. + + +@item +@code{argument-regexp} The regexp used to match any one of the switches. +E.g. @code{\\(--\\(topo\\|author-date\\|date\\)-order\\)}. +@end itemize + +@node Predicate Slots +@section Predicate Slots + +Suffix and group objects share some predicate slots that control +whether a group or suffix should be available depending on some state. +Only one of these slots can be used at the same time. It is undefined +what happens if you use more than one. + +@itemize +@item +@code{if} Enable if predicate returns non-nil. + +@item +@code{if-not} Enable if predicate returns nil. + +@item +@code{if-non-nil} Enable if variable's value is non-nil. + +@item +@code{if-nil} Enable if variable's value is nil. + +@item +@code{if-mode} Enable if major-mode matches value. + +@item +@code{if-not-mode} Enable if major-mode does not match value. + +@item +@code{if-derived} Enable if major-mode derives from value. + +@item +@code{if-not-derived} Enable if major-mode does not derive from value. +@end itemize + +One more slot is shared between group and suffix classes, @code{level}. Like +the slots documented above it is a predicate, but it is used for a +different purpose. The value has to be an integer between 1 +and 7. @code{level} controls whether it should be available depending on +whether the user wants that or not. See @ref{Enabling and Disabling Suffixes}. + +@node Related Abstractions and Packages +@chapter Related Abstractions and Packages + +@menu +* Comparison With Prefix Keys and Prefix Arguments:: +* Comparison With Other Packages:: +@end menu + +@node Comparison With Prefix Keys and Prefix Arguments +@section Comparison With Prefix Keys and Prefix Arguments + +While transient commands were inspired by regular prefix keys and +prefix arguments, they are also quite different and much more complex. + +The following diagrams illustrate some of the differences. + +@itemize +@item +@code{(c)} represents a return to the command loop. + +@item +@code{(+)} represents the user's choice to press one key or another. + +@item +@code{@{WORD@}} are possible behaviors. + +@item +@code{@{NUMBER@}} is a footnote. +@end itemize + +@subsection Regular Prefix Commands + +See @ref{Prefix Keys,,,elisp,}. + +@example + ,--> command1 --> (c) + | +(c)-(+)-> prefix command or key --+--> command2 --> (c) + | + `--> command3 --> (c) +@end example + +@subsection Regular Prefix Arguments + +See @ref{Prefix Command Arguments,,,elisp,}. + +@example + ,----------------------------------, + | | + v | +(c)-(+)---> prefix argument command --(c)-(+)-> any command --> (c) + | ^ | + | | | + `-- sets or changes --, ,-- maybe used --' | + | | | + v | | + prefix argument state | + ^ | + | | + `-------- discards --------' +@end example + +@subsection Transients + +(∩`-´)⊃━☆゚.*・。゚ + +This diagram ignores the infix value and external state: + +@example +(c) + | ,- @{stay@} ------<-,-<------------<-,-<---, +(+) | | | | + | | | | | + | | ,--> infix1 --| | | + | | | | | | + | | |--> infix2 --| | | + v v | | | | + prefix -(c)-(+)-> infix3 --' ^ | + | | | + |---------------> suffix1 -->--| | + | | | + |---------------> suffix2 ----@{1@}------> @{exit@} --> (c) + | | + |---------------> suffix3 -------------> @{exit@} --> (c) + | | + `--> any command --@{2@}-> @{warn@} -->--| + | | + |--> @{noop@} -->--| + | | + |--> @{call@} -->--' + | + `------------------> @{exit@} --> (c) +@end example + +This diagram takes the infix value into account to an extend, while +still ignoring external state: + +@example +(c) + | ,- @{stay@} ------<-,-<------------<-,-<---, +(+) | | | | + | | | | | + | | ,--> infix1 --| | | + | | | | | | | + | | ,--> infix2 --| | | + v v | | | | | + prefix -(c)-(+)-> infix3 --' | | + | | ^ | + | | | | + |---------------> suffix1 -->--| | + | | ^ | | + | | | | | + |---------------> suffix2 ----@{1@}------> @{exit@} --> (c) + | | ^ | | + | | | | v + | | | | | + |---------------> suffix3 -------------> @{exit@} --> (c) + | | ^ | | + | sets | | v + | | maybe | | + | | used | | + | | | | | + | | infix --' | | + | `---> value | | + | ^ | | + | | | | + | hides | | + | | | | + | `--------------------------<---| + | | | + `--> any command --@{2@}-> @{warn@} -->--| | + | | | + |--> @{noop@} -->--| | + | | | + |--> @{call@} -->--' ^ + | | + `------------------> @{exit@} --> (c) +@end example + +This diagram provides more information about the infix value +and also takes external state into account. + +@example + ,----sets--- "anything" + | + v + ,---------> external + | state + | | | + | initialized | ☉‿⚆ + sets from | + | | maybe + | ,----------' used + | | | +(c) | | v + | ,- @{stay@} --|---<-,-<------|-----<-,-<---, +(+) | | | | | | | + | | | v | | | | + | | ,--> infix1 --| | | | + | | | | | | | | | + | | | | v | | | | + | | ,--> infix2 --| | | | + | | | | ^ | | | | + v v | | | | | | | + prefix -(c)-(+)-> infix3 --' | | | + | | ^ | ^ | + | | | v | | + |---------------> suffix1 -->--| | + | | | ^ | | | + | | | | v | | + |---------------> suffix2 ----@{1@}------> @{exit@} --> (c) + | | | ^ | | | + | | | | | | v + | | | | v | | + |---------------> suffix3 -------------> @{exit@} --> (c) + | | | ^ | | + | sets | | | v + | | initalized maybe | | + | | from used | | + | | | | | | + | | `-- infix --' | | + | `---> value -----------------------------> persistent + | ^ ^ | | across + | | | | | invocations -, + | hides | | | | + | | `----------------------------------------------' + | | | | + | `--------------------------<---| + | | | + `--> any command --@{2@}-> @{warn@} -->--| | + | | | + |--> @{noop@} -->--| | + | | | + |--> @{call@} -->--' ^ + | | + `------------------> @{exit@} --> (c) +@end example + +@itemize +@item +@code{@{1@}} Transients can be configured to be exited when a suffix command +is invoked. The default is to do so for all suffixes expect for +those that are common to all transients and which are used to +perform tasks such as providing help and saving the value of the +infix arguments for future invocations. The behavior can also be +specified for individual suffix commands individually and may even +depend on state. + + +@item +@code{@{2@}} Transients can be configured to allow the user to invoke +non-suffix commands. The default is to not allow that and instead +warn the user. +@end itemize + +Despite already being rather complex, even the last diagram leaves out +many details. Most importantly it implies that the decision whether +to remain transient is made later than it actually is made (for the +most part a function on @code{pre-command-hook} is responsible). But such +implementation details are of little relevance to users and are +covered elsewhere. + +@node Comparison With Other Packages +@section Comparison With Other Packages + +@subsection Magit-Popup + +Transient is the successor to Magit-Popup (see @ref{Top,,,magit-popup,}). + +One major difference between these two implementations of the same +ideas is that while Transient uses transient keymaps and embraces the +command-loop, Magit-Popup implemented an inferior mechanism that does +not use transient keymaps and that instead of using the command-loop +implements a naive alternative based on @code{read-char}. + +Magit-Popup does not use classes and generic functions and defining a +new command type is near impossible as it involves adding hard-coded +special-cases to many functions. Because of that only a single new +type was added, which was not already part of Magit-Popup's initial +release. + +A lot of things are hard-coded in Magit-Popup. One random example is +that the key bindings for switches must begin with "-" and those for +options must begin with "=". + +@subsection Hydra + +Hydra (see @uref{https://github.com/abo-abo/hydra}) is another package that +provides features similar to those of Transient. + +Both packages use transient keymaps to make a set of commands +temporarily available and the @code{lv} library to show these commands in the +echo area. (The author of Hydra is also the author of @code{lv}, which is +maintained in the same repository.) + +A Hydra "body" is equivalent to a Transient "prefix" and a Hydra +"head" is equivalent to a Transient "suffix". Hydra has no equivalent +of a Transient "infix". + +Both hydras and transients can be used as simple command dispatchers. +Used like this they are similar to regular prefix commands and prefix +keys, except that the available commands are shown in the echo area. + +(Another package that does this is @code{which-key}. It does so automatically +for any incomplete key sequence. The advantage of that approach is +that no additional work is necessary; the disadvantage is that the +available commands are not organized semantically.) + +Both Hydra and Transient provide features that go beyond simple +command dispatchers: + +@itemize +@item +Invoking a command from a hydra does not necessarily exit the hydra. +That makes it possible to invoke the same command again, but using a +shorter key sequence (i.e. the key that was used to enter the hydra +does not have to be pressed again). + +Transient supports that too, but for not this feature is not a focus +and the interface is a bit more complicated. A very basic example +using the current interface: + +@lisp +(define-transient-command outline-navigate () + :transient-suffix 'transient--do-stay + :transient-non-suffix 'transient--do-warn + [("p" "next visible heading" outline-previous-visible-heading) + ("n" "next visible heading" outline-next-visible-heading)]) +@end lisp + + +@item +Transient support infix arguments; values that are set by infix +commands and then consumed by the invoked suffix command(s). + +To my knowledge, Hydra does not support that. +@end itemize + +Both packages make it possible to specify how exactly the available +commands are outlined: + +@itemize +@item +With Hydra this is often done using an explicit format string, which +gives authors a lot of flexibility and makes it possible to do fancy +things. + +The downside of this is that it becomes harder for a user to add +additional commands to an existing hydra and to change key bindings. + + +@item +Transient allows the author of a transient to organize the commands +into groups and the use of generic functions allows authors of +transients to control exactly how a certain command type is +displayed. + +However while Transient support giving sections a heading it does +not currently support giving the displayed information more +structure by, for example, using box-drawing characters. + +That could be implemented by defining a new group class, which lets +the author specify a format string. It should be possible to +implement that without modifying any existing code, but it does not +currently exist. +@end itemize + +@node Keystroke Index +@appendix Keystroke Index + +@printindex ky + +@node Command Index +@appendix Command Index + +@printindex cp + +@node Function Index +@appendix Function Index + +@printindex fn + +@node Variable Index +@appendix Variable Index + +@printindex vr + +@bye diff --git a/lisp/Makefile b/lisp/Makefile new file mode 100644 index 0000000..280b317 --- /dev/null +++ b/lisp/Makefile @@ -0,0 +1,45 @@ +-include ../config.mk +include ../default.mk + +lisp: $(ELCS) loaddefs + +loaddefs: $(PKG)-autoloads.el + +%.elc: %.el + @printf "Compiling $<\n" + @$(EMACS) -Q --batch $(EMACS_ARGS) \ + $(LOAD_PATH) --funcall batch-byte-compile $< + +CLEAN = $(ELCS) $(PKG)-autoloads.el + +clean: + @printf "Cleaning...\n" + @rm -rf $(CLEAN) + +define LOADDEFS_TMPL +;;; $(PKG)-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name \ +(or (file-name-directory #$$) (car load-path)))) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; $(PKG)-autoloads.el ends here +endef +export LOADDEFS_TMPL +#' + +$(PKG)-autoloads.el: $(ELS) + @printf "Generating $@\n" + @printf "%s" "$$LOADDEFS_TMPL" > $@ + @$(EMACS) -Q --batch --eval "(progn\ + (setq make-backup-files nil)\ + (setq vc-handled-backends nil)\ + (setq default-directory (file-truename default-directory))\ + (setq generated-autoload-file (expand-file-name \"$@\"))\ + (setq find-file-visit-truename t)\ + (update-directory-autoloads default-directory))" diff --git a/lisp/transient.el b/lisp/transient.el new file mode 100644 index 0000000..74d5bdf --- /dev/null +++ b/lisp/transient.el @@ -0,0 +1,2583 @@ +;;; transient.el --- Transient commands -*- lexical-binding: t; -*- + +;; Copyright (C) 2018-2019 Jonas Bernoulli + +;; Author: Jonas Bernoulli <jonas@bernoul.li> +;; Homepage: https://github.com/magit/transient +;; Package-Requires: ((emacs "25.1") (dash "2.15.0") (lv "0.14.0")) +;; Keywords: bindings + +;; This file is not part of GNU Emacs. + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published +;; by the Free Software Foundation; either version 3 of the License, +;; or (at your option) any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; For a full copy of the GNU GPL see http://www.gnu.org/licenses. + +;;; Commentary: + +;; Taking inspiration from prefix keys and prefix arguments, Transient +;; implements a similar abstraction involving a prefix command, infix +;; arguments and suffix commands. We could call this abstraction a +;; "transient command", but because it always involves at least two +;; commands (a prefix and a suffix) we prefer to call it just a +;; "transient". + +;; When the user calls a transient prefix command, then a transient +;; (temporary) keymap is activated, which binds the transient's infix +;; and suffix commands, and functions that control the transient state +;; are added to `pre-command-hook' and `post-command-hook'. The +;; available suffix and infix commands and their state are shown in +;; the echo area until the transient is exited by invoking a suffix +;; command. + +;; Calling an infix command causes its value to be changed, possibly +;; by reading a new value in the minibuffer. + +;; Calling a suffix command usually causes the transient to be exited +;; but suffix commands can also be configured to not exit the +;; transient state. + +;;; Code: + +(require 'cl-lib) +(require 'dash) +(require 'eieio) +(require 'format-spec) +(require 'lv) + +(eval-when-compile + (require 'subr-x)) + +(declare-function info 'info) +(declare-function Man-find-section 'man) +(declare-function Man-next-section 'man) + +;;; Options + +(defgroup transient nil + "Transient commands." + :group 'bindings) + +(defcustom transient-show-popup t + "Whether to show the current transient in the echo area. + +If t, then show the popup as soon as a transient command is +invoked. If nil, then do not show the popup unless the user +explicitly requests it, by pressing the prefix \"C-x\". If a +number, then show the popup after this many seconds of inactivity +or when the user explicitly requests it." + :package-version '(transient . "0.1.0") + :group 'transient + :type '(choice (const :tag "instantly" t) + (const :tag "on demand" nil) + (number :tag "after delay" 1))) + +(defcustom transient-show-common-commands nil + "Whether to show common transient commands in the echo area. + +These commands are always shown after typing the prefix key +\"C-x\" when a transient command is active. To toggle the value +of this variable use \"C-x t\" when a transient is active." + :package-version '(transient . "0.1.0") + :group 'transient + :type 'boolean) + +(defcustom transient-highlight-mismatched-keys nil + "Whether to highlight keys that do not match their argument. + +This only affects infix arguments that represent command-line +arguments. When this option is non-nil, then the key binding +for infix argument are highlighted when only a long argument +(e.g. \"--verbose\") is specified but no shor-thand (e.g \"-v\"). +In the rare case that a short-hand is specified but does not +match the key binding, then it is highlighed differently. + +The highlighting is done using using `transient-mismatched-key' +and `transient-nonstandard-key'." + :package-version '(transient . "0.1.0") + :group 'transient + :type 'boolean) + +(defcustom transient-substitute-key-function nil + "Function used to modify key bindings. + +This function is called with one argument, the prefix object, +and must return a key binding description, either the existing +key description it finds in the `key' slot, or a substitution. + +This is intended to let users replace certain prefix keys, but +while discouraged, it could also be used to make other +substitutions, but that is discouraged. + +For example, \"=\" is hard to reach using my custom keyboard +layout, so I substitute \"(\" for that, which is easy to reach +using a layout optimized for lisp. + + (setq transient-substitute-key-function + (lambda (obj) + (let ((key (oref obj key))) + (if (string-match \"\\\\`\\\\(=\\\\)[a-zA-Z]\" key) + (replace-match \"(\" t t key 1) + key)))))" + :package-version '(transient . "0.1.0") + :group 'transient + :type '(choice (const :tag "Transform no keys (nil)" nil) function)) + +(defcustom transient-detect-key-conflicts nil + "Whether to detect key binding conflicts. + +Conflicts are detected when a transient prefix command is invoked +and results in an error, which prevents the transient from being +used." + :package-version '(transient . "0.1.0") + :group 'transient + :type 'boolean) + +(defcustom transient-default-level 4 + "Control what suffix levels are made available by default. + +Each suffix command is placed on a level and each prefix command +has a level, which controls which suffix commands are available. +Integers between 1 and 7 (inclusive) are valid levels. + +The levels of individual transients and/or their individual +suffixes can be changed individually, by invoking the prefix and +then pressing \"C-x l\". + +The default level for both transients and their suffixes is 4. +This option only controls the default for transients. The default +suffix level is always 4. The author of a transient should place +certain suffixes on a higher level if they expect that it won't be +of use to most users, and they should place very important suffixes +on a lower level so that the remain available even if the user +lowers the transient level. + +\(Magit currently places nearly all suffixes on level 4 and lower +levels are not used at all yet. So for the time being you should +not set a lower level here and using a higher level might not +give you as many additional suffixes as you hoped.)" + :package-version '(transient . "0.1.0") + :group 'transient + :type '(choice (const :tag "1 - fewest suffixes" 1) + (const 2) + (const 3) + (const :tag "4 - default" 4) + (const 5) + (const 6) + (const :tag "7 - most suffixes" 7))) + +(defcustom transient-levels-file + (locate-user-emacs-file (convert-standard-filename "transient/levels.el")) + "File used to save levels of transients and their suffixes." + :package-version '(transient . "0.1.0") + :group 'transient + :type 'file) + +(defcustom transient-values-file + (locate-user-emacs-file (convert-standard-filename "transient/values.el")) + "File used to save values of transients." + :package-version '(transient . "0.1.0") + :group 'transient + :type 'file) + +(defcustom transient-history-file + (locate-user-emacs-file (convert-standard-filename "transient/history.el")) + "File used to save history of transients and their infixes." + :package-version '(transient . "0.1.0") + :group 'transient + :type 'file) + +(defcustom transient-history-limit 10 + "Number of history elements to keep when saving to file." + :package-version '(transient . "0.1.0") + :group 'transient + :type 'integer) + +(defcustom transient-save-history t + "Whether to save history of transient commands when exiting Emacs." + :package-version '(transient . "0.1.0") + :group 'transient + :type 'boolean) + +;;; Faces + +(defgroup transient-faces nil + "Faces used by Transient." + :group 'transient) + +(defface transient-heading '((t :inherit font-lock-keyword-face)) + "Face used for headings." + :group 'transient-faces) + +(defface transient-key '((t :inherit font-lock-builtin-face)) + "Face used for keys." + :group 'transient-faces) + +(defface transient-argument '((t :inherit font-lock-warning-face)) + "Face used for enabled arguments." + :group 'transient-faces) + +(defface transient-value '((t :inherit font-lock-string-face)) + "Face used for values." + :group 'transient-faces) + +(defface transient-inactive-argument '((t :inherit shadow)) + "Face used for inactive arguments." + :group 'transient-faces) + +(defface transient-inactive-value '((t :inherit shadow)) + "Face used for inactive values." + :group 'transient-faces) + +(defface transient-unreachable '((t :inherit shadow)) + "Face used for suffixes unreachable from the current prefix sequence." + :group 'transient-faces) + +(defface transient-unreachable-key '((t :inherit shadow)) + "Face used for keys unreachable from the current prefix sequence." + :group 'transient-faces) + +(defface transient-nonstandard-key '((t :underline t)) + "Face optionally used to highlight keys conflicting with short-argument. +Also see option `transient-highlight-mismatched-keys'." + :group 'transient-faces) + +(defface transient-mismatched-key '((t :underline t)) + "Face optionally used to highlight keys without a short-argument. +Also see option `transient-highlight-mismatched-keys'." + :group 'transient-faces) + +(defface transient-enabled-suffix + '((t :background "green" :foreground "black" :weight bold)) + "Face used for enabled levels while editing suffix levels. +See info node `(transient)Enabling and Disabling Suffixes'." + :group 'transient-faces) + +(defface transient-disabled-suffix + '((t :background "red" :foreground "black" :weight bold)) + "Face used for disables levels while editing suffix levels. +See info node `(transient)Enabling and Disabling Suffixes'." + :group 'transient-faces) + +;;; Persistence + +(defun transient--read-file-contents (file) + (with-demoted-errors "Transient error: %S" + (and (file-exists-p file) + (with-temp-buffer file + (insert-file-contents file) + (read (current-buffer)))))) + +(defvar transient-values + (transient--read-file-contents transient-values-file) + "Values of transient commands. +The value of this variable persists between Emacs sessions +and you usually should not change it manually.") + +(defun transient-save-values () + (make-directory (file-name-directory transient-values-file) t) + (setq transient-values (cl-sort transient-values #'string< :key #'car)) + (with-temp-file transient-values-file + (insert (pp-to-string transient-values)))) + +(defvar transient-levels + (transient--read-file-contents transient-levels-file) + "Levels of transient commands. +The value of this variable persists between Emacs sessions +and you usually should not change it manually.") + +(defun transient-save-levels () + (make-directory (file-name-directory transient-levels-file) t) + (setq transient-levels (cl-sort transient-levels #'string< :key #'car)) + (with-temp-file transient-levels-file + (insert (pp-to-string transient-levels)))) + +(defvar transient-history + (transient--read-file-contents transient-history-file) + "History of transient commands and infix arguments. +The value of this variable persists between Emacs sessions +(unless `transient-save-history' is nil) and you usually +should not change it manually.") + +(defun transient-save-history () + (make-directory (file-name-directory transient-history-file) t) + (setq transient-history + (cl-sort (mapcar (pcase-lambda (`(,key . ,val)) + (cons key (-take transient-history-limit + (delete-dups val)))) + transient-history) + #'string< :key #'car)) + (with-temp-file transient-history-file + (insert (pp-to-string transient-history)))) + +(defun transient-maybe-save-history () + "Save the value of `transient-history'. +If `transient-save-history' is nil, then do nothing." + (when transient-save-history + (transient-save-history))) + +(unless noninteractive + (add-hook 'kill-emacs-hook 'transient-maybe-save-history)) + +;;; Classes +;;;; Prefix + +(defclass transient-prefix () + ((prototype :initarg :prototype) + (command :initarg :command) + (level :initarg :level) + (variable :initarg :variable :initform nil) + (value :initarg :value :initform nil) + (scope :initarg :scope :initform nil) + (history :initarg :history :initform nil) + (history-pos :initarg :history-pos :initform 0) + (man-page :initarg :man-page :initform nil) + (info-manual :initarg :info-manual :initform nil) + (transient-suffix :initarg :transient-suffix :initform nil) + (transient-non-suffix :initarg :transient-non-suffix :initform nil)) + "Transient prefix command. + +Each transient prefix command consists of a command, which is +stored in a symbols function slot and an object, which is stored +in the `transient--prefix' property of the same object. + +When a transient prefix command is invoked, then a clone of that +object is stored in the global variable `transient--prefix' and +the prototype is stored in the clones `prototype' slot.") + +;;;; Suffix + +(defclass transient-child () + ((level + :initarg :level + :initform 1 + :documentation "Enable if level of prefix is equal or greater.") + (if + :initarg :if + :initform nil + :documentation "Enable if predicate returns non-nil.") + (if-not + :initarg :if-not + :initform nil + :documentation "Enable if predicate returns nil.") + (if-non-nil + :initarg :if-non-nil + :initform nil + :documentation "Enable if variable's value is non-nil.") + (if-nil + :initarg :if-nil + :initform nil + :documentation "Enable if variable's value is nil.") + (if-mode + :initarg :if-mode + :initform nil + :documentation "Enable if major-mode matches value.") + (if-not-mode + :initarg :if-not-mode + :initform nil + :documentation "Enable if major-mode does not match value.") + (if-derived + :initarg :if-derived + :initform nil + :documentation "Enable if major-mode derives from value.") + (if-not-derived + :initarg :if-not-derived + :initform nil + :documentation "Enable if major-mode does not derive from value.")) + "Abstract superclass for group and and suffix classes. + +It is undefined what happens if more than one `if*' predicate +slot is non-nil." + :abstract t) + +(defclass transient-suffix (transient-child) + ((key :initarg :key) + (command :initarg :command) + (transient :initarg :transient) + (format :initarg :format :initform " %k %d") + (description :initarg :description :initform nil)) + "Superclass for suffix command.") + +(defclass transient-infix (transient-suffix) + ((transient :initform t) + (argument :initarg :argument) + (shortarg :initarg :shortarg) + (value :initform nil) + (multi-value :initarg :multi-value :initform nil) + (allow-empty :initarg :allow-empty :initform nil) + (history-key :initarg :history-key :initform nil) + (reader :initarg :reader :initform nil) + (prompt :initarg :prompt :initform nil) + (choices :initarg :choices :initform nil) + (format :initform " %k %d (%v)")) + "Transient infix command." + :abstract t) + +(defclass transient-argument (transient-infix) () + "Abstract superclass for infix arguments." + :abstract t) + +(defclass transient-switch (transient-argument) () + "Class used for command-line argument that can be turned on and off.") + +(defclass transient-option (transient-argument) () + "Class used for command-line argument that can take a value.") + +(defclass transient-variable (transient-infix) + ((variable :initarg :variable) + (format :initform " %k %d %v")) + "Abstract superclass for infix commands that set a variable." + :abstract t) + +(defclass transient-switches (transient-argument) + ((argument-format :initarg :argument-format) + (argument-regexp :initarg :argument-regexp)) + "Class used for sets of mutually exclusive command-line switches.") + +(defclass transient-files (transient-infix) () + "Class used for the \"--\" argument. +All remaining arguments are treated as files. +They become the value of this this argument.") + +;;;; Group + +(defclass transient-group (transient-child) + ((suffixes :initarg :suffixes :initform nil) + (hide :initarg :hide :initform nil) + (description :initarg :description :initform nil)) + "Abstract superclass of all group classes." + :abstract t) + +(defclass transient-column (transient-group) () + "Group class that displays each element on a separate line.") + +(defclass transient-row (transient-group) () + "Group class that displays all elements on a single line.") + +(defclass transient-columns (transient-group) () + "Group class that displays elements organized in columns. +Direct elements have to be groups whose elements have to be +commands or string. Each subgroup represents a column. This +class takes care of inserting the subgroups' elements.") + +(defclass transient-subgroups (transient-group) () + "Group class that wraps other groups. + +Direct elements have to be groups whose elements have to be +commands or strings. This group inserts an empty line between +subgroups. The subgroups are responsible for displaying their +elements themselves.") + +;;; Define + +(defmacro define-transient-command (name arglist &rest args) + "Define NAME as a transient prefix command. + +ARGLIST are the arguments that command takes. +DOCSTRING is the documentation string and is optional. + +These arguments can optionally be followed by key-value pairs. +Each key has to be a keyword symbol, either `:class' or a keyword +argument supported by the constructor of that class. The +`transient-prefix' class is used if the class is not specified +explicitly. + +GROUPs add key bindings for infix and suffix commands and specify +how these bindings are presented in the echo area. At least one +GROUP has to be specified. See info node `(transient)Binding +Suffix and Infix Commands'. + +The BODY is optional. If it is omitted, then ARGLIST is also +ignored and the function definition becomes: + + (lambda () + (interactive) + (transient-setup \\='NAME)) + +If BODY is specified, then it must begin with an `interactive' +form that matches ARGLIST, and it must call `transient-setup'. +It may however call that function only when some condition is +satisfied; that is one of the reason why you might want to use +an explicit BODY. + +All transients have a (possibly nil) value, which is exported +when suffix commands are called, so that they can consume that +value. For some transients it might be necessary to have a sort +of secondary value, called a scope. Such a scope would usually +be set in the commands `interactive' form and has to be passed +to the setup function: + + (transient-setup \\='NAME nil nil :scope SCOPE) + +\(fn NAME ARGLIST [DOCSTRING] [KEYWORD VALUE]... GROUP... [BODY...])" + (declare (debug (&define name lambda-list + [&optional lambda-doc] + [&rest keywordp sexp] + [&rest vectorp] + [&optional ("interactive" interactive) def-body]))) + (pcase-let ((`(,class ,slots ,suffixes ,docstr ,body) + (transient--expand-define-args args))) + `(progn + (defalias ',name + ,(if body + `(lambda ,arglist ,@body) + `(lambda () + (interactive) + (transient-setup ',name)))) + (put ',name 'function-documentation ,docstr) + (put ',name 'transient--prefix + (,(or class 'transient-prefix) :command ',name ,@slots)) + (put ',name 'transient--layout + ',(cl-mapcan (lambda (s) (transient--parse-child name s)) + suffixes))))) + +(defmacro define-suffix-command (name arglist &rest args) + "Define NAME as a transient suffix command. + +ARGLIST are the arguments that the command takes. +DOCSTRING is the documentation string and is optional. + +These arguments can optionally be followed by key-value pairs. +Each key has to be a keyword symbol, either `:class' or a +keyword argument supported by the constructor of that class. +The `transient-suffix' class is used if the class is not +specified explicitly. + +The BODY must begin with an `interactive' form that matches +ARGLIST. Use the function `transient-args' or the low-level +variable `current-transient-suffixes' if the former does not +give you all the required details. This should, but does not +necessarily have to be, done inside the `interactive' form; +just like for `prefix-arg' and `current-prefix-arg'. + +\(fn NAME ARGLIST [DOCSTRING] [KEYWORD VALUE]... BODY...)" + (declare (debug (&define name lambda-list + [&optional lambda-doc] + [&rest keywordp sexp] + ("interactive" interactive) + def-body))) + (pcase-let ((`(,class ,slots ,_ ,docstr ,body) + (transient--expand-define-args args))) + `(progn + (defalias ',name (lambda ,arglist ,@body)) + (put ',name 'function-documentation ,docstr) + (put ',name 'transient--suffix + (,(or class 'transient-suffix) :command ',name ,@slots))))) + +(defmacro define-infix-command (name _arglist &rest args) + "Define NAME as a transient infix command. + +ARGLIST is always ignored and reserved for future use. +DOCSTRING is the documentation string and is optional. + +The key-value pairs are mandatory. All transient infix commands +are equal to each other (but not eq), so it is meaningless to +define an infix command without also setting at least `:class' +and one other keyword (which it is depends on the used class, +usually `:argument' or `:variable'). + +Each key has to be a keyword symbol, either `:class' or a keyword +argument supported by the constructor of that class. The +`transient-switch' class is used if the class is not specified +explicitly. + +The function definitions is always: + + (lambda (obj value) + (interactive + (let ((obj (transient-suffix-object))) + (list obj (transient-infix-read obj)))) + (transient-infix-set obj value) + (transient--show)) + +`transient-infix-read' and `transient-infix-set' are generic +functions. Different infix commands behave differently because +the concrete methods are different for different infix command +classes. In rare case the above command function might not be +suitable, even if you define your own infix command class. In +that case you have to use `transient-suffix-command' to define +the infix command and use t as the value of the `:transient' +keyword. + +\(fn NAME ARGLIST [DOCSTRING] [KEYWORD VALUE]...)" + (declare (debug (&define name lambda-list + [&optional lambda-doc] + [&rest keywordp sexp]))) + (pcase-let ((`(,class ,slots ,_ ,docstr ,_) + (transient--expand-define-args args))) + `(progn + (defalias ',name ,(transient--default-infix-command)) + (put ',name 'function-documentation ,docstr) + (put ',name 'transient--suffix + (,(or class 'transient-switch) :command ',name ,@slots))))) + +(defalias 'define-infix-argument 'define-infix-command + "Define NAME as a transient infix command. + +Only use this alias to define an infix command that actually +sets an infix argument. To define a infix command that, for +example, sets a variable use `define-infix-command' instead. + +\(fn NAME ARGLIST [DOCSTRING] [KEYWORD VALUE]...)") + +(defun transient--expand-define-args (args) + (let (class keys suffixes docstr) + (when (stringp (car args)) + (setq docstr (pop args))) + (while (keywordp (car args)) + (let ((k (pop args)) + (v (pop args))) + (if (eq k :class) + (setq class v) + (push k keys) + (push v keys)))) + (while (vectorp (car args)) + (push (pop args) suffixes)) + (list (if (eq (car-safe class) 'quote) + (cadr class) + class) + (nreverse keys) + (nreverse suffixes) + docstr + args))) + +(defun transient--parse-child (prefix spec) + (cl-etypecase spec + (vector (when-let ((c (transient--parse-group prefix spec))) (list c))) + (list (when-let ((c (transient--parse-suffix prefix spec))) (list c))) + (string (list spec)) + (integer (list spec)))) + +(defun transient--parse-group (prefix spec) + (setq spec (append spec nil)) + (cl-symbol-macrolet + ((car (car spec)) + (pop (pop spec))) + (let (level class args) + (when (integerp car) + (setq level pop)) + (when (stringp car) + (setq args (plist-put args :description pop))) + (while (keywordp car) + (let ((k pop)) + (if (eq k :class) + (setq class pop) + (setq args (plist-put args k pop))))) + (vector (or level (oref-default 'transient-child level)) + (or class + (if (vectorp car) + 'transient-columns + 'transient-column)) + args + (cl-mapcan (lambda (s) (transient--parse-child prefix s)) spec))))) + +(defun transient--parse-suffix (prefix spec) + (let (level class args) + (cl-symbol-macrolet + ((car (car spec)) + (pop (pop spec))) + (when (integerp car) + (setq level pop)) + (when (or (stringp car) + (vectorp car)) + (setq args (plist-put args :key pop))) + (when (or (stringp car) + (eq (car-safe car) 'lambda) + (and (symbolp car) + (not (commandp car)) + (commandp (cadr spec)))) + (setq args (plist-put args :description pop))) + (cond + ((keywordp car) + (error "Need command, got %S" car)) + ((symbolp car) + (setq args (plist-put args :command pop))) + ((or (stringp car) + (and car (listp car))) + (let ((arg pop)) + (cl-typecase arg + (list + (setq args (plist-put args :shortarg (car arg))) + (setq args (plist-put args :argument (cadr arg))) + (setq arg (cadr arg))) + (string + (when-let ((shortarg (transient--derive-shortarg arg))) + (setq args (plist-put args :shortarg shortarg))) + (setq args (plist-put args :argument arg)))) + (setq args (plist-put args :command + (intern (format "transient:%s:%s" + prefix arg)))) + (cond ((and car (not (keywordp car))) + (setq class 'transient-option) + (setq args (plist-put args :reader pop))) + ((not (string-suffix-p "=" arg)) + (setq class 'transient-switch)) + (t + (setq class 'transient-option) + (setq args (plist-put args :reader 'read-string)))))) + (t + (error "Needed command or argument, got %S" car))) + (while (keywordp car) + (let ((k pop)) + (if (eq k :class) + (setq class pop) + (setq args (plist-put args k pop)))))) + (unless (plist-get args :key) + (when-let ((shortarg (plist-get args :shortarg))) + (setq args (plist-put args :key shortarg)))) + (list (or level (oref-default 'transient-child level)) + (or class 'transient-suffix) + args))) + +(defun transient--default-infix-command () + (cons 'lambda '((obj value) + (interactive + (let ((obj (transient-suffix-object))) + (list obj (transient-infix-read obj)))) + (transient-infix-set obj value) + (transient--show)))) + +(defun transient--ensure-infix-command (obj) + (let ((cmd (oref obj command))) + (unless (or (commandp cmd) + (get cmd 'transient--infix-command)) + (put cmd 'transient--infix-command + (transient--default-infix-command))))) + +(defun transient--derive-shortarg (arg) + (save-match-data + (and (string-match "\\`\\(-[a-zA-Z]\\)\\(\\'\\|=\\)" arg) + (match-string 1 arg)))) + +;;; Edit + +(defun transient--insert-suffix (prefix loc suffix action) + (let* ((suf (transient--parse-suffix prefix suffix)) + (mem (transient--layout-member prefix loc))) + (if mem + (progn + (when-let ((old (transient--layout-member + prefix (plist-get (nth 2 suf) :command)))) + (setcar old (cadr old)) + (setcdr old (cddr old)) + (setq mem (transient--layout-member prefix loc))) + (cl-ecase action + (insert (setcdr mem (cons (car mem) (cdr mem))) + (setcar mem suf)) + (append (setcdr mem (cons suf (cdr mem)))) + (replace (setcar mem suf)))) + (message "Cannot insert %S into %s; %s not found" suffix prefix loc)))) + +(defun transient-insert-suffix (prefix loc suffix) + "Insert a SUFFIX into PREFIX before LOC. +PREFIX is a prefix command, a symbol. +SUFFIX is a suffix command specification list of the + same form as expected by `define-transient-command'. +LOC is a command or a key vector or a key description + (a string as returned by `key-description')." + (declare (indent defun)) + (transient--insert-suffix prefix loc suffix 'insert)) + +(defun transient-append-suffix (prefix loc suffix) + "Insert a SUFFIX into PREFIX after LOC. +PREFIX is a prefix command, a symbol. +SUFFIX is a suffix command specification list of the + same form as expected by `define-transient-command'. +LOC is a command, a key vector or a key description + (a string as returned by `key-description')." + (declare (indent defun)) + (transient--insert-suffix prefix loc suffix 'append)) + +(defun transient-replace-suffix (prefix loc suffix) + "Replace the suffix at LOC in PREFIX with SUFFIX. +PREFIX is a prefix command, a symbol. +SUFFIX is a suffix command specification list of the + same form as expected by `define-transient-command'. +LOC is a command, a key vector or a key description + (a string as returned by `key-description')." + (declare (indent defun)) + (transient--insert-suffix prefix loc suffix 'replace)) + +(defun transient-remove-suffix (prefix loc) + "Remove the suffix at LOC in PREFIX. +PREFIX is a prefix command, a symbol. +LOC is a command, a key vector or a key description + (a string as returned by `key-description')." + (declare (indent defun)) + (when-let ((mem (transient--layout-member prefix loc))) + (setcar mem (cadr mem)) + (setcdr mem (cddr mem)))) + +(defun transient-get-suffix (prefix loc) + "Return the suffix at LOC from PREFIX. +PREFIX is a prefix command, a symbol. +LOC is a command, a key vector or a key description + (a string as returned by `key-description')." + (if-let ((mem (transient--layout-member prefix loc))) + (car mem) + (error "%s not found in %s" loc prefix))) + +(defun transient-suffix-put (prefix loc prop value) + "Edit the suffix at LOC in PREFIX, setting PROP to VALUE. +PREFIX is a prefix command, a symbol. +LOC is a command, a key vector or a key description + (a string as returned by `key-description'). +PROP has to be a keyword. What keywords and values + are valid depends on the type of the suffix." + (let ((elt (transient-get-suffix prefix loc))) + (setf (nth 2 elt) + (plist-put (nth 2 elt) prop value)))) + +(defun transient--layout-member (prefix loc) + (if-let ((layout (get prefix 'transient--layout))) + (cl-labels + ((key (loc) + (when (vectorp loc) + (setq loc (key-description loc))) + (when (stringp loc) + (setq loc (kbd loc))) + loc) + (mem (layout loc) + (cond + ((and (listp layout) + (vectorp (car layout))) + (--any (mem it loc) layout)) + ((vectorp layout) + (if (vectorp (car (aref layout 3))) + (--any (mem it loc) + (aref layout 3)) + (cl-member-if (lambda (suffix) + (mem suffix loc)) + (aref layout 3)))) + ((and (listp layout) + (if (symbolp loc) + (eq (plist-get (nth 2 layout) :command) loc) + (equal (key (plist-get (nth 2 layout) :key)) loc))) + layout)))) + (mem layout (key loc))) + (error "%s is not a transient command" prefix))) + +;;; Variables + +(defvar current-transient-prefix nil + "The transient from which this suffix command was invoked. +This is an object representing that transient, use +`current-transient-command' to get the respective command.") + +(defvar current-transient-command nil + "The transient from which this suffix command was invoked. +This is a symbol representing that transient, use +`current-transient-object' to get the respective object.") + +(defvar current-transient-suffixes nil + "The suffixes of the transient from which this suffix command was invoked. +This is a list of objects. Usually it is sufficient to instead +use the function `transient-args', which returns a list of +values. In complex cases it might be necessary to use this +variable instead.") + +(defvar post-transient-hook nil + "Hook run after exiting a transient.") + +(defvar transient--prefix nil) +(defvar transient--layout nil) +(defvar transient--suffixes nil) + +(defconst transient--stay t "Do not exist the transient.") +(defconst transient--exit nil "Do exit the transient.") + +(defvar transient--exitp nil "Whether to exit the transient.") +(defvar transient--showp nil "Whether the transient is show in echo area.") +(defvar transient--helpp nil "Whether help-mode is active.") +(defvar transient--editp nil "Whether edit-mode is active.") + +(defvar transient--timer nil) + +(defvar transient--stack nil) + +(defvar transient--debug nil "Whether put debug information into *Messages*.") + +(defvar transient--history nil) + +;;; Identities + +(defun transient-suffix-object (&optional command) + "Return the object associated with the current suffix command. + +Each suffix commands is associated with an object, which holds +additional information about the suffix, such as its value (in +the case of an infix command, which is a kind of suffix command). + +This function is intended to be called in the interactive form of +infix commands, whose command definition usually (at least when +defined using `define-infix-command') is this: + + (lambda (obj value) + (interactive + (let ((obj (transient-suffix-object))) + (list obj (transient-infix-read obj)))) + (transient-infix-set obj value) + (transient--show)) + +Such commands need to be able to access their associated object +to guide how `transient-infix-read' reads the new value and to +store the read value. Other suffix commands (including non-infix +commands) may also need the object to guide their behavior. + +This function attempts to return the object associated with the +current suffix command even if the suffix command was not invoked +from a transient. (For some suffix command that is a valid thing +to do, for others it is not.) In that case nil may be returned +if the command was not defined using one of the macros intended +to define such commands. + +The optional argument COMMAND is intended for internal use. If +you are contemplating using it in your own code, then you should +probably use this instead: + + (get COMMAND 'transient--suffix)" + (if transient--prefix + (cl-find-if (lambda (obj) + (eq (transient--suffix-command obj) + (or command this-original-command))) + transient--suffixes) + (when-let ((obj (get (or command this-command) 'transient--suffix)) + (obj (clone obj))) + (transient-init-scope obj) + (transient-init-value obj) + obj))) + +(defun transient--suffix-command (arg) + "Return the command specified by ARG. + +Given a suffix specified by ARG, this function returns the +respective command or a symbol that represents it. It could +therefore be considere the inverse of `transient-suffix-object'. + +Unlike that function it is only intended for internal use though, +and it is more complicated to describe because of some internal +tricks it has to account for. You do not actually have to know +any of this. + +ARG can be a `transient-suffix' object, a symbol representing a +command, or a command (which can be either a fbound symbol or a +lambda expression). + +If it is an object, then the value of its `command' slot is used +as follows. If ARG satisfies `commandp', then that is returned. +Otherwise it is assumed to be a symbol that merely represents the +command. In that case the lambda expression that is stored in +the symbols `transient--infix-command' property is returned. + +Therefore, if ARG is an object, then this function always returns +something that is callable as a command. + +ARG can also be something that is callable as a function. If it +is a symbol, then that is returned. Otherwise it is a lambda +expression and a symbol that merely representing that command is +returned. + +Therefore, if ARG is something that is callable as a command, +then this function always returns a symbol that is, or merely +represents that command. + +The reason that there are \"symbols that merely represent a +command\" is that by avoiding to binding a symbol as a command we +can prevent it from being offered as a completion candidates for +`execute-extended-command'. That is useful for infix arguments, +which usually do not work corretly unless called from a +transient. Unfortunately this only works for infix arguments +that are defined inline in the defintion of of a transient prefix +command; explicitly defined infix arguments continue to polute +the command namespace. It would be better if all this were made +unnecessary by a `execute-extended-command-ignore' symbol property +but unfortunately that does not exist (yet?)." + (if (cl-typep arg 'transient-suffix) + (let ((sym (oref arg command))) + (if (commandp sym) + sym + (get sym 'transient--infix-command))) + (if (symbolp arg) + arg + ;; ARG is an interactive lambda. The symbol returned by this + ;; is not actually a command, just a symbol representing it + ;; for purposes other than invoking it as a command. + (oref (transient-suffix-object) command)))) + +;;; Keymaps + +(defvar transient-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-p") 'universal-argument) + (define-key map (kbd "C--") 'negative-argument) + (define-key map (kbd "C-v") 'transient-show) + (define-key map (kbd "?") 'transient-help) + (define-key map (kbd "C-h") 'transient-help) + (define-key map (kbd "M-p") 'transient-history-prev) + (define-key map (kbd "M-n") 'transient-history-next) + ;; While setting suffix levels `transient-common-commands' + ;; isn't used, making this duplication necessary. + (define-key map (kbd "C-g") 'transient-quit-one) + (define-key map (kbd "C-q") 'transient-quit-all) + (define-key map (kbd "C-z") 'transient-suspend) + map) + "Base keymap used by all transients.") + +(defvar transient-edit-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "?") 'transient-help) + (define-key map (kbd "C-h") 'transient-help) + (define-key map (kbd "C-x l") 'transient-set-level) + (define-key map (kbd "C-g") 'transient-quit-one) + (define-key map (kbd "C-q") 'transient-quit-all) + (define-key map (kbd "C-z") 'transient-suspend) + map) + "Keymap that is active while a transient in is in \"edit mode\".") + +(defvar transient-sticky-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-g") 'transient-quit-seq) + (define-key map (kbd "C-q") 'transient-quit-all) + (define-key map (kbd "C-z") 'transient-suspend) + map) + "Keymap that is active while an incomplete key sequence is active.") + +(defvar transient--common-command-prefixes '(?\C-x)) + +(put 'transient-common-commands + 'transient--layout + (cl-mapcan + (lambda (s) (transient--parse-child 'transient-common-commands s)) + `([:hide (lambda () + (and (not (memq (car transient--redisplay-key) + transient--common-command-prefixes)) + (not transient-show-common-commands))) + ["Value commands" + ("C-x s " "Set" transient-set) + ("C-x C-s" "Save" transient-save) + (,(if (featurep 'jkl) "M-i " "M-p ") + "Previous value" transient-history-prev) + (,(if (featurep 'jkl) "M-k " "M-n ") + "Next value" transient-history-next) + ] + ["Sticky commands" + ;; Like `transient-sticky-map' except that + ;; "C-g" has to be bound to a different command. + ("C-g" "Quit prefix or transient" transient-quit-one) + ("C-q" "Quit transient stack" transient-quit-all) + ("C-z" "Suspend transient stack" transient-suspend)] + ["Customize" + ("C-x t" transient-toggle-common + :description (lambda () + (if transient-show-common-commands + "Hide common commands" + "Show common permanently"))) + ("C-x l" "Show/hide suffixes" transient-set-level)]]))) + +(defvar transient-predicate-map + (let ((map (make-sparse-keymap))) + (define-key map [handle-switch-frame] 'transient--do-suspend) + (define-key map [transient-suspend] 'transient--do-suspend) + (define-key map [transient-help] 'transient--do-stay) + (define-key map [transient-set-level] 'transient--do-stay) + (define-key map [transient-history-prev] 'transient--do-stay) + (define-key map [transient-history-next] 'transient--do-stay) + (define-key map [universal-argument] 'transient--do-stay) + (define-key map [negative-argument] 'transient--do-stay) + (define-key map [transient-quit-all] 'transient--do-quit-all) + (define-key map [transient-quit-one] 'transient--do-quit-one) + (define-key map [transient-quit-seq] 'transient--do-stay) + (define-key map [transient-show] 'transient--do-stay) + (define-key map [transient-update] 'transient--do-stay) + (define-key map [transient-toggle-common] 'transient--do-stay) + (define-key map [transient-set] 'transient--do-call) + (define-key map [transient-save] 'transient--do-call) + (define-key map [describe-key-briefly] 'transient--do-stay) + (define-key map [describe-key] 'transient--do-stay) + map) + "Base keymap used to map common commands to their transient behavior. + +The \"transient behavior\" of a command controls, among other +things, whether invoking the command causes the transient to be +exited or not and whether infix arguments are exported before +doing so. + +Each \"key\" is a command that is common to all transients and +that is bound in `transient-map', `transient-edit-map', +`transient-sticky-map' and/or `transient-common-command'. + +Each binding is a \"pre-command\", a function that controls the +transient behavior of the respective command. + +For transient commands that are bound in individual transients, +the transient behavior is specified using the `:transient' slot +of the corresponding object.") + +(defvar transient--transient-map nil) +(defvar transient--predicate-map nil) +(defvar transient--redisplay-map nil) +(defvar transient--redisplay-key nil) + +(defun transient--push-keymap (map) + (transient--debug " push %s%s" map (if (symbol-value map) "" " VOID")) + (with-demoted-errors "transient--push-keymap: %S" + (internal-push-keymap (symbol-value map) 'overriding-terminal-local-map))) + +(defun transient--pop-keymap (map) + (transient--debug " pop %s%s" map (if (symbol-value map) "" " VOID")) + (with-demoted-errors "transient--pop-keymap: %S" + (internal-pop-keymap (symbol-value map) 'overriding-terminal-local-map))) + +(defun transient--make-transient-map () + (let ((map (make-sparse-keymap))) + (set-keymap-parent map (if transient--editp + transient-edit-map + transient-map)) + (dolist (obj transient--suffixes) + (let ((key (oref obj key))) + (when (vectorp key) + (setq key (key-description key)) + (oset obj key key)) + (when transient-substitute-key-function + (setq key (save-match-data + (funcall transient-substitute-key-function obj))) + (oset obj key key)) + (let ((kbd (kbd key)) + (cmd (transient--suffix-command obj))) + (when-let ((conflict (and transient-detect-key-conflicts + (transient--lookup-key map kbd)))) + (unless (eq cmd conflict) + (error "Cannot bind %S to %s and also %s" + (string-trim key) + cmd conflict))) + (define-key map kbd cmd)))) + map)) + +(defun transient--make-predicate-map () + (let ((map (make-sparse-keymap))) + (set-keymap-parent map transient-predicate-map) + (dolist (obj transient--suffixes) + (let* ((cmd (transient--suffix-command obj)) + (sub-prefix (and (symbolp cmd) (get cmd 'transient--prefix)))) + (unless (lookup-key transient-predicate-map (vector cmd)) + (define-key map (vector cmd) + (if (slot-boundp obj 'transient) + (let ((do (oref obj transient))) + (pcase do + (`t (if sub-prefix + 'transient--do-replace + 'transient--do-stay)) + (`nil 'transient--do-exit) + (_ do))) + (if sub-prefix + 'transient--do-replace + (or (oref transient--prefix transient-suffix) + 'transient--do-exit))))))) + map)) + +(defun transient--make-redisplay-map () + (setq transient--redisplay-key + (cl-case this-command + (transient-update + (setq transient--showp t) + (setq unread-command-events + (listify-key-sequence (this-single-command-raw-keys)))) + (transient-quit-seq + (setq unread-command-events + (butlast (listify-key-sequence + (this-single-command-raw-keys)) + 2)) + (butlast transient--redisplay-key)) + (t nil))) + (let ((topmap (make-sparse-keymap)) + (submap (make-sparse-keymap))) + (when transient--redisplay-key + (define-key topmap (vconcat transient--redisplay-key) submap) + (set-keymap-parent submap transient-sticky-map)) + (map-keymap-internal + (lambda (key def) + (when (and (not (eq key ?\e)) + (listp def) + (keymapp def)) + (define-key topmap (vconcat transient--redisplay-key (list key)) + 'transient-update))) + (if transient--redisplay-key + (lookup-key transient--transient-map (vconcat transient--redisplay-key)) + transient--transient-map)) + topmap)) + +;;; Setup + +(defun transient-setup (&optional name layout edit &rest params) + "Setup the transient specified by NAME. + +This function is called by transient prefix commands to setup the +transient. In that case NAME is mandatory, LAYOUT and EDIT must +be nil and PARAMS may be (but usually is not) used to set e.g. the +\"scope\" of the transient (see `transient-define-prefix'). + +This function is also called internally in which case LAYOUT and +EDIT may be non-nil." + (transient--debug 'setup) + (cond + ((not name) + ;; Switching between regular and edit mode. + (transient--pop-keymap 'transient--transient-map) + (transient--pop-keymap 'transient--redisplay-map) + (setq name (oref transient--prefix command)) + (setq params (list :scope (oref transient--prefix scope)))) + ((not (or layout ; resuming parent/suspended prefix + current-transient-command)) ; entering child prefix + (transient--stack-zap)) ; replace suspended prefix, if any + (edit + ;; Returning from help to edit. + (setq transient--editp t))) + (transient--init-objects name layout params) + (transient--history-init transient--prefix) + (setq transient--predicate-map (transient--make-predicate-map)) + (setq transient--transient-map (transient--make-transient-map)) + (setq transient--redisplay-map (transient--make-redisplay-map)) + (transient--redisplay) + (transient--init-transient) + (transient--suspend-which-key-mode)) + +(defun transient--init-objects (name layout params) + (setq transient--prefix + (let* ((proto (get name 'transient--prefix)) + (clone (apply #'clone proto + :prototype proto + :level (or (alist-get + t (alist-get name transient-levels)) + transient-default-level) + params)) + (value (oref proto value))) + (if (functionp value) + (oset clone value (funcall value)) + (when-let ((saved (assq name transient-values))) + (oset clone value (cdr saved)))) + clone)) + (setq transient--layout + (or layout + (let ((levels (alist-get name transient-levels))) + (cl-mapcan (lambda (c) (transient--init-child levels c)) + (append (get name 'transient--layout) + (and (not transient--editp) + (get 'transient-common-commands + 'transient--layout))))))) + (setq transient--suffixes + (cl-labels ((s (def) + (cl-etypecase def + (integer nil) + (string nil) + (list (cl-mapcan #'s def)) + (transient-group (cl-mapcan #'s (oref def suffixes))) + (transient-suffix (list def))))) + (cl-mapcan #'s transient--layout)))) + +(defun transient--init-child (levels spec) + (cl-etypecase spec + (vector (transient--init-group levels spec)) + (list (transient--init-suffix levels spec)) + (string (list spec)) + (integer (list spec)))) + +(defun transient--init-group (levels spec) + (pcase-let ((`(,level ,class ,args ,children) (append spec nil))) + (when (transient--use-level-p level) + (let ((obj (apply class :level level args))) + (when (transient--use-suffix-p obj) + (when-let ((suffixes + (cl-mapcan (lambda (c) (transient--init-child levels c)) + children))) + (oset obj suffixes suffixes) + (list obj))))))) + +(defun transient--init-suffix (levels spec) + (pcase-let* ((`(,level ,class ,args) spec) + (cmd (plist-get args :command)) + (level (or (alist-get (transient--suffix-command cmd) levels) + level))) + (let ((fn (and (symbolp cmd) + (symbol-function cmd)))) + (when (autoloadp fn) + (transient--debug " autoload %s" cmd) + (autoload-do-load fn))) + (when (transient--use-level-p level) + (let ((obj (if-let ((proto (and cmd + (symbolp cmd) + (get cmd 'transient--suffix)))) + (apply #'clone proto :level level args) + (apply class :level level args)))) + (transient--init-suffix-key obj) + (transient--ensure-infix-command obj) + (when (transient--use-suffix-p obj) + (transient-init-scope obj) + (transient-init-value obj) + (list obj)))))) + +(cl-defmethod transient--init-suffix-key ((obj transient-suffix)) + (unless (slot-boundp obj 'key) + (error "No key for %s" (oref obj command)))) + +(cl-defmethod transient--init-suffix-key ((obj transient-argument)) + (if (cl-typep obj 'transient-switches) + (cl-call-next-method obj) + (unless (slot-boundp obj 'shortarg) + (when-let ((shortarg (transient--derive-shortarg (oref obj argument)))) + (oset obj shortarg shortarg))) + (unless (slot-boundp obj 'key) + (if (slot-boundp obj 'shortarg) + (oset obj key (oref obj shortarg)) + (error "No key for %s" (oref obj command)))))) + +(defun transient--use-level-p (level &optional edit) + (or (and transient--editp (not edit)) + (and (>= level 1) + (<= level (oref transient--prefix level))))) + +(defun transient--use-suffix-p (obj) + (with-slots + (if if-not if-nil if-non-nil if-mode if-not-mode if-derived if-not-derived) + obj + (cond + (if (funcall if)) + (if-not (not (funcall if-not))) + (if-non-nil (symbol-value if-non-nil)) + (if-nil (not (symbol-value if-nil))) + (if-mode (eq major-mode if-mode)) + (if-not-mode (not (eq major-mode if-not-mode))) + (if-derived (derived-mode-p if-derived)) + (if-not-derived (not (derived-mode-p if-not-derived))) + (t)))) + +;;; Flow-Control + +(defun transient--init-transient () + (transient--debug 'init-transient) + (transient--push-keymap 'transient--transient-map) + (transient--push-keymap 'transient--redisplay-map) + (add-hook 'pre-command-hook #'transient--pre-command) + (add-hook 'minibuffer-setup-hook #'transient--minibuffer-setup) + (add-hook 'minibuffer-exit-hook #'transient--minibuffer-exit) + (add-hook 'post-command-hook #'transient--post-command) + (advice-add 'abort-recursive-edit :after #'transient--minibuffer-exit) + (when transient--exitp + ;; This prefix command was invoked as the suffix of another. + ;; Prevent `transient--post-command' from removing the hooks + ;; that we just added. + (setq transient--exitp 'replace))) + +(defun transient--pre-command () + (transient--debug 'pre-command) + (cond + ((memq this-command '(transient-update transient-quit-seq)) + (transient--pop-keymap 'transient--redisplay-map)) + ((and transient--helpp + (not (memq this-command '(transient-quit-one + transient-quit-all)))) + (cond + ((transient-help) + (transient--do-suspend) + (setq this-command 'transient-suspend) + (transient--pre-exit)) + (t + (setq this-command 'transient-undefined)))) + ((and transient--editp + (not (memq this-command '(transient-quit-one + transient-quit-all + transient-help)))) + (setq this-command 'transient-set-level)) + (t + (setq transient--exitp nil) + (when (eq (if-let ((fn (or (lookup-key transient--predicate-map + (vector this-original-command)) + (oref transient--prefix transient-non-suffix)))) + (let ((action (funcall fn))) + (when (eq action transient--exit) + (setq transient--exitp (or transient--exitp t))) + action) + (setq this-command + (let ((keys (this-command-keys-vector))) + (if (eq (aref keys (1- (length keys))) ?\C-g) + 'transient-noop + 'transient-undefined))) + transient--stay) + transient--exit) + (transient--pre-exit))))) + +(defun transient--pre-exit () + (let ((window (selected-window))) + (lv-delete-window) + (select-window window)) + (transient--timer-cancel) + (transient--pop-keymap 'transient--transient-map) + (transient--pop-keymap 'transient--redisplay-map) + (remove-hook 'pre-command-hook #'transient--pre-command) + (unless transient--showp + (message "")) + (setq transient--transient-map nil) + (setq transient--predicate-map nil) + (setq transient--redisplay-map nil) + (setq transient--redisplay-key nil) + (setq transient--showp nil) + (setq transient--helpp nil) + (setq transient--editp nil) + (setq transient--prefix nil) + (setq transient--layout nil) + (setq transient--suffixes nil)) + +(defun transient--export () + (setq current-transient-prefix transient--prefix) + (setq current-transient-command (oref transient--prefix command)) + (setq current-transient-suffixes transient--suffixes) + (transient--history-push)) + +(defun transient--minibuffer-setup () + (transient--debug 'minibuffer-setup) + (unless (> (minibuffer-depth) 1) + (unless transient--exitp + (transient--pop-keymap 'transient--transient-map) + (transient--pop-keymap 'transient--redisplay-map) + (remove-hook 'pre-command-hook #'transient--pre-command)) + (remove-hook 'post-command-hook #'transient--post-command))) + +(defun transient--minibuffer-exit () + (transient--debug 'minibuffer-exit) + (unless (> (minibuffer-depth) 1) + (unless transient--exitp + (transient--push-keymap 'transient--transient-map) + (transient--push-keymap 'transient--redisplay-map) + (add-hook 'pre-command-hook #'transient--pre-command)) + (add-hook 'post-command-hook #'transient--post-command))) + +(defun transient--post-command () + (transient--debug 'post-command) + (if transient--exitp + (progn + (unless (and (eq transient--exitp 'replace) + (or transient--prefix + ;; The current command could act as a prefix, + ;; but decided not to call `transient-setup'. + (prog1 nil (transient--stack-zap)))) + (remove-hook 'minibuffer-setup-hook #'transient--minibuffer-setup) + (remove-hook 'minibuffer-exit-hook #'transient--minibuffer-exit) + (advice-remove 'abort-recursive-edit #'transient--minibuffer-exit) + (remove-hook 'post-command-hook #'transient--post-command)) + (setq current-transient-prefix nil) + (setq current-transient-command nil) + (setq current-transient-suffixes nil) + (let ((resume (and transient--stack + (not (memq transient--exitp '(replace suspend)))))) + (setq transient--exitp nil) + (setq transient--helpp nil) + (setq transient--editp nil) + (run-hooks 'post-transient-hook) + (when resume + (transient--stack-pop)))) + (transient--pop-keymap 'transient--redisplay-map) + (setq transient--redisplay-map (transient--make-redisplay-map)) + (transient--push-keymap 'transient--redisplay-map) + (unless (eq this-command (oref transient--prefix command)) + (transient--redisplay)))) + +(defun transient--stack-push () + (transient--debug 'stack-push) + (push (list (oref transient--prefix command) + transient--layout + transient--editp + :scope (oref transient--prefix scope)) + transient--stack)) + +(defun transient--stack-pop () + (transient--debug 'stack-pop) + (and transient--stack + (prog1 t (apply #'transient-setup (pop transient--stack))))) + +(defun transient--stack-zap () + (transient--debug 'stack-zap) + (setq transient--stack nil)) + +(defun transient--redisplay () + (if (or (eq transient-show-popup t) + transient--showp) + (transient--show) + (when (and (numberp transient-show-popup) + (not transient--timer)) + (transient--timer-start)) + (transient--show-brief))) + +(defun transient--timer-start () + (setq transient--timer + (run-at-time transient-show-popup nil + (lambda () + (transient--timer-cancel) + (transient--show))))) + +(defun transient--timer-cancel () + (when transient--timer + (cancel-timer transient--timer) + (setq transient--timer nil))) + +(defun transient--debug (arg &rest args) + (when transient--debug + (if (symbolp arg) + (message "-- %-16s (cmd: %s, exit: %s)" + arg this-command transient--exitp) + (apply #'message arg args)))) + +(defun transient--emergency-exit () + "Exit the current transient command after an error occured. +Beside being used with `condition-case', this function also has +to be a member of `debugger-mode-hook', else the debugger would +be unusable and exiting it by pressing \"q\" would fail because +the transient command would still be active and that key would +either be unbound or do something else." + (when transient--prefix + (setq transient--stack nil) + (setq transient--exitp t) + (transient--pre-exit) + (transient--post-command))) + +(add-hook 'debugger-mode-hook 'transient--emergency-exit) + +(defmacro transient--with-emergency-exit (&rest body) + (declare (indent defun)) + `(condition-case nil + ,(macroexp-progn body) + (error (transient--emergency-exit)))) + +;;; Pre-Commands + +(defun transient--do-stay () + "Call the command without exporting variables and stay transient." + transient--stay) + +(defun transient--do-noop () + "Call `transient-noop' and stay transient." + (setq this-command 'transient-noop) + transient--stay) + +(defun transient--do-warn () + "Call `transient-undefined' and stay transient." + (setq this-command 'transient-undefined) + transient--stay) + +(defun transient--do-call () + "Call the command after exporting variables and stay transient." + (transient--export) + transient--stay) + +(defun transient--do-exit () + "Call the command after exporting variables and exit the transient." + (transient--export) + (transient--stack-zap) + transient--exit) + +(defun transient--do-replace () + "Call the transient prefix command, replacing the active transient." + (transient--export) + (transient--stack-push) + (setq transient--exitp 'replace) + transient--exit) + +(defun transient--do-suspend () + "Suspend the active transient, saving the transient stack." + (transient--stack-push) + (setq transient--exitp 'suspend) + transient--exit) + +(defun transient--do-quit-one () + "If active, quit help or edit mode, else exit the active transient." + (cond (transient--helpp + (setq transient--helpp nil) + transient--stay) + (transient--editp + (setq transient--editp nil) + (transient-setup) + transient--stay) + (t transient--exit))) + +(defun transient--do-quit-all () + "Exit all transients without saving the transient stack." + (transient--stack-zap) + transient--exit) + +;;; Commands + +(defun transient-noop () + "Do nothing at all." + (interactive)) + +(defun transient-undefined () + "Warn the user that the pressed key is not bound to any suffix." + (interactive) + (message "Unbound suffix: `%s' (Use `%s' to abort, `%s' for help)" + (propertize (key-description (this-single-command-keys)) + 'face 'font-lock-warning-face) + (propertize "C-g" 'face 'transient-key) + (propertize "?" 'face 'transient-key))) + +(defun transient-toggle-common () + "Toggle whether common commands are always shown." + (interactive) + (setq transient-show-common-commands (not transient-show-common-commands))) + +(defun transient-suspend () + "Suspend the current transient. +It can later be resumed using `transient-resume' while no other +transient is active." + (interactive)) + +(defun transient-quit-all () + "Exit all transients without saving the transient stack." + (interactive)) + +(defun transient-quit-one () + "Exit the current transients, possibly returning to the previous." + (interactive)) + +(defun transient-quit-seq () + "Abort the current incomplete key sequence." + (interactive)) + +(defun transient-update () + "Redraw the transient's state in the echo area." + (interactive)) + +(defun transient-show () + "Show the transient's state in the echo area." + (interactive) + (setq transient--showp t)) + +(defvar-local transient--restore-winconf nil) + +(defvar transient-resume-mode) + +(defun transient-help () + "Show help for the active transient or one of its suffixes." + (interactive) + (if (called-interactively-p 'any) + (setq transient--helpp t) + (with-demoted-errors "transient-help: %S" + (when (lookup-key transient--transient-map + (this-single-command-raw-keys)) + (setq transient--helpp nil) + (let ((winconf (current-window-configuration))) + (transient-show-help + (if (eq this-original-command 'transient-help) + transient--prefix + (or (transient-suffix-object) + this-original-command))) + (setq transient--restore-winconf winconf)) + (fit-window-to-buffer nil (frame-height) (window-height)) + (transient-resume-mode) + (message "Type \"q\" to resume transient command.") + t)))) + +(defun transient-set-level (&optional command level) + "Set the level of the transient or one of its suffix commands." + (interactive + (let ((command this-original-command)) + (and (or (not (eq command 'transient-set-level)) + (and transient--editp + (setq command (oref transient--prefix command)))) + (list command + (let ((keys (this-single-command-raw-keys))) + (and (lookup-key transient--transient-map keys) + (read-number + (format "Set level for `%s': " + (transient--suffix-command command))))))))) + (cond + ((not command) + (setq transient--editp t) + (transient-setup)) + (level + (let* ((prefix (oref transient--prefix command)) + (alist (alist-get prefix transient-levels)) + (key (transient--suffix-command command))) + (if (eq command prefix) + (progn (oset transient--prefix level level) + (setq key t)) + (oset (transient-suffix-object command) level level)) + (setf (alist-get key alist) level) + (setf (alist-get prefix transient-levels) alist)) + (transient-save-levels)) + (t + (transient-undefined)))) + +(defun transient-set () + "Save the value of the active transient for this Emacs session." + (interactive) + (oset (oref transient--prefix prototype) value (transient-args)) + (transient--history-push)) + +(defun transient-save () + "Save the value of the active transient persistenly across Emacs sessions." + (interactive) + (let ((value (transient-args))) + (oset (oref transient--prefix prototype) value value) + (setf (alist-get (oref transient--prefix command) transient-values) value) + (transient-save-values)) + (transient--history-push)) + +(defun transient-history-next () + "Switch to the next value used for the active transient." + (interactive) + (let* ((obj transient--prefix) + (pos (1- (oref obj history-pos))) + (hst (oref obj history))) + (if (< pos 0) + (user-error "End of history") + (oset obj history-pos pos) + (oset obj value (nth pos hst)) + (mapc #'transient-init-value transient--suffixes)))) + +(defun transient-history-prev () + "Switch to the previous value used for the active transient." + (interactive) + (let* ((obj transient--prefix) + (pos (1+ (oref obj history-pos))) + (hst (oref obj history)) + (len (length hst))) + (if (> pos (1- len)) + (user-error "End of history") + (oset obj history-pos pos) + (oset obj value (nth pos hst)) + (mapc #'transient-init-value transient--suffixes)))) + +(defun transient-resume () + "Resume a previously suspended stack of transients." + (interactive) + (cond (transient--stack + (let ((winconf transient--restore-winconf)) + (kill-local-variable 'transient--restore-winconf) + (when transient-resume-mode + (transient-resume-mode -1) + (quit-window)) + (when winconf + (set-window-configuration winconf))) + (transient--stack-pop)) + (transient-resume-mode + (kill-local-variable 'transient--restore-winconf) + (transient-resume-mode -1) + (quit-window)) + (t + (message "No suspended transient command")))) + +;;; Value +;;;; Core + +(defun transient-args (&optional prefix separate) + "Return the value of the transient from which the current suffix was called. + +If optional PREFIX is non-nil, then it should be a symbol, a +transient prefix command. In that case only return the value +of the transient if the suffix was actually invoked from that +transient. Otherwise return nil. This function is also used +internally, in which PREFIX can also be a `transient-prefix' +object. + +If optional SEPARATE is non-nil, then separate the arguments +into two groups. If SEPARATE is t, then separate into atoms +and conses (nil isn't a valid value, so it doesn't matter that +that is both an atom and a cons). + +SEPARATE can also be a predicate function, in which case the +first element is a list of the values for which it returns +non-nil and the second a list of the values for which it +returns nil. + +For transients that are used to pass arguments to a subprosess +\(such as git), `stringp' is a useful value for SEPARATE, it +separates non-positional arguments from positional arguments. +The value of Magit's file argument for example looks like this: +\(\"--\" file...)." + (let ((val (if (and (cl-typep prefix 'transient-prefix)) + (delq nil (mapcar 'transient-infix-value + transient--suffixes)) + (and (or (not prefix) + (eq prefix current-transient-command)) + (delq nil (mapcar 'transient-infix-value + current-transient-suffixes)))))) + (if separate + (-separate (if (eq separate t) #'atom separate) val) + val))) + +;;;; Init + +(cl-defgeneric transient-init-scope (obj) + "Set the scope of the suffix object OBJ. + +The scope is actually a property of the transient prefix, not of +individual suffixes. However it is possible to invoke a suffix +command directly instead of from a transient. In that case, if +the suffix expects a scope, then it has to determine that itself +and store it in its `scope' slot. + +This function is called for all suffix commands, but unless a +concrete method is implemented this falls through to the default +implementation, which is a noop.") + +(cl-defmethod transient-init-scope ((_ transient-suffix)) + "Noop." nil) + +(cl-defgeneric transient-init-value (obj) + "Set the initial value of the object OBJ. + +This function is called for all suffix commands, but unless a +concrete method is implemented this falls through to the default +implementation, which is a noop. In other words this usually +only does something for infix commands, but note that this is +not implemented for the abstract class `transient-infix', so if +your class derives from that directly, then you must implement +a method.") + +(cl-defmethod transient-init-value ((_ transient-suffix)) + "Noop." nil) + +(cl-defmethod transient-init-value ((obj transient-switch)) + (oset obj value + (car (member (oref obj argument) + (oref transient--prefix value))))) + +(cl-defmethod transient-init-value ((obj transient-option)) + (oset obj value + (transient--value-match (format "\\`%s\\(.*\\)" (oref obj argument))))) + +(cl-defmethod transient-init-value ((obj transient-switches)) + (oset obj value + (transient--value-match (oref obj argument-regexp)))) + +(defun transient--value-match (re) + (when-let ((match (cl-find-if (lambda (v) + (and (stringp v) + (string-match re v))) + (oref transient--prefix value)))) + (match-string 1 match))) + +(cl-defmethod transient-init-value ((obj transient-files)) + (oset obj value + (cdr (assoc "--" (oref transient--prefix value))))) + +;;;; Read + +(cl-defgeneric transient-infix-read (obj) + "Determine the new value of the infix object OBJ. + +This function merely determines the value; `transient-infix-set' +is used to actually store the new value in the object. + +For most infix classes this is done by reading a value from the +user using the reader specified by the `reader' slot (using the +`transient-infix' method described below). + +For some infix classes the value is changed without reading +anything in the minibuffer, i.e. the mere act of invoking the +infix command determines what the new value should be, based +on the previous value.") + +(cl-defmethod transient-infix-read :around ((obj transient-infix)) + "Exit the transient in case of an error. + +Without this Emacs would get stuck in an inconsistent state, +which might make it necessary to kill it from the outside." + (transient--with-emergency-exit + (cl-call-next-method obj))) + +(cl-defmethod transient-infix-read ((obj transient-infix)) + "Read a value while taking care of history. + +This method is suitable for a wide variety of infix commands, +including but not limitted to inline arguments and variables. + +If you do not use this method for your own infix class, then +you should likely replicate a lot of the behavior of this +method. If you fail to do so, then users might not appreciate +the lack of history, for example. + +Only for very simple classes that toggle or cycle through a very +limitted number of possible values should you replace this with a +simple method that does not handle history. (E.g. for a command +line switch the only possible values are \"use it\" and \"don't use +it\", in which case it is pointless to preserve history.)" + (with-slots (value multi-value allow-empty choices) obj + (if (and value + (not multi-value) + (not allow-empty) + transient--prefix) + (oset obj value nil) + (let* ((overriding-terminal-local-map nil) + (reader (oref obj reader)) + (prompt (transient-prompt obj)) + (value (if multi-value (mapconcat #'identity value ",") value)) + (history-key (or (oref obj history-key) + (oref obj command))) + (transient--history (alist-get history-key transient-history)) + (transient--history (if (or (null value) + (eq value (car transient--history))) + transient--history + (cons value transient--history))) + (initial-input (car transient--history)) + (history (cons 'transient--history (if initial-input 1 0))) + (value + (cond + (reader (funcall reader prompt initial-input history)) + (multi-value + (completing-read-multiple prompt choices nil nil + initial-input history)) + (choices + (completing-read prompt choices nil t initial-input history)) + (t (read-string prompt initial-input history))))) + (cond ((and (equal value "") (not allow-empty)) + (setq value nil)) + ((and (equal value "\"\"") allow-empty) + (setq value ""))) + (when value + (setf (alist-get history-key transient-history) + (delete-dups transient--history))) + value)))) + +(cl-defmethod transient-infix-read ((obj transient-switch)) + "Toggle the switch on or off." + (if (oref obj value) nil (oref obj argument))) + +(cl-defmethod transient-infix-read ((obj transient-switches)) + "Cycle through the mutually exclusive switches. +The last value is \"don't use any of these switches\"." + (let ((choices (mapcar (apply-partially #'format (oref obj argument-format)) + (oref obj choices)))) + (if-let ((value (oref obj value))) + (cadr (member value choices)) + (car choices)))) + +;;;; Readers + +(defun transient-read-existing-directory (prompt _initial-input _history) + "Read an existing directory." + (expand-file-name (read-directory-name prompt nil nil t))) + +(defun transient-read-number-N0 (prompt initial-input history) + "Read a natural number (including zero) and return it as a string." + (transient--read-number-N prompt initial-input history t)) + +(defun transient-read-number-N+ (prompt initial-input history) + "Read a natural number (excluding zero) and return it as a string." + (transient--read-number-N prompt initial-input history nil)) + +(defun transient--read-number-N (prompt initial-input history include-zero) + (save-match-data + (cl-block nil + (while t + (let ((str (read-from-minibuffer prompt initial-input nil history))) + (cond ((string-equal str "") + (cl-return nil)) + ((string-match-p (if include-zero + "\\`\\(0\\|[1-9][0-9]*\\)\\'" + "\\`[1-9][0-9]*\\'") + str) + (cl-return str)))) + (message "Please enter a natural number (%s zero)." + (if include-zero "including" "excluding")) + (sit-for 1))))) + +(defun transient-read-date (prompt default-time _history) + "Read a date using `org-read-date' (which see)." + (require 'org) + (when (fboundp 'org-read-date) + (org-read-date 'with-time nil nil prompt default-time))) + +;;;; Prompt + +(cl-defgeneric transient-prompt (obj) + "Return the prompt to be used to read infix object OBJ's value.") + +(cl-defmethod transient-prompt ((obj transient-infix)) + "Return the prompt to be used to read infix object OBJ's value. + +This implementation should be suitable for almost all infix +commands. + +If the value of OBJ's `prompt' slot is non-nil, then it must be +a string or a function. If it is a string, then use that. If +it is a function, then call that with OBJ as the only argument. +That function must return a string, which is then used as the +prompt. + +Otherwise, if the value of either the `argument' or `variable' +slot of OBJ is a string, then base the prompt on that (prefering +the former), appending either \"=\" (if it appears to be a +command-line option) or \": \". + +Finally fall through to using \"(BUG: no prompt): \" as the +prompt." + (if-let ((prompt (oref obj prompt))) + (let ((prompt (if (functionp prompt) + (funcall prompt obj) + prompt))) + (if (stringp prompt) + prompt + "(BUG: no prompt): ")) + (or (when-let ((arg (and (slot-boundp obj 'argument) (oref obj argument)))) + (if (and (stringp arg) (string-suffix-p "=" arg)) + arg + (concat arg ": "))) + (when-let ((var (and (slot-boundp obj 'variable) (oref obj variable)))) + (and (stringp var) + (concat var ": "))) + "(BUG: no prompt): "))) + +;;;; Set + +(cl-defgeneric transient-infix-set (obj value) + "Set the value of infix object OBJ to value.") + +(cl-defmethod transient-infix-set ((obj transient-infix) value) + "Set the value of infix object OBJ to value. + +This implementation should be suitable for almost all infix +commands. It simply calls `oset'." + (oset obj value value)) + +;;;; Use + +(cl-defgeneric transient-infix-value (obj) + "Return the value of the suffix object OBJ. + +This function is called by `transient-args' (which see), meaning +this function is how the value of a transient is determined so +that the invoked suffix command can use it. + +Currently most values are strings, but that is not set in stone. +Nil is not a value, it means \"no value\". + +Usually only infixes have a value, but see the method for +`transient-suffix'.") + +(cl-defmethod transient-infix-value ((_ transient-suffix)) + "Return nil, which means \"no value\". + +Infix arguments contribute the the transient's value while suffix +commands consume it. This function is called for suffixes anyway +because a command that both contributes to the transient's value +and also consumes it is not completely unconceivable. + +If you define such a command, then you must define a derived +class and implement this function because this default method +does nothing." nil) + +(cl-defmethod transient-infix-value ((obj transient-infix)) + "Return the value of OBJ's `value' slot." + (oref obj value)) + +(cl-defmethod transient-infix-value ((obj transient-option)) + "Return (concat ARGUMENT VALUE) or nil. + +ARGUMENT and VALUE are the values of the respective slots of OBJ. +If VALUE is nil, then return nil. VALUE may be the empty string, +which is not the same as nil." + (when-let ((value (oref obj value))) + (concat (oref obj argument) value))) + +(cl-defmethod transient-infix-value ((_ transient-variable)) + "Return nil, which means \"no value\". + +Setting the value of a variable is done by, well, setting the +value of the variable. I.e. this is a side-effect and does not +contribute to the value of the transient." + nil) + +(cl-defmethod transient-infix-value ((obj transient-files)) + "Return (concat ARGUMENT VALUE) or nil. + +ARGUMENT and VALUE are the values of the respective slots of OBJ. +If VALUE is nil, then return nil. VALUE may be the empty string, +which is not the same as nil." + (when-let ((value (oref obj value))) + (cons (oref obj argument) value))) + +;;; History + +(defun transient--history-push () + (let* ((obj transient--prefix) + (cmd (oref obj command)) + (val (transient-args)) + (hst (cons val (delete val (alist-get cmd transient-history))))) + (setf (alist-get cmd transient-history) hst))) + +(cl-defgeneric transient--history-init (obj) + "Initialize OBJ's `value' slot. +This is the transient-wide history; many individual infixes also +have a history of their own.") + +(cl-defmethod transient--history-init ((obj transient-prefix)) + "Initialize OBJ's `value' slot from the variable `transient-history'." + (let ((val (oref obj value)) + (cmd (oref obj command))) + (oset obj history + (cons val (delete val (alist-get cmd transient-history)))))) + +;;; Draw + +(defvar transient--source-buffer nil) + +(defun transient--show-brief () + (let ((message-log-max nil)) + (message + "%s %s" + (oref transient--prefix command) + (mapconcat + #'identity + (sort + (cl-mapcan + (lambda (suffix) + (let ((key (kbd (oref suffix key)))) + ;; Don't list any common commands. + (and (not (memq (oref suffix command) + `(,(lookup-key transient-map key) + ,(lookup-key transient-sticky-map key) + ;; From transient-common-commands: + transient-set + transient-save + transient-history-prev + transient-history-next + transient-quit-one + transient-toggle-common))) + (list (propertize (kbd (oref suffix key)) + 'face 'transient-key))))) + transient--suffixes) + #'string<) + (propertize "|" 'face 'transient-unreachable-key))))) + +(defun transient--show () + (transient--timer-cancel) + (setq transient--showp t) + (let ((transient--source-buffer (current-buffer))) + (with-temp-buffer + (let ((groups (cl-mapcan (lambda (group) + (let ((hide (oref group hide))) + (and (not (and (functionp hide) + (funcall hide))) + (list group)))) + transient--layout)) + group) + (while (setq group (pop groups)) + (transient--insert-group group) + (when groups + (insert ?\n)))) + (when (or transient--helpp transient--editp) + (transient--insert-help)) + (let ((lv-force-update t)) + (lv-message "%s" (buffer-string)))))) + +(cl-defgeneric transient--insert-group (group) + "Format GROUP and its elements and insert the result.") + +(cl-defmethod transient--insert-group :before ((group transient-group)) + "Insert GROUP's description, if any." + (when-let ((desc (transient-format-description group))) + (insert desc ?\n))) + +(cl-defmethod transient--insert-group ((group transient-row)) + (dolist (suffix (oref group suffixes)) + (insert (transient-format suffix)) + (insert " ")) + (insert ?\n)) + +(cl-defmethod transient--insert-group ((group transient-column)) + (dolist (suffix (oref group suffixes)) + (insert (transient-format suffix)) + (unless (integerp suffix) + (insert ?\n)))) + +(cl-defmethod transient--insert-group ((group transient-columns)) + (let* ((columns + (mapcar + (lambda (column) + (let ((rows (mapcar 'transient-format (oref column suffixes)))) + (when-let ((desc (transient-format-description column))) + (push desc rows)) + rows)) + (oref group suffixes))) + (rs (apply #'max (mapcar #'length columns))) + (cs (length columns)) + (cw (--map (apply #'max (mapcar #'length it)) columns)) + (cc (-reductions-from (apply-partially #'+ 3) 0 cw))) + (dotimes (r rs) + (dotimes (c cs) + (insert (make-string (- (nth c cc) (current-column)) ?\s)) + (when-let ((cell (nth r (nth c columns)))) + (insert cell)) + (when (= c (1- cs)) + (insert ?\n)))))) + +(cl-defmethod transient--insert-group ((group transient-subgroups)) + (let* ((subgroups (oref group suffixes)) + (n (length subgroups))) + (dotimes (s n) + (transient--insert-group (nth s subgroups)) + (when (< s (1- n)) + (insert ?\n))))) + +(cl-defgeneric transient-format (obj) + "Format and return OBJ for display. + +When this function is called, then the current buffer is some +temporary buffer. If you need the buffer from which the prefix +command was invoked to be current, then do so by temporarily +making `transient--source-buffer' current.") + +(cl-defmethod transient-format ((arg string)) + "Return the string ARG after applying the `transient-heading' face." + (propertize arg 'face 'transient-heading)) + +(cl-defmethod transient-format ((_ null)) + "Return a string containing just the newline character." + "\n") + +(cl-defmethod transient-format ((arg integer)) + "Return a string containing just the ARG character." + (char-to-string arg)) + +(cl-defmethod transient-format :around ((obj transient-suffix)) + "When edit-mode is enabled, then prepend the level information." + (concat (and transient--editp + (let ((level (oref obj level))) + (propertize (format " %s " level) + 'face (if (transient--use-level-p level t) + 'transient-enabled-suffix + 'transient-disabled-suffix)))) + (cl-call-next-method obj))) + +(cl-defmethod transient-format ((obj transient-infix)) + "Return a string generated using OBJ's `format'. +%k is formatted using `transient-format-key'. +%d is formatted using `transient-format-description'. +%f is formatted using `transient-format-value'." + (format-spec (oref obj format) + `((?k . ,(transient-format-key obj)) + (?d . ,(transient-format-description obj)) + (?v . ,(transient-format-value obj))))) + +(cl-defmethod transient-format ((obj transient-suffix)) + "Return a string generated using OBJ's `format'. +%k is formatted using `transient-format-key'. +%d is formatted using `transient-format-description'." + (format-spec (oref obj format) + `((?k . ,(transient-format-key obj)) + (?d . ,(transient-format-description obj))))) + +(cl-defgeneric transient-format-key (obj) + "Format OBJ's `key' for display and return the result.") + +(cl-defmethod transient-format-key ((obj transient-suffix)) + "Format OBJ's `key' for display and return the result." + (let ((key (oref obj key))) + (if transient--redisplay-key + (let ((len (length transient--redisplay-key)) + (seq (cl-coerce (edmacro-parse-keys key t) 'list))) + (cond + ((equal (-take len seq) transient--redisplay-key) + (let ((pre (key-description (vconcat (-take len seq)))) + (suf (key-description (vconcat (-drop len seq))))) + (setq pre (replace-regexp-in-string "RET" "C-m" pre t)) + (setq pre (replace-regexp-in-string "TAB" "C-i" pre t)) + (setq suf (replace-regexp-in-string "RET" "C-m" suf t)) + (setq suf (replace-regexp-in-string "TAB" "C-i" suf t)) + ;; We use e.g. "-k" instead of the more correct "- k", + ;; because the former is prettier. If we did that in + ;; the definition, then we want to drop the space that + ;; is reinserted above. False-positives are possible + ;; for silly bindings like "-C-c C-c". + (unless (string-match-p " " key) + (setq pre (replace-regexp-in-string " " "" pre)) + (setq suf (replace-regexp-in-string " " "" suf))) + (concat (propertize pre 'face 'default) + (and (string-prefix-p (concat pre " ") key) " ") + (propertize suf 'face 'transient-key) + (save-excursion + (when (string-match " +\\'" key) + (match-string 0 key)))))) + ((transient--lookup-key transient-sticky-map (kbd key)) + (propertize key 'face 'transient-key)) + (t + (propertize key 'face 'transient-unreachable-key)))) + (propertize key 'face 'transient-key)))) + +(cl-defmethod transient-format-key :around ((obj transient-argument)) + (let ((key (cl-call-next-method obj))) + (cond ((not transient-highlight-mismatched-keys)) + ((not (slot-boundp obj 'shortarg)) + (add-face-text-property + 0 (length key) 'transient-nonstandard-key nil key)) + ((not (string-equal key (oref obj shortarg))) + (add-face-text-property + 0 (length key) 'transient-mismatched-key nil key))) + key)) + +(cl-defgeneric transient-format-description (obj) + "Format OBJ's `description' for display and return the result.") + +(cl-defmethod transient-format-description ((obj transient-child)) + "The `description' slot may be a function, in which case that is +called inside the correct buffer (see `transient-insert-group') +and its value is returned to the caller." + (when-let ((desc (oref obj description))) + (if (functionp desc) + (with-current-buffer transient--source-buffer + (funcall desc)) + desc))) + +(cl-defmethod transient-format-description ((obj transient-group)) + "Format the description by calling the next method. If the result +doesn't use the `face' property at all, then apply the face +`transient-heading' to the complete string." + (when-let ((desc (cl-call-next-method obj))) + (if (text-property-not-all 0 (length desc) 'face nil desc) + desc + (propertize desc 'face 'transient-heading)))) + +(cl-defmethod transient-format-description :around ((obj transient-suffix)) + "Format the description by calling the next method. If the result +is nil, then use \"(BUG: no description)\" as the description. +If the OBJ's `key' is currently unreachable, then apply the face +`transient-unreachable' to the complete string." + (let ((desc (or (cl-call-next-method obj) + (propertize "(BUG: no description)" 'face 'error)))) + (if (transient--key-unreachable-p obj) + (propertize desc 'face 'transient-unreachable) + desc))) + +(cl-defgeneric transient-format-value (obj) + "Format OBJ's value for display and return the result.") + +(cl-defmethod transient-format-value ((obj transient-suffix)) + (propertize (oref obj argument) + 'face (if (oref obj value) + 'transient-argument + 'transient-inactive-argument))) + +(cl-defmethod transient-format-value ((obj transient-option)) + (let ((value (oref obj value))) + (propertize (concat (oref obj argument) value) + 'face (if value + 'transient-value + 'transient-inactive-value)))) + +(cl-defmethod transient-format-value ((obj transient-switches)) + (with-slots (value argument-format choices) obj + (format (propertize argument-format + 'face (if value + 'transient-value + 'transient-inactive-value)) + (concat + (propertize "[" 'face 'transient-inactive-value) + (mapconcat + (lambda (choice) + (propertize choice 'face + (if (equal (format argument-format choice) value) + 'transient-value + 'transient-inactive-value))) + choices + (propertize "|" 'face 'transient-inactive-value)) + (propertize "]" 'face 'transient-inactive-value))))) + +(cl-defmethod transient-format-value ((obj transient-files)) + (let ((argument (oref obj argument))) + (if-let ((value (oref obj value))) + (propertize (concat argument " " + (mapconcat (lambda (f) (format "%S" f)) + (oref obj value) " ")) + 'face 'transient-argument) + (propertize argument 'face 'transient-inactive-argument)))) + +(defun transient--key-unreachable-p (obj) + (and transient--redisplay-key + (let ((key (oref obj key))) + (not (or (equal (-take (length transient--redisplay-key) + (cl-coerce (edmacro-parse-keys key t) 'list)) + transient--redisplay-key) + (transient--lookup-key transient-sticky-map (kbd key))))))) + +(defun transient--lookup-key (keymap key) + (let ((val (lookup-key keymap key))) + (and val (not (integerp val)) val))) + +;;; Help + +(cl-defgeneric transient-show-help (obj) + "Show help for OBJ's command.") + +(cl-defmethod transient-show-help ((obj transient-prefix)) + "Show the info manual, manpage or command doc-string. +Show the first one that is specified." + (if-let ((manual (oref obj info-manual))) + (info manual) + (if-let ((manpage (oref obj man-page))) + (transient--show-manpage manpage) + (transient--describe-function (oref obj command))))) + +(cl-defmethod transient-show-help ((_ transient-suffix)) + "Show the command doc-string." + (if (eq this-original-command 'transient-help) + (if-let ((manpage (oref transient--prefix man-page))) + (transient--show-manpage manpage) + (transient--describe-function (oref transient--prefix command))) + (transient--describe-function this-original-command))) + +(cl-defmethod transient-show-help ((obj transient-infix)) + "Show the manpage if defined or the command doc-string. +If the manpage is specified, then try to jump to the correct +location." + (if-let ((manpage (oref transient--prefix man-page))) + (transient--show-manpage manpage (oref obj argument)) + (transient--describe-function this-original-command))) + +;; `cl-generic-generalizers' doesn't support `command' et al. +(cl-defmethod transient-show-help (cmd) + "Show the command doc-string." + (transient--describe-function cmd)) + +(defun transient--show-manpage (manpage &optional argument) + (select-window (get-buffer-window (man manpage))) + (when argument + (transient--goto-argument-description argument))) + +(defun transient--describe-function (fn) + (describe-function fn) + (select-window (get-buffer-window (help-buffer)))) + +(defun transient--goto-argument-description (arg) + (goto-char (point-min)) + (let ((case-fold-search nil) + ;; This matches preceding/proceeding options. Options + ;; such as "-a", "-S[<keyid>]", and "--grep=<pattern>" + ;; are matched by this regex without the shy group. + ;; The ". " in the shy group is for options such as + ;; "-m parent-number", and the "-[^[:space:]]+ " is + ;; for options such as "--mainline parent-number" + (others "-\\(?:. \\|-[^[:space:]]+ \\)?[^[:space:]]+")) + (when (re-search-forward + ;; Should start with whitespace and may have + ;; any number of options before and/or after. + (format + "^[\t\s]+\\(?:%s, \\)*?\\(?1:%s\\)%s\\(?:, %s\\)*$" + others + ;; Options don't necessarily end in an "=" + ;; (e.g., "--gpg-sign[=<keyid>]") + (string-remove-suffix "=" arg) + ;; Simple options don't end in an "=". Splitting this + ;; into 2 cases should make getting false positives + ;; less likely. + (if (string-suffix-p "=" arg) + ;; "[^[:space:]]*[^.[:space:]]" matches the option + ;; value, which is usually after the option name + ;; and either '=' or '[='. The value can't end in + ;; a period, as that means it's being used at the + ;; end of a sentence. The space is for options + ;; such as '--mainline parent-number'. + "\\(?: \\|\\[?=\\)[^[:space:]]*[^.[:space:]]" + ;; Either this doesn't match anything (e.g., "-a"), + ;; or the option is followed by a value delimited + ;; by a "[", "<", or ":". A space might appear + ;; before this value, as in "-f <file>". The + ;; space alternative is for options such as + ;; "-m parent-number". + "\\(?:\\(?: \\| ?[\\[<:]\\)[^[:space:]]*[^.[:space:]]\\)?") + others) + nil t) + (goto-char (match-beginning 1))))) + +(defun transient--insert-help () + (unless (looking-back "\n\n" 2) + (insert "\n")) + (when transient--helpp + (insert + (format (propertize "\ +Type a %s to show help for that suffix command, or %s to show manual. +Type %s to exit help.\n" + 'face 'transient-heading) + (propertize "<KEY>" 'face 'transient-key) + (propertize "?" 'face 'transient-key) + (propertize "C-g" 'face 'transient-key)))) + (when transient--editp + (unless transient--helpp + (insert + (format (propertize "\ +Type a %s to set level for that suffix command. +Type %s to set what levels are available for this prefix command.\n" + 'face 'transient-heading) + (propertize "<KEY>" 'face 'transient-key) + (propertize "C-x l" 'face 'transient-key)))) + (with-slots (level) transient--prefix + (insert + (format (propertize " +Suffixes on levels %s are available. +Suffixes on levels %s and %s are unavailable.\n" + 'face 'transient-heading) + (propertize (format "1-%s" level) + 'face 'transient-enabled-suffix) + (propertize " 0 " + 'face 'transient-disabled-suffix) + (propertize (format ">=%s" (1+ level)) + 'face 'transient-disabled-suffix)))))) + +(defvar transient-resume-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [remap Man-quit] 'transient-resume) + (define-key map [remap Info-exit] 'transient-resume) + (define-key map [remap quit-window] 'transient-resume) + map) + "Keymap for `transient-resume-mode'. + +This keymap remaps every command that would usually just quit the +documentation buffer to `transient-resume', which additionally +resumes the suspended transient.") + +(define-minor-mode transient-resume-mode + "Auxiliary minor-mode used to resume a transient after viewing help.") + +;;; Compatibility + +(declare-function which-key-mode "which-key" (&optional arg)) + +(defun transient--suspend-which-key-mode () + (when (bound-and-true-p which-key-mode) + (which-key-mode -1) + (add-hook 'post-transient-hook 'transient--resume-which-key-mode))) + +(defun transient--resume-which-key-mode () + (unless transient--prefix + (which-key-mode 1) + (remove-hook 'post-transient-hook 'transient--resume-which-key-mode))) + +;;; Font-Lock + +(defconst transient-font-lock-keywords + (eval-when-compile + `((,(concat "(" + (regexp-opt (list "define-transient-command" + "define-infix-command" + "define-infix-argument" + "define-suffix-command") + t) + "\\_>[ \t'\(]*" + "\\(\\(?:\\sw\\|\\s_\\)+\\)?") + (1 'font-lock-keyword-face) + (2 'font-lock-function-name-face nil t))))) + +(font-lock-add-keywords 'emacs-lisp-mode transient-font-lock-keywords) + +;;; _ +(provide 'transient) +;; Local Variables: +;; indent-tabs-mode: nil +;; End: +;;; transient.el ends here |
