Repository: tbanel/uniline Branch: main Commit: cda161166752 Files: 41 Total size: 461.0 KB Directory structure: gitextract_1mwm1f40/ ├── .gitignore ├── LICENSE ├── README.org ├── tests/ │ ├── bench01.el │ ├── bench02.el │ ├── bench03.el │ ├── bench04.el │ ├── bench05.el │ ├── bench06.el │ ├── bench07.el │ ├── bench08.el │ ├── bench09.el │ ├── bench10.el │ ├── bench11.el │ ├── bench12.el │ ├── bench13.el │ ├── bench14.el │ ├── bench15.el │ ├── bench16.el │ ├── bench17.el │ ├── bench18.el │ ├── bench19.el │ ├── bench20.el │ ├── bench21.el │ ├── bench22.el │ ├── bench23.el │ ├── bench24.el │ ├── bench25.el │ ├── bench26.el │ ├── bench27.el │ ├── bench28.el │ ├── bench29.el │ ├── bench30.el │ ├── bench31.el │ ├── bench33.el │ └── uniline-bench.el ├── uniline-core.el ├── uniline-hydra.el ├── uniline-transient.el ├── uniline.el └── uniline.info ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *~ *.elc ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.org ================================================ # -*- mode: org; coding:utf-8; -*- #+TITLE: Uniline #+OPTIONS: ^:{} authors:Thierry Banel, toc:nil #+LATEX_HEADER: \usepackage{pmboxdraw} * Getting started in 10 seconds :PROPERTIES: :CUSTOM_ID: getting-started-in-10-seconds :END: - Type =M-x uniline-mode= - Move cursor with the arrow-keys on the keyboard =→ ← ↑ ↓= - Quit =C-c C-c= [[file:images/first-drawing.png]] #+begin_example ╷ ╭─────────╮ ╰───┤my first ├─╮ │drawing │ ╰───╮ ╰─────────╯ │ ╭────┬───────╯ ╰────╯ #+end_example * New :PROPERTIES: :CUSTOM_ID: new :END: Customization & settings now consistently available from the application menu, from Hydra, and from Transient. Type = *=. * Table of Contents :PROPERTIES: :TOC: :include all :depth 3 :force () :ignore (this) :local (nothing) :CUSTOM_ID: table-of-contents :END: :CONTENTS: - [[#getting-started-in-10-seconds][Getting started in 10 seconds]] - [[#new][New]] - [[#gallery-pure-unicode-diagrams-in-emacs][Gallery: pure UNICODE diagrams in Emacs]] - [[#document-a-command][Document a command]] - [[#connect-boxes-with-arrows][Connect boxes with arrows]] - [[#explain-decisions-trees][Explain decisions trees]] - [[#draw-lines-or-blocks][Draw lines or blocks]] - [[#outline-the-general-relativity-and-the-schrödingers-equations][Outline the General Relativity and the Schrödinger's equations]] - [[#explain-the-structure-of-a-sentence-in-a-foreign-language][Explain the structure of a sentence in a foreign language]] - [[#draw-electronic-diagrams][Draw electronic diagrams]] - [[#explain-lisp-lists][Explain Lisp lists]] - [[#draw-sketched-objects][Draw sketched objects]] - [[#pure-text][Pure text]] - [[#beware][Beware!]] - [[#a-minor-mode-for-drawing][A minor mode for drawing]] - [[#minor-mode][Minor mode]] - [[#draw-lines-by-moving-the-cursor][Draw lines by moving the cursor]] - [[#infinite--buffer][Infinite ∞ buffer]] - [[#brush-style][Brush style]] - [[#text-direction][Text direction]] - [[#the-insert-key][The key]] - [[#glyphs-------insertion--modification][Glyphs ▷ ▶ → □ ◆ ╮─ insertion & modification]] - [[#arrows-glyphs------][Arrows glyphs ▷ ▶ → ▹ ▸ ↔]] - [[#intersection-glyphs---][Intersection glyphs ■ ◆ ●]] - [[#fine-tweaking-of-lines][Fine tweaking of lines]] - [[#rectangular-actions][Rectangular actions]] - [[#drawing-a-rectangle][Drawing a rectangle]] - [[#filling-a-rectangle][Filling a rectangle]] - [[#moving-a-rectangular-region][Moving a rectangular region]] - [[#copying-killing-yanking-a-rectangular-region][Copying, killing, yanking a rectangular region]] - [[#dashed-lines-and-other-styles][Dashed lines and other styles]] - [[#ascii-to-unicode][ASCII to UNICODE]] - [[#long-range-actions-contour-and-flood-fill][Long range actions: contour and flood-fill]] - [[#tracing-a-contour][Tracing a contour]] - [[#flood-fill][Flood-fill]] - [[#macros][Macros]] - [[#which-fonts][Which fonts?]] - [[#recommended-fonts][Recommended fonts]] - [[#use-case-mixing-fonts][Use case: mixing fonts]] - [[#hydra-or-transient][Hydra or Transient?]] - [[#selecting-hydra-or-transient][Selecting Hydra or Transient]] - [[#instantly-selecting-hydra-or-transient][Instantly selecting Hydra or Transient]] - [[#one-liner-menus][One-liner menus]] - [[#the-hydra-interface][The Hydra interface]] - [[#the-transient-interface][The Transient interface]] - [[#customization][Customization]] - [[#interface-type][Interface type]] - [[#insert-key][Insert key]] - [[#maximum-steps-when-drawing-a-contour][Maximum steps when drawing a contour]] - [[#cursor-type][Cursor type]] - [[#hint-style][Hint style]] - [[#welcome-message-visibility][Welcome message visibility]] - [[#line-spacing][Line spacing]] - [[#font][Font]] - [[#upward-infiniteness-][Upward infiniteness ∞]] - [[#how-uniline-behaves-with-its-environment][How Uniline behaves with its environment?]] - [[#language-environment][Language environment]] - [[#compatibility-with-picture-mode][Compatibility with Picture-mode]] - [[#compatibility-with-artist-mode][Compatibility with Artist-mode]] - [[#compatibility-with-whitespace-mode][Compatibility with Whitespace-mode]] - [[#compatibility-with-org-mode][Compatibility with Org Mode]] - [[#org-mode-and-latex][Org Mode and LaTex]] - [[#what-about-t-tabs][What about \t tabs?]] - [[#what-about-l-page-separation][What about ^L page separation?]] - [[#emacs-on-the-linux-console][Emacs on the Linux console]] - [[#emacs-on-a-graphical-terminal-emulator][Emacs on a graphical terminal emulator]] - [[#emacs-on-windows][Emacs on Windows]] - [[#compatibility-with-asciiflow][Compatibility with ASCIIFlow]] - [[#lisp-api][Lisp API]] - [[#move-the-cursor][Move the cursor]] - [[#brush][Brush]] - [[#example-lisp-function-to-draw-a-plus-sign][Example: Lisp function to draw a plus sign]] - [[#long-range-actions-contour-flood-fill-rectangle][Long range actions (contour, flood-fill, rectangle)]] - [[#constants][Constants]] - [[#macro-and-text-direction][Macro and text direction]] - [[#insert-and-tweak-glyphs][Insert and tweak glyphs]] - [[#change-to-alternate-styles][Change to alternate styles]] - [[#mouse-support][Mouse support]] - [[#installation][Installation]] - [[#use-package-the-straightforward-way][use-package, the straightforward way]] - [[#without-use-package][Without use-package]] - [[#related-packages][Related packages]] - [[#author-contributors][Author, contributors]] - [[#license][License]] :END: * Gallery: pure UNICODE diagrams in Emacs :PROPERTIES: :CUSTOM_ID: gallery-pure-unicode-diagrams-in-emacs :END: Draw diagrams like those: ** Document a command :PROPERTIES: :CUSTOM_ID: document-a-command :END: [[file:images/document-command.png]] #+begin_example pdfjam source.pdf 3-5,9 ╶─────▲────▲────────▲──▲╴ command╶╯ │ │ │ input file╶──╯ │ │ select pages 3,4,5╶───╯ │ and page 9╶──────────────╯ #+end_example ** Connect boxes with arrows :PROPERTIES: :CUSTOM_ID: connect-boxes-with-arrows :END: [[file:images/boxes-arrows.png]] #+begin_example ╭───────────────────────╮ ╷123╭────▶┤ hundred and something │ ╰───╯ ╰───────────────────────╯ ╭────▶──╮A╷ ╭───╮ ┏━━━┓ ╔═══╗ │ ╰─╯ 0╶─→┤ 1 ┝━━━▶┫ 2 ┣═══▷╣ 3 ╟──●────▶──╮B╷ ╰───╯ ┗━┯━┛ ╚═╤═╝ │ ╰─╯ ╰────←───╯ ╰────▶──╮C╷ ╰─╯ ╔══════════╗ ║ 1 ║ ▐▀▀▀▀▀▀▀▀▜ ║ ╭─────╫───╮ ◁──▷ ▐ 3 ▐ ╚════╪═════╝ 2 │ ▐▄▄▄▄▄▄▄▄▟ ╰─────────╯ #+end_example ** Explain decisions trees :PROPERTIES: :CUSTOM_ID: explain-decisions-trees :END: [[file:images/decision-tree.png]] #+begin_example ┏━━━━━━━━━━━━┓ ┃which color?┃ ┗━┯━━━━━━━━━━┛ │ ╭──────╮ │ ╭──┤yellow├─▷╮good─choice╭□ ▽ │ ╰──────╯ ╰═══════════╯ ╰──● ╭───╮ ┏━━━━━┓ ├──┤red├───▷┨dark?┠──╮ │ ╰───╯ ┗━━━━━┛ │ │ ╭───◁──────────────╯ │ │ ╭───╮ │ ╰─●─┤yes├▷╮regular─red╭─□ │ │ ╰───╯ ╰═══════════╯ │ │ ╭──╮ │ ╰─┤no├─▷╮pink╭────────□ │ ╰──╯ ╰════╯ │ ╭────╮ ├──┤blue├───▷╮next week╭──□ │ ╰────╯ ╰═════════╯ │ ╭─────╮ ╰──┤white├──▷╮available╭──□ ╰─────╯ ╰═════════╯ #+end_example ** Draw lines or blocks :PROPERTIES: :CUSTOM_ID: draw-lines-or-blocks :END: [[file:images/lines-blocks.png]] #+begin_example ╭─╮←─╮ ╭╮ │ │ ╰──╴max 235 ╭╮││ ╭╯ │ │╰╯│╭─╯ │ ╭╮ │ ││ │ ╭─╮││╭╮ ╭──╮╭╮ │ ╰╯ ╰╮ ╭╯ ╰╯╰╯│ ╭╯ ╰╯╰─╮ │ │ ╭╮ ◁─╯ ╰──╯ ╰──╯ ╰─╯╰────▷ ◀════════════════════════════════════════▶ ╭────────╮ ▲ │all time│ ┃ ▄ ▗▟█ ←─┤highest │ Qdx █▌ ████ ╰────────╯ ┃ ▗▄█▌ █████▙ ┃ ▟███████▄█████████▄▄▄ ▗▄ ┃▐▄▄████████████████████████████▄▄▖ ╺━━━━━━━━━━╸time╺━━━━━━━━━━━━━━━━▶ #+end_example ** Outline the General Relativity and the Schrödinger's equations :PROPERTIES: :CUSTOM_ID: outline-the-general-relativity-and-the-schrödingers-equations :END: [[file:images/general-relativity-equation.png]] #+begin_example ╭─────────────────────╴G: Einstein tensor │ ╭────╴κ: Gravitational coupling constant ╭──▽───╮ ╭───▽──╮ ┏━┷━━━━━━┷━━━━━━━━┷━━━━━━┷━━━┓ ┃ R - gR/2 + Λg = (8πG/c⁴)×T ┃◁╴General Relativity equation ┗━△━━━△△━━━━━△△━━━━━━△━△━━━△━┛ │ ││ ││ │ │ ╭╯ │ ││ ││ │ │ ╰╴Energy-impulsion tensor │ ││ ││ │ ╰───╴Speed of light │ ││ ││ ╰─────╴Gravitational constant │ ││ ╰┴────────────╴Cosmological constant │ │╰──────┴────────────╴Scalar curvature │ ╰───────╰────────────╴Metric tensor ╰────────────────────────╴Ricci tensor #+end_example [[file:images/schrodinger-equation.png]] #+begin_example ╭─────────────────────╴Derivative over time │ ╭──────────╭────╴State of quantum system at time t │ │ │ (the square of its absolute value ╭▽─╮ ╭─▽──╮ ╭─▽──╮ is the probability density) ┏━━━━━┷━━┷━┷━━━━┷━━━━━┷━━━━┷━┓ ┃ i ħ d/dt |Ψ(t)> = Ĥ |Ψ(t)> ┃◁─╴Schrödinger's equation ┗━△━△━━━━△━━━━△━━━━━△━━━━△━━━┛ │ │ ╰────╰─────┤────╰───╴Time │ │ ╰────────╴Hamiltonian │ ╰────────────────────────╴Reduced Plank constant ╰──────────────────────────╴Imaginary number i²=-1 #+end_example ** Explain the structure of a sentence in a foreign language :PROPERTIES: :CUSTOM_ID: explain-the-structure-of-a-sentence-in-a-foreign-language :END: (which language?) [[file:images/foreign-language-sentence.png]] #+begin_example ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ the pretty table is standing ┃ ┗┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ ╭────┬─────┬─────╴radicals ↕ ╭┴╮ ╭┴─╮ ╭┴─╮ ┏┷━━━┿━┿━━┿━━┿━━┿━━┿━━━┓ ┃ la bela tablo staras ┃ ┗━━━━┿━┿△━┿━━┿△━┿━━┿△━━┛ ╰─╯│ ╰──╯│ ╰──╯│ ┏━━━━━suffixes━━━━━┓ │ │ ╰──╂╴as: present tense┃ │ │ ┃ os: future tense ┃ │ │ ┃ is: past tense ┃ │ ╰────────╂╴ o: noun ┃ ╰──────────────╂╴ a: adjective ┃ ┃ e: adverb ┃ ┗━━━━━━━━━━━━━━━━━━┛ #+end_example ** Draw electronic diagrams :PROPERTIES: :CUSTOM_ID: draw-electronic-diagrams :END: [[file:images/electronic-circuit.png]] #+begin_example ╭────────╭──────────╮ ┏━━┓ │ ╭┴╮ ╰─┨5V┃ ╭┴╮ │░│ ┗━━┛ │░│ │░│1KΩ │░│10KΩ ╰┬╯ 5μF ╰┬╯ ├─────────────● → ╷╷ │ ┠─╯ amplified → ●─────┤├───┼──────┨ output input ╵╵ │ ┠▶╮ 500μF signal signal ╭┴╮ │ ╷╷ │░│ ├───┤├──╮ │░│1KΩ ╭┴╮ ╵╵ │ ╰┬╯ │░│ │ ╭────╮ │ │░│470Ω │ │ ╺━━┷━━╸ │ ╰┬╯ │ │ ╺━━━╸ ╰────────╰───────╰────╯ ╺━╸ #+end_example ** Explain Lisp lists :PROPERTIES: :CUSTOM_ID: explain-lisp-lists :END: [[file:images/lisp-lists.png]] #+begin_example '(a b c) ┏━━━┳━━━┓ ┏━━━┳━━━┓ ┏━━━┳━━━┓ ●━━━▶┫ ● ┃ ●─╂──▷┨ ● ┃ ●─╂──▷┨ ● ┃nil┃ ┗━┿━┻━━━┛ ┗━┿━┻━━━┛ ┗━┿━┻━━━┛ │ ╰──────────╮╰╮ │ ╭─────┬───────────╮ │ │ ╰─▷┤"a\0"│properties │ │ │ ├─────┼───────────┤ │ │ │"b\0"│properties ├◁╯ │ ├─────┼───────────┤ │ │"c\0"│properties ├◁──╯ ├─────┼───────────┤ │... │... │ ╵ ╵ ╵ #+end_example ** Draw sketched objects :PROPERTIES: :CUSTOM_ID: draw-sketched-objects :END: [[file:images/sketched-objects.png]] #+begin_example ◀─(-)────────(+)──▶ ~╭──────╮~ ▗──────────────╮ ~~│ ╭~~╮ │~~ ▐ ╰╮ ~│ ╵ ╵ │~ ╭□▐ 1.5 volts ╭╯□╮ ╰─╖ ╓─╯ │ ▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘ │ ╠━━╣ │ ╰──────╯ │ ╰─────────────────────────────╯ #+end_example [[file:images/water-sketch.png]] #+begin_example ╶╮ ╭╴ ┏┳┥▒▒▒▒▒▒▒┝╸ ┃┃│▒▒eau▒▒│ ┃┃│▒▒▒▒▒▒▒│ ╔═════╗ ┃┃╰──╮▒╭──╯ ║ ╶╮ ▽ ╭╴ ┃┃ ▒ ║ │ ░ │ ┃┃ ▒ ║ │░░░░░░░░░░░░░░│ ┃┃ ╚═════╝ │░░░░░░░░░░░░░░╞════▷▒▒ ┃┃ │░░░░░akvo░░░░░│ ╶╮ ▒ ╭╴ ┃┃ │░░░░░░░░░░░░░░│ │ ▒ │ ┃┃ ╰─┲┳━━━━━━━━┳┱─╯ │▒▒▒▒▒▒▒▒▒▒▒│ ┃┃ ┃┃ ┃┃ │▒▒▒water▒▒▒│ ┃┃ ┃┃ ┃┃ │▒▒▒▒▒▒▒▒▒▒▒│ ┃┃ ┃┃ ┃┃ ╰───────────╯ ▝▀▀▀▀▀▀▘ ▝▀▘ ▝▀▘ ▀▀▀▀▀▀▀▀▀▀▀▀▀ #+end_example ** Pure text :PROPERTIES: :CUSTOM_ID: pure-text :END: Those diagrams are pure text. There is nothing graphic. They are achieved using UNICODE characters. Therefore they can be drawn within any text formatted document, like Org Mode, Markdown, txt, comments in any programming language source code (C++, Python, Rust, D, JavaScript, GnuPlot, LaTex, whatever). Most often, the text file will be encoded as UTF-8. This is becoming the de-facto standard for text and source code files. Creating such diagrams by hand is painfully slow. Use =Uniline= to draw lines while you move the cursor with keyboard arrows. ** Beware! :PROPERTIES: :CUSTOM_ID: beware :END: If you see those diagrams miss-aligned, most likely the font used to display them does not support UNICODE block characters. See bellow the paragraph [[#which-fonts][Which fonts?]] for details. If you get misalignment when drawing, this could come from too wide characters. Emojis are an example. Usual characters may also be considered twice as wide as normal under some "language environments". See the paragraph [[#language-environment][Language environment]] for details. * A minor mode for drawing :PROPERTIES: :CUSTOM_ID: a-minor-mode-for-drawing :END: ** Minor mode :PROPERTIES: :CUSTOM_ID: minor-mode :END: =Uniline= is a minor mode. Activate it temporarily: =M-x uniline-mode= Exit it with: =C-c C-c= The current major mode is still active underneath =uniline-mode=. While in =uniline-mode=, overwriting is active, as well as long lines truncation. Also, a hollow cursor is provided (customizable). Those settings are reset to their previous state when exiting =uniline-mode=. ** Draw lines by moving the cursor :PROPERTIES: :CUSTOM_ID: draw-lines-by-moving-the-cursor :END: Use keyboard arrows to draw lines. By default, drawing lines only happens over empty space or over other lines. If there is already text, it will not be erased. However, by hitting the control-key while moving, lines overwrite whatever there is. The usual numeric prefix is available. For instance, to draw a line 12 characters wide downward, type: =M-12 = ** Infinite ∞ buffer :PROPERTIES: :CUSTOM_ID: infinite--buffer :END: The buffer is infinite ∞ in the south and east directions. Which means that when the cursor ends up outside the buffer, white space characters are automatically added. All algorithms also make use of the infiniteness of the buffer when needed. Those algorithms are: moving a rectangle, pasting a rectangle, drawing the external border of a rectangular region, or drawing the contour of a shape. The buffer is also infinite ∞ in the upward direction. That is customizable through the =uniline-infinite-up↑= variable. If its value is =t=, then the buffer is actually infinite ∞ upward. If it is =nil=, then the upper border of the buffer is a hard limit. To customize, type: =M-x customize-variable uniline-infinite-up↑= The buffer can be "narrowed", for instance with the =C-x n n= or =M-x narrow-to-region= command. In this case, the limits are those of the narrow region. When Uniline needs to bypass the up↑ or down↓ limits, it adds empty lines. When widening again the buffer, the region which was narrow will have increased. ** Brush style :PROPERTIES: :CUSTOM_ID: brush-style :END: Set the current brush with: - ~-~ single thin line =╭─┬─╮= - ~+~ single thick line =┏━┳━┓= - ~=~ double line =╔═╦═╗= - ~#~ quarter block =▙▄▟▀= - =~= toggle dotted lines =┄┄┄┄= - ~~ eraser - ~~ move without drawing anything The current brush and the current text direction (see [[#text-direction][Text direction]]) are reflected in the mode-line (at the bottom of the =Emacs= screen). It looks like this: [[file:images/mode-line.png]] #+begin_example current text current direction╶────╮ ╭───╴brush ▼ ▼ ══════════════════╧═══════╧══════════════ U:** buff (... →Uniline┼ ...) ═════════════════════════════════════════ #+end_example The dotted toggle ~~~ is a modifier for the single thin and thick lines. It circles along 3 styles: - plain lines, - 3 dots vertical, 2 dots horizontal, - 4 dots both vertical & horizontal, - back to plain line and so on. [[file:images/dotted-lines.png]] #+begin_example ║ thin ╷ thick ╷ ║ │ │ ══════════╬═════════╪═════════╡ ║ ╭╌╌╌╮ │ ┏╍╍╍┓ │ 3,2 dots ║ ┆ ┆ │ ┇ ┇ │ ║ ╰╌╌╌╯ │ ┗╍╍╍┛ │ ──────────╫─────────┼─────────┤ ║ ╭┈┈┈╮ │ ┏┉┉┉┓ │ 4,4 dots ║ ┊ ┊ │ ┋ ┋ │ ║ ╰┈┈┈╯ │ ┗┉┉┉┛ │ ──────────╨─────────┴─────────╯ #+end_example Note that the UNICODE standard offers very limited support for dotted lines. Only vertical and horizontal lines are available. So, no crossing of line is possible. In case a line crosses a dotted line, Uniline falls back to a plain line crossing character (but still preserving thickness). There is no dotted versions of double lines either. ** Text direction :PROPERTIES: :CUSTOM_ID: text-direction :END: Usually, inserting text in a buffer moves the cursor to the right. (And sometimes to the left for some locales). Any of the 4 directions can be selected under =Uniline=. Just type any of: - = C-= - = C-= - = C-= - = C-= The current direction is reflected in the mode-line, just before the word ="uniline"=. * The == key :PROPERTIES: :CUSTOM_ID: the-insert-key :END: The == key is a prefix for other keys: - for drawing arrows, squares, crosses, o-shapes glyphs, - for handling rectangles, - for inserting =# = - += which otherwise change the brush style, - for trying a choice of mono-spaced fonts. Why ==? Because: - =Uniline= tries to leave their original meaning to as many keys as possible, - the standard meaning of == is to toggle the =overwrite-mode=; but =Uniline= is already in =overwrite-mode=, and de-activating overwrite would break =Uniline=. So preempting == does not sacrifice anything. *Customization* Another key may be defined instead of ==. Type: #+begin_example M-x customize-variable uniline-key-insert #+end_example * Glyphs =▷ ▶ → □ ◆ ╮─= insertion & modification :PROPERTIES: :CUSTOM_ID: glyphs-------insertion--modification :END: Individual character glyphs may be inserted and changed. - Put the cursor where a glyphs should be edited or inserted. - Then press == (this key may be customized, see [[#insert-key][Insert key]]). Arrows, squares, circles, crosses may be handled. Also lines may be fine tweaked a single character at a time. ** Arrows glyphs =▷ ▶ → ▹ ▸ ↔= :PROPERTIES: :CUSTOM_ID: arrows-glyphs------ :END: When inserting an arrow, it points in the direction that the line drawing follows. =Uniline= supports 6 arrows types: =▷ ▶ → ▹ ▸ ↔= [[file:images/arrow-styles.png]] #+begin_example □ ╰─◁──▷─╮ □─╮ ╭─╮ ╭─╮ ╭─□ ╭─◀──▶─╯ △ ▲ ↑ ▵ ▴ ↕ ╰─←──→─╮ │ │ │ │ │ │ ╭─◃──▹─╯ ▽ ▼ ↓ ▿ ▾ ↕ ╰─◂──▸─╮ ╰─╯ ╰─╯ ╰─╯ ╭─↔──↔─╯ □ #+end_example Actually, there are tons of arrows of all styles in the UNICODE standard. Unfortunately, support by fonts is weak. So =Uniline= restrains itself to those six safe arrows. To insert an arrow, type: = a= or = a a= or = a a a=. (=a= cycles through the 6 styles, =A= cycles backward). = 4 a= is equivalent to = a a a a=, which is also equivalent to = A A A=. Those 3 shortcuts insert an arrow of this style: =▵▹▿◃=. The actual direction where the arrow points follows the last movement of the cursor. To change the direction of the arrow, use shift-arrow, for example: =S-= will change from =→= to =↑=. ** Intersection glyphs =■ ◆ ●= :PROPERTIES: :CUSTOM_ID: intersection-glyphs--- :END: There are a few UNICODE characters which are mono-space and symmetric in the 4 directions. They are great at line intersections: To insert a square =□ ■ ▫ ▪ ◆ ◊= type: = s s s…= (=s= cycles, =S= cycles backward). To insert a circular shape =· ∙ • ● ◦ Ø ø= type: = o o o…= (=o= cycles, =O= cycles backward). To insert a cross shape =╳ ╱ ╲ ÷ × ± ¤= type: = x x x…= (=x= cycles, =X= cycles backward). To insert a grey character =░▒▓█= from pure white to pure black type: = SPC SPC SPC…= or = DEL DEL DEL…= (space key goes from white to black, back-space key goes from black to white) To insert a usual ASCII letter or symbol, just type it. As the keys =- + = # ~= are preempted by =uniline-mode=, to type them, prefix them with ==. Example: = -= inserts a =-= and = += inserts a =+=. [[file:images/insert-glyphs.png]] #+begin_example │ ├────────────────────────────╮ ▼ ╭─arrows──────╮ ▼ ╭───╮ ╰──▶─(a)─┤ ▷ ▶ → ▹ ▸ ↔ │ ╰──▶─(+)─┤ + │ │ ╰─────────────╯ │ ╰───╯ │ ╭─squares─────╮ │ ╭───╮ ╰──▶─(s)─┤ □ ■ ▫ ▪ ◆ ◊ │ ╰──▶─(-)─┤ - │ │ ╰─────────────╯ │ ╰───╯ │ ╭─circles───────╮ │ ╭───╮ ╰──▶─(o)─┤ · ∙ • ● ◦ Ø ø │ ╰──▶─(=)─┤ = │ │ ╰───────────────╯ │ ╰───╯ │ ╭─crosses───────╮ │ ╭───╮ ╰──▷─(x)─┤ ╳ ╱ ╲ ÷ × ± ¤ │ ╰──▶─(#)─┤ # │ │ ╰───────────────╯ │ ╰───╯ │ ╭───────╮ │ ╭───╮ ╰──▶─(SPC DEL)─┤ ░▒▓█ │ ╰──▶─(~)─┤ ~ │ ╰───────╯ ╰───╯ #+end_example ** Fine tweaking of lines :PROPERTIES: :CUSTOM_ID: fine-tweaking-of-lines :END: [[file:images/fine-tweaking.png]] #+begin_example convert this ═══▶ into that ╭───────────╮ ╭───────────╮ │╶───┬────▷ │ │╶───╮────▷ │ │ │ │ │ │ │ │ │ │ │ │ ▀▀▀ │ │ ▀▟▀ │ ╰───────────╯ ╰───────────╯ #+end_example At the crossing of lines, it may be appealing to do small adjustments. In the above example, we removed a segment of line which occupies 1/4 of a character. This cannot be achieve with line tracing alone. We also modified a quarter-block line in a non-obvious way. - Put the point (the cursor) on the character where lines cross each other. - type =INS S- S-= == here refers to the right part of the character under the point. The 1/4 line segment will cycle through all displayable forms. On the second stroke, no segment will be displayed, which is what we want. Caveat! The UNICODE standard does not define all possible combinations including double line segments. (It does for all combinations of thin and tick lines). So sometimes, when working with double lines, the process may be frustrating. This works also for lines made of quarter-blocks. There are 4 quarter-blocks in a character, either on or off. Each of the 4 shifted keyboard arrows flips a quarter-block on-and-off. In the above example, the effect was achieved with: =INS S- S- S-= * Rectangular actions :PROPERTIES: :CUSTOM_ID: rectangular-actions :END: - Drawing, - filling, - moving, - copying & yanking, - change line & glyph styles, those actions may be performed on a rectangular selection. Select a rectangular region with =C-SPC= or =C-x SPC= and move the cursor. You may also use =S-= (== being any of the 4 directions) to extend the selection. The buffer grows as needed with white spaces to accommodate the selection. Selection extension mode is active when =shift-select-mode= is non-nil. Or you may use the mouse to highlight the desired region. All those region-highlighting are standard in =Emacs=, and unrelated to =Uniline=. Once you have a region highlighted, press == (this key can be customized, see [[#insert-key][Insert key]]). The selection becomes rectangular if it was not. You are offered a menu of possible actions. ** Drawing a rectangle :PROPERTIES: :CUSTOM_ID: drawing-a-rectangle :END: To draw a rectangle in one shot, select a region, press ==, then hit: - =r= to draw a rectangle inside the selection - =S-R= to draw a rectangle outside the selection - =C-r= to overwrite a rectangle inside the selection - =C-S-R= to overwrite a rectangle outside the selection If needed, change the brush with any of =- + = # = [[file:images/draw-rectangle.png]] #+begin_example ╭───────╮ r: inside╮╭───────╮ │ one │ ▗▄▄▄▄▄▄▖╭┤│▛▀▀▀▀▀▜│ │ ┏━━━━┿━━━━━━┓ ▐╭────╮▌│╰┼▌ ▐│ ╰──╂────╯ two ┃ ▐│ │▌│ │▙▄▄▄▄▄▟│ ┃ ╔═══════╋═╗ ▐│ ├▌╯ ╰─────┬─╯ ┗━━━╋━━━━━━━┛ ║ ▐╰────╯▌────────┴───╮ ║ three ║ ▝▀▀▀▀▀▀▘ R: outside╯ ╚═════════╝ ╭─────────╮ my text I │my text I│ want to ╶─R─▷ │want to │ box │box │ ╰─────────╯ #+end_example The usual =C-_= or =C-/= keys may be hit to undo, even with the region still active visually. ** Filling a rectangle :PROPERTIES: :CUSTOM_ID: filling-a-rectangle :END: While the rectangular mode is active, press =i= to fill the rectangle. You will be asked to choose a character. You have those options: - for a regular character like =t=, just type it. - =SPC= or =DEL= for a shade of grey =" ░▒▓█"= among the 5 available in UNICODE. =SPC= to make it darker and darker. =DEL= to make the rectangle lighter and lighter. - =C-y= to chose the first character in the top of the kill ring. The above selection is the same as for the flood-fill action (see [[#flood-fill][Flood-fill]]). ** Moving a rectangular region :PROPERTIES: :CUSTOM_ID: moving-a-rectangular-region :END: Select a region, then press ==. Use arrow keys to move the rectangle around. A numeric prefix may be used to move the rectangle that many characters. - Under =Hydra=, be sure to specify the numeric prefix with just digits, without the =Alt= key. Typing =15 = moves the rectangle 15 characters to the left. =M-15 = does not work. - Under =Transient=, use the =Alt= key, like anywhere else in =Emacs=. Type =M-15 = to move the selected rectangle 15 characters to the left. Press =q=, ==, or =C-g= to stop moving the rectangle. The =C-_= key may also be used to undo the previous movements, even though the selection is still active. [[file:images/move-rectangle.png]] #+begin_example ▲ │ ╭─────┴──────╮ │this is │ │my rectangle│ ◀───┤I want to ├──▶ │move │ ╰─────┬──────╯ │ ▼ #+end_example What is leakage? When moving a rectangular region, the rectangle leaves behind lines oriented in the movement direction. This is not a bug, but a feature. Leakage allows growing a drawing without breaking it in two parts. [[file:images/rect-region-leak.png]] #+begin_example ┏┯━━━┓ ┏┯━━━┓ ┏━┛│ ┗━╦━━┓ with ┏━┛│ ┗━╦━━┓ ┃ │ ║ ┃╶──╮leak ┃ │ ║ ┃ leaked ┃ │ ║ ┃ ╰────▶ ┃ │ ║ ┃◀──────╴ ┗━━┷━━━━━╩━━┛ ┃ │ ║ ┃ lines │ ┗━━┷━━━━━╩━━┛ │without │leak ╰──────╮ ▼ ┏┯━━━┓ ┏━┛│ ┗━╦━━┓ ┃ │ ║ ┃ broken ◀───────╴ ┃ │ ║ ┃ drawing ┗━━┷━━━━━╩━━┛ #+end_example ** Copying, killing, yanking a rectangular region :PROPERTIES: :CUSTOM_ID: copying-killing-yanking-a-rectangular-region :END: A rectangle can be copied or killed, then yanked somewhere else. Select a region, press ==, then: - =c= to copy - =k= to kill - =y= to yank (aka paste) This is similar to the =Emacs= standard rectangle handling: - =C-x r r= copy rectangle to register - =C-x r k= kill rectangle - =C-x r y= yank killed rectangle The first difference is that =Uniline= rectangles, when killed and yanked, do not move surrounding characters. The second difference is that the white characters of the yanked rectangle are considered transparent. As a result, only non-blank parts of the yanked rectangle are over-printed. =Uniline= and =Emacs= standard rectangle share the same storage for copied and killed rectangles, namely the =killed-rectangle= Lisp variable. So, a rectangle can be killed one way, and yanked another way. ** Dashed lines and other styles :PROPERTIES: :CUSTOM_ID: dashed-lines-and-other-styles :END: [[file:images/four-styles.png]] #+begin_example ╭────▷───╮ ┏━━━━▶━━━┓ ╔════▶═══╗ │ ╭─□──╮ │ ┃ ┏━■━━┓ ┃ ║ ╔═■══╗ ║ △ │ │ ▽ ▲ ┃ ┃ ▼ ▲ ║ ║ ▼ │ ╰───◦╯ │ ┃ ┗━━━•┛ ┃ ║ ╚═══•╝ ║ ╰───◁────╯ ┗━━━◀━━━━┛ ╚═══◀════╝ ╭╌╌╌╌▷╌╌╌╮ ┏╍╍╍╍▶╍╍╍┓ ┆ ╭╌□╌╌╮ ┆ ┇ ┏╍■╍╍┓ ┇ △ ┆ ┆ ▽ ▲ ┇ ┇ ▼ ┆ ╰╌╌╌◦╯ ┆ ┇ ┗╍╍╍•┛ ┇ ╰╌╌╌◁╌╌╌╌╯ ┗╍╍╍◀╍╍╍╍┛ ╭┈┈┈┈▷┈┈┈╮ ┏┉┉┉┉▶┉┉┉┓ ┊ ╭┈□┈┈╮ ┊ ┋ ┏┉■┉┉┓ ┋ △ ┊ ┊ ▽ ▲ ┋ ┋ ▼ ┊ ╰┈┈┈◦╯ ┊ ┋ ┗┉┉┉•┛ ┋ ╰┈┈┈◁┈┈┈┈╯ ┗┉┉┉◀┉┉┉┉┛ #+end_example A base drawing can be converted to dashed lines. Moreover, lines can be made either thin or thick. - Select the rectangular area you want to operate on (with mouse drag or =S-=, =S-= and so on as described earlier). - Type =INS=, then =s= (as "style"). You will be offered a choice of styles: - =3=: vertical lines will become 3 dashes per character, while horizontal ones will get 2 dashes per character. - =4=: vertical and horizontal lines will get 4 dashes per character. - =h=: thin lines corners, which are usually rounded, become hard angles. - =+=: thin lines and intersections become thick, empty glyphs get filled. - =-=: thick lines and intersections become thin, filled glyphs are emptied. - ~=~: thick and thin lines become double lines. - =0=: come back to standard base-line =Uniline= style: plain, not-dashed lines, thin corner rounded, ASCII art is converted to UNICODE. - =a=: apply the =aa2u-rectangle= function from the unrelated =ascii-art-to-unicode= package, to convert ASCII art to UNICODE (this only works if =ascii-art-to-unicode= is already installed). Converting parts of a drawing from one style to another can produce nice looking sketches. [[file:images/same-sketch-several-styles.png]] #+begin_example ╭───╮ ╭───╮ ╭───╮ │░░░│ │░░░│ │░░░┝━▶┓ ╭╌╌╌╌╌╮ │░░░╰───╯░░░╰───╯░░░│ ┃ ┆░░░░░╰╌╌╌╌╌╮ □░░░░░░░░░░░░░░░░░░░│ ┗━┥░░░░░░░░░░░┆ │░░░╭───╮░░░╭───╮░░░│ ┆░░░░░╭╌╌╌╌╌╯ ╰───╯ ╰─┰─╯ ╰─┰─╯ ╰╌╌┰╌╌╯ ▲ ┃ ▼ ┗━━━━━━━┻━━━━━━━━━┛ ┏━━━┓ ┏━━━┓ ┏━━━┓ ┃░░░┃ ┃░░░┃ ┃░░░┠─▷╮ ┏╍╍╍╍╍┓ ┃░░░┗━━━┛░░░┗━━━┛░░░┃ │ ┇░░░░░┗╍╍╍╍╍┓ ■░░░░░░░░░░░░░░░░░░░┃ ╰─┨░░░░░░░░░░░┇ ┃░░░┏━━━┓░░░┏━━━┓░░░┃ ┇░░░░░┏╍╍╍╍╍┛ ┗━━━┛ ┗━┯━┛ ┗━┯━┛ ┗╍╍┯╍╍┛ △ │ ▽ ╰───────┴─────────╯ #+end_example ** ASCII to UNICODE :PROPERTIES: :CUSTOM_ID: ascii-to-unicode :END: The standard base-line =Uniline= (=INS s 0=) or =aa2u-rectangle= (=INS s a=) conversions may be used to convert ASCII art to UNICODE. The original ASCII art may be drawn for instance by the =artist-mode= or the =picture-mode= packages. To use =aa2u-rectangle=, install the =ascii-art-to-unicode= package by Thien-Thi Nguyen (RIP), available on ELPA. =Uniline= does not requires a dependency on this package, by lazy evaluating any call to =aa2u-rectangle=. See https://elpa.gnu.org/packages/ascii-art-to-unicode.html [[file:images/ascii-2-unicode.png]] #+begin_example +-------------+ +--+ | +-->-| +-----+ ASCII art | 1 +--------+--+ | 3 | made by +----+--------+ | +----+---+ Artist-mode | 2 +-<----+ +-----------+ ╭─────────────╮ ╭──╮ │ ├──▷─│ ╰─────╮ Converted to │ 1 ╭────────┼──╮ │ 3 │ Uniline base style ╰────┼────────╯ │ ╰────┬───╯ INS s 0 │ 2 ├─◁────╯ ╰───────────╯ ┌─────────────┐ ┌──┐ │ ├──>─│ └─────┐ Converted by │ 1 ┌────────┼──┐ │ 3 │ aa2u-rectangle └────┼────────┘ │ └────┬───┘ INS s a │ 2 ├─<────┘ └───────────┘ #+end_example =INS s 0= with selection active calls the =uniline-change-style-standard= function. It converts what looks ASCII-art to UNICODE-art. Of course, there are ambiguities regarding whether a character is part of a sketch or not. The heuristic is to consider that a character is part of a sketch if it is surrounded by at least one other character which is part of a sketch. So, an isolated =-= minus character will be left alone, while two such characters =--= will be converted to UNICODE. Conversion will happens also for =<-= for instance. Here is a fairly convoluted ASCII-art example, along with its conversion by =INS s 0=: [[file:images/ascii-2-unicode-b.png]] #+begin_example ╭─↔--<-◁-◀--━+ +--->------==+ /----/ Rectangle1 |-----+-----+ Rectangle2 v v | | ^ " | "quote" +-\ ▼ ^^ \------------/ /-+-\ +------------+ " v | \--+------+--/ | | +----\----/--+ " >▷▶> \>--\ | | \---/ | | " v \==<===/ a=b 1=2 a-to-b +----+ ◁==/ >-> ╭─↔──◁─◁─◀──━┑ ╭───▷──────══╕ ╭────┤ Rectangle1 │─────╥─────┤ Rectangle2 ▽ ▽ │ │ △ ║ │ "quote" ├─╖ ▼ △^ ├────────────┤ ╭─╨─╮ ├────────────┤ ║ ▽ │ ╰──┬──────┬──╯ │ │ ╰────┬────┬──╯ ║ ▷▷▶▷ ╰▷──╮ │ │ ╰───╯ │ │ ║ ▽ ╘══◁═══╛ a=b 1=2 a-to-b ╰────╯ ◁══╝ ▷─▷ #+end_example * Long range actions: contour and flood-fill :PROPERTIES: :CUSTOM_ID: long-range-actions-contour-and-flood-fill :END: ** Tracing a contour :PROPERTIES: :CUSTOM_ID: tracing-a-contour :END: [[file:images/contour-tracing.png]] #+begin_example ╭──────────────╮ ╭─╯A.written.text╰────────╮ │outlined by the.`contour'│ ╰─╮function.gets╶┬────────╯ ╰╮a.surrounding╰───────╮ ╰─╮line.in.the.current│ ╰─╮brush.style╭─────╯ ╰───────────╯ #+end_example Choose or change the brush style with any of =-,+,=_,#,=. Put the cursor anywhere on the shape or outside but touching it. Then type: = c= A contour line is traced (or erased if brush style is ==) around the contiguous shape close to the cursor. When hitting capital letter: = S-C= the contour is overwritten. This means that if there was already a different style of line on the contour path, it is overwritten. The shape is distinguished because it floats in a blank characters ocean. For the shake of the contour function, blank characters are those containing lines as drawn by =Uniline= (including true blank characters). Locations outside the buffer are also considered blank. The algorithm has an upper limit of =10000= steps. This avoids an infinite loop in which the algorithm may end up in some rare cases. One of those cases is when the contour crosses a new-page character, displayed by =Emacs= as =^L=. =10000= steps require a fraction of a second to run. For shapes really huge, you may launch the contour command once again, at the point where the previous run ended. This =10000= steps limit is customizable. Type: #+begin_example M-x customize-variable uniline-contour-max-steps #+end_example ** Flood-fill :PROPERTIES: :CUSTOM_ID: flood-fill :END: [[file:images/flood-fill.png]] #+begin_example this.text.surrounds this.text.surrounds . / .▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒/ . //╶───▷╴.▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒// ... //// ...▒▒▒▒▒▒▒▒▒▒▒▒//// ...a.hole///// ...a.hole///// #+end_example A hollow shape is a contiguous region of identical characters (not necessarily blank), surrounded by a boundary of different characters. The end of the buffer in any direction is also considered a boundary. Put the cursor anywhere in the hole. Then type: = i= Answer by giving a character to fill the hole. If instead of a character, =SPC= or =DEL= is typed, then a shade of grey character is picked. =SPC= selects a darker grey than the one the point is on, while =DEL= selects a lighter. There are 5 shades of grey in the UNICODE standard: =" ░▒▓█"=. Those grey characters are well supported by the suggested fonts. =C-y= is also an option. The first character in the top of the kill ring will be chosen as the filling character. (The kill ring is filled by functions like =C-k= or =M-w=, unrelated to =Uniline=). Typing == or =C-g= aborts the filling operation. A rectangular shape may also be filled. - Mark a region - = i= - answer which character should be used to fill. There is no limit on the area to fill. Therefore, the filling operation may flood the entire buffer (but no more). * Macros :PROPERTIES: :CUSTOM_ID: macros :END: =Uniline= adds directional macros to the =Emacs= standard macros. Record a macro as usual with =C-x (= … =C-x )=. Then call it with the usual =C-x e=. But then, instead of executing the macro, a menu is offered to execute it in any of the 4 directions. When a macro is executed in a direction other than the one it was recorded, it is twisted in that direction. This means that recorded hits on the 4 keyboard arrows are rotated. It happens also for shift and control variations of those keys. Direction of text insertion is also rotated. There is still the classical =e= option to call the last recorded macro. So instead of the usual =C-x e=, type =C-x e e=. And of course, the usual repetition typing repeatedly =e= is available. Why are directional macros useful? To create fancy lines. For instance, if we want a doted-line instead of the continuous one, we record a macro for one step: #+begin_example C-x ( ;; begin recording INS o ;; insert a small dot ;; draw a line over 2 characters C-x ) ;; stop recording #+end_example Then we call this macro repeatedly in any of the 4 directions: [[file:images/macro-doted-line.png]] #+begin_example ·─·─·─·─· ╷ ·──· │ │ │ │ · · · · │ │ │ │ · ·─·─·─· · │ │ ·─·─·─·─·─·─· #+end_example We can draw complex shapes by just drawing one step. Hereafter, we call a macro in 4 directions, closing a square: [[file:images/macro-fancy-squares.png]] #+begin_example ╭╮╭╮╭╮╭╮╭╮╭╮ △ △ △ △ △ △ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╯╰╯╰╯╰╯╰╯╰╯│ ╶╯╶╯╶╯╶╯╶╯╶╯╷ ╭──╯∙╰─╯∙╰─╯∙╰─╯∙│ ▷┤□├▷┤□├▷┤□├▷┤□├▽ ╰╮ ╰╮ ◁╮ ╰▷ │∙ │ ╭┴┼─╯ ╰─╯ ╰─╯ ╰─┼┴╮ ╭╯ ╭╯ ╵ ╷ ╰╮ ╰╮ │□│ │□│ ╰╮ ╰╮ ◁╮ ╰▷ │ ∙│ ╰┬╯ ╰┬╯ ╭╯ ╭╯ ╵ ╷ ╭╯ ╭╯ △ ▽ ╰╮ ╰╮ ◁╮ ╰▷ │∙ │ ╭┴╮ ╭┴╮ ╭╯ ╭╯ ╵ ╷ ╰╮ ╰╮ │□│ │□│ ╰╮ ╰╮ ◁╮ ╰▷ │ ∙│ ╰┬┼─╮ ╭─╮ ╭─╮ ╭─┼┬╯ │╭╮╭╮╭╮╭╮╭╮╭─╯ ╵╭╴╭╴╭╴╭╴╭╴╭╴ │∙╭─╮∙╭─╮∙╭─╮∙╭──╯ △┤□├◁┤□├◁┤□├◁┤□├◁ ╰╯╰╯╰╯╰╯╰╯╰╯ ▽ ▽ ▽ ▽ ▽ ▽ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ #+end_example * Which fonts? :PROPERTIES: :CUSTOM_ID: which-fonts :END: A mono-space character font must be used. It must also support UNICODE. ** Recommended fonts :PROPERTIES: :CUSTOM_ID: recommended-fonts :END: Not all fonts are born equal. - =(set-frame-font "DejaVu Sans Mono" )= - =(set-frame-font "Unifont" )= - =(set-frame-font "Hack" )= - =(set-frame-font "JetBrains Mono" )= - =(set-frame-font "Cascadia Mono" )= - =(set-frame-font "Agave" )= - =(set-frame-font "JuliaMono" )= - =(set-frame-font "FreeMono" )= - =(set-frame-font "Iosevka Comfy Fixed" )= - =(set-frame-font "Iosevka Comfy Wide Fixed")= - =(set-frame-font "Aporetic Sans Mono" )= - =(set-frame-font "Aporetic Serif Mono" )= - =(set-frame-font "Source Code Pro" )= Those fonts are known to support the required UNICODE characters, AND display them as mono-space. There are fonts advertised as mono-space which give arbitrary widths to non-ASCII characters. That is bad for the kind of drawings done by =Uniline=. You may want to try any of the suggested fonts. Just hit the corresponding entry in the =Uniline= menu, or type = f=. You may also execute the above Lisp commands like that: =M-: (set-frame-font "DejaVu Sans Mono")= This setting is for the current session only. If you want to make it permanent, you may use the =Emacs= customization: = f *= or =M-x customize-face default= Beware that =Emacs= tries to compensate for missing UNICODE support by the current font. =Emacs= substitutes one font for another, character per character. The user may not notice until the drawings done under =Emacs= are displayed on another text editor or on the Web. Of course, using the suggested fonts and the UNICODEs drawn by =Uniline= keeps you away from those glitches. To know which font =Emacs= has chosen for a given character, type: =C-u C-x == Note that none of those commands downloads a font from the Web. The font should already be available. ** Use case: mixing fonts :PROPERTIES: :CUSTOM_ID: use-case-mixing-fonts :END: A user on GitHub, dmullis, exposed his use-case. A source-code base is usually edited with a font not in the Uniline list of recommended fonts. However, it is desirable to document the source code with Uniline, either directly along the source or in separate files. How to achieve that without messing with the fonts in several Emacs buffers? Several solutions have emerged from the discussion. - =face-remap-add-relative= A line like this at the top of the files reserved for Uniline drawings: #+begin_example -*- eval: (face-remap-add-relative 'default :family "DejaVu Sans Mono"); -*- #+end_example This confines its effect to just the one single buffer. - =uniline-mode-hook= Add a hook (a function called when entering =uniline-mode=): #+begin_example (add-hook 'uniline-mode-hook (lambda () (face-remap-add-relative 'default :family "DejaVu Sans Mono"))) #+end_example There are also =uniline-mode-on-hook= & =uniline-mode-off-hook= which can be handy. - =font-lock-comment-face= An alternative mean of limiting the scope of the font change is the Emacs standard font-lock mechanism. #+begin_example (customize-face '(font-lock-comment-face)) #+end_example Then check =Font Family=, type in value ="DejaVu Sans Mono",= and =C-x C-s=. Now any major mode that understands "comments" as distinct from other text can safely nest a Uniline drawing within its boundaries, all text outside the "comment" unaffected (except perhaps by spacing). Look also at the =font-lock-constant-face= face. - Org Mode In Org Mode, the usable faces could be =org-block=, =org-quote=, =org-verse=. But first the =org-fontify-quote-and-verse-blocks= variable must be set to =t=. - Markdown In Markdown mode, customize the =markdown-pre-face= or =markdown-code-face= faces. * Hydra or Transient? :PROPERTIES: :CUSTOM_ID: hydra-or-transient :END: The basic usage of =Uniline= should be easy: just move the point, and lines are traced. Change brush to draw thicker lines. More complex actions are summoned by the == key, with or without selection. This is a single key to remember. Then a textual menu is displayed, giving the possible keys continuations and their meaning. All that is achieved by the =Hydra= or =Transient= libraries, which are now part of =Emacs= (thanks!). The =Hydra= and =Transient= libraries offer similar features. Some users may prefer one or the other. =Uniline= was developed from day one with =Hydra=. =Transient= is a late addition. ** Selecting Hydra or Transient :PROPERTIES: :CUSTOM_ID: selecting-hydra-or-transient :END: Two files are compiled when installing =Uniline= - =uniline-hydra.el= - =uniline-transient.el= One of them should be loaded (but not both). There are several ways. The cleanest is =use-package=. Add those lines to your =~/.emacs= file: #+begin_src elisp (use-package uniline-hydra :bind ("C-" . uniline-mode)) #+end_src or: #+begin_src elisp (use-package uniline-transient :bind ("C-" . uniline-mode)) #+end_src The following key sequences can assist in modifying the =.emacs= file: - = * H= - = * T= Note: there used to be a customizable setting to switch between the two interfaces. This had many issues. One of them is that the native-compiler is blind to all user-customized settings. There is a third file, =uniline-code.elc=. Loading =uniline-hydra.elc= or =uniline-transient.elc= automatically loads =uniline-core.elc=. ** Instantly selecting Hydra or Transient :PROPERTIES: :CUSTOM_ID: instantly-selecting-hydra-or-transient :END: It is now possible to switch user interfaces on the fly. To do so, look at the "Customize" entry in the Uniline menu. This menu is available: - from the menu-bar at the top of the Emacs screen (if not made invisible), - by left-clicking on ="Uniline"= in the mode-line, at the bottom of the Emacs screen. Note that the changes are for the current session only. To permanently choose Hydra or Transient, change your =~/.emacs=initialization file as describe in [[#selecting-hydra-or-transient][Selecting Hydra or Transient]]. The actions performed by the menu are: - =(load-library "uniline-hydra")= - =(load-library "uniline-transient")= You can execute them directly or by other means. ** One-liner menus :PROPERTIES: :CUSTOM_ID: one-liner-menus :END: The multi-lines menus in Hydra and Transient are quite useful for casual users. For seasoned users, those huge textual menus may distract them from their workflow. It is now possible to switch to less distracting textual menus. They are displayed in the echo-area on a single line. To do so, type: - =C-t= within a sub-mode (glyph insertion mode, rectangle handling, etc.) - =C-h TAB= at the top-level. This will flip between the two sizes of textual menus. It also affects the welcome message, the one displayed when entering the =Uniline= minor mode. The current size is controlled by the =uniline-hint-style= variable: - =t= for full fledged messages over several lines - =1= for one-liner messages - =0= for no message at all The variable is "buffer-local", which means that it can take distinct values on distinct buffers. Its default value can be customized and saved for future sessions: =M-x customize-variable uniline-hint-style= After customization it can be changed later, on a buffer per buffer basis, with the =C-t= or =C-h TAB= keys. Transient natively offers a similar setting: =transient-show-popup=. (There is no such variable in Hydra). It can be customized with =t=, =nil=, =0= (zero), or a number. This is similar but not exactly the same as the Hydra behavior and the =uniline-hint-style=. the Transient setting stays in effect until the =C-t= or =C-h TAB= keys are not used, . As soon as one of those keys is invoked, =transient-show-popup= is toggled (which does not happens in Transient alone). The change is kept in effect throughout the =Uniline= session, but no longer. ** The Hydra interface :PROPERTIES: :CUSTOM_ID: the-hydra-interface :END: Put that in your =~/.emacs= file: #+begin_src elisp (use-package uniline-hydra :bind ("C-" . uniline-mode)) #+end_src It has been asked by =Transient=-only users to avoid installing the =Hydra= package. Currently, it is not possible to make dependencies conditional in =Melpa=. And removing the =Hydra= dependency would hurt =Hydra= users. Therefore, for the time being, the =Hydra= package is still installed when installing =Uniline= through =Melpa=. ** The Transient interface :PROPERTIES: :CUSTOM_ID: the-transient-interface :END: Put that in your =~/.emacs= file: #+begin_src elisp (use-package uniline-transient :bind ("C-" . uniline-mode)) #+end_src =Transient= interface was added recently to =Uniline=. This leaded to the splitting of the single =uniline.el= file into 4 source files. Hopefully, the added complexity remains hidden by the =Elpa= - =Melpa= packaging system. * Customization :PROPERTIES: :CUSTOM_ID: customization :END: Type: =M-x customize-group uniline=. Or =Menu bar ⟶ Options ⟶ Customize Emacs ⟶ Specific Group… ⟶ "uniline"=. This invokes the standard =Emacs= customization system. Your settings will be saved in the file pointed to by the =custom-file= variable if set, or your =~/.emacs= file. (Along with all your other settings unrelated to =Uniline=). Two settings are special: interface type (obsolete) & the insert key. The other settings are self-explanatory ** Interface type :PROPERTIES: :CUSTOM_ID: interface-type :END: The =uniline-interface= variable is *obsolete*. Choosing between =Hydra= or =Transient= interface is done by loading one or the other sub-package. This is best done in the =.emacs= initialization file. See [[#installation][Installation]] for details. Typing either of the following key sequences can assist in modifying the =.emacs= file: - = * H= - = * T= ** Insert key :PROPERTIES: :CUSTOM_ID: insert-key :END: By default, the == or =INS= key is the prefix for most of the =Uniline= actions. Some computers do not have an =INS= key, or it is bound to some other command (Apple?). This can be changed temporarily or permanently. The customization allows to set several keys at the same time. Depending on whether =Emacs= is run in a graphical environment or a text-only terminal, either the == or the == events are generated by the =INS= key. Therefore, by default =Uniline= defines both events as the =INS= key. Variable =uniline-key-insert=. ** Maximum steps when drawing a contour :PROPERTIES: :CUSTOM_ID: maximum-steps-when-drawing-a-contour :END: Defaults to =10000=. To avoid an infinite loop in some rare cases. Variable =uniline-contour-max-steps=. ** Cursor type :PROPERTIES: :CUSTOM_ID: cursor-type :END: Hollow by default, so that what is under the cursor remains visible. There is the option to leave the cursor as it is. Variable =uniline-cursor-type.= ** Hint style :PROPERTIES: :CUSTOM_ID: hint-style :END: Currently only applicable to the =Hydra=. It defaults to "full fledged menus". Variable =uniline-hint-style=. =Transient= offers a similar setting: =transient-show-popup=. ** Welcome message visibility :PROPERTIES: :CUSTOM_ID: welcome-message-visibility :END: Default is "on". Turn it "off" for less distraction. Even when turned of, the welcome message can still be displayed by pressing =C-h TAB=. Variable =uniline-show-welcome-message=. ** Line spacing :PROPERTIES: :CUSTOM_ID: line-spacing :END: The =line-spacing= setting in =Emacs= can change the display of a sketch. (This setting is unrelated to =Uniline=). The best looking effect is given by: : (setq line-spacing nil) You may want to change your current setting. =Uniline= may handle this variable some day. Right now, =line-spacing= is left as a matter of choice for everyone. [[file:images/line-spacing.png]] #+begin_example ╭────┬────────┬────╮ ╺┯━━━━┯┯━━┯┯━┯┯━━━━━━━━┯┯━━━━━━━┯┯━━━━━━┯╸ │▒▒▒▒╰────────╯▒▒▒▒│ │ │╰is╯╰a╯│ ││ │╰around╯ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ ╰this╯ ╰sentence╯╰hanging╯ │▒▒▒╭─╮▒▒▒▒▒▒╭─╮▒▒▒│ △ │▒▒▒╰─╯▒▒▒▒▒▒╰─╯▒▒▒│ │ △ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ ╰─────────┬────────╯ ╰──────────────────╯ verbs (setq line-spacing nil) #+end_example ** Font :PROPERTIES: :CUSTOM_ID: font :END: Face customization is unrelated to =Uniline=. However, =Uniline= can assist in choosing a good font and customizing the =default= face. See [[#which-fonts][Which fonts?]]. Type = f= to select a font just for the current =Uniline= session. Type =*= to enter the =Emacs= customization of the =default= face and retain your choice for future sessions. ** Upward infiniteness ∞ :PROPERTIES: :CUSTOM_ID: upward-infiniteness- :END: If the variable =uniline-infinite-up↑= is: - =t=, then the buffer grows at the top of the buffer (or at the top of the narrowed region), by adding empty lines as needed. - =nil=, then the top of the buffer (or the top of the narrowed region) is a non-trespassable limit. This is the default, and the behaviour of previous versions of Uniline. * How Uniline behaves with its environment? :PROPERTIES: :CUSTOM_ID: how-uniline-behaves-with-its-environment :END: ** Language environment :PROPERTIES: :CUSTOM_ID: language-environment :END: The so called "language environment" in Emacs can cause unwanted line breaks, like in this drawing: [[file:images/broken-lines-lang-env.png]] #+begin_example ╶───────────┬─────╮ │ │ │ │ │ │ ╰───────────╯ unexpected broken lines ╶─────┬───────────╮ │ │ │ │ │ │ ╰───────────╯ expected continuous lines #+end_example The above example was drawn first with the =Chinese-BIG5= language environment, then with the =English= environment. There is nothing specific about =Chinese-BIG5=. It is just an instance picked out from more than 100 language environments. #+begin_example C-x RET l Chinese-BIG5 C-x RET l English #+end_example In =Chinese-BIG5=, some characters are considered twice as wide as standard characters. Whereas in =English=, all characters needed by Uniline are 1 unit wide. Thanks to *rumengling* (GitHub) for discovering and diagnosing the issue! To workaround the issue, when entering =uniline-mode=, the width of all characters Uniline uses is checked. If some of them are more than 1, the =char-width-table= variable is patched. What are the consequences of this patch? The =char-width-table= variable is an Emacs global. Therefore the patch by Uniline will affect all buffers. As the characters touched by the patch are graphic, and have nothing to do with Chinese, it should not have any significance on text written in Chinese. It was pondered whether Uniline should put back =char-width-table= at its original value upon exiting =uniline-mode=, or leaving the patch. For now, it has been decided to leave it. Because anyway, intertwining several =uniline-mode= and changes to the language environment is intractable. In case of something, re-setting the language environment to its same value cancels the patch to =char-width-table= by Uniline. ** Compatibility with Picture-mode :PROPERTIES: :CUSTOM_ID: compatibility-with-picture-mode :END: =Picture-mode= and =uniline-mode= are compatible. Their features overlap somehow: - Both implement an unlimited buffer in east and south directions. - Both visually truncate long lines (actual text is not truncated). - Both set the overwrite mode (=uniline-mode= activates =overwrite-mode=, while =picture-mode= re-implements it) - Both are able to draw rectangles (=uniline-mode= in UNICODE, =picture-mode= in ASCII), copy and yank them. They also have features unique to each: - =Picture-mode= writes in 8 possible directions - =Picture-mode= handles TAB stops - =Uniline-mode= draws lines and arrows ** Compatibility with Artist-mode :PROPERTIES: :CUSTOM_ID: compatibility-with-artist-mode :END: =Artist-mode= and =uniline-mode= are mostly incompatible. This is because =artist-mode= preempts the arrow keys, which give access to a large part of =uniline-mode= features. However, it is possible to use both one after the other. ** Compatibility with Whitespace-mode :PROPERTIES: :CUSTOM_ID: compatibility-with-whitespace-mode :END: =Whitespace-mode= and =uniline-mode= are mostly compatible. Why activate =whitespace-mode= while in =uniline-mode=? Because =Uniline= creates a lot of white-spaces to implement an infinite buffer. And it is funny to look at this activity. To make =uniline-mode= and =whitespace-mode= fully compatible, disable the newline visualization: - =M-x customize-variable whitespace-style= - uncheck =(Mark) NEWLINEs= This is due to a glitch in =move-to-column= when a visual property is attached to newlines. And =uniline-mode= makes heavy use of =move-to-column=. ** Compatibility with Org Mode :PROPERTIES: :CUSTOM_ID: compatibility-with-org-mode :END: You may want to customize the shift extension mode in =Org Mode=. This is because =Org Mode= preempts =shift-select-mode= for other useful purposes. Just type: #+begin_example M-x customize-variable org-support-shift-select #+end_example and choose "when outside special context", which sets it to =t=. You then get the shift-selection from =Org Mode=, not from =Uniline=. The difference is that the =Uniline='s one handles the infinite-ness of the buffer. Other than that, =Uniline= is compatible with =Org Mode= Thanks to jdtsmith (GitHub) for sharing a funny fact he discovered. If a source block is created with the =Uniline= language (=Uniline= is *not* a language like =C++,= =Python=, or =Bash=), then it can be edited (=M-x org-edit-special=) with =uniline-mode= automatically activated. [[file:images/org-src-block.png]] #+begin_example #+begin_src uniline ╭───╮ ╭───╮ │ ╷ ╰───╯ ╷ │ │ ╰─ ╶─╯ │ ╰╮ ● ● ╭╯ │ ╷ │ ╰╮ ────╯ ╭╯ ╰───────╯ #+end_src #+end_example ** Org Mode and LaTex :PROPERTIES: :CUSTOM_ID: org-mode-and-latex :END: Use the =pmboxdraw= LaTex module. This gives limited support for "box drawing" characters in LaTex documents. Example: [[file:images/latex-block.png]] #+begin_example #+LATEX_HEADER: \usepackage{pmboxdraw} #+begin_src text this works: ┌─────┐ ┌────────────┐ │ ├───────┤ │ └─────┘ │ │ ┌─────┐ ┌────┤ │ │ ├──┘ │ │ └─────┘ ┌────┤ │ ┌─────┐ │ │ │ │ ├──┘ └────────────┘ └─────┘ this does not quite work: ┏━━━┓ ┏━━┓ ┏━━━━━┓ ┃ ┃ ┃ ┣━━━━━┫ ┃ ┃ ┗━━┛ ┃ ┏┛ ┃ ┗━━━━━━━━━┛ ┗━━━━━━┛ but that is OK: ┏━━━┓ ┃ ┃ ┗━━━┛ that is OK too: ╺════╦══╗ ╔════╗ ║ A║ ║ B ╚══╗ ╚══╝ ╚═══════╝ this works: ├── dev └┬┬ release │├── new │└── old ├── graph └── non-graph #+end_src #+end_example Note that corners of thin lines should be sharp. There is no support for rounded corners. To export this Org Mode example to PDF through LaTex, type: =C-c C-E l o= ** What about =\t= tabs? :PROPERTIES: :CUSTOM_ID: what-about-t-tabs :END: Some files may contain tabs (the character =\t=). Those include programming code (Python, Perl, C++, D, Rust, JavaScript and so on). When =Uniline= draws something in the middle of a TAB, or right onto a TAB, it first converts it to spaces, then proceeds as usual. This process is invisible. So be cautious if TABs have a special meaning in the file. Also, rectangles are first untabified (if there are TABs) before moving them. This avoids some rare instances of misalignment. One way to see what is going on, is to activate the =whitespace-mode=. ** What about =^L= page separation? :PROPERTIES: :CUSTOM_ID: what-about-l-page-separation :END: =Uniline= does not work well with =^L= (page separation) character. Nor with similar characters, like =^T=. When trying to draw a line over such a character, the cursor may get stuck. This is because those characters occupy twice the width of a normal character. Just try to get away from =^L=, =^T= and such when drawing with =Uniline=. ** Emacs on the Linux console :PROPERTIES: :CUSTOM_ID: emacs-on-the-linux-console :END: Linux consoles are the 7 non-graphic screens which can be accessed usually typing =C-M-F1=, =C-M-F2=, and so on. Such a screen is also presented when connecting through =ssh= or =tls= into a non-graphical server. By default they use a font named "Fixed" with poor support for Unicode. However, it supports lines of the 3 types, mixing all of them in thin lines though. Another problem is that by default =S-= and =C-= are indistinguishable from ==. Same problem with ==, ==, == and ==. This has nothing to do with =Emacs=. A solution can be found here: https://www.emacswiki.org/emacs/MissingKeys ** Emacs on a graphical terminal emulator :PROPERTIES: :CUSTOM_ID: emacs-on-a-graphical-terminal-emulator :END: This is the =Emacs= launched from a terminal typing =emacs -nw=. In this environment, == does not exist. It is replaced by ==. This has already been taken into account by =Uniline= by duplicating the key-bindings for the two flavors of this key. If you decide to bind globally =C-= to the toggling of =Uniline= minor mode as suggested, then you will have to do the same for =C-=, for example with =use-package= in your =~/.emacs= file: #+begin_src elisp (use-package uniline :defer t :bind ("C-" . uniline-mode) :bind ("C-" . uniline-mode)) #+end_src ** Emacs on Windows :PROPERTIES: :CUSTOM_ID: emacs-on-windows :END: On Windows the only native mono-spaced fonts are =Lucida Console= and =Courier New=. They are not mono-spaced for the Unicodes used by =Uniline=. Often, the =Consolas= font is present on Windows. It supports quite well the required Unicodes to draw lines. A few glyphs produce unaligned result though. They should be avoided under =Consolas=: =△▶▹◆= Of course, other fonts may be installed. It is quite easy. ** Compatibility with ASCIIFlow :PROPERTIES: :CUSTOM_ID: compatibility-with-asciiflow :END: ASCIIFlow is a ASCII-UNICODE diagram drawing tool (as Uniline). It works on a web browser. Just open https://asciiflow.com and start drawing. There is no server, ASCIIFlow operates locally on your PC. Your diagrams survive web browser sessions, as they are saved locally behind the scene. When your drawing is complete, you can export it to Emacs-Uniline: - Click on the download button - Select ="ASCII Extended"= - Paste your diagram in Emacs with =C-y= - Modify it with Uniline For the other way around, a Uniline drawing can be exported to ASCIIFlow: - Copy it from Emacs (with =M-w= for instance). - In ASCIIFlow, choose ="Select & Move"= - Type =C-v= - Edit with ASCIIFlow * Lisp API :PROPERTIES: :CUSTOM_ID: lisp-api :END: Could =Uniline= be programmed (versus used interactively)? Yes! The API is usable programmatically: ** Move the cursor :PROPERTIES: :CUSTOM_ID: move-the-cursor :END: Move cursor while drawing lines by calling any of the 4 directions functions: - =uniline-write-up↑= - =uniline-write-ri→= - =uniline-write-dw↓= - =uniline-write-lf←= They expect a repeat =count= (usually 1) and optionally =force=t= to overwrite the buffer ** Brush :PROPERTIES: :CUSTOM_ID: brush :END: Set the current brush by calling any of the following: - =uniline--set-brush-nil= ;; write nothing - =uniline--set-brush-0= ;; eraser - =uniline--set-brush-1= ;; single thin line╶─╴ - =uniline--set-brush-2= ;; single thick line╺━╸ - =uniline--set-brush-3= ;; double line╺═╸ - =uniline--set-brush-block= ;; blocks ▙▄▟▀ Those functions are equivalent to: - =(setq uniline--brush nil)= - =(setq uniline--brush 0)= - =(setq uniline--brush 1)= - =(setq uniline--brush 2)= - =(setq uniline--brush 3)= - =(setq uniline--brush :block)= except the functions also update the mode-line. ** Example: Lisp function to draw a plus sign :PROPERTIES: :CUSTOM_ID: example-lisp-function-to-draw-a-plus-sign :END: For instance, if we want to create a function to draw a "plus" sign, we can code it as follows: #+begin_src elisp (defun uniline-draw-plus () (interactive) (uniline-write-ri→ 1) (uniline-write-dw↓ 1) (uniline-write-ri→ 1) (uniline-write-dw↓ 1) (uniline-write-lf← 1) (uniline-write-dw↓ 1) (uniline-write-lf← 1) (uniline-write-up↑ 1) (uniline-write-lf← 1) (uniline-write-up↑ 1) (uniline-write-ri→ 1) (uniline-write-up↑ 1)) #+end_src Calling =M-x uniline-draw-plus= will result in this nice little plus-shape: [[file:images/plus-shape.png]] #+begin_example ╭╮ ╭╯╰╮ ╰╮╭╯ ╰╯ generated by M-x uniline-draw-plus #+end_example We may modify the function to accept the size of the shape as a parameter: #+begin_src elisp (defun uniline-draw-plus (size) (interactive "Nsize? ") (uniline-write-ri→ size) (uniline-write-dw↓ size) (uniline-write-ri→ size) (uniline-write-dw↓ size) (uniline-write-lf← size) (uniline-write-dw↓ size) (uniline-write-lf← size) (uniline-write-up↑ size) (uniline-write-lf← size) (uniline-write-up↑ size) (uniline-write-ri→ size) (uniline-write-up↑ size)) #+end_src The =(interactive "Nsize? ")= form prompts user for the size of the shape if not given as a parameter. This API works in any mode, not only in =Uniline= minor mode. It takes care of the infiniteness of the buffer in the right and down directions. ** Long range actions (contour, flood-fill, rectangle) :PROPERTIES: :CUSTOM_ID: long-range-actions-contour-flood-fill-rectangle :END: There are other useful functions operating on many characters at once. Contour tracing and flood-filling are among them: - =uniline-contour= - =uniline-fill= The following functions operate on a rectangular region, which must be active prior to calling them: - =uniline-draw-inner-rectangle= - =uniline-draw-outer-rectangle= - =uniline-copy-rectangle= - =uniline-kill-rectangle= - =uniline-yank-rectangle= - =uniline-fill-rectangle= - =uniline-move-rect-up↑= - =uniline-move-rect-ri→= - =uniline-move-rect-dw↓= - =uniline-move-rect-lf←= ** Constants :PROPERTIES: :CUSTOM_ID: constants :END: Constants for the 4 directions: - =uniline-direction-up↑= ;; constant 0 - =uniline-direction-ri→= ;; constant 1 - =uniline-direction-dw↓= ;; constant 2 - =uniline-direction-lf←= ;; constant 3 ** Macro and text direction :PROPERTIES: :CUSTOM_ID: macro-and-text-direction :END: Changing text direction: - =uniline-text-direction-up↑= - =uniline-text-direction-ri→= - =uniline-text-direction-dw↓= - =uniline-text-direction-lf←= or (in this case the mode-line is not updated): - =(setq uniline-text-direction uniline-direction-up↑)= - =(setq uniline-text-direction uniline-direction-ri→)= - =(setq uniline-text-direction uniline-direction-dw↓)= - =(setq uniline-text-direction uniline-direction-lf←)= Call macro in any direction: - =uniline-call-macro-in-direction-up↑= - =uniline-call-macro-in-direction-ri→= - =uniline-call-macro-in-direction-dw↓= - =uniline-call-macro-in-direction-lf←= ** Insert and tweak glyphs :PROPERTIES: :CUSTOM_ID: insert-and-tweak-glyphs :END: Insert and cycle intersection glyphs: - =uniline-insert-fw-arrow= - =uniline-insert-fw-square= - =uniline-insert-fw-oshape= - =uniline-insert-fw-cross= - =uniline-insert-fw-grey= - =uniline-insert-bw-arrow= - =uniline-insert-bw-square= - =uniline-insert-bw-oshape= - =uniline-insert-bw-cross= - =uniline-insert-bw-grey= Rotate arrow or tweak 4-half-lines or 4-block characters: - =uniline-rotate-up↑= - =uniline-rotate-ri→= - =uniline-rotate-dw↓= - =uniline-rotate-lf←= Here are the lowest level functions. Move point, possibly extending the buffer in right and bottom directions: - =uniline-move-to-column= - =uniline-move-to-line= - =uniline-move-to-lin-col= - =uniline-move-to-delta-column= - =uniline-move-to-delta-line= ** Change to alternate styles :PROPERTIES: :CUSTOM_ID: change-to-alternate-styles :END: A drawing in a rectangular selection may have its style changed: - =uniline-change-style-dot-3-2= ;; 3 dashes vert. ┆, 2 horiz. ╌ - =uniline-change-style-dot-4-4= ;; 4 dashes vert. ┊ & horiz. ┈ - =uniline-change-style-standard= ;; back to Uniline base style - =uniline-change-style-hard-corners= ;; rounded corners╭╴become hard┌ - =uniline-change-style-thin= ;; convert to ╭╴ thin lines - =uniline-change-style-thick= ;; convert to ┏╸ thick lines - =uniline-change-style-double= ;; convert to ╔═ thick lines - =uniline-aa2u-rectangle= ;; call aa2u to convert ASCII to Unicode The above functions require a region to be marked. * Mouse support :PROPERTIES: :CUSTOM_ID: mouse-support :END: The out-of-the-box mouse support of =Emacs= works perfectly. Except when the mouse clicks on a position outside the buffer. This happens when clicking past the end of a too short line, or past the end of the buffer. To handle those cases, a few standard =Emacs= functions have been extended to add blank characters or blank lines. Doing so, the mouse-click now falls on a valid part of the buffer. Of course, those extensions are only active on =uniline-mode= activated buffers. Beware that when the window is at the same time zoomed with =C-x C-+ C--= AND horizontally scrolled with =C-x <=, the cursor positioning is not accurate. This is due to =Emacs= limitations and bugs. Just click twice to fix the inaccuracy. * Installation :PROPERTIES: :CUSTOM_ID: installation :END: ** use-package, the straightforward way :PROPERTIES: :CUSTOM_ID: use-package-the-straightforward-way :END: The =use-package= library became the de-facto standard to manage packages in your =.emacs= initialization file. The =use-package= library comes along with Emacs. It can (among other services) delay loading external packages until they are used, and bind keyboard shortcuts to the package's entry points. Add the following lines to your =.emacs= file, and reload it, if not already done. This says that the popular Melpa repository is one of the central store of third parties packages. To day, it provides almost 7000 packages to choose from. #+begin_src elisp (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t) (package-initialize) #+end_src Alternately you may customize this variable: #+begin_example M-x customize-variable package-archives #+end_example Then add those lines in your Emacs initialization file (usually =~/.emacs=): #+begin_src elisp (use-package uniline-hydra :bind ("C-" . uniline-mode)) #+end_src or: #+begin_src elisp (use-package uniline-transient :bind ("C-" . uniline-mode)) #+end_src This tell Emacs: - Be prepared to load =uniline-mode= when the user request it, but do not load it now. - Bind the =C-= keys to the function =uniline-mode=. This shortens the longer =M-x uniline-mode= command. Any other key combinations can be bound, as you prefer. == happens to also be the key used inside =Uniline= (customizable). - Load either the =uniline-hydra= or the =uniline-transient= file, as you prefer. This gives Uniline one or the other flavour of user-interface. There is an alias to =uniline-hydra=: #+begin_src elisp (use-package uniline :bind ("C-" . uniline-mode)) #+end_src If you are using [[https://github.com/radian-software/straight.el][straight.el]] with =use-package=, and have =(setq straight-use-package-by-default t)=, you have the following options: #+begin_src elisp ;; uniline-hydra using the alias (use-package uniline) ;;; uniline-hydra explicitly requested (use-package uniline-hydra :straight uniline) ;;; install and load uniline-transient (use-package uniline-transient :straight uniline) #+end_src ** Without use-package :PROPERTIES: :CUSTOM_ID: without-use-package :END: Download the package from Melpa: #+begin_src elisp (package-install "uniline") #+end_src Alternately, you can download the Lisp files, and load them manually: #+begin_src elisp (load-file "uniline-hydra.el") ;; interpreted form (load-file "uniline-hydra.elc") ;; byte-compiled form (load-file "uniline-hydra.eln") ;; native-compiled form ;; this automatically ;; loads "uniline-core.el" ;; or "uniline-core.elc" ;; or "uniline-core.eln" #+end_src or if you prefer the Transient interface over the Hydra one: #+begin_src elisp (load-file "uniline-transient.el") ;; interpreted form (load-file "uniline-transient.elc") ;; byte-compiled form (load-file "uniline-transient.eln") ;; native-compiled form ;; this automatically ;; loads "uniline-core.el" ;; or "uniline-core.elc" ;; or "uniline-core.eln" #+end_src You should prefer the byte-compiled or native-compiled forms over the interpreted forms, because there are a lot of optimizations performed at compile time. You may want to give =uniline-mode= a key-binding. A way to do that without =use-package= is to add those lines to your initialization file (usually =~/.emacs=): #+begin_src elisp (require 'uniline-hydra) (bind-keys :package uniline-hydra ("C-" . uniline-mode)) #+end_src The downside is that =Uniline= will be loaded as soon as =Emacs= is launched, rather than deferred until invoked. * Related packages :PROPERTIES: :CUSTOM_ID: related-packages :END: - =artist-mode=: the ASCII art mode built into =Emacs=. - =ascii-art-to-unicode=: as the name suggest, converts ASCII drawings to UNICODE, giving results similar to those of =Uniline=. - =picture-mode=: as in =Uniline=, the buffer is infinite in east & south directions. - =ascii-art-to-unicode= ASCII art to UNICODE in =Emacs=. This is a standard ELPA package by Thien-Thi Nguyen (rest in peace). =Uniline= may call it to convert ASCII art drawings to equivalent UNICODE. =Uniline= arranges to not require a dependency on =ascii-art-to-unicode= by lazy evaluating a call to =aa2u=. - =org-pretty-table=: Org Mode tables /appear/ to be drawn in UNICODE characters (actually they are still in ASCII). - =boxes=: draws artistic boxes around text, with nice looking unicorns, flowers, parchments, all in ASCII art. - =org-drawio=: a bridge between the Draw.Io editor and =Emacs=, producing drawing similar to those of =Uniline=, but in =.svg=. - =syntree=: draws ASCII trees on-the-fly from description. - =unicode-enbox=: create a UNICODE box around a text; input and output are strings. - =unicode-fonts=: in =Emacs=, helps alleviate the lack of full UNICODE coverage of most fonts. - =org-superstar=: prettify headings and plain lists in Org Mode, using UNICODE glyphs. - =charmap=: UNICODE table viewer for =Emacs=. - =insert-char-preview=: insert UNICODEs with character preview in completion prompt. - =list-unicode-display=: list all UNICODE characters, or a selection of them. - =show-font=: show font features in a buffer. - =ob-svgbob=: convert your ascii diagram scribbles into happy little SVG - =el-easydraw=: a full featured SVG editor right inside your =Emacs= - =asciiflow=: (not =Emacs=) draw on the web, then copy-paste your UNICODE text - =ascii-draw=: like =asciiflow= with Unicodes. - =dot-to-ascii.ggerganov.com:= (not =Emacs=) describe your schema in the Graphviz language, and copy-past your UNICODE text. - =monosketch=: (not =Emacs=) draw on the web, then copy-paste your UNICODE text. - =ibm-box-drawing-hydra.el=: keyboard interface to insert UNICODE box-drawing characters one at a time. - =excalidraw.com=: inline drawing, but not in Unicode or Ascii. - =org-excalidraw=: integrate SVG images generated by excalidraw into Org Mode. - =rcd-box=: create tables surrounded by box-drawing characters from Lisp descriptions. - =ob-diagram=: generate various diagrams using diagrams backend. - =ob-mermaid=: generate Mermaid diagrams within org-mode babel. - =quail-boxdrawing.el=: input method for box drawing characters. - =make-box.el=: box around part of a buffer. - =vim drawit ascii diagrams=: in Vim, in ASCII. - =MarkDeep=: (Casual Effects): write in Markdown, render on the Web on the fly. Uniline may be used to author part of the Markdown source. - =org-utf-to-xetex=: export Org-Mode utf-8 documents to various formats preserving smileys and other Unicode characters. - =image-to-ascii=: turns a photo into ASCII. - =ascii-maze-generator=: web-inline generator of mazes, with the same lines drawn by Uniline. - =diagon.arthursonzogni.com=: web-inline drawing of diagrams similar to those that Unline enables. - =cascii.html=: a single Html-JavaScript file to draw Unicode diagrams. - =elm-svgbob=: think of Ditaa for converting Ascii to SVG. - =rasciigraph=: script to plot time-series in Unicode. - =asciichart-sharp=: another script to plot time-series in Unicode. - =d2=, =ob-d2=: think of Mermaid to convert diagram textual descriptions to SVG. - =dag-draw.el=: in Emacs program diagrams using Lisp, the output is Unicode. - =https://mbork.pl/2025-11-10_ASCII_art_timeline_diagrams=: not a package, just an example of how easy is to draw time-series in Ascii with Emacs. - =pikchr=: describe objects and their relationship in Markdown, render in Unicode. - =ob-pikchr.el=: integration of =pikchr= in Emacs Org-Mode. - =GoAT=: Go-based Text-Art to SVG refinement. - =SvgBob=: another script to convert Ascii to SVG (like Ditaa). Written in Rust. - =Durdraw=: an Ascii, Unicode and Ansi art editor for Unix-like systems. With colors. - =figlet=: makes large letters out of ordinary text, in text. There is an Emacs integration by J. Kotta. * Author, contributors :PROPERTIES: :CUSTOM_ID: author-contributors :END: - Thierry Banel, author Feedback: - Chris Rayner (@riscy), gave recommendations prior to insertion in MELPA - Adam Porter (@alphapapa), suggested submitting =Uniline= to =ELPA=; should I? - Joost Kremers https://github.com/joostkremers found a bug in the minor-mode key-binding definitions, and incompatibility with - DogLooksGood https://github.com/DogLooksGood gave feedback on inserting usual characters not moving the cursor - LuciusChen & lhindir on GitHub, arthurno1 & karthink on Reddit, pushed toward =Transient= as the default interface instead of =Hydra= - karthink noted that =Transient= was now built into =Emacs=, loosening the dependencies conundrum, arthurno1 participated in the =Hydra= - =Transient= discussion - karthink pointed to the new =Aporetic= font family, which was then added to the =Uniline= supported fonts - rumengling on GitHub found and diagnosed the misaligned lines issue produced by some "language environments" (see [[#language-environment][Language environment]]). - tpapp documented the installation using Straight, and fixed some typos. - tskinner-oppfi (GitHub) repoted Elpaca packaging break due to a bad version number. Contributors: - JD Smith (jdtsmith on GitHub) rewrote the =:lighter= for added flexibility (the information in the mode-line about the state of =Uniline=) - JD Smith also pointed to =#+begin_src uniline= Org Mode block suprising behavior (editing its content automatically switches to =uniline-mode=) Utilities: - Oleh Krehel alias abo-abo for his package =Hydra= - The =Magit= team for the =Transient= library - Thien-Thi Nguyen (RIP) for his package =ascii-art-to-unicode= * License :PROPERTIES: :CUSTOM_ID: license :END: Copyright (C) 2024-2026 Thierry Banel Uniline 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. Uniline 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 . ================================================ FILE: tests/bench01.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (let ((uniline-infinite-up↑ nil)) (uniline-bench "" "b RET c - 3* 5* C-SPC 6* 11* 2*RET 2* + 4* 2*a 3* a S- 8* RET 2* 14* - 3*" "\ │ bc╶┴─╮ ╰─┰──╯ ━━━━━━△━━◀━━━┛ ")) ================================================ FILE: tests/bench02.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (let ((uniline-infinite-up↑ nil)) (uniline-bench "\ initial text good " "C-SPC 12* 2* 3* R q 2* 2* 2* 3* 3* 3* 2* 3* C-SPC 19* 3* 2* c C-a 22* C-SPC y 2* 3* 4* = 6* s s s 5* o 6* " "\ ╷ │ ╭────────────╮ ╭──╯ ╭────────────╮ ╭──╯ │initial text│ q │initial text│ q │ good ├─╯ │ good ├─╯ ╰──────╥─────╯ ╰───╥────────╯ ╚═════·════▫═════╝ ")) ================================================ FILE: tests/bench03.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "" "2* 2* 3* # 6* 3* 6* 3* 6* 3* 6* 4* 3* 6* 12* 15* C-SPC 4* 12* r # R 4* 11* 4* 11* 23* 2* C-SPC 4* 16* 2* 3* C-c C-c" "\ ▗▄▄▄▄▄▄▄▄▄▄▄▄▖ ▐╭──────────╮▌ ▄▄ ▐│ │▌ ▝▀▀▌ ▛▀▀▌ ▌▐ ▐│ │▌ ▙▄▄▌ ▙▄▄▌▐ ▐│ │▌ ▗▄▄▄▄▄▟ ▐╰──────────╯▌ ▝▀▀▀▀▀▀▀▀▀▀▀▀▘ ") ================================================ FILE: tests/bench04.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "" " 4* 2* 4* M = 3* M-6 3* 2* a M-7 3*a 3* M-4 3* 2* 2* s 2* 2*s 3* M-6 x 5*o 4* M-9 C-SPC M-6 M-12 c M-13 M-6 C-SPC y " "\ ╔═════╗ ╔═════╗ ║ □ ║ ║ □ ║ ║ ■ ║ ║ ■ ║ ╔←══M═══◁═╝ ╔←══M═══◁═╝ ║ ◦ ║ ║ ◦ ║ ║ ╳ ║ ║ ╳ ║ ╚═══╝ ╚═══╝ ") ================================================ FILE: tests/bench05.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "\ aaaa aaaa aaa aaaaaaaa" " M-> C-SPC M-26 c C-SPC y c C-SPC y c C-SPC y c C-SPC M-9 C-r M-7 C-SPC M-6 C-r = R RET" "\ aaaa aa╭─────╦╤╤══aaaaaaaa aaaa │aaa ║┏┿━━━┓aaaaaaaa aaa│ aaaa║┃│aaa┃║ aaaaaaaa a╰─────╫╂╯ a┃a aaaaaaaa ║┗━━━━┛║ ╚══════╝ ") ================================================ FILE: tests/bench06.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "\ mùq v 2iii hu 1 u é u au uuuu uuuu " " 2* 2* C-SPC 6* 9* c M-3 M-5 C-SPC y 6* c 7* j j 2* i SPC" "\ ╭───╮ │mùq╰╮ ╭──┴─┬╮v╰─╮ jmùq │2iii│╰╮hu│ j░░░v │1╶┬─╯ ╰╮u│ 2iii░░░hu ╰╮é╰╮╭──╯u│ 1░░░░░░░u │au╰╯uuuu│ é░░░░░░u ╰╮uuuu╭──╯ au░░uuuu ╰────╯ uuuu ") ================================================ FILE: tests/bench07.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (let ((uniline-infinite-up↑ nil)) (uniline-bench "\ aaaaa aaaaa a a a a a a a aaaaaa a aaaa a a aaaa a" " c" "\ aaaaa╷ ╷aaaaa╷ a╭─╮a│ │a╭───╯ a│ │a╰──╯a│ a│ │aaaaaa│ a╰─┴┬─────╯ aaaa│ ╶─╮a│ ╶─╯a│ aaaa│ a╭──╯ ╶╯ ")) ================================================ FILE: tests/bench08.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (let ((uniline-infinite-up↑ nil)) (uniline-bench "\ aaaa hhhh hhhh a hh hh a hhhhhhhhhh gggg oooooooo g o o g o o g o o g o o gggg o o gggg o o g o o g o o g ooooo gggg " " c 8* + c M-< 7* 2* c 8* c i * 2* c" "\ aaaa╻ ╻hhhh╻ ╻hhhh╻ a┏━━┛ ┗━┓hh┗━━━━┛hh┏━┛ a┃ ┃hhhhhhhhhh┃ ╺┛ ┗━━━━━━━━━━┛ ╶───╮ ╭────────╮ gggg│ ╭╯oooooooo╰╮ ╶─╮g│ │o╭──────╮o│ │g│ │o│******│o│ │g│ │o│******│o│ ╶─╯g│ │o│******│o│ gggg│ │o╰╮*****│o│ gggg│ ╰╮o╰╮****│o│ ╶─╮g│ ╰╮o╰╮***│o│ │g│ ╰╮o╰───╯o│ ╶─╯g│ ╰╮ooooo╭╯ gggg│ ╰─────╯ ╶───╯ ")) ================================================ FILE: tests/bench09.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (let ((uniline-infinite-up↑ nil)) (uniline-bench "\ ggggggggggg gg ggggg ggggggg g g g gggggggggg g ggggggg pppppp g pppppp ggggg gg g gggg g " " 5* c" "\ ggggggggggg╷ gg ╷ggggg╷ ggggggg╭─╮g╰──────╯g╭───╯ ╶────╮g│ │gggggggggg│ ╶────╯g│ ╰──────────╯ ggggggg│pppppp g╶───┬─╯pppppp ggggg│ ╶─╮gg│ ╶─╯g╭╯ gggg│ g╭──╯ ╶╯ ")) ================================================ FILE: tests/bench10.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; a macro cannot be defined inside another macro, ;; so this macro is defined outside (setq last-kbd-macro (kbd " C- C- ")) (uniline-bench "\ aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa " " C-x 4*e 3* 3* 5* 3* " "\ ╭╮╭╮╭╮╭╮╭╮╭╮ ╶┼┴╯╰╯╰╯╰╯╰╯│ ╰╴aaaaaaaaa╶╮aaaaaaa ╭aaaaaaaaaaa╯aaaaaaa ╰╴aaaaaaaaa╶╮aaaaaaa ╭╯ ╭╯ ╰╮ ╰╮ │╭╮╭╮╭╮╭╮╭─╯ ╰╯╰╯╰╯╰╯╰╯ ") ================================================ FILE: tests/bench11.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Check if TABs are converted to SPC when drawing on them (uniline-bench "\ tabs ? 2 tabs " " M-> M-< 2* 2* 2* 10* 2* s " "\ ╭─────tabs╮? │ │ 2 tabs ╶─╯ □ ") ================================================ FILE: tests/bench12.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test rectangle fill (uniline-bench "" " 3* 5* 6*S- 11*S- i SPC 3* 7* 6*S- 8*S- i y 2* 5*S- S- i SPC " "\ ░░░░░░░░░░░ ░░▒▒▒▒▒░░░░ ░░▒▒▒▒▒░░░░ ░░░░░░░░░░░ ░░░░░░░yyyyyyyy ░░░░░░░yyyyyyyy ░░░░░░░yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy ") ================================================ FILE: tests/bench13.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test glyph insertion (uniline-bench "" " 2* 4* 4* 2* a 2* 3* s 3* 2* 3* 3* 2* 2*o 2* 2* 4* 2*S- 3* 2*S- 3* 2* 2*S- 3* 2*S- 13* 2* 2*S-" "\ □ ╾───╮──╯──╮ │ │ ▽ ╰──╼ │ ∙ ") ================================================ FILE: tests/bench14.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "" " 2* 3* 12*S- 6*S- # r S- 4* 4* S- 5* S- S- S- S- 3* S- 3* 2*S- S- S- 5* S- S- 6* S- S- S- 3* 4* a S- 2* a S- 2* a S- 2* 2*S- 9* 2*S- 2*S- S- S- 2* 2*S- S- S- " "\ █▀▀▀▀▀▛▀▀▀▀▙ ▌ ▐ ▌ ▐ ▌╰─┤△─▽─◁─╴▟ ▌ ▐ ▌ ▐ ▐▄▄▄▄▄█▄▄▄▄▛ ") ================================================ FILE: tests/bench15.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "" " 3* 10* 2* 3* a 2*a 3*a 4*a 5*a 6*a 3* A 2*A a 4*A a S- 3* A 18* A S- a 2* 2* 4*A 2* 3*a S- a S- 3*a 5*a 6*a S- 3* a 3* S- 3* 5* S- " "\ ╭──▷▶→▿▸↔──╮ ↔──▷ ↕ ▴ ▾ ↑ ▷ ▷ ↓ ← ▷ ╰─←─╴ ◁↕↔──╯ ") ================================================ FILE: tests/bench16.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "" " 5* a 2* A 2* a S- 2* A S- C-a C-k C-y C-y a 2* a 2* a 2* a C-a C-k C-y C-a C-y C-a 5* 2*A 2* A 2* 2*A 2* A" "\ ▷ ↔ △ ↕ ▶ ◁ ▲ ◁ ↔ ↔ ↕ ↔ ") ================================================ FILE: tests/bench17.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "" "3* a b c C-SPC 3*C-b 2* 2* d e f C-SPC C-a C-SPC 3*S- S- 2* " "\ ╭ abc def ") ================================================ FILE: tests/bench18.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Check # blocks contour (uniline-bench "" " 3* 6*t 3* 2*y 5* 4*r C- 4*o 5*o C- 2* 5* # c" "\ ▗▄▄▄▄▄▄▖ ▐tttttt▌ ▐███yy▛▘ ▐rrrr█▌ ▗▄▟█oooo▌ ooooo▛▀▀▘ ▀▀▀▀▀▘ ") ================================================ FILE: tests/bench19.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (let ((uniline-infinite-up↑ nil)) (uniline-bench "" " 3* C- tttt C- yyyyyy C- hhhhh C- gggg C- aaaaaaaa C- bbbbbbbbbbbbbb C- ff C- ffff 5* dddddd ggggg 4*SPC hhh 5*SPC iiii hh hhhhhh h M-< 19* ii ii ii iiii ii ii ii # c M-< 3* # c 12* C-a M-f c # c" "\ ▐t▖ ▐gggga▖ ▐ii▄▖ ▗▟ii▖ ▐t▌ ▐h▛▀▜a▌ ▝▜ii▙▖▗▟ii▛▘ ▐t▌ ▐h▌ ▐a▌ ▝▜ii▙▟ii▛▘ ▐t▙▄▄▄▟h▌ ▐a▌ ▝▜iiii▛▘ ▐yyyyyyh▌ ▐a▌ ▝▀▀▀▀▘ ▗▄▟█▛▀▀▀▀▀▘ ▐a▌ ffff▌ ▐a▌ f███▙▄▄▄▄▄▄▄▟a▌ fbbbbbbbbbbbbb▌ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘ ▗▄▄▄▄▄▖ dddddd▌ ggggg█▙▖ ▀▀▀▜hhh▙▄▖ ▗▄▖▝▜iiii▌ hh▙▄▟█h▛▀▘ ▜hhhhhh▌ ▝▀▀▀▀▀▀▘ ")) ================================================ FILE: tests/bench20.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test fine tweaking (uniline-bench "" " 2* 2* 4* 3* 2* 3* = 4* 4* 5* 2* 2*S- 2* S- S- 2* # 8* 3* S- " "\ ╔══╘═━━┓ ╟───╮ ╻┃ ▝▀▟▀▘ ║ ╰─┠┚ ║ ┃ ╚━━━━━┛ ") ================================================ FILE: tests/bench21.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test rectangle tracing & moving (uniline-bench "" " 5* 11* 15*S- 31*S- 2*S- # R 2* 3* 13*S- 25*S- S- S- # r 2* 2* 3* 9*S- 18*S- r 2* 2* 3* 5*S- 11*S- S- = r 2* 5* 2* 3* 4* 19*S- 16*S- 2* 2* 9*S- 27*S- 12*S- 2* 2* 15* 2* 27*S- 12*S- 9*S- 2* 2* 19* 9* 24*S- 21*S- 2* " "\ ▗▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▖ ▐ ▌ ▐ ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ ▌ ▐ ▌ ▐ ▌ ▐ ▌ ╭────────────────────╮ ▐ ▌ ▐ ▌ │ │ ▐ ▌ ▐ ▌ │ ╔═════════════╗ │ ▐ ▌ ▐ ▌ │ ║ ║ │ ▐ ▌ ▐ ▌ │ ║ ║ │ ▐ ▌ ▐ ▌ │ ║ ║ │ ▐ ▌ ▐ ▌ │ ║ ║ │ ▐ ▌ ▐ ▌ │ ║ ║ │ ▐ ▌ ▐ ▌ │ ║ ║ │ ▐ ▌ ▐ ▌ │ ║ ║ │ ▐ ▌ ▐ ▌ │ ║ ║ │ ▐ ▌ ▐ ▌ │ ╚═════════════╝ │ ▐ ▌ ▐ ▌ │ │ ▐ ▌ ▐ ▌ ╰────────────────────╯ ▐ ▌ ▐ ▌ ▐ ▌ ▐ ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ ▌ ▐ ▌ ▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘ ") ================================================ FILE: tests/bench22.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "" " 2* M-3 M-9 M-3 M-5 M-4 M-11 2* a 3* 2*a 3* 3*a 3* 4*a 4* 5*a 2* 2* C-x SPC M-5 M-16 c M-5 M-4 C-SPC y s h 3*A M-3 M-20 C-SPC y s 4 3*B M-3 C-SPC y s + 4 3*C M-3 M-15 M-40 C-x SPC y s + 3*D M-3 M-0 M-8 C-x SPC y C-x SPC y s 3 3*E M-3 M-20 C-x SPC y s 4 3*F M-3 M-19 C-SPC y s + 3*G M-3 M-3 M-5 M-18 C-x SPC y s + 3 3*H M-3 " "\ ╭──────────╮ AAA ┌──────────┐ CCC ┏┉┉┉┉┉┉┉┉┉┉┓ ╭───┼────╮ │ ┌───┼────┐ │ ┏┉┉┉╋┉┉┉┉┓ ┋ ╰─╮ │ │ ▽ └─┐ │ │ ▽ ┗┉┓ ┋ ┋ ▼ │ │ │ ╭──╯ │ │ │ ┌──┘ ┋ ┋ ┋ ┏┉┉┛ ▴ ╰────╯ ▼ ▴ └────┘ ▼ ▴ ┗┉┉┉┉┛ ▼ ╰───◃──←──╯ └───◃──←──┘ ┗┉┉┉◂┉┉←┉┉┛ EEE ╭╌╌╌╌╌╌╌╌╌╌╮ FFF ╭┈┈┈┈┈┈┈┈┈┈╮ GGG ┏━━━━━━━━━━┓ ╭╌╌╌┼╌╌╌╌╮ ┆ ╭┈┈┈┼┈┈┈┈╮ ┊ ┏━━━╋━━━━┓ ┃ ╰╌╮ ┆ ┆ ▽ ╰┈╮ ┊ ┊ ▽ ┗━┓ ┃ ┃ ▼ ┆ ┆ ┆ ╭╌╌╯ ┊ ┊ ┊ ╭┈┈╯ ┃ ┃ ┃ ┏━━┛ ▴ ╰╌╌╌╌╯ ▼ ▴ ╰┈┈┈┈╯ ▼ ▴ ┗━━━━┛ ▼ ╰╌╌╌◃╌╌←╌╌╯ ╰┈┈┈◃┈┈←┈┈╯ ┗━━━◂━━←━━┛ DDD ┏━━━━━━━━━━┓ HHH ┏╍╍╍╍╍╍╍╍╍╍┓ ┏━━━╋━━━━┓ ┃ ┏╍╍╍╋╍╍╍╍┓ ┇ ┗━┓ ┃ ┃ ▼ ┗╍┓ ┇ ┇ ▼ ┃ ┃ ┃ ┏━━┛ ┇ ┇ ┇ ┏╍╍┛ ▴ ┗━━━━┛ ▼ ▴ ┗╍╍╍╍┛ ▼ ┗━━━◂━━←━━┛ ┗╍╍╍◂╍╍←╍╍┛ ") ================================================ FILE: tests/bench23.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "" " 2* 5* M-x p i c t u r e - m o d e 5*S- 19*S- 2*S- C-c C-r 3* 10* 5*S- 17*S- C-c C-r 6* M-x M-p ( - r e x i t 2* 5* 10* 5* 10* 9*S- 27*S- 2*S- s 6* M-x 2*M-p 5*S- 9*S- C-c C-r M-x 2*M-p 6* 5* 10* 5* 7*S- 7*S- s 4* 8* 5*S- 10*S- s = " "\ ┏━━━━━---+ ╭──━┃━━━━━───|───╮ │ ┃ | │ │ ┃ ┏━━───|──────────╮ │ ┃ ┃ | ║ │ │ ┗━━╋━━---+ ║ │ ╰──━━━━╋━━─══════╝ │ ┃ │ ╰───══════════───╯ ") ================================================ FILE: tests/bench24.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test undo (uniline-bench "\ uuuuuu uuuuuu" "M-> # 2* 2* 7* 3* 6*C-_ 2* 4* 3* M-4 M-6 4* 4* 3* 4*S- M-8 S- 2* s 5*C-_ 2*" "\ ╭─────╮ uuuuuu │ │uuuuuu▖ │ ▗▄▄▛▘ ╰──▐──╯ ") ================================================ FILE: tests/bench25.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "" " 2* 5*a 5*a M-7 5*t 2* M-5 2*S- M-7 S- r 2* 4* 3*o M-4 C-SPC 2* M-7 c M-5 C-x SPC y 2* M-12 C-SPC y 3* 3* C-SPC y " "\ ╭─────╮ a│aaa• │ ╭─────╮ ╰──╭─────╮ │ ╭─────╮ tt│tt • │ ╰─│───• │ ╰─────╯ ╰─────╯ ") ================================================ FILE: tests/bench26.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test ASCII → Uniline base-line (uniline-bench "\ +-------------+ +--------------+ /-----/ Rectangle 1 |-------------+ Rectangle 2 | | | | | \"quote\" +- | \\-------------/ /-----\\ +--------------+ | \\--+-------+--/ | vav | +----\\-----/---+ \\>--\\ | | \\-----/ | | v \\-->----/ +--<--+ " " M-< C-SPC M-> 54* s " "\ ╭─────────────╮ ╭──────────────╮ ╭─────┤ Rectangle 1 │─────────────┤ Rectangle 2 │ │ │ │ │ \"quote\" ├─ │ ├─────────────┤ ╭─────╮ ├──────────────┤ │ ╰──┬───────┬──╯ │ vav │ ╰────┬─────┬───╯ ╰▷──╮ │ │ ╰─────╯ │ │ ▽ ╰──▷────╯ ╰──◁──╯ ") ================================================ FILE: tests/bench27.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test drawing right onto a TAB and also on the trail of a TAB ;; (drawing should occur) ;; Test writing on those 2 characters ╜┡ which are in 2-entries buckets ;; in the uniline--char-to-4halfs hash-table (uniline-bench "" " 3 0 2 0 16* 8* = 2* 2*S- S- S- C-x h M-x t a b i f y 8* 10* 14* 2* 2* C-SPC 4*C-p 3*C-b c 4* C-SPC y 7* 4*" "\ ╶───────┬─────────────────────╮ │ │ │ │ │ ╷ │ │ ╻ │ │ │ ║ ║ │ │ ╺╜ ╺┤ │ │ ┡╸ ┝╸ │ │ ╹ │ │ │ │ │ ╰─────────────────╯ │ │ │ │ │ │ │ │ │ │ ╵ ") ================================================ FILE: tests/bench28.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (let ((uniline-infinite-up↑ nil)) (uniline-bench "aa\t\ta\t\ta\t\t\t\t \t\t a " "2*C-k 29*C-y M-< 5* 3* 2* 7* 2* 9* 16* 2* 12* 3* 10* 5*C-n 14*r 12* 16*r 14* 12*r 2*C-b c 12* 3* 11*r 9* 12*r 8* 8*r 2* 2*r 3*r C-SPC 3* 2* 3* 11* 2* c 4* 6* 10* C-SPC y " "\ aa───╮ a ╷a╷ a aa ╰──╮ a ╭──────┼a┼──╮ a aa ╰───────a╮ │ │a│ │ a aa ╭───────a┼──────╯ │a│ │ a aa ╰───────a╯ ╶────┼a┼──╯ a aa a │a│ a aa a │a│ a aa a │a│ a aa a ╭───────╯a╰────╮ a aa a │rrrrrrrrrrrrrr╰───╮ a aa a ╰─╮rrrrrrrrrrrrrrrr│ a aa a ╰─╮rrrrrrrrrrrr╭─╯ a aa a ╰───╮a╭──────╯ a aa a │a│ a aa a │a│ rrrrrrrrrrr a aa a │a│ rrrrrrrrrrrr a aa a │a│ rrrrrrrr a aa a │a│ rr a aa a │a│ rrr a aa a │a│ a aa a │a│ a aa a │a│ rrrrrrrrrrr a aa a │a│ rrrrrrrrrrrr a aa a │a│ rrrrrrrr a aa a │a│ rr a aa a │a│ rrr a aa a │a│ a aa a │a│ a aa a │a│ a ╰─╯ ")) ================================================ FILE: tests/bench29.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . (uniline-bench "\ aaaaaa aaaaaaaa aaaa aaaa aaa aa" " 2* 3* - 2* ~ 3* 2* 2* 3* 5* 2* 2* 3* ~ 2* 2* 2* 4* 3* 5* 2* 5* ~ 3* 2* ~ 5* 2* 4* 2* 8* 3* 3* C-SPC 2* 6* r r c" "\ ╭┄╮ ┆ ┆ ┏┅┅┅┅┅┅┓ ╶─┄┄┄╯ ┆ ╭┈╮ ╭┄┄┄┄╮ ┇aaaaaa┗┅┅┓ ╭┄┬┄┬╯ ╭┈┼┈╯ ┆ ┆ ┗┓aaaaaaaa┗┅┅┓ ╰┄╯ ╰┄┄┼┈╯ ┏┉┉┉┉━━━┓ ╰┄┄┄┄╯ ┗┅┓aaaa╻aaaa┇ ┊ ┋ ┏┅╋┅┅┅┅┅╸ ┗┅┅┅┅┻┓aaa┇ ┕┉┉┉┉┛ ┏┅┅╋┅┛ ┇aa┏┛ ┇ ┗┓ ┗┅┅┛ ┗┅┅┅┛ ") ================================================ FILE: tests/bench30.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test leakage when moving a rectangle down, from all styles of lines: ;; plain, 3-dotted, 4-dotted, thin and thick, double, block (uniline-bench "" " 2* 5* s 6* 5* 5* 5* = 5* 5* # 11* 9* C-SPC 5* 23* c 5* 24* C-SPC y s 24* C-SPC y s 5* 25* C-SPC 4* 74* ~ " "\ □─────╮ ╔════╗ ▗ □╌╌╌╌╌╮ ╔╍╍╍╍╗ ▗ □┈┈┈┈┈╮ ╔┉┉┉┉╗ ▗ │ ┃ ║ ▐ ┆ ┇ ┇ ▐ ┊ ┋ ┋ ▐ │ ┃ ║ ▐ ┆ ┇ ┇ ▐ ┊ ┋ ┋ ▐ │ ┃ ║ ▐ ┆ ┇ ┇ ▐ ┊ ┋ ┋ ▐ │ ┃ ║ ▐ ┆ ┇ ┇ ▐ ┊ ┋ ┋ ▐ │ ┃ ║ ▐ ┆ ┇ ┇ ▐ ┊ ┋ ┋ ▐ │ ┃ ║ ▐ ┆ ┇ ┇ ▐ ┊ ┋ ┋ ▐ ┕━━━━┛ ╹▀▀▀▀▀ ┕╍╍╍╍┛ ╹▀▀▀▀▀ ┕┉┉┉┉┛ ╹▀▀▀▀▀ ") ================================================ FILE: tests/bench31.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test leakage when moving a rectangle right, and another left. ;; Test for all styles of lines: ;; plain, 3-dotted, 4-dotted, thin and thick, double, block (uniline-bench "" " 2* 3* 4*o 6* 6* = 6* # 12* 2* C-SPC 5* 9* c 8* 5* C-SPC y s 5* C-SPC y s 5* 9* C-SPC 16* 4* ~ 7* C-SPC 16* 4* ~ ~ " "\ ●──────────╮ ┏━━━━━━━━━━┙ ╚══════════╗ ▛▀▀▀▀▀▀▀▀▀▀╹ ▘ ●╌╌╌╌╌╌╌╌╌╌╮ ┏╍╍╍╍╍╍╍╍╍╍┙ ╚╍╍╍╍╍╍╍╍╍╍╗ ▛▀▀▀▀▀▀▀▀▀▀╹ ▘ ●┈┈┈┈┈┈┈┈┈┈╮ ┏┉┉┉┉┉┉┉┉┉┉┙ ╚┉┉┉┉┉┉┉┉┉┉╗ ▛▀▀▀▀▀▀▀▀▀▀╹ ▘ ") ================================================ FILE: tests/bench33.el ================================================ ;;; uniline.el --- Draw lines, boxes, & arrows with the keyboard -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;; Test leakage when moving a rectangle down, from all styles of lines: ;; plain, 3-dotted, 4-dotted, thin and thick, double, block (let ((uniline-infinite-up↑ t)) (uniline-bench "" " s 2* 3* 3* 3* 2* # 6* 2* 3* 4* 5* 4* 4* 2* 4* 3*a 2* 6*a 4* 6*a 4*SPC 4*a 3* 2*a 3* C- 9*a c 8* 2* 4*r 5* 4*r C-SPC 5* 3* 2* 3* R r 3* 12* C- 5*y C- 3*y" "\ yyy y ╭─────╮ y │ rrrr│ y ╭──────╮ ╭────╮ │ rrrr│ y ▐▀▀▙▄▖ ╭─╯aaaaaa│ │aaaa│ ╰─────┴─────╴y ┏━━┓ ▐ ▌ ╭╯aaaaaa╶─┴──┴╴aa╭╯ ┃ ┃ ▛▀ ▙▖ │aaa╭─╮aaaaaaaaa╭╯ □╼━┓ ┃ ╹▀▀▘ ╰───╯ ╰─────────╯ ┗━━┛ ")) ================================================ FILE: tests/uniline-bench.el ================================================ ;;; uniline-bench.el --- Regression tests for Uniline -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; Package-Requires: ((emacs "29.1") (hydra "0.15.0")) ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;;; Commentary: ;; Running regression tests ;; Just load this file: ;; (load "uniline-bench.el") ;; or ;; (eval-buffer) ;; ;; If OK, a message is displayed ;; If ERROR, two windows are displayed, the actual and the expected sketchs ;; with points on the first difference. ;; Creating a new bench ;; - Eval (uniline-bench-create) ;; - Draw a sketch ;; - When done, type $ ;; A Lisp buffer implementing the new test is displayed ;; - Save it permanently in a file ending in *.el along with this one ;;; Code: (defvar uniline-bench-result nil "Boolean where a bench result is stored. t if the bench ran as expected. nil if there was an error.") (defun uniline-bench (initial commands result) "Run a bench. COMMANDS is a string describing a sequence of keyboard strokes, supposed to draw a sketch using uniline minor-mode. Its format is the one used to store keyboard macros. RESULT is a string representing the expected result. INITIAL is the initial content of the buffer" (ignore-errors (kill-buffer "*uniline-interactive*")) (switch-to-buffer "*uniline-interactive*") (insert initial) (goto-char (point-min)) ;; (set-default 'uniline-hint-style 1) (let ((uniline-show-welcome-message nil)) (uniline-mode 1)) (if (fboundp 'hydra-keyboard-quit) (hydra-keyboard-quit)) ;; clear any left-over from previous bench (if (fboundp 'transient-quit-all) (transient-quit-all)) ;; clear any left-over from previous bench (setq uniline--which-quadrant (uniline--char-to-4quadb ?▘)) (uniline-set-brush-1) (uniline-set-brush-0dots) (execute-kbd-macro (kbd commands)) (when nil ;; ignore trailing spaces in resulting drawing (goto-char (point-min)) (replace-regexp (rx (+ space) eol) "") ;; ignore trailing spaces in expected result (setq result (replace-regexp-in-string (rx (+ space) eol) "" result))) (setq uniline-bench-result (string-equal (buffer-substring (point-min) (point-max)) result)) (unless uniline-bench-result (delete-other-windows) (switch-to-buffer "*uniline-interactive*") (goto-char (point-min)) (ignore-errors (kill-buffer "*uniline-expected*")) (switch-to-buffer-other-window "*uniline-expected*") (insert result) (goto-char (point-min)) (compare-windows nil)) uniline-bench-result) (defun uniline-bench-create () "Interactively create a bench. An empty buffer is made available, with uniline mode active. Draw a sketch. When done, type $. A Lisp buffer able to automatically re-run the drawing is presented. Save it in a *.el file along with other benches." (interactive) (ignore-errors (kill-buffer "*uniline-interactive*")) (switch-to-buffer "*uniline-interactive*") (local-set-key "$" (lambda () (interactive) (throw 'exit nil))) (message "draw the initial state of buffer, prior to invoking Uniline, type $ when done") (recursive-edit) (setq uniline-initial-text (buffer-substring-no-properties (point-min) (point-max))) (goto-char (point-min)) (uniline-mode) (local-set-key "$" 'uniline-bench-collect) (message "draw a sketch, type $ whend done") (kmacro-start-macro nil)) (defun uniline-bench-collect () "Called when typing $ to close the interactive drawing. Do not call it directly." (interactive) (kmacro-end-macro 1) (ignore-errors (kill-buffer "b.el")) (switch-to-buffer "b.el") (insert "(uniline-bench\n\"\\\n" uniline-initial-text "\"\n\n\"") (insert (key-description (kmacro--keys (kmacro last-kbd-macro)))) (insert "\"\n\n\"\\\n") (insert-buffer-substring "*uniline-interactive*") (insert "\")\n") (lisp-mode)) (defmacro uniline-bench-numcompact-n (n) "If N is 3, replace in the buffer to 3* It is a macro, because N is a dynamic variable which must be inserted into a regexp." `(save-excursion (replace-regexp (rx " " (group (+ (any "a-zA-Z<>-"))) (= ,(1- n) (+ blank) (backref 1))) ,(format " %d*\\1" n)))) (defun uniline-bench-numcompact () "Change to 3*. The `kbd' function understand this notation. And it is easier to read. Put the cursor inside the string to modify." (interactive) (search-backward "\"") (narrow-to-region (point) (progn (forward-sexp) (point))) (goto-char (point-min)) (uniline-bench-numcompact-n 27) (uniline-bench-numcompact-n 26) (uniline-bench-numcompact-n 25) (uniline-bench-numcompact-n 24) (uniline-bench-numcompact-n 23) (uniline-bench-numcompact-n 22) (uniline-bench-numcompact-n 21) (uniline-bench-numcompact-n 20) (uniline-bench-numcompact-n 19) (uniline-bench-numcompact-n 18) (uniline-bench-numcompact-n 17) (uniline-bench-numcompact-n 16) (uniline-bench-numcompact-n 15) (uniline-bench-numcompact-n 14) (uniline-bench-numcompact-n 13) (uniline-bench-numcompact-n 12) (uniline-bench-numcompact-n 11) (uniline-bench-numcompact-n 10) (uniline-bench-numcompact-n 9) (uniline-bench-numcompact-n 8) (uniline-bench-numcompact-n 7) (uniline-bench-numcompact-n 6) (uniline-bench-numcompact-n 5) (uniline-bench-numcompact-n 4) (uniline-bench-numcompact-n 3) (uniline-bench-numcompact-n 2) (widen)) (defun uniline-bench-run (&rest files) "Run all benches, or a specified list. When FILES is nil, the benches are all files with *.el suffix. Stops on the first error, presenting two buffers, - one with the actual drawing, - the other with the expected drawing, with points on the first difference. If there are no errors, a summary is presented." (interactive) (unless files (setq files (directory-files "." nil "^bench.*\\.el$"))) (let* ((buf (current-buffer)) (nbpassed 0) (nbfailed 0) (failed (cl-loop for file in files do (load (format "%s%s" default-directory file) nil nil t) (if uniline-bench-result (cl-incf nbpassed) (cl-incf nbfailed) (message "%s FAILED" file)) unless uniline-bench-result collect file))) (switch-to-buffer buf) (message "%s PASSED / %s FAILED %s" nbpassed nbfailed failed))) (pcase 0 (0 (uniline-bench-run)) (1 (garbage-collect) (profiler-start 'cpu+mem) (uniline-bench-run) (profiler-stop) (profiler-report)) (2 (garbage-collect) (elp-instrument-package "uniline") (uniline-bench-run) (elp-results) (elp-restore-all))) (if nil (uniline-bench-run "bench26.el" "bench27.el")) (provide 'uniline-bench) ;;; uniline-bench.el ends here ================================================ FILE: uniline-core.el ================================================ ;;; uniline-core.el --- Add▶ ■─UNICODE based diagrams─■ to▶ ■─text files─■ -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; Package-Requires: ((emacs "29.1")) ;; Keywords: convenience, text ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;;; Commentary: ;; ┏━━━━━━━┓ ;; ╭──────╮ ┃ thick ┣═◁═╗ ;; │ thin ┝◀━━━┫ box ┃ ║ ;; │ box │ ┗━━━━━━━┛ ║ ;; ╰───┬──╯ ╔══════╩═╗ ;; ↓ ║ double ║ ;; ╰────────────╢ box ║ ;; ╚════╤═══╝ ;; ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ │ ;; ▌quadrant-blocks▐─◁─╯ ;; ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ ;; ;;╭─Pure text────────────────□ ;;│ UNICODE characters are available to draw nice boxes and lines. ;;│ They come in 4 flavours: thin, thick, double, and quadrant-blocks. ;;│ Uniline makes it easy to draw and combine all 4 flavours. ;;│ Use the arrows on the keyboard to move around leaving a line behind. ;;╰──────────────────────────╮ ;;╭─Minor mode───────────────╯ ;;│ Uniline is a minor mode. Enter it with: ;;│ M-x uniline-mode ;;│ Leave it with: ;;│ C-c C-c ;;╰──────────────────────────╮ ;;╭─Fonts────────────────────╯ ;;│ A font able to displays the needed UNICODE characters have to ;;│ be used. It works well with the following families: ;;│ - DejaVu Sans Mono ;;│ - Unifont ;;│ - Hack ;;│ - JetBrains Mono ;;│ - Cascadia Mono ;;│ - Agave ;;│ - JuliaMono ;;│ - FreeMono ;;│ - Iosevka Comfy Fixed, Iosevka Comfy Wide Fixed ;;│ - Aporetic Sans Mono, Aporetic Serif Mono ;;│ - Source Code Pro ;;╰──────────────────────────╮ ;;╭─UTF-8────────────────────╯ ;;│ Also, the encoding of the file must support UNICODE. ;;│ One way to do that, is to add a line like this one ;;│ at the top of your file: ;;│ -*- coding:utf-8; -*- ;;╰──────────────────────────╮ ;;╭─Hydra or Transient───────╯ ;;│ Uniline comes with two flavours of user interfaces: ;;│ Hydra and Transient. ;;│ Both versions are compiled when installing the package. ;;│ ;;│ Then one or the other packages must be loaded (not both) ;;│ for example with: ;;│ (require 'uniline-hydra) ;;│ or ;;│ (use-package uniline-hydra ;;│ :bind ("C-" . uniline-mode)) ;;│ ;;│ This file, uniline-core.el, is the largest one, the one ;;│ implementing all the core functions independent from ;;│ Hydra or Transient ;;╰──────────────────────────□ ;;; Requires: (require 'cl-lib) (require 'rect) (cl-proclaim '(optimize (speed 3) (safety 1))) ;;; Code: ;;;╭────────────────╮ ;;;│Coding decisions│ ;;;╰────────────────╯ ;; Why so much use of (defmacro)? ;; Because we handle 4 directions: north, east, south, west. ;; We end up writing slightly the same algorithms 4 times. ;; It is easier to centralise the algorithms in a set ;; of (defmacro) to write them just once. ;; Isn't (defun) enough and more readable than (defmacro)? ;; Yes, absolutely! ;; We can centralize algorithms in a set of (defun). ;; However a (defun) generically handling all four directions ;; misses nice code-folding. ;; For instance somewhere we have: ;; (ash 3 (* 2 dir)) ;; If the compiler sees `dir' as `uniline-direction-dw↓', ;; for example, then it can fold this expression to just 48, ;; which is nice© ;; We call four times a (defmacro) with hard-coded directions. ;; The hard-coded parameter must go all the way down ;; to the last instructions needing that direction. ;; So we have (defmacro) calling other (defmacro). ;; What is the purpose `eval-when-compile'? ;; It folds down a numerical expression to just one number ;; for instance ;; (eval-when-compile (ash 3 (* 2 uniline-direction-lf←))) ;; is converted to just 192 ;; This happens both in interpreted and byte-compiled code ;; Otherwise the operations ash, multiplication, ;; and retrieval from `uniline-direction-lf←' ;; would be computed over and over at runtime, ;; with always the same 192 result. ;; We could put directly 192 in the source code, ;; but this would defeat maintenance and readability. ;; What is the purpose `eval-and-compile'? ;; The byte-compiler expands all defmacro' called in defun's. ;; In turn, those defmacro's need to access some defconst's, ;; notably `uniline-direction-up↑' and sisters. ;; So those defconst's are made available to the byte-compiler ;; as well as to the runtime, by embedding them in a ;; `eval-and-compile' declaration. ;; When a decision makes the byte-compiled code better ;; (faster to load and run), at the expense of a slower ;; interpreted counterpart, then go ahead, bias toward ;; byte-compiled code. ;;;╭────────────────────────────────────────────────────────╮ ;;;│4 directions, infinite buffer in right & down directions│ ;;;╰────────────────────────────────────────────────────────╯ (eval-and-compile (defconst uniline-direction-up↑ 0) (defmacro uniline-direction-up↑ () 0) (defconst uniline-direction-ri→ 1) (defmacro uniline-direction-ri→ () 1) (defconst uniline-direction-dw↓ 2) (defmacro uniline-direction-dw↓ () 2) (defconst uniline-direction-lf← 3) (defmacro uniline-direction-lf← () 3)) ;; △ △ △ ;; usual constant╶──╯ ╰──────────────────────╮ │ ;; when we insist in having a numeric byte-code: │ │ ;; constant 2╶───────────────────────────────────╯ │ ;; varref uniline-direction-dw↓╶─────────────────────╯ (eval-when-compile ; not needed at runtime (defmacro uniline--switch-with-cond (dir &rest body) "Macro to simplify `cond' applied to possible values of DIR. BODY is ((CASE1 EXPR1...) (CASE2 EXPR2...) ...) DIR is an expression which is compared to each of the CASE1, CASE2, ... until it matches one. Then the corresponding EXPRN is executed. It expands to a (cond) form. It is somehow similar to `cl-case', except that the cases are evaluated while `cl-case' quotes them." (declare (indent 1)) `(cond ,@(cl-loop for c in body collect `((eq ,dir (eval-when-compile ,(car c))) ,@(cdr c))) (t (error "direction not known")))) (defmacro uniline--switch-with-table (dir &rest body) "Macro to return a value by looking in a table at the DIR entry. BODY is ((CASE1 EXPR1) (CASE2 EXPR2) ...) It is similar to `uniline--switch-with-cond', but instead of executing an EXPRN, it just returns it as is. It is expanded into a lookup into a vector or a plist, depending on the CASES. It it faster than an equivalent (cond) form." (declare (indent 1)) (let* ((lambda (if (eq (caar body) 'lambda) (pop body))) (max (cl-loop for c in body for e = (eval (car c)) always (and (fixnump e) (<= 0 e 30)) maximize e))) (if max ;; create a vector for fast lookup (let ((vec (make-vector (1+ max) nil))) (cl-loop for c in body do (aset vec (eval (car c)) (if lambda (funcall lambda (eval (car c))) (eval (cadr c))))) (if (fixnump dir) (aref vec dir) `(aref ,vec ,dir))) ;; create a plist for versatile lookup (let ((plist (cl-loop for c in body collect (eval (car c)) collect (if lambda (funcall lambda (eval (car c))) (eval (cadr c)))))) (if (fixnump dir) (plist-get plist dir) `(plist-get ',plist ,dir)))))) ) (eval-when-compile ; not needed at runtime (defmacro uniline--reverse-direction (dir) "Reverse DIR. DIR is any of the 4 `uniline-direction-*'. Exchange left with right, up with down." `(% (+ 2 ,dir) 4)) (defmacro uniline--turn-right (dir) "Return DIR turned 90° clockwise. DIR & returned values are in [0,1,2,3]." `(% (1+ ,dir) 4)) (defmacro uniline--turn-left (dir) "Return DIR turned 90° anti-clockwise. DIR & returned values are in [0,1,2,3]." `(% (+ 3 ,dir) 4)) ) (defmacro uniline-move-to-column (x) "Move to column X staying on the same line. Add blanks if line is too short. Move to 0 if X negative." ;; x is never numeric in the Uniline calls ;; so it is pointless to optimize this case `(move-to-column (max ,x 0) t)) (defmacro uniline-move-to-delta-column (x) "Move X characters, staying on the same line. Add blanks if line is too short. X may be negative to move backward. Move to 0 if target is beyond the left border of buffer." (cond ((eq x 0) ()) ;; this case never happens in Uniline calls ((eq x 1) `(move-to-column (1+ (current-column) ) t)) ((eq x -1) `(move-to-column (max (1- (current-column) ) 0) t)) ((and (numberp x) (>= x 0)) (progn `(move-to-column (+ (current-column) ,x) t))) (t `(move-to-column (max (+ (current-column) ,x) 0) t)))) (defcustom uniline-infinite-up↑ nil "Is the buffer infinitely extensible in the upper direction? If not, the begining of buffer is a hard limit, as usual in most Emacs modes. Note that if the buffer is narrowed to a region, for example through the use of C-x n n, then the buffer the narrowed region may grow in both the upper and lower direction by automatic insertion of blank lines." :type 'boolean :group 'uniline) (defun uniline--forward-line-force (y p c) "Helper function to move cursor Y lines. Create lines at the end of the buffer if there are not enough lines to honor Y. Y may be negative. Does not preserve current column. P may be (point) for a relative Y move, or (point-min) for an absolute Y move. C is the expected column number." ;; here we fix a bug in the return of (forward-line): ;; when (forward-line) cannot move enough lines ;; because it is stuck at the end of buffer, ;; it erroneously returns one less missing forwards ;; but the bug does not appears if the end-of-buffer ;; is at the beginning-of-line ;; so here we get out of the corner-case of the ;; (forward-line) bug, by ensuring that there is an empty ;; line at the end of buffer (goto-char (point-max)) (or (bolp) (insert ?\n)) (goto-char p) (let ((n (forward-line y))) (if (>= n 0) (insert-byte ?\n n) (when uniline-infinite-up↑ (insert-byte ?\n (- n)) (forward-line n)))) (move-to-column c t)) (defmacro uniline-move-to-line (y) "Move to line Y, while staying on the same column. Create blank lines at the end of the buffer if needed, or blank characters at the end of target line. Y=0 means first line in buffer." `(uniline--forward-line-force ,y (point-min) (current-column))) (defmacro uniline-move-to-delta-line (y) "Move Y lines while staying on the same column. Create blank lines at the end of the buffer if needed, or blank characters at the end of target line. Y may be negative to move backward." `(uniline--forward-line-force ,y (point) (current-column))) (defmacro uniline-move-to-lin-col (y x) "Move to line Y and column X. Create blank lines at the end of the buffer if needed, or blank characters at the end of target line if needed. Y=0 means first line of buffer. X=0 means first column of buffer." `(uniline--forward-line-force ,y (point-min) ,x)) (eval-when-compile ; not needed at runtime (defmacro uniline--move-in-direction (dir &optional nb) "Move NB char in direction DIR. NB defaults to 1. This is a macro, therefore it is as if writing directly (uniline-move-to-delta-line -1) and such, with no overhead." (declare (debug (form))) (unless nb (setq nb 1)) (let ((mnb (if (fixnump nb) (- nb) `(- ,nb)))) (uniline--switch-with-cond dir (uniline-direction-up↑ `(uniline-move-to-delta-line ,mnb)) (uniline-direction-ri→ `(uniline-move-to-delta-column , nb)) (uniline-direction-dw↓ `(uniline-move-to-delta-line , nb)) (uniline-direction-lf← `(uniline-move-to-delta-column ,mnb)))))) (eval-when-compile ; not needed at runtime (defmacro uniline--at-border-p (dir) "Test if at a non-trespass-able border of buffer. This happens at the first line or at the first column, when trying to go further when DIR is up or left: `uniline-direction-up↑' or `uniline-direction-lf←'. In the bottom & right directions the buffer is infinite." (declare (debug (form))) (setq dir (eval dir)) (uniline--switch-with-table dir (uniline-direction-up↑ '(eq (pos-bol) 1)) (uniline-direction-ri→ nil) (uniline-direction-dw↓ nil) (uniline-direction-lf← '(bolp))))) (defun uniline--char-after (&optional point) "Same as `char-after', except for right and bottom edges of buffer. Outside the buffer, return a blank character. POINT default to `(point)', as for `char-after'" (let ((c (char-after point))) (if (or (not c) (eq c ?\n)) ? ;; eol & eob are considered blank c))) ;;;╭───────────────────╮ ;;;│Perfect hash tables│ ;;;╰───────────────────╯ ;; When an hash-table is constant, with entries known at compile-time, ;; then a perfect hash-table can be built. Perfect means that there ;; are no collisions: each bucket contains one entry, or none, never ;; two entries. This makes the hash-table very efficient. ;; The usual multiple entries scanning can be entirely bypassed. ;; ;; Uniline has 13 constant hash-tables. We want to make them ;; perfect, collision-less. 3 Options: ;; ;; 1- Define a custom hash-function like ;; (% key 191) ;; by looking for the right constant like 191, we can get a ;; collision-less hash-table ;; as shown by (internal--hash-table-histogram) ;; - Unfortunately, this makes the resulting hash-table roughly ;; 3 times slower than a regular (make-hash-table), even one ;; with collisions. ;; ;; 2- Use a custom vector instead of a hash-table, along with a ;; hashing function like ;; (% key (length custom-vector)) ;; This amounts to re-creating hash-tables with vectors. ;; - The result is slightly slower than (make-hash-table), even ;; with collisions, about 30% slower. ;; ;; 3- Use a standard hash-table and tweak its size ;; (make-hash-table :size 114) ;; - Unfortunately, the specified size is not retained in the ;; compiled *.elc file ;; Beside, the specified size gets rounded to the next power of 2 ;; ;; 4- Create the constant hash-table with ;; (make-hash-table :size 114) ;; when loading the compiled *.elc file ;; Store in the *.elc a list of pairs (key . value) ;; - This bypasses the #s(hash-table …) form that was handy in the ;; *.elc, but at no cost in the *.elc size and almost nothing ;; for the hash-table creation process at load-time ;; ;; This 4th solution is implemented hereafter through a macro ;; which bundles: ;; - (defconst …) ;; - (make-hash-table :size my-preferred-size) ;; - populating the hash-table from a list of pairs loaded from *.elc (eval-when-compile ;; not needed at runtime (defmacro uniline--defconst-hash-table (name pairs size test doc) "Bundles construction of a constant hash-table in one place. NAME is the name of the hash-table to be passed to `defconst' PAIRS is a list of (key . value) pairs to populate the table SIZE is the desired number of buckets TEST is the comparison function between 2 keys, like `eq' or `equal'" (declare (indent 1) (doc-string 5)) `(defconst ,name (let ((table (make-hash-table :size ,size :test ,test))) (dolist (pair ,pairs) (puthash (car pair) (cdr pair) table)) table) ,doc))) ;; Display useful metrics to tune constant hash-tables ;; those created by `uniline--defconst-hash-table' ;; Useful only in development cycle. (if nil (defun uniline--hash-tables-metrics () (interactive) (switch-to-buffer "*hash-tables metrics*") (erase-buffer) (local-set-key "g" 'uniline--hash-tables-metrics) (insert "\ try to make a single element list in the last column (collision-less) by adjusting table size ╰───┬╯ (3th parameter of `uniline--defconst-hash-table') ╰───────────╮ type g to refresh │ ╭───┴───╮ table buckets resize histogram ╰─┬─╯ ╰──┬──╯ ╰─┬──╯ ╰───┬───╯ │ entries │ index │ threshold │ │ ╰───┬─╯ │ ╰─┬─╯ │ ╰───┬───╯ │ ╭────────────────┴───────────────╮╭┴─╮╭┴─╮╭┴─╮╭┴─╮╭──┴──╮╭────┴──────────▷ ") (cl-loop for table in '(uniline--char-to-4halfs uniline--glyphs-reverse-hash-fw uniline--glyphs-reverse-hash-bw uniline--char-to-4quadb1 uniline--keystroke-to-dir-shift uniline--char-to-dot-3-2-char uniline--char-to-dot-4-4-char uniline--char-to-standard-char uniline--char-to-hard-corner-char uniline--char-to-thin-char uniline--char-to-thick-char uniline--char-to-double-line) do (let ((hash (eval table))) (insert (format "│%-33s│%3d│%3d│%3d│%3g│%5g│%s\n" table (hash-table-count hash) (hash-table-size hash) (internal--hash-table-index-size hash) (hash-table-rehash-size hash) (hash-table-rehash-threshold hash) (internal--hash-table-histogram hash))))))) ;;;╭─────────────────────────────────────────────────────╮ ;;;│Reference tables of ┼ 4 half-lines UNICODE characters│ ;;;╰─────────────────────────────────────────────────────╯ ;; Hereafter `4halfs' means a representation of a UNICODE character ;; made of half-lines, like ┖ or ┶, as a single number. ;; ←← ↓↓ →→ ↑↑ directions ;; □□ □□ □□ ■■ ;; 76 54 32 10 bits ;; There are 4 half-lines, one in each direction ;; (north, east, west, south). Each half line may have one of ;; 4 styles: ;; ╭────────╴style of line ;; │ ╭┬────two bits ;; ▼ ▼▼ ;; blank ( ) □□ ;; thin (─) □■ ;; thick (━) ■□ ;; double (═) ■■ ;; So a `4halfs' number goes from 0 to 4x4x4x4 = 256. ;; This representation is great because it is easily handled by ;; the bits manipulation primitives: `logior', `logand', `ash'. (eval-when-compile ; not needed at runtime (defmacro uniline--shift-4half (4half to-dir &optional from-dir) "Shift 4HALF bits in TO-DIR direction. 4HALF is a number made of 2 bits, in the range [0..3] 0: no line 1: thin line 2: thick line 3: double line DIR is 1 of 4 directions: 0: uniline-direction-up↑ 1: uniline-direction-ri→ 2: uniline-direction-dw↓ 3: uniline-direction-lf← If FROM-DIR is given, the 4HALF bits pattern is supposed to be shifted in the FROM-DIR direction. Otherwise, it is not shifted. The result is the bit pattern 4HALF << (2*(TO-DIR - FROM-DIR))" (condition-case nil (setq to-dir (eval to-dir)) ;; fold if to-dir is a numerical sexpr (error nil)) ;; otherwise leave dir alone (condition-case nil (setq from-dir (eval from-dir)) ;; fold if to-dir is a numerical sexpr (error nil)) ;; otherwise leave dir alone (let ((dir (if from-dir (if (and (fixnump to-dir) (fixnump from-dir)) (- to-dir from-dir) `(- ,to-dir ,from-dir)) to-dir))) (cond ((eq dir 0) 4half) ((fixnump dir) (if (fixnump 4half) (ash 4half (* 2 dir)) `( ash ,4half ,(* 2 dir)))) (t `( ash ,4half (* 2 ,dir)))))) (defmacro uniline--extract-reverse-4half (4half dir) "Extract a 2 bits pattern from the 8 bits pattern 4HALF. Extract the 2 bits in reverse direction of DIR, and shift them in direction DIR." (let ((odir (uniline--reverse-direction dir))) `(uniline--shift-4half (logand ,4half ,(uniline--shift-4half 3 odir)) ,dir ,odir))) (defun uniline--pack-4halfs (urld) "Encode a description of lines into a single number. A character contains half lines upward, right, downward, left. Each of those half lines may have one of 4 styles: 0: no line 1: thin line 2: thick line 3: double line Example: ╀ has description 2 1 1 1 ▲ ▲ ▲ ▲ thick=2 upward╶────────────╯ ╰─┴─┴──╮ and thin=1 in the other directions╶─╯ The parameter URLD is a list of 4 numbers in [0..3] for the 4 directions. A single number encoding all possible combinations has a range of [0..256). It is handy to index vectors rather than 4 dimensions matrices." (logior (uniline--shift-4half (car urld) uniline-direction-up↑) (uniline--shift-4half (cadr urld) uniline-direction-ri→) (uniline--shift-4half (caddr urld) uniline-direction-dw↓) (uniline--shift-4half (cadddr urld) uniline-direction-lf←))) (defun uniline--unpack-4halfs (4halfs) (list (logand (uniline--shift-4half 4halfs 0 uniline-direction-up↑) 3) (logand (uniline--shift-4half 4halfs 0 uniline-direction-ri→) 3) (logand (uniline--shift-4half 4halfs 0 uniline-direction-dw↓) 3) (logand (uniline--shift-4half 4halfs 0 uniline-direction-lf←) 3)))) (eval-when-compile ; not used at runtime (defconst uniline--list-of-available-halflines '(;;╭───────────────unicode char ;;│ ╭─┬─┬─┬──────4half description ;;▽ ▽ ▽ ▽ ▽ (?\t 0 0 0 0 ) ;; TAB character considered as a space ( ?  0 0 0 0 ) ;; NO-BREAK SPACE considered as a space (8200 0 0 0 0) ;; PUNCTUATION SPACE considered as a space ( ? 0 0 0 0 ) ;; real space comes AFTER the exotic spaces ( ?╵ 1 0 0 0 ) ( ?╹ 2 0 0 0 ) ( ?╶ 0 1 0 0 ) ( ?└ 1 1 0 0 ) ( ?╰ 1 1 0 0 ) ;; prefer rounded corner ( ?┖ 2 1 0 0 ) ( ?╺ 0 2 0 0 ) ( ?┕ 1 2 0 0 ) ( ?┗ 2 2 0 0 ) ( ?╷ 0 0 1 0 ) ;;( ?| 1 0 1 0 ) ;; recognize vertical ASCII pipe ( ?┆ 1 0 1 0 ) ( ?┊ 1 0 1 0 ) ( ?│ 1 0 1 0 ) ;; prefer plain lines ( ?┇ 2 0 2 0 ) ( ?┋ 2 0 2 0 ) ( ?┃ 2 0 2 0 ) ;; prefer plain lines ( ?╿ 2 0 1 0 ) ( ?┌ 0 1 1 0 ) ( ?╭ 0 1 1 0 ) ;; prefer rounded corner ( ?├ 1 1 1 0 ) ( ?┞ 2 1 1 0 ) ( ?┍ 0 2 1 0 ) ( ?┝ 1 2 1 0 ) ( ?┡ 2 2 1 0 ) ( ?╻ 0 0 2 0 ) ( ?╽ 1 0 2 0 ) ( ?┎ 0 1 2 0 ) ( ?┟ 1 1 2 0 ) ( ?┠ 2 1 2 0 ) ( ?┏ 0 2 2 0 ) ( ?┢ 1 2 2 0 ) ( ?┣ 2 2 2 0 ) ( ?╴ 0 0 0 1 ) ( ?┘ 1 0 0 1 ) ( ?╯ 1 0 0 1 ) ;; prefer rounded corner ( ?┚ 2 0 0 1 ) ;;( ?- 0 1 0 1 ) ;; recognize ASCII minus ( ?┄ 0 1 0 1 ) ( ?┈ 0 1 0 1 ) ( ?╌ 0 1 0 1 ) ( ?─ 0 1 0 1 ) ;; prefer plain lines ( ?┴ 1 1 0 1 ) ( ?┸ 2 1 0 1 ) ( ?╼ 0 2 0 1 ) ( ?┶ 1 2 0 1 ) ( ?┺ 2 2 0 1 ) ( ?┐ 0 0 1 1 ) ( ?╮ 0 0 1 1 ) ;; prefer rounded corner ( ?┤ 1 0 1 1 ) ( ?┦ 2 0 1 1 ) ( ?┬ 0 1 1 1 ) ;;( ?+ 1 1 1 1 ) ;; recognize ASCII plus ( ?┼ 1 1 1 1 ) ( ?╀ 2 1 1 1 ) ( ?┮ 0 2 1 1 ) ( ?┾ 1 2 1 1 ) ( ?╄ 2 2 1 1 ) ( ?┒ 0 0 2 1 ) ( ?┧ 1 0 2 1 ) ( ?┨ 2 0 2 1 ) ( ?┰ 0 1 2 1 ) ( ?╁ 1 1 2 1 ) ( ?╂ 2 1 2 1 ) ( ?┲ 0 2 2 1 ) ( ?╆ 1 2 2 1 ) ( ?╊ 2 2 2 1 ) ( ?╸ 0 0 0 2 ) ( ?┙ 1 0 0 2 ) ( ?┛ 2 0 0 2 ) ( ?╾ 0 1 0 2 ) ( ?┵ 1 1 0 2 ) ( ?┹ 2 1 0 2 ) ( ?┉ 0 2 0 2 ) ( ?┅ 0 2 0 2 ) ( ?╍ 0 2 0 2 ) ( ?━ 0 2 0 2 ) ;; prefer plain lines ( ?┷ 1 2 0 2 ) ( ?┻ 2 2 0 2 ) ( ?┑ 0 0 1 2 ) ( ?┥ 1 0 1 2 ) ( ?┩ 2 0 1 2 ) ( ?┭ 0 1 1 2 ) ( ?┽ 1 1 1 2 ) ( ?╃ 2 1 1 2 ) ( ?┯ 0 2 1 2 ) ( ?┿ 1 2 1 2 ) ( ?╇ 2 2 1 2 ) ( ?┓ 0 0 2 2 ) ( ?┪ 1 0 2 2 ) ( ?┫ 2 0 2 2 ) ( ?┱ 0 1 2 2 ) ( ?╅ 1 1 2 2 ) ( ?╉ 2 1 2 2 ) ( ?┳ 0 2 2 2 ) ( ?╈ 1 2 2 2 ) ( ?╋ 2 2 2 2 ) ( ?╒ 0 3 1 0 ) ( ?╞ 1 3 1 0 ) ( ?║ 3 0 3 0 ) ( ?╓ 0 1 3 0 ) ( ?╟ 3 1 3 0 ) ( ?╔ 0 3 3 0 ) ( ?╠ 3 3 3 0 ) ( ?╜ 3 0 0 1 ) ( ?╨ 3 1 0 1 ) ( ?╖ 0 0 3 1 ) ( ?╢ 3 0 3 1 ) ( ?╥ 0 1 3 1 ) ( ?╫ 3 1 3 1 ) ( ?╛ 1 0 0 3 ) ( ?╝ 3 0 0 3 ) ( ?═ 0 3 0 3 ) ( ?╧ 1 3 0 3 ) ( ?╩ 3 3 0 3 ) ( ?╕ 0 0 1 3 ) ( ?╡ 1 0 1 3 ) ( ?╤ 0 3 1 3 ) ( ?╪ 1 3 1 3 ) ( ?╗ 0 0 3 3 ) ( ?╣ 3 0 3 3 ) ( ?╦ 0 3 3 3 ) ( ?╬ 3 3 3 3 ) ( ?╙ 3 1 0 0 ) ( ?╘ 1 3 0 0 ) ( ?╚ 3 3 0 0 )))) (eval-when-compile ; not used at runtime (defconst uniline--list-of-double-halflines '(;; ╭─missing ╭─┬─replacement ;; │ │ ╰───────╮ ;; ▽ ▽ ▽ ((0 0 0 3) (0 0 0 2)) ;; ╸ ((0 0 2 3) (0 0 3 3)) ;; ╗ ((0 0 3 0) (0 0 2 0)) ;; ╻ ((0 0 3 2) (0 0 3 3)) ;; ╗ ((0 1 0 3) (0 3 0 3)) ;; ═ ((0 1 1 3) (0 3 1 3)) ;; ╤ ((0 1 2 3) (0 1 2 2)) ;; ┱ ((0 1 3 2) (0 1 2 2)) ;; ┱ ((0 1 3 3) (0 3 3 3)) ;; ╦ ((0 2 0 3) (0 3 0 3)) ;; ═ ((0 2 1 3) (0 3 1 3)) ;; ╤ ((0 2 2 3) (0 3 3 3)) ;; ╦ ((0 2 3 0) (0 3 3 0)) ;; ╔ ((0 2 3 1) (0 1 3 1)) ;; ╥ ((0 2 3 2) (0 3 3 3)) ;; ╦ ((0 2 3 3) (0 3 3 3)) ;; ╦ ((0 3 0 0) (0 2 0 0)) ;; ╺ ((0 3 0 1) (0 3 0 3)) ;; ═ ((0 3 0 2) (0 3 0 3)) ;; ═ ((0 3 1 1) (0 3 1 3)) ;; ╤ ((0 3 1 2) (0 3 1 3)) ;; ╤ ((0 3 2 0) (0 3 3 0)) ;; ╔ ((0 3 2 1) (0 2 2 1)) ;; ┲ ((0 3 2 2) (0 3 3 3)) ;; ╦ ((0 3 2 3) (0 3 3 3)) ;; ╦ ((0 3 3 1) (0 3 3 3)) ;; ╦ ((0 3 3 2) (0 3 3 3)) ;; ╦ ((1 0 2 3) (1 0 1 3)) ;; ╡ ((1 0 3 0) (3 0 3 0)) ;; ║ ((1 0 3 1) (3 0 3 1)) ;; ╢ ((1 0 3 2) (1 0 2 2)) ;; ┪ ((1 0 3 3) (3 0 3 3)) ;; ╣ ((1 1 0 3) (1 3 0 3)) ;; ╧ ((1 1 1 3) (1 3 1 3)) ;; ╪ ((1 1 2 3) (1 1 2 2)) ;; ╅ ((1 1 3 0) (3 1 3 0)) ;; ╟ ((1 1 3 1) (3 1 3 1)) ;; ╫ ((1 1 3 2) (1 1 2 2)) ;; ╅ ((1 1 3 3) (3 3 3 3)) ;; ╬ ((1 2 0 3) (1 3 0 3)) ;; ╧ ((1 2 1 3) (1 3 1 3)) ;; ╪ ((1 2 2 3) (1 2 2 2)) ;; ╈ ((1 2 3 0) (1 2 2 0)) ;; ┢ ((1 2 3 1) (1 2 2 1)) ;; ╆ ((1 2 3 2) (1 2 2 2)) ;; ╈ ((1 2 3 3) (3 3 3 3)) ;; ╬ ((1 3 0 1) (1 3 0 3)) ;; ╧ ((1 3 0 2) (1 3 0 3)) ;; ╧ ((1 3 1 1) (1 3 1 3)) ;; ╪ ((1 3 1 2) (1 3 1 3)) ;; ╪ ((1 3 2 0) (1 2 2 0)) ;; ┢ ((1 3 2 1) (1 2 2 1)) ;; ╆ ((1 3 2 2) (1 2 2 2)) ;; ╈ ((1 3 2 3) (1 3 1 3)) ;; ╪ ((1 3 3 0) (3 3 3 0)) ;; ╠ ((1 3 3 1) (3 3 3 3)) ;; ╬ ((1 3 3 2) (3 3 3 3)) ;; ╬ ((1 3 3 3) (3 3 3 3)) ;; ╬ ((2 0 0 3) (3 0 0 3)) ;; ╝ ((2 0 1 3) (1 0 1 3)) ;; ╡ ((2 0 2 3) (3 0 3 3)) ;; ╣ ((2 0 3 0) (3 0 3 0)) ;; ║ ((2 0 3 1) (3 0 3 1)) ;; ╢ ((2 0 3 2) (3 0 3 3)) ;; ╣ ((2 0 3 3) (3 0 3 3)) ;; ╣ ((2 1 0 3) (2 1 0 2)) ;; ┹ ((2 1 1 3) (2 1 1 2)) ;; ╃ ((2 1 2 3) (2 1 2 2)) ;; ╉ ((2 1 3 0) (3 1 3 0)) ;; ╟ ((2 1 3 1) (3 1 3 1)) ;; ╫ ((2 1 3 2) (2 1 2 2)) ;; ╉ ((2 1 3 3) (3 3 3 3)) ;; ╬ ((2 2 0 3) (3 3 0 3)) ;; ╩ ((2 2 1 3) (2 2 1 2)) ;; ╇ ((2 2 2 3) (2 2 2 2)) ;; ╋ ((2 2 3 0) (3 3 3 0)) ;; ╠ ((2 2 3 1) (2 2 2 1)) ;; ╊ ((2 2 3 2) (2 2 2 2)) ;; ╋ ((2 2 3 3) (3 3 3 3)) ;; ╬ ((2 3 0 0) (3 3 0 0)) ;; ╚ ((2 3 0 1) (2 2 0 1)) ;; ┺ ((2 3 0 2) (3 3 0 3)) ;; ╩ ((2 3 0 3) (3 3 0 3)) ;; ╩ ((2 3 1 0) (1 3 1 0)) ;; ╞ ((2 3 1 1) (2 2 1 1)) ;; ╄ ((2 3 1 2) (2 2 1 2)) ;; ╇ ((2 3 1 3) (1 3 1 3)) ;; ╪ ((2 3 2 0) (3 3 3 0)) ;; ╠ ((2 3 2 1) (2 2 2 1)) ;; ╊ ((2 3 2 2) (2 2 2 2)) ;; ╋ ((2 3 2 3) (3 3 3 3)) ;; ╬ ((2 3 3 0) (3 3 3 0)) ;; ╠ ((2 3 3 1) (3 3 3 3)) ;; ╬ ((2 3 3 2) (3 3 3 3)) ;; ╬ ((2 3 3 3) (3 3 3 3)) ;; ╬ ((3 0 0 0) (2 0 0 0)) ;; ╹ ((3 0 0 2) (3 0 0 3)) ;; ╝ ((3 0 1 0) (3 0 3 0)) ;; ║ ((3 0 1 1) (3 0 3 1)) ;; ╢ ((3 0 1 2) (2 0 1 2)) ;; ┩ ((3 0 1 3) (3 0 3 3)) ;; ╣ ((3 0 2 0) (3 0 3 0)) ;; ║ ((3 0 2 1) (3 0 3 1)) ;; ╢ ((3 0 2 2) (3 0 3 3)) ;; ╣ ((3 0 2 3) (3 0 3 3)) ;; ╣ ((3 0 3 2) (3 0 3 3)) ;; ╣ ((3 1 0 2) (2 1 0 2)) ;; ┹ ((3 1 0 3) (3 3 0 3)) ;; ╩ ((3 1 1 0) (3 1 3 0)) ;; ╟ ((3 1 1 1) (3 1 3 1)) ;; ╫ ((3 1 1 2) (2 1 1 2)) ;; ╃ ((3 1 1 3) (3 3 3 3)) ;; ╬ ((3 1 2 0) (3 1 3 0)) ;; ╟ ((3 1 2 1) (3 1 3 1)) ;; ╫ ((3 1 2 2) (2 1 2 2)) ;; ╉ ((3 1 2 3) (3 3 3 3)) ;; ╬ ((3 1 3 2) (3 1 3 1)) ;; ╫ ((3 1 3 3) (3 3 3 3)) ;; ╬ ((3 2 0 0) (3 3 0 0)) ;; ╚ ((3 2 0 1) (2 2 0 1)) ;; ┺ ((3 2 0 2) (3 3 0 3)) ;; ╩ ((3 2 0 3) (3 3 0 3)) ;; ╩ ((3 2 1 0) (2 2 1 0)) ;; ┡ ((3 2 1 1) (2 2 1 1)) ;; ╄ ((3 2 1 2) (2 2 1 2)) ;; ╇ ((3 2 1 3) (3 3 3 3)) ;; ╬ ((3 2 2 0) (3 3 3 0)) ;; ╠ ((3 2 2 1) (2 2 2 1)) ;; ╊ ((3 2 2 2) (2 2 2 2)) ;; ╋ ((3 2 2 3) (3 3 3 3)) ;; ╬ ((3 2 3 0) (3 3 3 0)) ;; ╠ ((3 2 3 1) (3 1 3 1)) ;; ╫ ((3 2 3 2) (3 3 3 3)) ;; ╬ ((3 2 3 3) (3 3 3 3)) ;; ╬ ((3 3 0 1) (3 3 0 3)) ;; ╩ ((3 3 0 2) (3 3 0 3)) ;; ╩ ((3 3 1 0) (3 3 3 0)) ;; ╠ ((3 3 1 1) (3 3 3 3)) ;; ╬ ((3 3 1 2) (3 3 3 3)) ;; ╬ ((3 3 1 3) (3 3 3 3)) ;; ╬ ((3 3 2 0) (3 3 3 0)) ;; ╠ ((3 3 2 1) (3 3 3 3)) ;; ╬ ((3 3 2 2) (3 3 3 3)) ;; ╬ ((3 3 2 3) (3 3 3 3)) ;; ╬ ((3 3 3 1) (3 3 3 3)) ;; ╬ ((3 3 3 2) (3 3 3 3)) ;; ╬ ))) (eval-and-compile (defconst uniline--4halfs-to-char (eval-when-compile (let ((table (make-vector (* 4 4 4 4) nil))) (cl-loop for x in uniline--list-of-available-halflines do (aset table (uniline--pack-4halfs (cdr x)) (car x))) (cl-loop for x in uniline--list-of-double-halflines do (aset table (uniline--pack-4halfs (car x)) (aref table (uniline--pack-4halfs (cadr x))))) (cl-loop for x in '( ( ?┆ 1 0 1 0 ) ( ?┇ 2 0 2 0 ) ( ?┄ 0 1 0 1 ) ( ?┅ 0 2 0 2 )) for i = (uniline--pack-4halfs (cdr x)) for c = (aref table i) do (if (numberp c) (aset table i (setq c (vector c nil nil)))) (aset c 1 (car x))) (cl-loop for x in '( ( ?┊ 1 0 1 0 ) ( ?┋ 2 0 2 0 ) ( ?┈ 0 1 0 1 ) ( ?┉ 0 2 0 2 )) for i = (uniline--pack-4halfs (cdr x)) for c = (aref table i) do (if (numberp c) (aset table i (setq c (vector c nil nil)))) (aset c 2 (car x))) table)) "Convert a 4halfs description to a UNICODE character. The 4halfs description is (UP RI DW LF) packed into a single integer. As there are no UNICODE character for every combination, the visually closest UNICODE character is retrieved. So for instance up=1 (thin up), ri=2 (thick right), dw=0 (blank down), lf=0 (blank left), is encoded into up +4*ri +16*dw +64*lf = 9, which in turn is converted to ┕.")) (eval-and-compile (uniline--defconst-hash-table uniline--char-to-4halfs (eval-when-compile (cl-loop for x in uniline--list-of-available-halflines collect (cons (car x) (uniline--pack-4halfs (cdr x))))) 256 'eq ;; the setting 256 'eq ;; creates 1 collision between almost never used characters ;; this is the best that can be done "Convert a UNICODE character to a 4halfs description. The UNICODE character is supposed to represent a combination of half lines in 4 directions and in 4 brush styles. The retrieved value is a 4halfs description is (UP RI DW LF) packed into a single integer. If the UNICODE character is not a box-drawing one, nil is returned. So for instance, the character ┸ is converted to (2 1 0 1) meaning: 2 = thick up 1 = thin right 0 = blank down 1 = thin left Values (2 1 0 1) are encoded into 2 + 4*1 + 0*16 + 1*64 = 70 This table is the reverse of `uniline--4halfs-to-char' without the fall-back characters.")) (eval-and-compile (defvar-local uniline-brush 1 "Controls the style of line. Possible values are as follows: nil: no action except cursor movements 0: erase, 1: simple line like ╰─┤ 2: thick line like ┗━┫ 3: double line like ╚═╣ :block block drawing like ▙▄▟▀") (defvar-local uniline-brush-dots 0 "Sub brush to control whether lines are plain or dotted. 0: plain lines 1: 3 dots vertical, 2 dots horizontal 2: 4 dots both vertical & horizontal") (defun uniline--4halfs-to-char-aref (4halfs) "Access the array `uniline--4halfs-to-char' through this function. Because sometimes an indirection is needed to desambiguate between several versions of a glyph. For instance there are at least 3 versions of a vertical thin line: - │ plain line - ┆ 3 dotted line - ┊ 4 dotted line" (let ((c (aref uniline--4halfs-to-char 4halfs))) (if (not (numberp c)) ;; `not' is on purpose to make a shorter bytecode (aref c uniline-brush-dots) c))) ) (when nil ;; This inactive code was used to generate the ;; `uniline--list-of-double-halflines' list above. ;; As the ╬ double line glyphs combinations were not all defined ;; in the UNICODE standard, a penalty system was applied to look ;; for the closest possible alternate glyph. ;; For example, ├ and ╠ exist, but a mixture of both do no exist, ;; hence a line like this one ;; ((3 3 1 0) (3 3 3 0)) ;; ╠ ;; which says that, when this ├ is needed, with the upward and ;; rightward branches in double line style, then fallback to ╠ ;; There is no need to re-run it, except if one wants to change ;; the penalties applied. ;; ;; To run it re-compute `uniline--4halfs-to-char' ;; - but with only the first cl-loop over ;; `uniline--list-of-available-halflines' ;; - not the loop over ;; `uniline--list-of-double-halflines' (defun uniline--penalty1 (a b) (pcase a (0 (if (eq b 0) 0 1000)) (1 (pcase b (0 1000) (1 0) (2 3) (3 2))) (2 (pcase b (0 1000) (1 3) (2 0) (3 1))) (3 (pcase b (0 1000) (1 4) (2 3) (3 0))))) (defun uniline--penalty (u1 r1 d1 l1 u2 r2 d2 l2) (+ (uniline--penalty1 u1 u2) (uniline--penalty1 r1 r2) (uniline--penalty1 d1 d2) (uniline--penalty1 l1 l2))) (switch-to-buffer "replace.el") (dotimes (u1 4) (dotimes (r1 4) (dotimes (d1 4) (dotimes (l1 4) (unless (uniline--4halfs-to-char-aref (uniline--pack-4halfs (list u1 r1 d1 l1))) (let ((m 9999) m0 u3 r3 d3 l3) (dotimes (u2 4) (dotimes (r2 4) (dotimes (d2 4) (dotimes (l2 4) (when (uniline--4halfs-to-char-aref (uniline--pack-4halfs (list u2 r2 d2 l2))) (setq m0 (uniline--penalty u1 r1 d1 l1 u2 r2 d2 l2)) (if (< m0 m) (setq m m0 u3 u2 r3 r2 d3 d2 l3 l2))))))) (insert (format "((%d %d %d %d) (%d %d %d %d)) ;; %c\n" u1 r1 d1 l1 u3 r3 d3 l3 (uniline--4halfs-to-char-aref (uniline--pack-4halfs (list u3 r3 d3 l3)))))))))))) ;;;╭────────────────────────────────────────────────────────╮ ;;;│Reference tables of △▶↓□◆● arrows & other UNICODE glyphs│ ;;;╰────────────────────────────────────────────────────────╯ (eval-when-compile ; helper constant not needed at runtime (defconst uniline--glyphs-tmp '( ;; arrows (a ?△ ?▷ ?▽ ?◁) ;; white *-pointing triangle (a ?▲ ?▶ ?▼ ?◀) ;; black *-pointing triangle (a ?↑ ?→ ?↓ ?←) ;; *wards arrow (a ?▵ ?▹ ?▿ ?◃) ;; white *-pointing small triangle (a ?▴ ?▸ ?▾ ?◂) ;; black *-pointing small triangle (a ?↕ ?↔ ?↕ ?↔) ;; up down arrow, left right arrow ;; Those commented-out arrows are monospaces and supported ;; by the 6 fonts. But they do not have 4 directions. ;;(a ?‹ ?› ?› ?‹) ;; single *-pointing angle quotation mark ;; squares (s ?□) ;; white square (s ?■) ;; black square (s ?▫) ;; white small square (s ?▪) ;; black small square ;;(s ?◇) ;; white diamond ;; glitch with Source Code Pro (s ?◆) ;; black diamond (s ?◊) ;; lozenge ;; o shapes (o ?·) ;; middle dot (o ?∙) ;; bullet operator (o ?•) ;; bullet (o ?●) ;; black circle (o ?◦) ;; white bullet (o ?Ø) ;; latin capital letter o with stroke (o ?ø) ;; latin small letter o with stroke ;; crosses (x ?╳) ;; box drawings light diagonal cross (x ?╱) ;; box drawings light diagonal upper right to lower left (x ?╲) ;; box drawings light diagonal upper left to lower right (x ?÷) ;; division sign (x ?×) ;; multiplication sign (x ?±) ;; plus-minus sign (x ?¤) ;; currency sign ;; 5 shades of grey (g ? ) (g ?░) (g ?▒) (g ?▓) (g ?█) ;; The following commented-out glyphs are possible additions ;; when using the DejaVu Sans Mono font ;; Other fonts either do not support those glyphs ;; or do not make them monospaced ;;(t ?⋏ ?≻ ?⋎ ?≺) ;; precedes ;;(t ?⊥ ?⊢ ?⊤ ?⊣) ;; * tack ;;(t ?⋂ ?⊃ ?⋃ ?⊂) ;; subset of ;;(a ?⇡ ?⇢ ?⇣ ?⇠) ;; *wards dashed arrow ;;(a ?⇑ ?⇒ ?⇓ ?⇐) ;; *wards double arrow ;;(a ?⇧ ?⇨ ?⇩ ?⇦) ;; *wards white arrow ;;(b ?↥ ?↦ ?↧ ?↤) ;; *wards arrow from bar ;;(b ?↟ ?↠ ?↡ ?↞) ;; *wards two headed arrow ;;(b ?⇈ ?⇉ ?⇊ ?⇇) ;; *wards paired arrows ;;(b ?☝ ?☞ ?☟ ?☜) ;; white * pointing index ;;(b ?⇞ ?⇻ ?⇟ ?⇺) ;; *wards arrow with double vertical stroke ;;(c ?⬘ ?⬗ ?⬙ ?⬖) ;; diamond with * half black ;;(c ?◓ ?◑ ?◒ ?◐) ;; circle with * half black ;;(b ?⍐ ?⍈ ?⍗ ?⍇) ;; apl functional symbol quad *wards arrow ;;(s ?▢) ;; white square with rounded corners ;;(s ?▣) ;; white square containing black small square ;;(s ?▩) ;; square with diagonal crosshatch fill ;;(s ?▤) ;; square with horizontal fill ;;(s ?▥) ;; square with vertical fill ;;(s ?▦) ;; square with orthogonal crosshatch fill ;;(s ?▧) ;; square with upper left to lower right fill ;;(s ?▨) ;; square with upper right to lower left fill ;;(o ?○) ;; white circle ;;(o ?◎) ;; bullseye ;;(o ?✪) ;; circled white star ;;(o ?o) ;; latin small letter o ;;(o ?O) ;; latin capital letter o ;;(o ?◍) ;; circle with vertical fill ;;(o ?◉) ;; fisheye ;;(o ?❂) ;; circled open centre eight pointed star ;;(o ?⚙) ;; gear ;;(o ?☹) ;; white frowning face ;;(o ?☺) ;; white smiling face ;;(o ?✆) ;; telephone location sign ;;(o ?✇) ;; tape drive ;;(x ?☓) ;; saltire ;;(x ?+) ;; plus sign ;;(x ?✔) ;; heavy check mark ;;(x ?✖) ;; heavy multiplication x ;;(x ?✚) ;; heavy greek cross ;;(x ?✜) ;; heavy open centre cross ;;(x ?x) ;; latin small letter x ;;(x ?X) ;; latin capital letter x ;;(x ?✙) ;; outlined greek cross ;;(x ?✛) ;; open centre cross ;;(d ?⚀) ;; die face-1 ;;(d ?⚁) ;; die face-2 ;;(d ?⚂) ;; die face-3 ;;(d ?⚃) ;; die face-4 ;;(d ?⚄) ;; die face-5 ;;(d ?⚅) ;; die face-6 ;;(d ?⊡) ;; squared dot operator ;;(d ?☒) ;; ballot box with x ;;(d ?☑) ;; ballot box with check ;;(d ?⊞) ;; squared plus ;;(l ?◇) ;; white diamond ;;(l ?◆) ;; black diamond ;;(l ?✦) ;; black four pointed star ;;(l ?✧) ;; white four pointed star ;;(l ?◈) ;; white diamond containing black small diamond ;;(l ?♠) ;; black spade suit ;;(l ?♥) ;; black heart suit ;;(l ?♦) ;; black diamond suit ;;(l ?♣) ;; black club suit ;;(l ?♤) ;; white spade suit ;;(l ?♡) ;; white heart suit ;;(l ?♢) ;; white diamond suit ;;(l ?♧) ;; white club suit ;;(f ?✿) ;; black florette ;;(f ?❀) ;; white florette ;;(f ?❄) ;; snowflake ;;(f ?✾) ;; six petalled black and white florette ;;(f ?❁) ;; eight petalled outlined black florette ;;(f ?❅) ;; tight trifoliate snowflake ;;(f ?❆) ;; heavy chevron snowflake ;;(w ?☼) ;; white sun with rays ;;(w ?☀) ;; black sun with rays ;;(w ?✫) ;; open centre black star ;;(w ?✭) ;; outlined black star ;;(w ?✩) ;; stress outlined white star ;;(w ?✪) ;; circled white star ;;(w ?✬) ;; black centre white star ;;(w ?✮) ;; heavy outlined black star ;;(w ?✯) ;; pinwheel star ;;(w ?✱) ;; heavy asterisk ;;(w ?✲) ;; open centre asterisk ;;(w ?✳) ;; eight spoked asterisk ;;(w ?✴) ;; eight pointed black star ;;(w ?✵) ;; eight pointed pinwheel star ;;(w ?✶) ;; six pointed black star ;;(w ?✷) ;; eight pointed rectilinear black star ;;(w ?✸) ;; heavy eight pointed rectilinear black star ;;(w ?✹) ;; twelve pointed black star ;;(w ?❂) ;; circled open centre eight pointed star ;;(k ?✻) ;; teardrop-spoked asterisk ;;(k ?✽) ;; heavy teardrop-spoked asterisk ;;(k ?❉) ;; balloon-spoked asterisk ;;(k ?❊) ;; eight teardrop-spoked propeller asterisk ;;(k ?✺) ;; sixteen pointed asterisk ;;(k ?✼) ;; open centre teardrop-spoked asterisk ;;(k ?❃) ;; heavy teardrop-spoked pinwheel asterisk ;;(k ?❋) ;; heavy eight teardrop-spoked propeller asterisk ;;(k ?❇) ;; sparkle ;;(k ?❈) ;; heavy sparkle )) "List of good looking UNICODE glyphs. Those are: - arrows in 4 directions, - lists of symmetric-in-4-directions characters. There are hundred of UNICODEs, but most of them are not fixed-width or height, even in mono-spaced fonts. Here we selected only the fixed-size ones.") (eval-and-compile (defconst uniline--glyphs-bw (eval-when-compile (let ((r (reverse uniline--glyphs-tmp))) ;; nconc is used to create a circular list on purpose (nconc r r))) "List of good looking UNICODE glyphs. Those are: - arrows in 4 directions, - lists of symmetric-in-4-directions characters. There are hundred of UNICODEs, but most of them are not fixed-width or height, even in mono-spaced fonts. Here we selected only the fixed-size ones. This list is ciurcular in backward order.")) (eval-and-compile (defconst uniline--glyphs-fw (eval-when-compile (let ((r (cl-copy-list uniline--glyphs-tmp))) ;; nconc is used to create a circular list on purpose (nconc r r))) "List of good looking UNICODE glyphs. Those are: - arrows in 4 directions, - lists of symmetric-in-4-directions characters. There are hundred of UNICODEs, but most of them are not fixed-width or height, even in mono-spaced fonts. Here we selected only the fixed-size ones. This list is ciurcular in forward order.")) (eval-when-compile ; not needed at runtime (defun uniline--duplicate (list) "Return not-nil if LIST is duplicate-free. Using `eq'." (while (and (cdr list) (not (memq (car list) (cdr list)))) (pop list)) (cdr list)) (defun uniline--make-glyph-hash (list) "Helper function to build `uniline--glyphs-reverse-hash-*'. Used only at package initialization. LIST is `uniline--glyphs-fbw'." (let ((pairs ())) (cl-loop for ll on list do (if (cddar ll) ;; glyph is directional, like ▲ ▶ ▼ ◀ (cl-loop for cc in (cdar ll) for i from 0 do (push (cons cc (cons (if (uniline--duplicate (car ll)) t ; special case ↕↔↕↔ is NOT fully directional i) ; fully directional, i gives the direction ll)) pairs)) ;; glyph is not directional, like ■ ● ◆ (push (cons (cadar ll) (cons nil ll)) pairs)) ;; explicitly break out of circular list if (eq (cdr ll) list) return nil) pairs))) (uniline--defconst-hash-table uniline--glyphs-reverse-hash-fw (eval-when-compile (uniline--make-glyph-hash uniline--glyphs-fw)) 128 'equal ; `equal' instead of `eq' to lower table size without collisions "Same as `uniline--glyphs-fw' reversing keys & values.") (uniline--defconst-hash-table uniline--glyphs-reverse-hash-bw (eval-when-compile (uniline--make-glyph-hash uniline--glyphs-bw)) 128 'equal ; `equal' instead of `eq' to lower table size without collisions "Same as `uniline--glyphs-bw' reversing keys & values.") ;;;╭───────────────────────────────────────────────────────────╮ ;;;│Reference tables of ▙▄▟▀ quadrant-blocks UNICODE characters│ ;;;╰───────────────────────────────────────────────────────────╯ ;; Hereafter `4quadb' means a representation of a quadrant-block ;; UNICODE character as a single number. This number must hold ;; all combinations of the 4 quarter-of-a-blocks. ;; Each of the 4 quarters may be present or absent. ;; Therfore `4quadb' is a number from 0 to 2x2x2x2 = 16. ;; Hereafter, the arbitrary choosen bits allocation is as follow: ;; ;; 2^1: here──→───╮ ;; ╭─┴╮ ;; 2^0: here──→─┤▘▝│ ;; 2^2: here──→─┤▖▗│ ;; ╰─┬╯ ;; 2^3: here──→───╯ ;; ;; For instance, the character ▚ is made of two quarter blocks ;; - one in the up-left corner → constant 2^0 ;; - the other in the down-right corner → constant 2^3 ;; The position in the `uniline--4quadb-to-char' of ▚ ;; will be 2^0 + 2^3 = 9 (eval-and-compile (defconst uniline--4quadb-to-char [ ? ?▘ ?▝ ?▀ ?▖ ?▌ ?▞ ?▛ ?▗ ?▚ ?▐ ?▜ ?▄ ?▙ ?▟ ?█ ] "Convert a quadrant bitmap into a UNICODE character. A quadrant bitmap is a number in [0..16) made of 4 bits. Each bit says whether or not there is a quadrant-block at a position. The order of characters in this table is not important, provided that a particular quarter is always represented by the same bit, and this bit is 1 when this quarter is black. Everything in the code hereafter follow the choosen ordering of this table.")) (eval-and-compile (uniline--defconst-hash-table uniline--char-to-4quadb1 (eval-when-compile (append '((?\t . 0) ;; TAB considered as space character (?  . 0) ;; NO-BREAK SPACE (8200 . 0)) ;; PUNCTUATION SPACE (cl-loop for c across uniline--4quadb-to-char for i from 0 collect (cons c i)))) 64 'eq "Convert a UNICODE character to a quadrant bitmap. Reverse of `uniline--4quadb-to-char'")) (eval-and-compile (defmacro uniline--char-to-4quadb (char) "Return a bit pattern (a 4quadb). It represents a UNICODE character like ?▙ in CHAR. Return nil if CHAR is not a 4quadb character." (if (fixnump char) (gethash char uniline--char-to-4quadb1) `( gethash ,char uniline--char-to-4quadb1)))) (eval-and-compile (defconst uniline--4quadb-pushed (eval-when-compile (let ((table (make-vector 4 nil))) ; ╭─╴fill with zero because many (cl-loop for i from 0 to 3 ; ▽ entries will be zero anyway do (aset table i (make-vector 16 0))) (cl-flet ((fill-dir (subtable &rest keyvals) ;; first seed the TABLE entries for single-bit quadrant blocks ;; and what they become when pushed in DIR direction (cl-loop for (k v) on keyvals by #'cddr do (aset subtable (uniline--char-to-4quadb k) (uniline--char-to-4quadb v))) ;; then fill in entries for all 16 quadrant blocks, by logically ;; composing their bits from single-bits (cl-loop for i from 0 to 15 do ; ╭╴consider each of the 4 bits (aset subtable ; │ and if bit=1, get entry╶─╮ i ; ╰─╮ ╭─╯ (logior ; ▽ ▽ (if (eq (logand i 1) 0) 0 (aref subtable 1)) (if (eq (logand i 2) 0) 0 (aref subtable 2)) (if (eq (logand i 4) 0) 0 (aref subtable 4)) (if (eq (logand i 8) 0) 0 (aref subtable 8))))))) (fill-dir (aref table uniline-direction-up↑) ?▖ ?▘ ?▗ ?▝) (fill-dir (aref table uniline-direction-ri→) ?▘ ?▝ ?▖ ?▗) (fill-dir (aref table uniline-direction-dw↓) ?▘ ?▖ ?▝ ?▗) (fill-dir (aref table uniline-direction-lf←) ?▝ ?▘ ?▗ ?▖)) table)) "For each of the 16 quadrant blocks, this table tells what it becomes when pushed half-a-char-width in all 4 directions. For instance [▞] pushed right→ becomes [▗], pushed up↑ becomes [▘] Access it with this snippet: (uniline--4quadb-pushed dir 4quadb)") (defmacro uniline--4quadb-pushed (dir 4quadb) "Accessor to the `uniline--4quadb-pushed' array. Folds to a single number if DIR & 4QUADB are themselves numbers." (condition-case nil (setq dir (eval dir)) ;; fold if dir is a numerical sexpr (error nil)) ;; otherwise leave dir alone (if (and (fixnump dir) (fixnump 4quadb)) (aref (aref uniline--4quadb-pushed dir) 4quadb) ` (aref (aref uniline--4quadb-pushed ,dir) ,4quadb)))) ;;;╭──────────────────────╮ ;;;│Inserting a character │ ;;;╰──────────────────────╯ ;; Formerly, `self-insert-command' was used directly ;; along with the `post-self-insert-hook' to avoid the cursor ;; moving right. ;; ;; But `self-insert-command' has too many side-effects. ;; Besides, other utilities like `electric-pair-mode' assume that ;; `last-command-event' is equal to the parameter given to ;; `self-insert-command'. ;; ;; A side effect of using `post-self-insert-hook' was that inserting ;; an ordinary character did not move the cursor. ;; ;; For those reasons, direct usage of `self-insert-command' was ;; replaced by a simple call to `insert' with `delete-char' to ;; simulate the `overwrite-mode'. (defun uniline--insert-char (char) "Insert CHAR in overwrite mode avoiding cursor moving." ;; automatic untabification with (move-to-column … t) ;; is not enough when cursor goes right onto the beginning of a TAB (if (eq (char-after) ?\t) (untabify (point) (1+ (point)))) ;; `insert' before `delete-char' to preserve `point-marker' (insert char) (or (eolp) (delete-char 1)) (backward-char)) ;;;╭────────────────────────────────────────────────────────╮ ;;;│Low level management of ┏╾╯half-lines UNICODE characters│ ;;;╰────────────────────────────────────────────────────────╯ (eval-when-compile ; not needed at runtime (defmacro uniline--insert-4halfs (4halfs) "Insert at (point) a UNICODE like ┬. The UNICODE character is described by the 4HALFS bits pattern. The (point) does not move." `(uniline--insert-char (uniline--4halfs-to-char-aref ,4halfs)))) (eval-when-compile ; not needed at runtime (defmacro uniline--insert-4quadb (4quadb) "Insert at (point) a UNICODE like ▙. The UNICODE character is described by the 4QUADB bits pattern. The (point) does not move." `(uniline--insert-char (aref uniline--4quadb-to-char ,4quadb)))) ;;;╭───────────────────────────────────────────────────────────────╮ ;;;│Low level management of ▙▄▟▀ quadrant-blocks UNICODE characters│ ;;;╰───────────────────────────────────────────────────────────────╯ (defvar-local uniline--which-quadrant (uniline--char-to-4quadb ?▘) "Where is the quadrant cursor. To draw lines with quadrant-blocks like this ▙▄▟▀, it is required to keep track where is the quadrant-cursor. It can be at 4 sub-locations: north-east, south-west, etc.") (defun uniline--quadrant-undo (i) "Re-position `uniline--which-quadrant' on undo. This function sets the cursor to I. It is called from the deep intricacies of the standard undo machinery." (setq uniline--which-quadrant i)) (defun uniline--store-undo-quadrant-cursor () "Helper function to store the quandrant-cursor in the undo history. The standard Emacs undo mechanism already restores the point location. The quadrant cursor is 1/4 smaller than the regular cursor, and needs a specific action to restore it." (setq buffer-undo-list `((apply uniline--quadrant-undo ,uniline--which-quadrant) ,(point) ,@buffer-undo-list))) (defun uniline--write-one-4quadb (force) "Helper function to write the quadrant cursor at point. It adds the quadrant-block described by `uniline--which-quadrant' at `point', preserving already present quadrant-blocks. When FORCE is not nil, overwrite a possible non quadrant-block character at point." (let ((bits (uniline--char-to-4quadb (uniline--char-after)))) (cond (bits (uniline--insert-4quadb (logior bits uniline--which-quadrant))) (force (uniline--insert-4quadb uniline--which-quadrant))))) ;;;╭────────────────────────────────╮ ;;;│Test blanks in the neighbourhood│ ;;;╰────────────────────────────────╯ (defun uniline--blank-after (p) "Return non-nil if P points to a 4halfs or 4quadb character. This includes - a blank - a 4half drawing character like ┴ ┻ ╩ - a 4quad drawing character like ▙ - a new line - P is nil The last two cases will be changed to an actual blank character by virtue of the infinite buffer." (or (not p) (let ((c (char-after p))) (or (memq c '(?\n nil)) ;; the case c==nil never happens in Uniline calls (gethash c uniline--char-to-4halfs) (uniline--char-to-4quadb c))))) (eval-when-compile ; not needed at runtime (defmacro uniline--neighbour-point (dir) "Return the (point) one char away from current (point) in DIR direction. Return nil if no such point exists because it would fall outside the buffer. The buffer is not modified. This macro seems large, but actually it is a bag of 4 small algorithms. Just one out of those 4 algorithms is retrieved on a call." (setq dir (eval dir)) (uniline--switch-with-table dir (uniline-direction-ri→ '(unless (eolp) (1+ (point)))) (uniline-direction-lf← '(unless (bolp) (1- (point)))) (uniline-direction-up↑ '(let ((here (point)) (c (current-column))) (prog1 (and (eq (forward-line -1) 0) (eq (move-to-column c) c) (point)) (goto-char here)))) (uniline-direction-dw↓ '(let ((here (point)) (c (current-column))) (prog1 (and (eq (forward-line 1) 0) (eq (move-to-column c) c) (point)) (goto-char here))))))) (defun uniline--blank-neighbour1 (dir) "Return non-nil if the neighbour of current point in DIR is blank. The neighbour is one character away in the DIR direction. Blank include: - actual blank - 4half drawing character like ┴ ┻ ╩ - 4quad drawing character like ▙ - new line - neighbour is outside buffer." (uniline--blank-after (uniline--switch-with-cond dir (uniline-direction-up↑ (uniline--neighbour-point uniline-direction-up↑)) (uniline-direction-ri→ (uniline--neighbour-point uniline-direction-ri→)) (uniline-direction-dw↓ (uniline--neighbour-point uniline-direction-dw↓)) (uniline-direction-lf← (uniline--neighbour-point uniline-direction-lf←))))) (eval-when-compile ;; not used at runtime ;; a (defmacro) vs. a (defsubst) saves 2 bytecodes (defmacro uniline--blank-neighbour4 (dir) "Return non-nil if the quarter point cursor can move in DIR while staying on the same (point)." ;; Try evaluating ;; (macroexpand-all '(uniline--blank-neighbour4 dir)) ;; or ;; (disassemble '(uniline--blank-neighbour4 dir)) ;; You will see a short 9 byte-codes function which references ;; a constant vector. This is the fastest it can be. (condition-case nil (setq dir (eval dir)) ;; fold if dir is a numerical sexpr (error nil)) ;; otherwise leave dir alone `(eq (logand uniline--which-quadrant (uniline--switch-with-table ,dir ;; This lambda computes the values in the resulting lookup table ;; for each entry. It is run at compile-time, never at run-time. (lambda (dir) (uniline--4quadb-pushed dir (uniline--char-to-4quadb ?█) ; this is constant 15 = 0b1111 )) (uniline-direction-up↑) (uniline-direction-ri→) (uniline-direction-dw↓) (uniline-direction-lf←))) 0))) (defun uniline--blank-neighbour (dir) "Return non-nil if the neighbour in DIR direction is blank. Blank include: - actual blank - 4half drawing character like ┴ ┻ ╩ - 4quad drawing character like ▙ - new line - neighbour is outside buffer - when the cursor is :block and there is still room to move in DIRection while staying on the same (point)." (or (and (eq uniline-brush :block) (uniline--blank-neighbour4 dir)) (and (uniline--blank-neighbour1 dir)))) ;;;╭──────────────────────────────────────────────────╮ ;;;│High level drawing in half-lines & quadrant-blocks│ ;;;╰──────────────────────────────────────────────────╯ (defvar-local uniline--arrow-direction (uniline-direction-up↑) "Where the next arrow should point to. This might be 0, 1, 2, 3, as defined by the four constants `uniline-direction-up↑', `uniline-direction-lf←', ...") (defun uniline--write-one-4halfs-impl (brush force 4halfmask 4quadmask) "Draw half a line. If there are too few characters on the row where the line will be drawn, fill it with blank characters. Cursor does not move. BRUSH is `uniline-brush' turned in the direction of drawing. When FORCE is not nil, overwrite a possible non-4halfs character. 4HALFMASK is a bit-mask to erase 4halfs lines found at (point). 4QUADMASK is a bit-mask to erase 4quads blocks found at (point)." (let ((bits (gethash (uniline--char-after) uniline--char-to-4halfs))) (cond ;; 1st case: (char-after) is a line-character like ├, ;; or any character if FORCE ;; then change a half-line of this character ;; for example changing it from ├ to ┽ (bits (uniline--insert-4halfs (logior (logand bits 4halfmask) brush))) ;; 2nd case: (char-after) is a block character like ▜, ;; and the brush is the eraser ;; then clear only half of this character ((eq uniline-brush 0) (if (setq bits (uniline--char-to-4quadb (uniline--char-after))) (uniline--insert-4quadb (logand 4quadmask bits)))) ;; 3th case: force (force (uniline--insert-4halfs brush))))) (eval-when-compile ; not needed at runtime (defmacro uniline--write-one-4halfs (dir force) "Draw half a line in the direction DIR. If there are too few characters on the row where the line will be drawn, fill it with blank characters. Cursor does not move. When FORCE is not nil, overwrite a possible non-4halfs character." `(uniline--write-one-4halfs-impl (uniline--shift-4half uniline-brush ,dir) ,force ,(lognot (uniline--shift-4half 3 dir)) ,(uniline--4quadb-pushed (uniline--reverse-direction dir) (uniline--char-to-4quadb ?█))))) ; this is constant 15 = 0b1111 (eval-when-compile ; not needed at runtime (defmacro uniline--write-impl (dir repeat force) "Move cursor in direction DIR drawing or erasing lines. Or extending region. This is an implementation function for the 4 actual functions. - If region is already active, just extend it without drawing. - If the brush is set to `:block', draw one quadrant-block. Then move cursor half a character. This could move (point) one char, or leave it where it is. - Otherwise, draw or erase pairs of half-lines. `uniline-brush' gives the style of line (it may be an eraser). REPEAT is the length of the line to draw or erase, or the number of quadrant-blocks to draw, or the length to extend region. REPEAT defaults to 1. When FORCE is not nil, overwrite characters which are not lines." (let* ((dir (eval dir)) ;; to convert 'uniline-direction-dw↓ into 2 (odir (uniline--reverse-direction dir))) `(progn (unless ,repeat (setq ,repeat 1)) (setq uniline--arrow-direction ,dir) (handle-shift-selection) (cond ((region-active-p) ;; region is marked, continue extending it (uniline--move-in-direction ,dir ,repeat) (setq deactivate-mark nil)) ((eq uniline-brush :block) ;; draw quadrant-blocks ▝▙▄▌ (uniline--store-undo-quadrant-cursor) (cl-loop repeat ,repeat do (if (uniline--blank-neighbour4 ,odir) (uniline--move-in-direction ,dir)) (setq uniline--which-quadrant ;; this huge expression is evaluated only at compile time ;; and folded to a mere, fast reference to a constant vector ;; for instance: ;; (aref [nil 4 8 nil 1 nil nil nil 2] uniline--which-quadrant) ,(cond ((memq dir (list uniline-direction-up↑ uniline-direction-dw↓)) '(uniline--switch-with-table uniline--which-quadrant ((uniline--char-to-4quadb ?▘) (uniline--char-to-4quadb ?▖)) ((uniline--char-to-4quadb ?▖) (uniline--char-to-4quadb ?▘)) ((uniline--char-to-4quadb ?▗) (uniline--char-to-4quadb ?▝)) ((uniline--char-to-4quadb ?▝) (uniline--char-to-4quadb ?▗)))) ((memq dir (list uniline-direction-ri→ uniline-direction-lf←)) '(uniline--switch-with-table uniline--which-quadrant ((uniline--char-to-4quadb ?▘) (uniline--char-to-4quadb ?▝)) ((uniline--char-to-4quadb ?▖) (uniline--char-to-4quadb ?▗)) ((uniline--char-to-4quadb ?▗) (uniline--char-to-4quadb ?▖)) ((uniline--char-to-4quadb ?▝) (uniline--char-to-4quadb ?▘)))))) (uniline--write-one-4quadb ,force))) (uniline-brush ;; draw lines ╰──╮ (cl-loop repeat ,repeat do (uniline--write-one-4halfs ,dir ,force) (uniline--move-in-direction ,dir) (uniline--write-one-4halfs ,odir ,force))) (t ;; brush is nil, just move point (uniline--move-in-direction ,dir ,repeat))))))) (defun uniline-write-up↑ (repeat &optional force) "Move cursor up drawing or erasing glyphs, or extending region. - If region is already active, just extend it without drawing. - If the brush is set to blocks, draw one quadrant-block. Then move cursor half a character. - Otherwise, draw or erase pairs of half-lines. `uniline-brush' gives the style of line (it may be an eraser). REPEAT is the length of the line to draw or erase, or the number of quadrant-blocks to draw, or the length to extend region. REPEAT defaults to 1. When FORCE is not nil, overwrite characters which are not lines." (interactive "P") (uniline--write-impl uniline-direction-up↑ repeat force)) (defun uniline-write-ri→ (repeat &optional force) "Move cursor right drawing or erasing glyphs, or extending region. - If region is already active, just extend it without drawing. - If the brush is set to blocks, draw one quadrant-block. Then move cursor half a character. - Otherwise, draw or erase pairs of half-lines. `uniline-brush' gives the style of line (it may be an eraser). REPEAT is the length of the line to draw or erase, or the number of quadrant-blocks to draw, or the length to extend region. REPEAT defaults to 1. When FORCE is not nil, overwrite characters which are not lines." (interactive "P") (uniline--write-impl uniline-direction-ri→ repeat force)) (defun uniline-write-dw↓ (repeat &optional force) "Move cursor down drawing or erasing glyphs, or extending region. - If region is already active, just extend it without drawing. - If the brush is set to blocks, draw one quadrant-block. Then move cursor half a character. - Otherwise, draw or erase pairs of half-lines. `uniline-brush' gives the style of line (it may be an eraser). REPEAT is the length of the line to draw or erase, or the number of quadrant-blocks to draw, or the length to extend region. REPEAT defaults to 1. When FORCE is not nil, overwrite characters which are not lines." (interactive "P") (uniline--write-impl uniline-direction-dw↓ repeat force)) (defun uniline-write-lf← (repeat &optional force) "Move cursor left drawing or erasing glyphs, or extending region. - If region is already active, just extend it without drawing. - If the brush is set to blocks, draw one quadrant-block. Then move cursor half a character. - Otherwise, draw or erase pairs of half-lines. `uniline-brush' gives the style of line (it may be an eraser). REPEAT is the length of the line to draw or erase, or the number of quadrant-blocks to draw, or the length to extend region. REPEAT defaults to 1. When FORCE is not nil, overwrite characters which are not lines." (interactive "P") (uniline--write-impl uniline-direction-lf← repeat force)) (defun uniline-overwrite-up↑ (repeat) "Like `uniline-write-up↑' but overwriting. REPEAT is the length of the line to draw, defaulting to 1." (interactive "P") (uniline-write-up↑ repeat t)) (defun uniline-overwrite-ri→ (repeat) "Like `uniline-write-ri→' but overwriting. REPEAT is the length of the line to draw, defaulting to 1." (interactive "P") (uniline-write-ri→ repeat t)) (defun uniline-overwrite-dw↓ (repeat) "Like `uniline-write-dw↓' but overwriting. REPEAT is the length of the line to draw, defaulting to 1." (interactive "P") (uniline-write-dw↓ repeat t)) (defun uniline-overwrite-lf← (repeat) "Like `uniline-write-lf←' but overwriting. REPEAT is the length of the line to draw, defaulting to 1." (interactive "P") (uniline-write-lf← repeat t)) (defun uniline--write (dir &optional force) "Move cursor one char in DIR direction. Doing so, draw or erase glyphs, or extend region. - If region is already active, just extend it without drawing. - If the brush is set to blocks, draw one quadrant-block. Then move cursor half a character. - Otherwise, draw or erase pairs of half-lines. `uniline-brush' gives the style of line (it may be an eraser). When FORCE is not nil, overwrite whatever is in the buffer." (funcall (uniline--switch-with-table dir (uniline-direction-up↑ 'uniline-write-up↑) (uniline-direction-ri→ 'uniline-write-ri→) (uniline-direction-dw↓ 'uniline-write-dw↓) (uniline-direction-lf← 'uniline-write-lf←)) 1 force)) ;;;╭────╮ ;;;│Fill│ ;;;╰────╯ (defun uniline--choose-fill-char () "Interactively choose a character to fill a shape. This character will replace the character the cursor is on throughout the shape. Some values of the input character are replaced by computed values: - `C-y' chooses the first character in the kill ring - `SPC' selects a darker shade of grey than the character the point is on. - `DEL' selects a lighter shade of grey than the character the point is on. there are 5 shades of grey in the UNICODΕ standard: \" ░▒▓█\". - `RET' means abort filling." (let ((char (read-char "Fill with (any char, C-y, SPC, DEL, RET)? "))) (cond ((eq char 13) nil) ; RET: abort filling ((eq char ?) ; yank: 1st char of the kill ring (aref (car kill-ring) 0)) ((eq char ? ) ; SPC: next shade of grey (or (cadr (memq (uniline--char-after) '(? ?░ ?▒ ?▓ ?█ ))) ? )) ((eq char ?) ; DEL: prev shade of grey (or (cadr (memq (uniline--char-after) '(?█ ?▓ ?▒ ?░ ? ))) ?█)) (t char)))) (defun uniline-fill (&optional char) "Fill a hollow shape with character CHAR. CHAR is optional, if not given, user is queried. The hole is the set of contiguous identical characters. The character at point is used as reference for other identical characters." (interactive) (unless char (setq char (uniline--choose-fill-char))) ;; why is stack initialized with twice the current point? ;; the first is for starting the filling process ;; the second is for returning to the starting point after filling (let ((currentchar (uniline--char-after)) (stack (list (point) (point)))) (unless (eq char currentchar) (while stack (goto-char (pop stack)) (when (eq (char-after) currentchar) ; not (uniline--char-after) ! (uniline--insert-char char) (let ((p (uniline--neighbour-point uniline-direction-up↑))) (if p (push p stack))) (let ((p (uniline--neighbour-point uniline-direction-ri→))) (if p (push p stack))) (let ((p (uniline--neighbour-point uniline-direction-dw↓))) (if p (push p stack))) (let ((p (uniline--neighbour-point uniline-direction-lf←))) (if p (push p stack)))))))) ;;;╭────────────────────────╮ ;;;│Undo rectangle selection│ ;;;╰────────────────────────╯ ;; Rectangle functions operate on a visually highlighted rectangular selection ;; Keeping the selection highlighted ;; - after changing the rectangle (move, fill, contour, kill, yank…) ;; - when undoing a rectangle change ;; gives a sense of confidence. ;; To achieve that, we add the point and mark (the selection) into the ;; regular Emacs undo machinery, so as to restore it. (defun uniline--undo-restore-selection (i p) "Function called by the Emacs undo system to restore selection. I is the mark, P is the point." (set-mark i) (goto-char p) (activate-mark) (rectangle-mark-mode 1) (setq deactivate-mark nil)) (defun uniline--record-undo-rectangle-selection () "Add the selection (point and mark) into the Emacs undo stack." (setq buffer-undo-list `((apply uniline--undo-restore-selection ,(mark) ,(point)) ,@buffer-undo-list))) ;; Here is the list of all interactive functions which leave ;; the region visually active on the rectangle they just operated on. ;; ;; To correclty undo such rectangle operations, the selection should ;; first be deactivated. This is because some changes in the buffer ;; are on the fringe of the region, and are not prpoperly undone. ;; ;; However deactivating the selection proved to be quite tricky to achieve. ;; ;; Therefore we leave the region highlighted, and we instructs `undo' ;; to disregard the region. The `undo' mechanism provides for that, ;; by looking at the `undo-inhibit-region' property of the `last-command' ;; ;; The following list was hand-compiled by looking for interactive ;; functions which call `uniline--operate-on-rectangle', either directly ;; or through a utility function. (dolist (fun '(uniline-move-rect-up↑ uniline-move-rect-dw↓ uniline-move-rect-ri→ uniline-move-rect-lf← uniline-fill-rectangle uniline-draw-inner-rectangle uniline-draw-outer-rectangle uniline-yank-rectangle uniline-change-style-standard uniline-aa2u-rectangle uniline-change-style-dot-3-2 uniline-change-style-dot-4-4 uniline-change-style-hard-corners uniline-change-style-thin uniline-change-style-thick uniline-change-style-double)) (put fun 'undo-inhibit-region t)) ;;;╭───────────────────────────────────╮ ;;;│High level management of rectangles│ ;;;╰───────────────────────────────────╯ (eval-when-compile ; not needed at runtime (defmacro uniline--operate-on-rectangle (&rest body) "Execute BODY with some variables describing selection. This is a helper macro to write actual functions. Assumes that a selection is active. The variables BEG BEGX BEGY (point, column, line of beginning) END ENDX ENDY (point, column, line of ending) are made available to BODY for easy handling of the selection. The selection may be reversed in any way, the variables are sets as if the selection was made from the upper-most, left-most to the lower-most, right-most points. It works even when in `rectangle-mark-mode'. Note that ENDX & ENDY point outside the selection in such a way that ENDX-BEGX is the width of the rectangle, ENDY-BEGY is the height After execution of the body, selection is activated from BEGX,BEGY inclusive to ENDX,ENDY exclusive in `rectangle-mark-mode'." (declare (debug (body))) `(when (region-active-p) (rectangle-mark-mode -1) ; otherwise sometimes end is wrong (let* ((deactivate-mark) ; kludge needed to avoid deactivating the mark (beg (region-beginning)) (end (region-end)) (begx (progn (goto-char beg) (current-column))) (begy (1- (line-number-at-pos))) (endx (progn (goto-char end) (current-column))) (endy (line-number-at-pos))) (when (< endx begx) (setq endx (prog1 begx (setq begx endx))) (setq beg (+ beg (- begx endx))) (setq end (+ end (- endx begx)))) ,@body (uniline-move-to-lin-col (1- endy) endx) (set-mark (point)) (uniline-move-to-lin-col begy begx) (rectangle-mark-mode 1))))) (eval-when-compile ; not needed at runtime (defmacro uniline--compute-leakage-4halfs (dir) "Compute lines leakage from two directions for 4halfs characters. When a rectangle moves, it leaves blank chars. Those chars are filled with leakage from their two neighbours, in DIR direction, and its opposite. For instance consider a situation like this: ╶┬╴ <<< empty space ┗╸ A space is leaved empty after translation. Then the leakage of the two glyphs fills in E: ╶┬╴ ╽<<< filled with leakage ┗╸" (setq dir (eval dir)) (let ((odir (uniline--reverse-direction dir))) `(let* ((cc (uniline--char-after)) (4c (gethash cc uniline--char-to-4halfs 0)) (leak (uniline--extract-reverse-4half 4c ,dir)) (p (uniline--neighbour-point ,odir))) (if p (setq leak (logior leak (uniline--extract-reverse-4half (gethash (uniline--char-after p) uniline--char-to-4halfs 0) ,odir)))) ;; here the case of dotted line is handled: ;; `leak' and `4c' may be the same code, for example 02-00-02-00 ;; denoting 3 possible actual characters: ┇ ┋ ┃ ;; in this case, we recover and return the actual character in the buffer ;; not the syntetic `leak' (if (and (eq leak 4c) (not (eq 4c 0))) cc (uniline--4halfs-to-char-aref leak)))))) (eval-when-compile ; not needed at runtime (defmacro uniline--compute-leakage-quadb (dir) "Compute lines leakage from two directions for 4quadb characters. When a rectangle moves, it leaves blank chars. Those chars are filled with leakage from their two neighbours, in DIR direction, and its opposite. For instance consider a situation like this: ▌ <<< empty space ▙ A space is leaved empty after translation. Then the leakage of the two glyphs fills in E: ▌ ▌<<< filled with leakage ▙" (setq dir (eval dir)) (let ((odir (uniline--reverse-direction dir))) `(let ((leak ;; leak from (point) (uniline--4quadb-pushed ,dir (or (uniline--char-to-4quadb (uniline--char-after)) 0))) (lean ;; leak from neighbour of (point) (let ((p (uniline--neighbour-point ,odir))) (and p (uniline--char-to-4quadb (uniline--char-after p)))))) (aref uniline--4quadb-to-char (if (not lean) leak (logior leak (uniline--4quadb-pushed ,odir lean) ))))))) (eval-when-compile ; not needed at runtime (defmacro uniline--compute-leakage (dir) "Compute lines leakage from two directions: DIR and its opposite." `(let ((c (uniline--compute-leakage-4halfs ,dir))) (if (eq c ? ) (uniline--compute-leakage-quadb ,dir) c)))) (eval-when-compile ; not needed at runtime (defmacro uniline--compute-leakage-on-region (dir y begx endx) "Compute the leakage of all characters in a region. Region is BEGX..ENDX x Y..Y, it is one char high. The region just below is considered when DIR is ↑, the region just above is considered when DIR is ↓. Return a string which will be inserted back in the buffer." `(cl-loop with s = (make-vector (- ,endx ,begx) ? ) for x from ,begx below ,endx for i from 0 do (uniline-move-to-lin-col ,y x) (aset s i (uniline--compute-leakage ,dir)) finally return (concat s)))) (defun uniline--exchange-region-string (y begx endx hand) "Replace the region BEGX..ENDX x Y..Y with HAND. Region is 1 character high. HAND is a string of the same length as the region. Return the replaced region as a string." (uniline-move-to-line y) (let* ((end (progn (uniline-move-to-column endx) (point))) (beg (progn (uniline-move-to-column begx) (point))) (line (buffer-substring beg end))) (delete-region beg end) (goto-char beg) (insert hand) line)) (defun uniline--untabify-rectangle (begy endy) "Untabify all lines over which the rectangle operates. BEGY and ENDY are the line-numbers of the beggining en ending of the rectangle. The costly `untabify' function is called only if there are TAB characters in the region." (let ((end (progn (uniline-move-to-line (1- endy)) (end-of-line) (point))) (beg (progn (uniline-move-to-line begy) (beginning-of-line) (point)))) ;; (point) is at beg (when (search-forward "\t" end t) (untabify beg end)))) (defun uniline-move-rect-up↑ (repeat) "Move the rectangle marked by selection one line upper. Truncate the selection if it touches the upper side of the buffer. REPEAT tells how many characters the rectangle should move, defaulting to 1. ↑ ↑ ↑ ↑ ░░░░░░░ ░░░░░░░ ░░░░░░░ " (interactive "P") (uniline--record-undo-rectangle-selection) (cl-loop repeat (or repeat 1) do (uniline--operate-on-rectangle (uniline--untabify-rectangle begy endy) (if (and (eq begy 0) uniline-infinite-up↑) (save-excursion (uniline-move-to-line -1)) (setq begy (max (1- begy) 0) endy (1- endy))) (cl-loop with hand = (uniline--compute-leakage-on-region uniline-direction-up↑ endy begx endx) for y from endy downto begy do (setq hand (uniline--exchange-region-string y begx endx hand)))))) (defun uniline-move-rect-dw↓ (repeat) "Move the rectangle marked by selection one line down. The buffer is infinite at the bottom. REPEAT tells how many characters the rectangle should move, defaulting to 1. ░░░░░░░ ░░░░░░░ ░░░░░░░ ↓ ↓ ↓ ↓ " (interactive "P") (uniline--record-undo-rectangle-selection) (cl-loop repeat (or repeat 1) do (uniline--operate-on-rectangle (uniline--untabify-rectangle begy endy) (cl-loop with hand = (uniline--compute-leakage-on-region uniline-direction-dw↓ begy begx endx) for y from begy to endy do (setq hand (uniline--exchange-region-string y begx endx hand))) (setq begy (1+ begy) endy (1+ endy))))) (defun uniline-move-rect-ri→ (repeat) "Move the rectangle marked by selection one char to the left. The buffer is infinite at its right side. REPEAT tells how many characters the rectangle should move, defaulting to 1. ░░░░░░░→ ░░░░░░░→ ░░░░░░░→ " (interactive "P") (uniline--record-undo-rectangle-selection) (cl-loop repeat (or repeat 1) do (uniline--operate-on-rectangle (uniline--untabify-rectangle begy endy) (cl-loop for y from begy below endy do (uniline-move-to-lin-col y begx) (insert (uniline--compute-leakage uniline-direction-ri→)) (uniline-move-to-column (1+ endx)) (or (eolp) (delete-char 1))) (setq begx (1+ begx) endx (1+ endx))))) (defun uniline-move-rect-lf← (repeat) "Move the rectangle marked by selection one char to the left. Truncate the selection if it touches the left side of the buffer. REPEAT tells how many characters the rectangle should move, defaulting to 1. ←░░░░░░░ ←░░░░░░░ ←░░░░░░░ " (interactive "P") (uniline--record-undo-rectangle-selection) (cl-loop repeat (or repeat 1) do (uniline--operate-on-rectangle (uniline--untabify-rectangle begy endy) (setq begx (max (1- begx) 0) endx (max (1- endx) 0)) (cl-loop for y from (1- endy) downto begy do (uniline-move-to-lin-col y endx) (insert (prog1 (uniline--compute-leakage uniline-direction-lf←) (uniline-move-to-delta-column 1))) (uniline-move-to-column begx) (delete-char 1))))) (defun uniline-fill-rectangle () "Fill the rectangle marked by selection. Interactively choose the filling character. See `uniline--choose-fill-char'. ░░░░░░░ ░░░░░░░ ░░░░░░░ " (interactive) (uniline--record-undo-rectangle-selection) (uniline--operate-on-rectangle (set-mark end) ;; ensure we are inside the region before (goto-char beg) ;; asking for the filling character (rectangle-mark-mode 1) (let ((char (uniline--choose-fill-char))) (cl-loop for y from begy below endy do (uniline-move-to-lin-col y begx) (cl-loop for x from begx below endx do (uniline--insert-char char) (uniline-move-to-delta-column 1)))))) (defun uniline-draw-inner-rectangle (&optional force) "Draws a rectangle inside a rectangular selection. Use the current brush style, which may be thin, thick, double line, block, or eraser. When FORCE is not nil, overwrite whatever is there." (interactive) (uniline--record-undo-rectangle-selection) (uniline--operate-on-rectangle (let ((width (- endx begx 1)) (height (- endy begy 1))) (goto-char beg) (if (eq uniline-brush :block) (setq width (+ width width 1) height (+ height height 1) uniline--which-quadrant (uniline--char-to-4quadb ?▘))) (let ((mark-active nil)) ;; otherwise brush would be inactive (uniline-write-ri→ width force) (uniline-write-dw↓ height force) (uniline-write-lf← width force) (uniline-write-up↑ height force))))) (defun uniline-overwrite-inner-rectangle () "Draws a rectangle inside a rectangular selection. Use the current brush style, which may be thin, thick, double line, block, or eraser. Overwrite whatever is there." (interactive) (uniline-draw-inner-rectangle t)) (defun uniline-draw-outer-rectangle (&optional force) "Draws a rectangle outside a rectangular selection. Use the current brush style, which may be thin, thick, double line, block, or eraser. When FORCE is not nil, overwrite whatever is there." (interactive) (uniline--record-undo-rectangle-selection) (uniline--operate-on-rectangle (let ((width (- endx begx -1)) (height (- endy begy -1)) (mark-active nil)) ; otherwise brush is inactive (goto-char beg) (if (<= begx 0) (setq width (1- width)) ; at leftmost side of buffer (uniline-move-to-delta-column -1)) (let ((uniline-infinite-up↑ t)) ; forcefully add a line (uniline-move-to-delta-line -1)) ; above the rectangular region (if (eq uniline-brush :block) (setq width (+ width width -1) height (+ height height -1) uniline--which-quadrant (uniline--char-to-4quadb ?▗))) (uniline-move-to-column (1- begx)) (uniline-write-ri→ width force) (uniline-write-dw↓ height force) (uniline-write-lf← width force) (when (> begx 0) (uniline-write-up↑ height force) (setq begx (1- begx))) (setq endx (1+ endx)) (if (eq begy 0) ; an additional line was needed (if uniline-infinite-up↑ (setq endy (+ 2 endy)) (setq endy (1+ endy)) (goto-char (point-min)) (delete-line)) (setq begy (1- begy)) ; no additionl line (setq endy (1+ endy)))))) (defun uniline-overwrite-outer-rectangle () "Draws a rectangle outside a rectangular selection. Use the current brush style, which may be thin, thick, double line, block, or eraser. Overwrite whatever is there." (interactive) (uniline-draw-outer-rectangle t)) (defun uniline-copy-rectangle () "Copy the selected rectangle in the kill storage." (interactive) (rectangle-mark-mode -1) (copy-rectangle-as-kill (region-beginning) (region-end)) (deactivate-mark)) (defun uniline-kill-rectangle () "Kill the selected rectangle. It differs from the standard Emacs `kill-rectangle' in that it leaves a rectangle of space characters." (interactive) (uniline--record-undo-rectangle-selection) (copy-rectangle-as-kill (region-beginning) (region-end)) (clear-rectangle (region-beginning) (region-end)) (deactivate-mark)) (defun uniline-yank-rectangle () "Insert a previously cut rectangle. It differs from the standard Emacs `yank-rectangle' in that it overwrites the rectangle." (interactive) (uniline--record-undo-rectangle-selection) (uniline--operate-on-rectangle (goto-char beg) (cl-loop for line in killed-rectangle do (cl-loop for char across line do (unless (eq char ? ) (uniline--insert-char char)) (uniline-move-to-delta-column 1)) (uniline-move-to-column begx) (uniline-move-to-delta-line 1)) (setq endx (+ begx (length (car killed-rectangle)))) (setq endy (+ begy (length killed-rectangle))))) ;;;╭──────────────╮ ;;;│Text direction│ ;;;╰──────────────╯ (defvar-local uniline-text-direction (uniline-direction-ri→) "Direction of text insertion. It can be any of the 4 values of `uniline-direction-up↑' `-ri→' `-dw↓' `-lf←' which means that typing a key on the keyboard moves the cursor in this direction. It can also be nil, which means that uniline makes no tweaking of the natural cursor movement upon insertion.") (defvar-local uniline--mode-line-brush nil "The current brush suitable for display in the `:lighter'.") (defvar-local uniline--mode-line-dir nil "The current text direction suitable for ddisplay in the `:lighter'.") (defun uniline--update-mode-line () "Computes the string which appears in the mode-line." (setq uniline--mode-line-dir (uniline--switch-with-table uniline-text-direction (nil " ") (uniline-direction-up↑ "↑") (uniline-direction-ri→ "→") (uniline-direction-dw↓ "↓") (uniline-direction-lf← "←")) uniline--mode-line-brush (uniline--switch-with-cond uniline-brush (nil " ") (0 "/") (1 (uniline--switch-with-table uniline-brush-dots (0 "┼") (1 "┆") (2 "┊"))) (2 (uniline--switch-with-table uniline-brush-dots (0 "╋") (1 "┇") (2 "┋"))) (3 "╬") (:block "▞"))) (force-mode-line-update)) (defun uniline--post-self-insert () "Change the cursor movement after `self-insert-command'. Usually the cursor moves to the right. Sometimes to the left for some locales, but this is not currently handled. This hook fixes the cursor movement according to `uniline-text-direction'" (let* ((pn (cond ((numberp current-prefix-arg) current-prefix-arg) ((and (consp current-prefix-arg) (numberp (car current-prefix-arg))) (car current-prefix-arg)) ((null current-prefix-arg) 1) (t (error "current-prefix-arg = %S" current-prefix-arg)))) (mn (- pn))) (uniline--switch-with-cond uniline-text-direction (uniline-direction-ri→ ()) ;; nothing to fix (uniline-direction-lf← (forward-char mn) (uniline-move-to-delta-column mn)) (uniline-direction-dw↓ (forward-char mn) (uniline-move-to-delta-line pn)) (uniline-direction-up↑ (forward-char mn) (uniline-move-to-delta-line mn)) (nil)))) (defun uniline-text-direction-up↑ () "Set text insertion direction up↑." (interactive) (setq uniline-text-direction (uniline-direction-up↑)) (uniline--update-mode-line)) (defun uniline-text-direction-ri→ () "Set text insertion direction right→." (interactive) (setq uniline-text-direction (uniline-direction-ri→)) (uniline--update-mode-line)) (defun uniline-text-direction-dw↓ () "Set text insertion direction down↓." (interactive) (setq uniline-text-direction (uniline-direction-dw↓)) (uniline--update-mode-line)) (defun uniline-text-direction-lf← () "Set text insertion direction left←." (interactive) (setq uniline-text-direction (uniline-direction-lf←)) (uniline--update-mode-line)) ;;;╭───────────────────────────╮ ;;;│Macro calls in 4 directions│ ;;;╰───────────────────────────╯ (defvar-local uniline--directional-macros (make-vector 8 nil) "A cache handling 4 versions of the current macro in 4 directions. It is needed to avoid repeatidly re-creating a new directional macro from the recorded macro. There are 4 entries indexed by `uniline-direction-up↑' `-ri→' `-dw↓' `-lf←' Each entry has 2 slots: - the recorded macro (a vector of key strokes) - the twisted macro (the same vector with and sisters twisted).") (eval-when-compile ; not used at runtime (defconst uniline--directional-keystrokes-table `(;; ╭───keystroke as used in keyboard macros ;; │ ╭─direction of the keystroke ;; │ │ ╭─shift-control modifiers ;; │ │ ╰─────────────────╮ ;; ▽ ▽ ▽ ( up . ,(+ uniline-direction-up↑ 0)) ( right . ,(+ uniline-direction-ri→ 0)) ( down . ,(+ uniline-direction-dw↓ 0)) ( left . ,(+ uniline-direction-lf← 0)) (S-up . ,(+ uniline-direction-up↑ 4)) (S-right . ,(+ uniline-direction-ri→ 4)) (S-down . ,(+ uniline-direction-dw↓ 4)) (S-left . ,(+ uniline-direction-lf← 4)) (C-up . ,(+ uniline-direction-up↑ 8)) (C-right . ,(+ uniline-direction-ri→ 8)) (C-down . ,(+ uniline-direction-dw↓ 8)) (C-left . ,(+ uniline-direction-lf← 8))))) (uniline--defconst-hash-table uniline--keystroke-to-dir-shift (eval-when-compile uniline--directional-keystrokes-table) 16 'eq "Hashtable to convert a directional keystroke into Uniline constants.") ;; it is impossible to guaranty that the table will stay collision-less ;; because keys are symbols, whose hash-values may change from ;; one Emacs session to another (defconst uniline--dir-shift-to-keystroke (eval-when-compile ;; not needed at runtime (cl-loop with vec = (make-vector (1+ (cl-loop for x in uniline--directional-keystrokes-table maximize (cdr x))) nil) for r in uniline--directional-keystrokes-table do (aset vec (cdr r) (car r)) finally return vec)) "Convert Uniline directional constants back into keystrokes.") (defun uniline-call-macro-in-direction (dir) "Call last keybord macro twisted in DIR direction. A twisted version of the last keybord macro is created, unless it is already present in the `uniline--directional-macros' cache" (interactive) (let* ((uniline-text-direction dir) (dir2 (* 2 dir)) (last-kbd-macro (or (and (eq (aref uniline--directional-macros dir2) last-kbd-macro) (aref uniline--directional-macros (1+ dir2))) (progn (aset uniline--directional-macros dir2 last-kbd-macro) (aset uniline--directional-macros (1+ dir2) (cl-loop with result = (vconcat last-kbd-macro) for r across-ref result for y = (gethash r uniline--keystroke-to-dir-shift) if y do (setf r (aref uniline--dir-shift-to-keystroke (logior (logand (+ y dir 3) 3) (logand y (eval-when-compile (lognot 3)))))) finally return result)))))) (execute-kbd-macro last-kbd-macro 1))) ;; Run the following cl-loop to automatically write a bunch ;; of 4 interactive functions ;; They have little value, except to be an interface between ;; `easy-menu-define', `defhydra', `transient-define-prefix', ;; and the real function `uniline-call-macro-in-direction' (when nil (insert "\n;; BEGIN -- Automatically generated\n") (cl-loop for dir in '("up↑" "ri→" "dw↓" "lf←") do (cl-prettyprint `(defun ,(intern (format "uniline-call-macro-in-direction-%s" dir)) () ,(format "Call macro in direction %s." dir) (interactive) (uniline-call-macro-in-direction (,(intern (format "uniline-direction-%s" dir))))))) (insert "\n;; END -- Automatically generated\n")) ;; BEGIN -- Automatically generated (defun uniline-call-macro-in-direction-up↑ nil "Call macro in direction up↑." (interactive) (uniline-call-macro-in-direction (uniline-direction-up↑))) (defun uniline-call-macro-in-direction-ri→ nil "Call macro in direction ri→." (interactive) (uniline-call-macro-in-direction (uniline-direction-ri→))) (defun uniline-call-macro-in-direction-dw↓ nil "Call macro in direction dw↓." (interactive) (uniline-call-macro-in-direction (uniline-direction-dw↓))) (defun uniline-call-macro-in-direction-lf← nil "Call macro in direction lf←." (interactive) (uniline-call-macro-in-direction (uniline-direction-lf←))) ;; END -- Automatically generated ;;;╭───────────────────────────╮ ;;;│High level brush management│ ;;;╰───────────────────────────╯ (defun uniline-set-brush-nil () "Change the current style of line to nothing. It means that cursor movements do not trace anything." (interactive) (setq uniline-brush nil) (uniline--update-mode-line)) (defun uniline-set-brush-0 () "Change the current style of line to the eraser. It means that moving the cursor horizontally erase horizontal lines, and moving vertically erase vertical lines. Characters other than lines or arrows are not touched." (interactive) (setq uniline-brush 0) (uniline--update-mode-line)) (defun uniline-set-brush-0dots () "Change the current style of line plain." (interactive) (setq uniline-brush-dots 0) (uniline--update-mode-line)) (defun uniline-set-brush-3dots () "Change the current style of line 3 dots vertical, 2 dots horizontal." (interactive) (setq uniline-brush-dots 1) (uniline--update-mode-line)) (defun uniline-set-brush-4dots () "Change the current style of line 4 dots vertical & horizontal." (interactive) (setq uniline-brush-dots 2) (uniline--update-mode-line)) (defun uniline-set-brush-dot-toggle () "Toggle dotted lines: plain → 3-2-dots → 4-4-dots → plain." (interactive) (setq uniline-brush-dots (% (1+ uniline-brush-dots) 3)) (uniline--update-mode-line)) (defun uniline-set-brush-1 () "Change the current style of line to a single thin line╶─╴." (interactive) (setq uniline-brush 1) (uniline--update-mode-line)) (defun uniline-set-brush-2 () "Change the current style of line to a single thick line╺━╸." (interactive) (setq uniline-brush 2) (uniline--update-mode-line)) (defun uniline-set-brush-3 () "Change the current style of line to a double line╺═╸." (interactive) (setq uniline-brush 3) (uniline--update-mode-line)) (defun uniline-set-brush-block () "Change the current style of line to blocks ▙▄▟▀." (interactive) (setq uniline-brush :block) (uniline--update-mode-line)) ;;;╭─────────────────────────────────────╮ ;;;│High level arrows & glyphs management│ ;;;╰─────────────────────────────────────╯ (defun uniline--insert-glyph (letter back repeat) "Insert or modify a glyph. LETTER is any of `a' for arrows `s' for squares `x' for crosses `o' for o-shapes `g' for shades of grey If a glyph of that category is already there under (point), it is modified with the next glyph in the same category. If not, whatever character under (point) is overwritten with the first glyph in the required category. BACK is t for backward scan, nil for forward scan in the same category. REPEAT is the usual universal argument for repetition of the same command." (if (and repeat (< repeat 0)) (setq repeat (- repeat) back (not back))) (let ((line ;; line is something like: ;; (3 ((a ?↑ ?→ ?↓ ?←) (a ?▲ ?▶ ?▼ ?◀) …)) ;; △ △ △ △ △ current character is ;; │ ╰──┴──┴──┴─╴one of those arrows ;; ╰─────────────────╴oriented in this direction ;; ;; line can also be like: ;; (t ((a ?↕ ?↔ ?↕ ?↔) (a ?↑ ?→ ?↓ ?←) …)) ;; △ △ △ △ △ current character is ;; │ ╰──┴──┴──┴─╴one of those arrows ;; ╰─────────────────╴with no definite orientation ;; ;; or line may be like: ;; (nil ((s ?■) (s ?▫) …)) ;; △ △ current character is ;; │ ╰────────╴this one ;; ╰────────────────╴and it has NO orientation (gethash (uniline--char-after) (cond (back uniline--glyphs-reverse-hash-bw) (t uniline--glyphs-reverse-hash-fw))))) (if (and line ; current character is one the known glyphs (fixnump (car line))) ; it has a north-south-east-west orientation (setq uniline--arrow-direction (car line))) (setq line (if line (cddr line) (if back uniline--glyphs-bw uniline--glyphs-fw))) (setq line (cl-loop for line2 on line if (or (null (caar line2)) ; currently useless (eq (caar line2) letter)) return line2 if (eq (cdr line2) line) return nil)) (when line (setq line (nthcdr (1- (or repeat 1)) line)) (uniline--insert-char (if (cddar line) (nth uniline--arrow-direction (cdar line)) (cadar line)))))) ;; Run the following cl-loop to automatically write a bunch ;; of 8 interactive functions ;; They have little value, except to be an interface between ;; `easy-menu-define', `defhydra', `transient-define-prefix', ;; and the real function `uniline--insert-glyph' (when nil (insert "\n;; BEGIN -- Automatically generated\n") (cl-loop for fb in '(f b) do (cl-loop for shape in '( (a . "arrow" ) (s . "square") (o . "oshape") (x . "cross" ) (g . "grey" )) do (cl-prettyprint `(defun ,(intern (format "uniline-insert-%sw-%s" fb (cdr shape))) (repeat) ,(format "Insert or modify a glyph of type %s in %s mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (cdr shape) (pcase fb ('f "forward") ('b "backward"))) (interactive "P") (uniline--insert-glyph ',(car shape) ,(eq fb 'b) repeat))))) (insert "\n;; END -- Automatically generated\n")) ;; BEGIN -- Automatically generated (defun uniline-insert-fw-arrow (repeat) "Insert or modify a glyph of type arrow in forward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 'a nil repeat)) (defun uniline-insert-fw-square (repeat) "Insert or modify a glyph of type square in forward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 's nil repeat)) (defun uniline-insert-fw-oshape (repeat) "Insert or modify a glyph of type oshape in forward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 'o nil repeat)) (defun uniline-insert-fw-cross (repeat) "Insert or modify a glyph of type cross in forward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 'x nil repeat)) (defun uniline-insert-fw-grey (repeat) "Insert or modify a glyph of type grey in forward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 'g nil repeat)) (defun uniline-insert-bw-arrow (repeat) "Insert or modify a glyph of type arrow in backward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 'a t repeat)) (defun uniline-insert-bw-square (repeat) "Insert or modify a glyph of type square in backward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 's t repeat)) (defun uniline-insert-bw-oshape (repeat) "Insert or modify a glyph of type oshape in backward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 'o t repeat)) (defun uniline-insert-bw-cross (repeat) "Insert or modify a glyph of type cross in backward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 'x t repeat)) (defun uniline-insert-bw-grey (repeat) "Insert or modify a glyph of type grey in backward mode. REPEAT cycles through glyphs list that many times, default to 1. See `uniline--insert-glyph'." (interactive "P") (uniline--insert-glyph 'g t repeat)) ;; END -- Automatically generated (eval-when-compile ; not needed at runtime (defmacro uniline--rotate-arrow (dir) "Rotate an arrow, or tweak a 4halfs or a 4quadb. - If character under point is a known arrow, turn it in DIR direction. - If character under point is a combination of 4halfs lines, then change the 4half segment pointing in the DIR direction. Repeateadly changing in the same direction cycles through at most 4 characters (with thin, thick, double, or no line at all in DIR direction). Sometimes, the cycle is shorter than 4, because UNICODE does not define all combinations of 4halfs when one of them is a double line. - If character under point is a combination of 4quadb blocks, then flip the block in DIR direction on-and-off. As the blocks are in the 4 corners of the character, DIR cannot point exactly to a block. Instead DIR is twisted 45° from the actual direction of the block." (setq dir (eval dir)) ; turn symbol like --direction-dw↓ to number like 2 (let* ((dir1 (1+ dir)) (ash3dir2 (uniline--shift-4half 3 dir)) ;; 3 is a bit-mask (ash1dir2 (uniline--shift-4half 1 dir)) ;; 1 is an increment (notash3dir2 (lognot ash3dir2)) (dir4 (uniline--4quadb-pushed (uniline--turn-left dir) (uniline--4quadb-pushed dir (uniline--char-to-4quadb ?█) ; this is constant 15 = 0b1111 )))) `(let ((pat ; pattern (gethash (uniline--char-after) uniline--glyphs-reverse-hash-fw))) (cond ;; If (point) is on a directional arrow ((car pat) (uniline--insert-char ; then change its direction (nth ,dir1 (cadr pat))) (setq uniline--arrow-direction ,dir)) ;; If point is on lines crossing ((setq pat (gethash (uniline--char-after) uniline--char-to-4halfs)) (let ((patdir (logand pat , ash3dir2)) ; pattern in DIR (patnot (logand pat ,notash3dir2)) ; pattern not in DIR patnew) ; new pattern to insert (while ; search for valid UNICODE (progn ; with one 4half segment changed (setq patdir (logand (+ patdir ,ash1dir2) ,ash3dir2)) (setq patnew (logior patnot patdir)) (not (eq (gethash (uniline--4halfs-to-char-aref patnew) uniline--char-to-4halfs) patnew)))) (uniline--insert-4halfs patnew))) ;; If point is on a quarter-char ((setq pat (uniline--char-to-4quadb (uniline--char-after))) (uniline--insert-4quadb (logxor pat ,dir4)))))))) ;; Run the following cl-loop to automatically write a bunch ;; of 4 interactive functions ;; They have little value, except to be an interface between ;; `easy-menu-define', `defhydra', `transient-define-prefix', ;; and the real function `uniline--rotate-arrow' (when nil (insert "\n;; BEGIN -- Automatically generated\n") (cl-loop for dir in '("up↑" "ri→" "dw↓" "lf←") do (cl-prettyprint `(defun ,(intern (format "uniline-rotate-%s" dir)) () ,(format "Rotate an arrow or tweak 4halfs. If character under point is an arrow, turn it %s. If character under point is a combination of 4halfs lines, then change the 4half segment pointing %s." dir dir) (interactive) (uniline--rotate-arrow (,(intern (format "uniline-direction-%s" dir))))))) (insert "\n;; END -- Automatically generated\n")) ;; BEGIN -- Automatically generated (defun uniline-rotate-up↑ nil "Rotate an arrow or tweak 4halfs. If character under point is an arrow, turn it up↑. If character under point is a combination of 4halfs lines, then change the 4half segment pointing up↑." (interactive) (uniline--rotate-arrow (uniline-direction-up↑))) (defun uniline-rotate-ri→ nil "Rotate an arrow or tweak 4halfs. If character under point is an arrow, turn it ri→. If character under point is a combination of 4halfs lines, then change the 4half segment pointing ri→." (interactive) (uniline--rotate-arrow (uniline-direction-ri→))) (defun uniline-rotate-dw↓ nil "Rotate an arrow or tweak 4halfs. If character under point is an arrow, turn it dw↓. If character under point is a combination of 4halfs lines, then change the 4half segment pointing dw↓." (interactive) (uniline--rotate-arrow (uniline-direction-dw↓))) (defun uniline-rotate-lf← nil "Rotate an arrow or tweak 4halfs. If character under point is an arrow, turn it lf←. If character under point is a combination of 4halfs lines, then change the 4half segment pointing lf←." (interactive) (uniline--rotate-arrow (uniline-direction-lf←))) ;; END -- Automatically generated ;;;╭───────╮ ;;;│Contour│ ;;;╰───────╯ (defcustom uniline-contour-max-steps 10000 "Maximum number of steps before stopping the contour algorithm. This is the number of characters drawn with lines╶─┬─══╦══━━┳━╸, or twice the number of characters drawn with block-characters ▝▙. This limit may be reached if the text is huge. Of course, the algorithm may be started again for an additional 10000 steps." :type 'natnum :group 'uniline) (defun uniline-contour (&optional force) "Draw a contour arround a block of characters. A block of characters is a contiguous set of non-blank characters. For the sake of the contour, a non-blank character is any character not in the 4halfs set. The style of the contour is determined by the current brush. This includes possibly the eraser, which erases an actual contour. When FORCE is not nil, overwrite whatever is in the buffer. ╭──────╮ │AAAAAA╰─╮ ╰─╮AAAAAA│ │AA╭───╯ ╰──╯ " (interactive) (while (not (uniline--blank-after (point))) (uniline-move-to-delta-column 1)) (let ((dir) (start (point-marker)) (q uniline--which-quadrant) (n 0)) ;; look for a surrounding wall successively in directions lf← up↑ ri→ dw↓. ;; stop as soon as hitting a wall. ;; then initialize dir-ection turning left from the wall. ;; so for instance, if there is empty space toward west lf←, ;; and a wall toward north up↑, then initial dir will be set to west lf←. ;; \\\\\\\\\\\\\\\\\ ;; ╺━━━━━━╳━━━━━━┓ \ ;; ↑ ┃ \ ;; ╭──╯──╮ ┃ \ ;; │ │ ┃ \ ;; ←─╮ ● ╰──→╳ \ ;; ╰──╯ │ ┃ \ ;; ╭─╭──╯ ┃ \ ;; error╶──╯ ↓ ┃ \ ;; ╹ \ (and (progn (setq dir (uniline--turn-left (uniline-direction-lf←))) (uniline--blank-neighbour1 (uniline-direction-lf←))) (progn (setq dir (uniline--turn-left (uniline-direction-up↑))) (uniline--blank-neighbour1 (uniline-direction-up↑))) (progn (setq dir (uniline--turn-left (uniline-direction-ri→))) (uniline--blank-neighbour1 (uniline-direction-ri→))) (progn (setq dir (uniline--turn-left (uniline-direction-dw↓))) (uniline--blank-neighbour1 (uniline-direction-dw↓))) (error "No border of any shape found around point")) (if (eq uniline-brush :block) (setq q (setq uniline--which-quadrant ;; this huge expression folds to just: ;; (aref [8 4 1 2] dir) ;; which is as fast as can be at runtime (uniline--switch-with-table dir (lambda (dir) (uniline--4quadb-pushed (uniline--reverse-direction dir) (uniline--4quadb-pushed (uniline--turn-right dir) (uniline--char-to-4quadb ?█)))) (uniline-direction-up↑) (uniline-direction-ri→) (uniline-direction-dw↓) (uniline-direction-lf←))))) (while (progn ;; change current dir-ection in whichever direction there is no wall. ;; in this example dir pointed north up↑, and was changed to west lf← ;; \\\\\\\\\\\\\\\\\\\\\\ ;; ╺━━━━━━━━╳━━━━━━┓\\\\\ ;; ↑ ┃\\\\\ ;; ╭───╰───╮ ┃\\\\\ ;; │ ╭─╮ │ ┃\\\\\ ;; no wall ←─╯ ● │ ╭─→╳\\\\\ ;; │ ╰─╯ ╹\\\\\ ;; ╰───╮───╮ \\\ ;; ↓ ╰error \ (let ((d dir)) (or (uniline--blank-neighbour (setq dir (uniline--turn-right dir))) (uniline--blank-neighbour (setq dir d)) (uniline--blank-neighbour (setq dir (uniline--turn-left dir))) (uniline--blank-neighbour (setq dir (uniline--turn-left dir))) (error "Cursor is surrounded by walls"))) ;; bumping into the left or upper borders? ;; move the (point) silently as if drawing outside the buffer ;; until finding a blank (or eolp)) which allows the (point) ;; to re-enter the actual buffer. (cond ;; bump into the left border ((and (eq dir (uniline-direction-lf←)) (uniline--at-border-p uniline-direction-lf←) (or (not (eq uniline-brush :block)) (uniline--blank-neighbour4 uniline-direction-ri→))) (while (and (eq (forward-line -1) 0) (not (uniline--blank-after (point))))) (if (uniline--at-border-p uniline-direction-up↑) (setq dir (uniline-direction-up↑) uniline--which-quadrant (uniline--char-to-4quadb ?▝)) (setq dir (uniline-direction-ri→) uniline--which-quadrant (uniline--char-to-4quadb ?▖)))) ;; bump into the upper border ((and (not uniline-infinite-up↑) (eq dir (uniline-direction-up↑)) (uniline--at-border-p uniline-direction-up↑) (or (not (eq uniline-brush :block)) (uniline--blank-neighbour4 uniline-direction-dw↓))) (while (progn (forward-char 1) (not (uniline--blank-after (point))))) (setq dir (uniline-direction-dw↓)) (setq uniline--which-quadrant (uniline--char-to-4quadb ?▘))) ;; not bumping into a border, so just draw (t (uniline--write dir force) (setq n (1+ n)))) (and (not (and (eq (point) (marker-position start)) (or (not (eq uniline-brush :block)) (eq uniline--which-quadrant q)))) (< n uniline-contour-max-steps)))) (set-marker start nil) (message "drew a %s steps contour" n))) ;;;╭─────────────────────────────╮ ;;;│Dashed lines and other styles│ ;;;╰─────────────────────────────╯ (defun uniline--change-style-hash (fromto) "Change some characters to similar characters in a rectangular selection. FROMTO is a hash-table telling what are the characters replacements. The changes are reversible in a single undo command." (interactive) (uniline--operate-on-rectangle (cl-loop for y from begy below endy do (uniline-move-to-line y) (cl-loop for x from begx below endx do (uniline-move-to-column x) (let ((rep (gethash (uniline--char-after) fromto))) (if rep (uniline--insert-char rep))))))) (uniline--defconst-hash-table uniline--char-to-dot-3-2-char '((?─ . ?╌) (?┄ . ?╌) (?┈ . ?╌) (?━ . ?╍) (?┅ . ?╍) (?┉ . ?╍) (?═ . ?╍) (?│ . ?┆) (?╎ . ?┆) (?┊ . ?┆) (?┃ . ?┇) (?╏ . ?┇) (?┋ . ?┇) (?║ . ?┇)) 16 'eq "Convert to 3 vertical & 2 horizontal dashes") (defun uniline-change-style-dot-3-2 () "Change plain lines to dashed lines in a rectangular selection. It retains thickness of the lines. Vertical lines will be 3-dots, while horizontal will be 2-dots." (interactive) (uniline--record-undo-rectangle-selection) (uniline--change-style-hash uniline--char-to-dot-3-2-char)) (uniline--defconst-hash-table uniline--char-to-dot-4-4-char '((?─ . ?┈) (?╌ . ?┈) (?┄ . ?┈) (?━ . ?┉) (?╍ . ?┉) (?┅ . ?┉) (?═ . ?┉) (?│ . ?┊) (?╎ . ?┊) (?┆ . ?┊) (?┃ . ?┋) (?╏ . ?┋) (?┇ . ?┋) (?║ . ?┋)) 32 'eq "Convert to 4 vertical & 4 horizontal dashes") (defun uniline-change-style-dot-4-4 () "Change plain lines to dashed lines in a rectangular selection. It retains thickness of the lines. Vertical and horizontzl lines will be 4-dots." (interactive) (uniline--record-undo-rectangle-selection) (uniline--change-style-hash uniline--char-to-dot-4-4-char)) (uniline--defconst-hash-table uniline--char-to-standard-char '((?╌ . ?─) (?┄ . ?─) (?┈ . ?─) (?╍ . ?━) (?┅ . ?━) (?┉ . ?━) (?╎ . ?│) (?┆ . ?│) (?┊ . ?│) (?╏ . ?┃) (?┇ . ?┃) (?┋ . ?┃) (?┐ . ?╮) (?└ . ?╰) (?┌ . ?╭) (?┘ . ?╯)) 64 'equal ; `equal' instead of `eq' to achieve collision-less table "Convert back to base-line style") (eval-when-compile ;; not used at runtime (defmacro uniline--infer-neighbour-4half (dir) "Return a 4half to seamlessly connect with neighbour in DIR direction. Return 0 if there is no neighbour, or if neighbour does not look like a connecting line or glyph." (setq dir (eval dir)) (let ((isvert (memq dir `(,uniline-direction-up↑ ,uniline-direction-dw↓))) (ishorz (memq dir `(,uniline-direction-lf← ,uniline-direction-ri→))) ;; 1 is thin half-line in direction DIR ──▷ (shift1 (uniline--shift-4half 1 dir)) ;; 1 is double half-line in direction DIR ══▷ (shift3 (uniline--shift-4half 3 dir))) `(let ((neighbour (uniline--neighbour-point ,dir))) (if (not neighbour) 0 (setq neighbour (uniline--char-after neighbour)) (let ((b (gethash neighbour uniline--char-to-4halfs))) (cond (b (uniline--extract-reverse-4half b ,dir)) ((memq neighbour '(?+ ?\\ ?/ ?' ?`)) ,shift1) ((eq neighbour ?#) ,shift3) ,@(when isvert `(((memq neighbour '(?^ ?v ?V ?| ?△ ?▽ ?▲ ?▼ ?↑ ?↓ ?▵ ?▿ ?▴ ?▾ ?↕)) ,shift1) ((eq neighbour ?\") ,shift3))) ,@(when ishorz `(((memq neighbour '(?> ?< ?- ?_ ?▷ ?◁ ?▶ ?◀ ?→ ?← ?▹ ?◃ ?▸ ?◂ ?↔)) ,shift1) ((eq neighbour ?=) ,shift3))) (t 0)))))))) (defun uniline-change-style-standard () "Change fancy lines styles to standard ones in a rectangular selection. This includes dashed lines, which become plain while preserving thickness, and hard corners which become rounded. Also ASCII-art is converted to UNICODE-art." (interactive) (uniline--record-undo-rectangle-selection) (uniline--change-style-hash uniline--char-to-standard-char) ;; Hereafter, we handle ASCII characters used to draw sketches ;; We distinguish two cases for those characters ;; - when they should be left alone as ASCII chars ;; - when they are part of a future UNICODE line ;; ASCII chars are converted to UNICODE glyphs only when they ;; are surrounded by UNICODE or ASCII characters that are ;; themselves part of a sketch or will be. ;; Surrounding is considered depending on the orientation of the ;; char: Vertical, horizontal, or both. ;; So for instance: ;; - surrounding is left and right ;; | surrounding is up and down ;; + surrounding is in the 4 directions ;; For UNICODE box drawing characters, an attempt is made to ;; complete them with half lines so that they will connect ;; seamlessly with their surrounding. (uniline--operate-on-rectangle (cl-loop for y from begy below endy do (uniline-move-to-line y) (cl-loop for x from begx below endx do (uniline-move-to-column x) (let ((char (uniline--char-after))) (if (memq char '(?^ ?v ?V ?| ?\" ?- ?_ ?> ?< ?= ?+ ?/ ?\\ ?' ?` ?# ?o ?O ?* ?.)) (let* ((4halfvert (logior (uniline--infer-neighbour-4half uniline-direction-up↑) (uniline--infer-neighbour-4half uniline-direction-dw↓))) (4halfhorz (logior (uniline--infer-neighbour-4half uniline-direction-lf←) (uniline--infer-neighbour-4half uniline-direction-ri→))) (4half (logior 4halfvert 4halfhorz)) (newchar (or (unless (eq 4halfvert 0) (uniline--switch-with-table char (?^ ?△) (?v ?▽) (?V ?▽) (?| ?│) (?\" ?║))) (unless (eq 4halfhorz 0) (uniline--switch-with-table char (?- ?─) (?_ ?─) (?> ?▷) (?< ?◁) (?= ?═))) (unless (eq 4half 0) (uniline--switch-with-table char (?O ?●) (?o ?◦) (?* ?●) (?. ?∙)))))) (if newchar (uniline--insert-char newchar) (unless (eq 4half 0) (if (memq char '(?+ ?# ?/ ?\\ ?' ?`)) (uniline--insert-4halfs 4half))))))))))) (uniline--defconst-hash-table uniline--char-to-hard-corner-char '((?╮ . ?┐) (?╰ . ?└) (?╭ . ?┌) (?╯ . ?┘)) 4 'eq "Convert rounded corners to hard ones") (defun uniline-change-style-hard-corners () "Change rounded corners to hard corners in a rectangular selection. This happens only for thin-lines corners, as UNICODE does not define thick-line or double-line rounded corners." (interactive) (uniline--record-undo-rectangle-selection) (uniline--change-style-hash uniline--char-to-hard-corner-char)) (uniline--defconst-hash-table uniline--char-to-thin-char (eval-when-compile (append (cl-loop for c being hash-keys of uniline--char-to-4halfs using (hash-values v) for 4halfs = (uniline--pack-4halfs (cl-loop for u in (uniline--unpack-4halfs v) collect (if (or (eq u 2) (eq u 3)) 1 u))) unless (eq 4halfs v) collect (cons c (uniline--4halfs-to-char-aref 4halfs))) '((?┅ . ?┄) (?┉ . ?┈) (?╍ . ?╌) (?┇ . ?┆) (?┋ . ?┊) (?▲ . ?△) (?▶ . ?▷) (?▼ . ?▽) (?◀ . ?◁) (?▴ . ?▵) (?▸ . ?▹) (?▾ . ?▿) (?◂ . ?◃) (?■ . ?□) (?▪ . ?▫) (?• . ?◦)))) 256 'eq "Convert black or heavy characters to white or light ones") (defun uniline-change-style-thin () "Change bold lines and glyphs to thin ones. This includes plain and dashed lines (e.g. ┴ to ┻, or ┅ to ┄) as well as glyphs (e.g. ■ to □ or ▼ to ▽)." (interactive) (uniline--record-undo-rectangle-selection) (uniline--change-style-hash uniline--char-to-thin-char)) (uniline--defconst-hash-table uniline--char-to-thick-char (eval-when-compile (append (cl-loop for c being hash-keys of uniline--char-to-4halfs using (hash-values v) for 4halfs = (uniline--pack-4halfs (cl-loop for u in (uniline--unpack-4halfs v) collect (if (or (eq u 1) (eq u 3)) 2 u))) unless (eq 4halfs v) collect (cons c (uniline--4halfs-to-char-aref 4halfs))) '((?┄ . ?┅) (?┈ . ?┉) (?╌ . ?╍) (?┆ . ?┇) (?┊ . ?┋) (?△ . ?▲) (?▷ . ?▶) (?▽ . ?▼) (?◁ . ?◀) (?▵ . ?▴) (?▹ . ?▸) (?▿ . ?▾) (?◃ . ?◂) (?□ . ?■) (?▫ . ?▪) (?◦ . ?•)))) 128 'equal ; `equal' instead of `eq' to achieve a small collision-less table "Convert white or light characters to black or heavy ones") (defun uniline-change-style-thick () "Change thin lines and glyphs to bold ones. This includes plain and dashed lines (e.g. ┴ to ┻, or ┄ to ┅) as well as glyphs (e.g. □ to ■ or ▽ to ▼)." (interactive) (uniline--record-undo-rectangle-selection) (uniline--change-style-hash uniline--char-to-thick-char)) (uniline--defconst-hash-table uniline--char-to-double-line (eval-when-compile (append (cl-loop for c being hash-keys of uniline--char-to-4halfs using (hash-values v) for 4halfs = (uniline--pack-4halfs (cl-loop for u in (uniline--unpack-4halfs v) collect (if (or (eq u 1) (eq u 2)) 3 u))) unless (eq 4halfs v) collect (cons c (uniline--4halfs-to-char-aref 4halfs))) '((?┄ . ?═) (?┅ . ?═) (?┈ . ?═) (?┉ . ?═) (?╌ . ?═) (?╍ . ?═) (?┆ . ?║) (?┇ . ?║) (?┊ . ?║) (?┋ . ?║)))) 128 'eq "Convert any line to double line.") (defun uniline-change-style-double () "Change thin lines and bold lines to double ones. This includes plain and dashed lines (e.g. ┻ to ╩, or ┄ to ═)." (interactive) (uniline--record-undo-rectangle-selection) (uniline--change-style-hash uniline--char-to-double-line)) (defun uniline-aa2u-rectangle () "Wrapper arround `aa2u-rectangle'." (interactive) (uniline--record-undo-rectangle-selection) (if (functionp 'aa2u-rectangle) (uniline--operate-on-rectangle ;; here we use `eval' on purpose, to get a loose coupling ;; with the `ascii-art-to-unicode' package; if not installed ;; the native compiler may complain that `aa2u-rectangle' ;; is not defined; no longer with `eval'. ;; but long after compiling `uniline', if the ;; `ascii-art-to-unicode' package is eventually installed, ;; the `aa2u-rectangle' will be called without the need to ;; recompile or native-recompile `uniline'. (eval `(aa2u-rectangle ,beg ,end))) (message "Install the ascii-art-to-unicode package prior to using aa2u. It is available on ELPA. Or use the '0 standard' style transformer instead."))) ;;;╭───────────────────────────╮ ;;;│Common to Hydra & Transient│ ;;;╰───────────────────────────╯ (defun uniline-customize-face () "Customize a temporary font to may-be set it for future sessions." (interactive) (customize-face-other-window 'default)) (defun uniline--is-font (letter) "Check if current font is the one presented by LETTER." (let ((name (uniline--switch-with-table letter (?d "DejaVu" ) (?u "Unifont" ) (?h "Hack" ) (?b "JetBrain" ) (?c "Cascadia" ) (?a "Agave" ) (?j "Julia" ) (?f "FreeMono" ) (?i "Iosevka Comfy Fixed" ) (?I "Iosevka Comfy Wide Fixed") (?p "Aporetic Sans Mono" ) (?P "Aporetic Serif Mono" ) (?s "Source Code" )))) (and name (string-match name (frame-parameter nil 'font))))) (defun uniline--font-name-ticked (letter) "Return the name of the font presented by LETTER, with a tick-glyph ▶ if current." (funcall (if (uniline--is-font letter) #'cdr #'car) (uniline--switch-with-table letter (?d '(" DejaVu" . "▶DejaVu" )) (?u '(" Unifont" . "▶Unifont" )) (?h '(" Hack" . "▶Hack" )) (?b '(" JetBrains" . "▶JetBrains" )) (?c '(" Cascadia" . "▶Cascadia" )) (?a '(" Agave" . "▶Agave" )) (?j '(" Julia" . "▶Julia" )) (?f '(" FreeMono" . "▶FreeMono" )) (?i '(" Iosevka" . "▶Iosevka" )) (?I '(" Iosevka Wide" . "▶Iosevka Wide" )) (?p '(" Aporetic Sans" . "▶Aporetic Sans" )) (?P '(" Aporetic Serif" . "▶Aporetic Serif" )) (?s '(" Source Code Pro" . "▶Source Code Pro"))))) (when nil ;; Those low-added-value functions are automatically generated. ;; Their purpose is to avoid lambdas in the definition ;; of Hydras and Tansients. (insert "\n;; BEGIN -- Automatically generated\n") (cl-loop for f in '((?d . "DejaVu Sans Mono" ) (?u . "Unifont" ) (?h . "Hack" ) (?b . "JetBrains Mono" ) (?c . "Cascadia Mono" ) (?a . "Agave" ) (?j . "JuliaMono" ) (?f . "FreeMono" ) (?i . "Iosevka Comfy Fixed" ) (?I . "Iosevka Comfy Wide Fixed") (?p . "Aporetic Sans Mono" ) (?P . "Aporetic Serif Mono" ) (?s . "Source Code Pro" )) do (insert (format "(defun uniline--set-font-%c ()\n" (car f))) (insert " (interactive)\n") (insert (format " (set-frame-font \"%s\"))\n" (cdr f)))) (insert "\n;; END -- Automatically generated\n")) ;; BEGIN -- Automatically generated (defun uniline--set-font-d () (interactive) (set-frame-font "DejaVu Sans Mono")) (defun uniline--set-font-u () (interactive) (set-frame-font "Unifont")) (defun uniline--set-font-h () (interactive) (set-frame-font "Hack")) (defun uniline--set-font-b () (interactive) (set-frame-font "JetBrains Mono")) (defun uniline--set-font-c () (interactive) (set-frame-font "Cascadia Mono")) (defun uniline--set-font-a () (interactive) (set-frame-font "Agave")) (defun uniline--set-font-j () (interactive) (set-frame-font "JuliaMono")) (defun uniline--set-font-f () (interactive) (set-frame-font "FreeMono")) (defun uniline--set-font-i () (interactive) (set-frame-font "Iosevka Comfy Fixed")) (defun uniline--set-font-I () (interactive) (set-frame-font "Iosevka Comfy Wide Fixed")) (defun uniline--set-font-p () (interactive) (set-frame-font "Aporetic Sans Mono")) (defun uniline--set-font-P () (interactive) (set-frame-font "Aporetic Serif Mono")) (defun uniline--set-font-s () (interactive) (set-frame-font "Source Code Pro")) ;; END -- Automatically generated (defun uniline--self-insert-+ () "Wrapper over `self-insert-command' ." (interactive) (self-insert-command 1 ?+)) (defun uniline--self-insert-- () "Wrapper over `self-insert-command' ." (interactive) (self-insert-command 1 ?-)) (defun uniline-text-direction-str () "Return a textual representation of current text direction." (interactive) (uniline--switch-with-table uniline-text-direction (uniline-direction-up↑ "↑") (uniline-direction-ri→ "→") (uniline-direction-dw↓ "↓") (uniline-direction-lf← "←"))) (defun uniline--rect-quit () "Quit this hydra or transient." (interactive) (deactivate-mark)) ;;;╭────────────────────╮ ;;;│Language environment│ ;;;╰────────────────────╯ ;; Some language environments give a double width to some characters ;; used by Uniline. For instance ;; M-x set-language-environment Chinese-BIG5 ;; gives a width of 2 to ─ ;; It does so through char-width-table, which is a global Emacs variable. ;; So we fix that by patching (setq char-width-table …) ;; to a new table which inherit from the original char-width-table, ;; and set the witdh of all needed characters to 1. ;; No attempt is done to revert the change on exiting uniline-mode, ;; because this would create more problems than it solves. ;; To revert char-width-table, just re-set the language environments: ;; C-x RET l Chinese-BIG5 (defun uniline--fix-char-width-table () "Create a descendent of char-width-table with characters widths set to 1. It does so for all characters Uniline creates. The process is lazy in the sense that if a character already has a width of 1, its entry in the table is left as is." (let ((allchars)) (maphash ;; list characters like ╶─┴╮╶╼━┻━┳━═══╩╦╸ (lambda (key _val) (push key allchars)) uniline--char-to-4halfs) (cl-loop ;; list characters like ▝▙▄█ ▗▄▖ ▖ ▘ for e across uniline--4quadb-to-char do (push e allchars)) (let ((x uniline--glyphs-fw)) ;; warning: circular list (while ;; list characters like ◁▲→□■·●╳ (progn (setq allchars (append (cdr (car x)) allchars)) (not (eq (setq x (cdr x)) uniline--glyphs-fw))))) (setq allchars (sort allchars #'<=)) (cl-loop ;; filter out characters already 1 in width, and duplicates for iter on allchars for curr = (car iter) with prev = nil do (if (or (eq prev curr) ;; duplicate (eq (aref char-width-table curr) 1)) ;; width already 1 (setcar iter nil)) (setq prev curr)) (setq allchars (delq nil allchars)) (if allchars ;; patch only if there are characters to patch (let ((ct (make-char-table nil))) (nconc allchars (list nil)) ;; a last entry to close the algorithm (cl-loop for curr in (cdr allchars) with prev = (car allchars) with start = (car allchars) do (if (eq curr prev) (message "duplicate cannot happen")) (unless (eq (1+ prev) curr) (set-char-table-range ct (cons start prev) 1) (setq start curr)) (setq prev curr)) (set-char-table-parent ct char-width-table) (setq char-width-table ct))))) ;;;╭─────────────╮ ;;;│Mouse support│ ;;;╰─────────────╯ ;; How it works? ;; The 3 mouse-button-1 handling functions of Emacs are ;; fairly complex. They are named: ;; mouse-set-point, mouse-drag-region, mouse-set-region. ;; ;; Here we just let them do whatever they want to. But first ;; we intercept them, so that Uniline can add blank ;; characters or lines if the mouse event falls outside the ;; buffer. ;; ;; The mouse events provide a (point) position, which is ;; wrong when outside the buffer. But they also provide a ;; position in pixels along with the width and height in ;; pixels of a typical character. This allows to reconstruct ;; a hopefully accurate (point) position, which we re-inject ;; in the mouse event in place of the wrong (point) position. ;; ;; Of course, things are tricky because the pixel-position is ;; relative to the upper-left corner of the displayed window, ;; while we need the buffer-position. We use the (window-start) ;; function to get the line number under the upper-left corner. ;; And the (scroll-left 0) function to get the column number ;; of this corner. ;; ;; But things get even trickier when the window is zoomed with ;; C-x C-+ C-- and it is scrolled with C-x <. In this case, ;; the result of (scroll-left 0) is wrong. It does not ;; account for the zoom. So we have to reconstruct the actual ;; value using the text-scale-mode-amount variable, which ;; contains the number of x1.2 zooms (1.2 is stored in the ;; text-scale-mode-step variable). ;; ;; But reconstruction trying to reverse a computation which ;; mixes floating point numbers along with rounding to ;; integers is impossible to do accurately. Therefore, the ;; mouse placement when the window is zoomed AND scrolled ;; horizontally is not perfect. ;; ;; Moreover, each click generates 1, 2, or 3 events. The ;; first one is used by Uniline to add blank characters if ;; needed, and adjust the point. The following events also ;; need their point being adjusted as well. But the following ;; events do not have the information that blanks have been ;; added. We do not want to adjust the point if the buffer ;; have not received additional blanks, because in this case ;; the point store in the event is right and accurate. We do ;; not want to ruin it with an unreliable approximation. ;; Therefore we put in place a variable used by the 1st event ;; to communicate information the the 2nd and 3th events. ;; ;; The Uniline intercepting functions are attached to the ;; uniline-mode keymap. Therefore they are active only in ;; uniline-mode. ;; ;; Nothing happens in a non-graphical environment, although ;; the interceptions and keymap-bindings are still present. ;; ;; Picture-mode & Artist-mode also handle the mouse. The ;; picture-mode way, unfortunately, breaks down when the ;; buffer is zoomed (C-x C-+). ;; ;; The artist-mode way smoothly handles zooming. But it is ;; completely off when window is zoomed and horizontally ;; scrolled. Artist-mode re-implements parts of the standard ;; Emacs event handling. This is because it needs to draw ;; while moving the mouse in many different styles. The ;; uniline-mode does not need this complexity. (require 'face-remap) (defun uniline--scroll-horiz () "Compute the actual horizontal scroll. There is a bug in Emacs that this function tries to fix. The scroll is returned by Emacs as a number of character, which is fine. But Emacs assumes that the window is not zoomed. When it is, the result is wrong. Here the scroll is zoomed back. Unfortunately, this is not reliable no matter what is attempted. Zoom is a floating point as powers of 1.2. Scroll is an integer number of characters." (let ((scroll (scroll-left 0))) (cond ((> text-scale-mode-amount 0) (cl-loop repeat text-scale-mode-amount do (setq scroll (ceiling (/ scroll text-scale-mode-step))))) ((< text-scale-mode-amount 0) (cl-loop repeat (- text-scale-mode-amount) do (setq scroll (ceiling (* scroll text-scale-mode-step)))))) scroll)) (defun uniline--intercept-mouse-1 (position) "Add blanks characters if mouse click falls outside buffer. Also adjust the buffer position coded in POSITION, so that it will be located right under the mouse event." ;; make the clicked window the selected one (set-frame-selected-window nil (posn-window position) t) (let* ((firstcol (uniline--scroll-horiz)) (firstlin (1- (line-number-at-pos (window-start)))) (last (point-max)) (pxy (posn-x-y position)) (wxy (posn-object-width-height position)) (x (+ (/ (car pxy) (car wxy)) firstcol)) (y (+ (/ (cdr pxy) (cdr wxy)) firstlin))) ;; possibly add blank characters (uniline-move-to-lin-col y x) (if (<= (point-max) last) ;; case where no blank characters where added ;; the point in the event can be trusted () ;; nothing to change ;; blanks were added (when (eobp) (insert "\n") (forward-char -1)) (setf (nth 1 position) (point)) (setf (nth 5 position) (point))))) (defun uniline-mouse-set-point (event &optional promote-to-region) "Drop-in replacement for the base mouse-set-point. It adds blank characters if necessary." (interactive "e\np") (uniline--intercept-mouse-1 (event-end event)) (mouse-set-point event promote-to-region)) ;;;╭──────────────╮ ;;;│Customizations│ ;;;╰──────────────╯ (defun uniline-customize-hydra-or-transient (type) "Attempt to tweak .emacs to setup the type of interface. TYPE is \"hydra\" or \"transient\"." (interactive) (let ((message)) (find-file user-init-file) (goto-char (point-min)) (if (re-search-forward (rx bol (* (not ";")) "uniline-" (group (+ word))) nil t) (let ((current (match-string 1))) (beginning-of-line) (cond ((string= current type) (setq message (format "Already configured as uniline-%s" type))) ((or (string= current "hydra") (string= current "transient")) (setq message "It seems your current configuration is here.")) (t (setq message (format "It seems your current configuration is the unknown uniline-%s" current))))) (setq message "It seems that nothing is currently configured in .emacs.") (goto-char (point-max))) (kill-new (format "(use-package uniline-%s\n :bind (\"C-\" . uniline-mode))\n" type)) (message "%s\nType C-y to insert the suggested new configuration." message))) ;;;╭──────────────────╮ ;;;│Uniline minor mode│ ;;;╰──────────────────╯ (defgroup uniline nil "Draw Unicode lines" :group 'text :link '(url-link :tag "GitHub" "https://github.com/tbanel/uniline")) (defcustom uniline-cursor-type 'hollow "The suggested cursor in Uniline is a the hollow one, because it has no prefered direction (up, down, right, left), and the character under the cursor remains visible. Yet, the cursor style is a matter of preference, so any possible choice is available." :type '(choice (const :tag "Frame default" t) (const :tag "Filled box" box) (cons :tag "Box with specified size" (const box) integer) (const :tag "Hollow cursor" hollow) (const :tag "Vertical bar" bar) (cons :tag "Vertical bar with specified height" (const bar) integer) (const :tag "Horizontal bar" hbar) (cons :tag "Horizontal bar with specified width" (const hbar) integer) (const :tag "None "nil)) :local t :group 'uniline) ;; toggle between normal hydra hints, and one-liners (defcustom uniline-hint-style t "Which kind of hint message should the Hydras menus display? t: large and detailed menus 1: one-line non-disturbing menus in the echo area 0: no menus Those values are loosely in sync with those defined by the `:verbosity' Hydra property." :type '(choice (const :tag "full fledged hints" t) (const :tag "one liner hints" 1) (const :tag "no hint" 0)) :local t :group 'uniline) (eval-and-compile (defun uniline--color-hint (face hint) "Return a colored message mimicking the Hydra way. HINT is the message string. It contains pairs of ^xxx^ carets which are to be removed from the message, while the text within will be colored. FACE is the face used to color text." (interactive) (replace-regexp-in-string "\\^.*?\\^" (lambda (x) (setq x (substring x 1 (1- (length x)))) (add-face-text-property 0 (length x) face nil x) x) hint t))) (defcustom uniline-show-welcome-message t "Whether to show the welcome message upon activating uniline-mode." :type 'boolean :group 'uniline) (defun uniline-dismiss-welcome-message () (interactive) (customize-variable 'uniline-show-welcome-message)) (defun uniline--welcome-message () "Display a message giving the main key-bindings of the minor mode." (interactive) (let ((message-log-max)) (message (cond ((eq uniline-hint-style t) (eval-when-compile (uniline--color-hint 'error "\ ╭─^^────────────╴Uniline╶╴mode╶─────────────────────────────╮ │^(Ctrl) → ↓ ← ↑^ (overwrite)/draw lines with current brush│ │^Shift → ↓ ← ↑^ extend selection │ │^- + = # DEL RET^ change brush style │ │^INS^ without selection insert glyphs, change font │ │^INS^ with selection handle rectangles │ │^C-h TAB^ switch small/large hints │ │^C-h DEL^ dismiss this message in the future│ │^C-c C-c^ quit uniline │ ╰─^^────────────────────────────────────────────────────────╯"))) ((eq uniline-hint-style 1) (eval-when-compile (uniline--color-hint 'error "trace: ^←→↑↓^ ovwr: ^C-←→↑↓^ selec: ^C-←→↑↓^ brush: ^-+=# DEL RET^ menu: (sel)^INS^ hint: ^C-h TAB^"))) (t nil))))) (declare-function uniline-toggle-hints "" (&optional notoggle)) (defun uniline-toggle-hints-welcome () "Toggle between styles of hydra hints, and display welcome message." (interactive) (uniline-toggle-hints) (uniline--welcome-message)) (defvar-local uniline--remember-settings nil "Remember settings before entering uniline minor-mode. It is a list containing: - `overwrite-mode' - `indent-tabs-mode' - `truncate-lines' - `cursor-type' - `post-self-insert-hook'") (defun uniline--mode-pre () "Change settings when entering uniline mode. And backup previous settings." (setq uniline--remember-settings (list overwrite-mode indent-tabs-mode truncate-lines cursor-type post-self-insert-hook)) (overwrite-mode 1) (indent-tabs-mode 0) (setq truncate-lines t) (setq cursor-type uniline-cursor-type) (add-hook 'post-self-insert-hook #'uniline--post-self-insert nil 'local) (uniline--fix-char-width-table) (uniline-toggle-hints t) (uniline--update-mode-line) (if uniline-show-welcome-message (uniline--welcome-message))) (defun uniline--mode-post () "Restore settings when exiting uniline mode." (overwrite-mode (if (nth 0 uniline--remember-settings) 1 0)) (indent-tabs-mode (if (nth 1 uniline--remember-settings) 1 0)) (setq truncate-lines (nth 2 uniline--remember-settings) cursor-type (nth 3 uniline--remember-settings) post-self-insert-hook (nth 4 uniline--remember-settings))) ;; This `unintern' instruction is useful during development ;; to ensure that M-x eval-buffer reloads 100% of the Lisp code ;; (unintern 'uniline-mode-map nil) (define-minor-mode uniline-mode "Minor mode to draw lines, boxes, & arrows using UNICODE characters. ┏━━━━━━━┓ ┏━━━━◀━━┫ thick ┣═◁═╗ ╭───┸──╮ ┃ box ┃ ║ │ thin │ ┗━━┯━━━━┛ ║ │ box ├───●───╯ ╔══════╩═╗ ╰───┬──╯ ╰─────╢ double ║ ╰───────▷────╢ box ║ ╚════════╝ here╶──────────────╮ ↓ △ ╭────────╮ ┏━━━━━┷━━━━━┓ A │ │ A+X │ ┃ A+Y ┃ │ │ │ ┃ ┃ ▽ ╰────────╯ ┗━━━━━━━━━━━┛ △ ╭────────╮ ╭───────────╮ B │ │ B+X │ │ B+Y │ ▽ ╰────────╯ ╰───────────╯ ◀━━━━━━━━▶◀━━━━━━━━━━━━▶ X Y v △ ▗▖ ▗ a │ ▗▟▄▟██▖ ▗▄▄▟█ l │ ▐▄███████▄ ▟█████▙ ▄▖ u─┴╴▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ e ├───time────────────▷ ╭─Keyboard arrows────────────╴ │ Use keyboard arrows to draw lines ╭─┲━╦═╗ │ Use control-arrows to overwrite whatever was there │ Use shift-arrows to extend the selection (or start a selection) ╰────────────────────────────╴ ╭─Brush style────────────────╴\\ │ \\[uniline-set-brush-1] for thin lines ╭─┬─╮ │ \\[uniline-set-brush-2] for thick lines ┏━┳━┓ │ \\[uniline-set-brush-3] for double lines ╔═╦═╗ │ \\[uniline-set-brush-block] for blocks ▙▄▟▀ │ \\[uniline-set-brush-0] to erase lines │ \\[uniline-set-brush-nil] to move cursor without drawing ╰────────────────────────────╴ ╭─Glyphs (region inactive)───╴ │ \\[uniline-launch-interface] when there is NO region highlighted, │ enter a sub-mode to draw a single character glyph, │ and change its orientation. ├─Intersection glyphs────────╴ │ \\`a' or \\`A' arrows ▷ ▶ → ▹ ▸ ↔ │ \\`s' or \\`S' squares □ ■ ◆ ◊ │ \\`o' or \\`O' circles · ● ◦ Ø ø │ \\`x' or \\`X' crosses ╳ ╱ ╲ ÷ × ± ¤ │ Shifting the key cycles backward ├─Arrow direction────────────╴ │ \\`S-' point arrow → right │ \\`S-' point arrow ← left │ \\`S-' point arrow ↑ up │ \\`S-' point arrow ↓ down ├─Tweak 1/4 line─────────────╴ │ \\`S-' change ¼ line → right │ \\`S-' change ¼ line ← left │ \\`S-' change ¼ line ↑ up │ \\`S-' change ¼ line ↓ down ├─Text direction─────────────╴ │ Usually when typing text, cursor moves to the right. │ \\`C-' text goes right→ │ \\`C-' text goes left ← │ \\`C-' text goes up ↑ │ \\`C-' text goes down ↓ ├─Insert characters──────────╴ │ In this sub-mode, the keys \\`-' \\`+' \\`=' \\`#' recover their │ basic meaning, which is to insert this character. ├─Other──────────────────────╴ │ \\`f' enter the fonts sub-menu │ \\`RET' or \\`q' exits the sub-mode │ Any other key exits the sub-mode and do whatever they │ are intended for. ╰────────────────────────────╴ ╭─Rectangles (region active)─╴ │ \\[uniline-launch-interface] when region IS highlighted, │ enter a sub-mode to handle rectangles, │ marked by the highlighted region. ├─Move rectangle─────────────╴ │ \\`S-' move rectangle → right │ \\`S-' move rectangle ← left │ \\`S-' move rectangle ↑ up │ \\`S-' move rectangle ↓ down ├─Draw rectangle─────────────╴ │ \\`r' draw an inner rectangle │ \\`R' draw an outer rectangle │ \\`C-r' overwrite an inner rectangle │ \\`C-S-R' overwrite an outer rectangle ├─Fill───────────────────────╴ │ \\`i' fill region with a character ├─Other──────────────────────╴ │ \\`C-_', \\`C-/', \\`C-x u' undo works outside selection │ \\`RET', \\`q' exit the rectangle sub-mode │ Any other key exits the sub-mode and do whatever they │ are intended for. ╰────────────────────────────╴ ╭╴Macros─────────────────────╴ │ Usual Emacs macros recording works as usual │ Last keybord macro can be twisted in any of the 4 directions │ \\[uniline-macro-exec] then \\`→' \\`←' \\`↑' \\`↓': directional call of last keyboard macro ╰────────────────────────────╴ ╭╴Alternate styles───────────╴ │ Highlight a region (a rectangle) then \\[uniline-launch-interface] \\`s' │ This enters a menu where alternative styles are applied │ to the rectangular selection │ \\`3' make 3 dots vertical, 2 dots horizontal lines │ \\`4' make 4 dots vertical and horizontal lines │ \\`h' convert round corners to hard ones │ \\`-' make thin lines │ \\`+' make thick lines │ \\`=' make double lines │ \\`0' come back to standard base line style, including from ASCII art │ \\`a' apply external package aa2u conversion from ASCII art to UNICODE ╰────────────────────────────╴ ╭─Fonts──────────────────────╴ │ Try out some mono-spaced fonts with support for the │ required UNICODE characters. │ \\[uniline-launch-interface] \\`f' enters a sub-menu to change the font │ type the first letter of the font name. │ This setting is just for the current Emacs session. │ \\`*' customize default font for future sessions. ╰────────────────────────────╴ ╭─Toggle hint sizes──────────╴ │ This is for changing the height of Hydra menus, │ between multiline to single-line and back, │ \\[uniline-toggle-hints-welcome] in base Uniline mode │ \\`TAB' in a \\[uniline-launch-interface]-activated menu ╰────────────────────────────╴ ╭─Quit───────────────────────╴\\ │ \\[uniline-mode] quit the Uniline minor mode. │ The state of the buffer (ex: `overwrite-mode' and cursor shape) │ will return to what it was prior to entering `uniline-mode' ╰────────────────────────────╴ Documentation here: (info \"uniline\")" :init-value nil ;; ╭───╴without that, mouse-1 on mode-line does not display the menu ;; ▽ :lighter (:eval (format " %sUniline%s" uniline--mode-line-dir uniline--mode-line-brush)) :keymap ;; defines uniline-mode-map '(([right] . uniline-write-ri→) ([left ] . uniline-write-lf←) ([up ] . uniline-write-up↑) ([down ] . uniline-write-dw↓) ([C-right] . uniline-overwrite-ri→) ([C-left ] . uniline-overwrite-lf←) ([C-up ] . uniline-overwrite-up↑) ([C-down ] . uniline-overwrite-dw↓) ([insert] . uniline-launch-interface) ([insertchar] . uniline-launch-interface) ([?\r] . uniline-set-brush-nil) ([delete] . uniline-set-brush-0) ([deletechar] . uniline-set-brush-0) ("-" . uniline-set-brush-1) ([kp-subtract] . uniline-set-brush-1) ("+" . uniline-set-brush-2) ([kp-add] . uniline-set-brush-2) ("=" . uniline-set-brush-3) ("#" . uniline-set-brush-block) ("~" . uniline-set-brush-dot-toggle) ([?\C-x ?e] . uniline-macro-exec) ([?\C-h ?\t] . uniline-toggle-hints-welcome) ([?\C-h delete] . uniline-dismiss-welcome-message) ([?\C-h deletechar] . uniline-dismiss-welcome-message) ([mouse-1] . uniline-mouse-set-point ) ([?\C-c ?\C-c] . uniline-mode)) :after-hook (if uniline-mode (uniline--mode-pre) (uniline--mode-post))) (defun uniline--keymap-remove-launch-interface (keymap) "Remove key-bindings in KEYMAP whose action is `uniline-launch-interface'. Do that recursively in child keymaps too." (cl-loop for on on keymap for bind = (car on) if (consp bind) do (cond ((eq (cdr bind) 'uniline-launch-interface) (setcar on nil)) ((and (consp (cdr bind)) (eq (cadr bind) 'keymap)) (uniline--keymap-remove-launch-interface (cdr bind))))) (delq nil keymap)) (defun uniline--set-insert-key (symbol keys) "Replace all key-bindings pointing to `uniline-launch-interface' with custom bindings to each key in KEYS. _SYMBOL is not used." (uniline--keymap-remove-launch-interface uniline-mode-map) (cl-loop for key in keys do (keymap-set uniline-mode-map key 'uniline-launch-interface)) (set-default-toplevel-value symbol keys)) (defcustom uniline-key-insert '("" "") "Prefix key (or keys) to invoke all Uniline minor mode functions. and by default. Use C-h k, then type a key (or key combination) to see the exact syntax. Do not confuse this key (which is active inside Uniline-mode) with the one used to invoke Uniline-mode." :type '(repeat (key)) :set 'uniline--set-insert-key :group 'uniline) (defun uniline-about () "Print a message containing the MELPA version and the repository URL." (interactive) (let ((path (cl-loop for p in load-path if (string-match (rx "/uniline-" (group (+ (any "0-9."))) eos) p) collect (match-string 1 p)))) (setq path (if (listp path) (car path) "")) (message "Uniline %s, https://github.com/tbanel/uniline" path))) (defvar uniline--current-interface nil "Remember whether Hydra or Transient is loaded. Its value is ?h or ?t") (easy-menu-define uniline-menu uniline-mode-map ;; ╭─that makes this menu appear upon clicking on the mode-line ;; ╰────────────────────────╮ ;; ▽ "Uniline mode menu. \\{uniline-mode-map}" '("Uniline" :visible t :active t ["Write right" uniline-write-ri→ t] ["Write left" uniline-write-lf← t] ["Write up" uniline-write-up↑ t] ["Write down" uniline-write-dw↓ t] ("Overwrite" ["Overwrite right" uniline-overwrite-ri→ t] ["Overwrite left" uniline-overwrite-lf← t] ["Overwrite up" uniline-overwrite-up↑ t] ["Overwrite down" uniline-overwrite-dw↓ t]) "----" ["─ light brush" uniline-set-brush-1 :style radio :selected (eq uniline-brush 1 )] ["━ heavy brush" uniline-set-brush-2 :style radio :selected (eq uniline-brush 2 )] ["═ double brush" uniline-set-brush-3 :style radio :selected (eq uniline-brush 3 )] ["▞ blocks brush" uniline-set-brush-block :style radio :selected (eq uniline-brush :block)] ["eraser brush" uniline-set-brush-0 :style radio :selected (eq uniline-brush 0 )] ["inactive brush" uniline-set-brush-nil :style radio :selected (eq uniline-brush nil )] "----" ["┄ 3-2 dots brush" uniline-set-brush-3dots :style radio :selected (eq uniline-brush-dots 1) :keys "~" ] ["┄ 4-4 dots brush" uniline-set-brush-4dots :style radio :selected (eq uniline-brush-dots 2) :keys "~~" ] ["┄ no dots brush" uniline-set-brush-0dots :style radio :selected (eq uniline-brush-dots 0) :keys "~~~"] "----" ("Insert glyph" ["Insert arrow ▷ ▶ → ▹ ▸ ↔" uniline-insert-fw-arrow :keys "INS a"] ["Insert square □ ■ ◆ ◊" uniline-insert-fw-square :keys "INS s"] ["Insert oshape · ● ◦ Ø ø" uniline-insert-fw-oshape :keys "INS o"] ["Insert cross ╳ ╱ ╲ ÷ × ± ¤" uniline-insert-fw-cross :keys "INS x"]) ("Rotate arrow, tweak ¼ line" ["Rotate arrow, tweak ¼ line → right" uniline-rotate-ri→ :keys "INS S-"] ["Rotate arrow, tweak ¼ line ← left" uniline-rotate-lf← :keys "INS S-" ] ["Rotate arrow, tweak ¼ line ↑ up" uniline-rotate-up↑ :keys "INS S-" ] ["Rotate arrow, tweak ¼ line ↓ down" uniline-rotate-dw↓ :keys "INS S-" ]) ("Rectangular region" :active (region-active-p) ["Move selection right" uniline-move-rect-ri→ :keys "INS "] ["Move selection left" uniline-move-rect-lf← :keys "INS " ] ["Move selection up" uniline-move-rect-up↑ :keys "INS " ] ["Move selection down" uniline-move-rect-dw↓ :keys "INS " ] "----" ["Copy" uniline-copy-rectangle :keys "INS c"] ["Kill" uniline-kill-rectangle :keys "INS k"] ["Yank, paste" uniline-yank-rectangle :keys "INS y"] "----" ["Trace rectangle inside selection" uniline-draw-inner-rectangle :keys "INS r" ] ["Trace rectangle around selection" uniline-draw-outer-rectangle :keys "INS R" ] ["Overwrite rectangle inside selection" uniline-overwrite-inner-rectangle :keys "INS C-r"] ["Overwrite rectangle around selection" uniline-overwrite-outer-rectangle :keys "INS C-R"] ["Fill" uniline-fill-rectangle :keys "INS i" ]) ("Alternate styles" :active (region-active-p) ["─ thin lines" uniline-change-style-thin :keys "INS s -"] ["━ thick lines" uniline-change-style-thick :keys "INS s +"] ["═ double lines" uniline-change-style-double :keys "INS s ="] ["╌ 3 dots vert, 2 dots horiz" uniline-change-style-dot-3-2 :keys "INS s 3"] ["┈ 4 dots vert & horiz" uniline-change-style-dot-4-4 :keys "INS s 4"] ["┌ hard corners" uniline-change-style-hard-corners :keys "INS s h"] ["╭ back to standard" uniline-change-style-standard :keys "INS s 0"] ["aa2u (ext. package)" uniline-aa2u-rectangle :keys "INS s a"]) ("Fill & contour" ["Contour" uniline-contour :keys "INS c"] ["Contour overw" (uniline-contour t) :keys "INS C"] ["Fill" uniline-fill :keys "INS i"]) ("Text insertion direction" ["→ right" uniline-text-direction-ri→ :keys "INS C-" :style radio :selected (eq uniline-text-direction (uniline-direction-ri→))] ["← left" uniline-text-direction-lf← :keys "INS C- " :style radio :selected (eq uniline-text-direction (uniline-direction-lf←))] ["↑ up" uniline-text-direction-up↑ :keys "INS C- " :style radio :selected (eq uniline-text-direction (uniline-direction-up↑))] ["↓ down" uniline-text-direction-dw↓ :keys "INS C- " :style radio :selected (eq uniline-text-direction (uniline-direction-dw↓))]) "----" ("Font" ["DejaVu Sans Mono" (set-frame-font "DejaVu Sans Mono" ) :keys "INS f d" :style radio :selected (uniline--is-font ?d)] ["Hack" (set-frame-font "Hack" ) :keys "INS f h" :style radio :selected (uniline--is-font ?h)] ["Cascadia Mono" (set-frame-font "Cascadia Mono" ) :keys "INS f c" :style radio :selected (uniline--is-font ?c)] ["JuliaMono" (set-frame-font "JuliaMono" ) :keys "INS f j" :style radio :selected (uniline--is-font ?j)] ["JetBrains Mono" (set-frame-font "JetBrains Mono" ) :keys "INS f b" :style radio :selected (uniline--is-font ?b)] ["FreeMono" (set-frame-font "FreeMono" ) :keys "INS f f" :style radio :selected (uniline--is-font ?f)] ["Source Code Pro" (set-frame-font "Source Code Pro" ) :keys "INS f s" :style radio :selected (uniline--is-font ?s)] ["Iosevka Comfy Fixed" (set-frame-font "Iosevka Comfy Fixed" ) :keys "INS f i" :style radio :selected (uniline--is-font ?i)] ["Iosevka Comfy Wide Fixed" (set-frame-font "Iosevka Comfy Wide Fixed") :keys "INS f I" :style radio :selected (uniline--is-font ?I)] ["Aporetic Sans Mono" (set-frame-font "Aporetic Sans Mono" ) :keys "INS f p" :style radio :selected (uniline--is-font ?p)] ["Aporetic Serif Mono" (set-frame-font "Aporetic Serif Mono" ) :keys "INS f P" :style radio :selected (uniline--is-font ?P)] ["Unifont" (set-frame-font "Unifont" ) :keys "INS f u" :style radio :selected (uniline--is-font ?u)] ["Agave" (set-frame-font "Agave" ) :keys "INS f a" :style radio :selected (uniline--is-font ?a)] ["Configure permanently" uniline-customize-face :keys "INS f *"]) ("Customize" ["Current session only:" :selected nil] ["Large hints sizes" uniline-toggle-hints :keys "C-t or C-h C-t" :style toggle :selected (eq uniline-hint-style t)] ["Hydra" (load-library "uniline-hydra" ) :style radio :selected (eq uniline--current-interface ?h)] ["Transient" (load-library "uniline-transient") :style radio :selected (eq uniline--current-interface ?t)] "----" ["Future sessions:" :selected nil] ["Uniline Group" (customize-group 'uniline)] ["Hydra (change .emacs)" (uniline-customize-hydra-or-transient 'hydra ) ] ["Transient (change .emacs)" (uniline-customize-hydra-or-transient 'transient) ] ["Line spacing" (customize-variable 'line-spacing)] ) ["Info" (info "uniline") :keys "M-: (info \"uniline\")"] ["Quit Uniline Mode" uniline-mode t] ["About" uniline-about t])) (provide 'uniline-core) ;;; uniline-core.el ends here ================================================ FILE: uniline-hydra.el ================================================ ;;; uniline-hydra.el --- Add▶ ■─UNICODE based diagrams─■ to▶ ■─text files─■ -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; Package-Requires: ((emacs "29.1") (hydra "0.15.0")) ;; Keywords: convenience, text ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;;; Commentary: ;; ┏━━━━━━━┓ ;; ╭──────╮ ┃ thick ┣═◁═╗ ;; │ thin ┝◀━━━┫ box ┃ ║ ;; │ box │ ┗━━━━━━━┛ ║ ;; ╰───┬──╯ ╔══════╩═╗ ;; ↓ ║ double ║ ;; ╰────────────╢ box ║ ;; ╚════╤═══╝ ;; ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ │ ;; ▌quadrant-blocks▐─◁─╯ ;; ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ ;; ;;╭─Pure text────────────────□ ;;│ UNICODE characters are available to draw nice boxes and lines. ;;│ They come in 4 flavours: thin, thick, double, and quadrant-blocks. ;;│ Uniline makes it easy to draw and combine all 4 flavours. ;;│ Use the arrows on the keyboard to move around leaving a line behind. ;;╰──────────────────────────╮ ;;╭─Minor mode───────────────╯ ;;│ Uniline is a minor mode. Enter it with: ;;│ M-x uniline-mode ;;│ Leave it with: ;;│ C-c C-c ;;╰──────────────────────────╮ ;;╭─Fonts────────────────────╯ ;;│ A font able to displays the needed UNICODE characters have to ;;│ be used. It works well with the following families: ;;│ - DejaVu Sans Mono ;;│ - Unifont ;;│ - Hack ;;│ - JetBrains Mono ;;│ - Cascadia Mono ;;│ - Agave ;;│ - JuliaMono ;;│ - FreeMono ;;│ - Iosevka Comfy Fixed, Iosevka Comfy Wide Fixed ;;│ - Aporetic Sans Mono, Aporetic Serif Mono ;;│ - Source Code Pro ;;╰──────────────────────────╮ ;;╭─UTF-8────────────────────╯ ;;│ Also, the encoding of the file must support UNICODE. ;;│ One way to do that, is to add a line like this one ;;│ at the top of your file: ;;│ -*- coding:utf-8; -*- ;;╰──────────────────────────╮ ;;╭─Hydra or Transient───────╯ ;;│ Uniline comes with two flavours of user interfaces: ;;│ Hydra and Transient. ;;│ Both versions are compiled when installing the package. ;;│ ;;│ Then one or the other packages must be loaded (not both) ;;│ for example with: ;;│ (require 'uniline-hydra) ;;│ or ;;│ (use-package uniline-hydra ;;│ :bind ("C-" . uniline-mode)) ;;│ ;;│ This file, uniline-hydra.el, implements the Hydra interface ;;│ and calls the functions defined by uniline-core.el ;;╰──────────────────────────□ ;;; Requires: (require 'uniline-core) ;; (require 'hydra) ;; no hard dependency ;;; Code: (eval-when-compile ;; temporarily fix a bug about Hydra generating too long docstrings (setq byte-compile-docstring-max-column 2000)) ;;;╭────────────────╮ ;;;│Hydra interfaces│ ;;;╰────────────────╯ (require 'hydra nil t) (unless (featurep 'hydra) (eval-and-compile (defun uniline-launch-interface () "Fake function only when Hydra requested but not installed" (interactive) (warn "Uniline-Hydra requested, but Hydra is not installed.")))) (eval-and-compile (declare-function uniline-transient-customize nil ()) (put 'uniline-transient-customize 'interactive-only nil) ;; to avoid a warning (declare-function uniline-customize-hydra-or-transient (type))) (when (featurep 'hydra) (eval-and-compile (defun uniline--is-font-str (letter) "Return a tick-glyph ▶ if current font is the one presented by LETTER." (if (uniline--is-font letter) "▶" " ")) (defhydra uniline-hydra-fonts (:hint nil :exit nil) ;; Docstring MUST begin with an empty line to benefit from substitutions (concat (replace-regexp-in-string "_\\([dhcjbfsiIuapP]\\)_ " "_\\1_%s(uniline--is-font-str ?\\1)" "\ ╭^─Try a font^──^─^───────────^─^───────────────────╮╭^─^───^─^──────╮ │_d_ DejaVu _b_ JetBrains _i_ Iosevka Comfy ││_*_ ^^configure│ │_h_ Hack _f_ FreeMono _I_ Iosevka Comfy Wide││_C-t_^^ tg hint│ │_c_ Cascadia _a_ Agave _p_ Aporetic Sans ││_?_ ^^info-mode│ │_j_ JuliaMono _u_ Unifont _P_ Aporetic Serif ││_RET_ _q_ exit│ │_s_ Source Code Pro^^╭───────^─^───────────────────╯╰^─^───^─^──────╯ ╰^─^────────────^─^───╯")) ("d" uniline--set-font-d) ("u" uniline--set-font-u) ("h" uniline--set-font-h) ("b" uniline--set-font-b) ("c" uniline--set-font-c) ("a" uniline--set-font-a) ("j" uniline--set-font-j) ("f" uniline--set-font-f) ("i" uniline--set-font-i) ("I" uniline--set-font-I) ("p" uniline--set-font-p) ("P" uniline--set-font-P) ("s" uniline--set-font-s) ("*" uniline-customize-face :exit t) ("C-t" uniline-toggle-hints) ("TAB" uniline-toggle-hints) ("?" (info "(uniline) Which fonts?")) ("q" () :exit t) ("RET" () :exit t)) (defhydra uniline-hydra-customize (:hint nil :exit t) " ╭^^╴current session╶╮╭^^╴future sessions╶───────────╮ │_f_ fonts ││_g_ Uniline group (settings) │ │_t_ transient ││_H_ Hydra (change .emacs) │ │_?_ info ││_T_ Transient (change .emacs) │ │_C-t_ large hints ││_l_ line spacing │ ╰^^─────────────────╯╰^^────────────────────────────╯" ("C-t" uniline-toggle-hints :exit nil) ("TAB" uniline-toggle-hints :exit nil) ("f" uniline-hydra-fonts/body) ("t" (progn (load-library "uniline-transient") (uniline-transient-customize))) ("?" (info "(uniline) Customization")) ("g" (customize-group "uniline")) ("H" (uniline-customize-hydra-or-transient "hydra" )) ("T" (uniline-customize-hydra-or-transient "transient")) ("l" (customize-variable (intern "line-spacing")))) ;; intern to avoid a quote (defhydra uniline-hydra-arrows (:hint nil :exit nil) ;; Docstring MUST begin with an empty line to benefit from substitutions (concat (string-replace "Text dir────" "Text dir─╴%s(uniline-text-direction-str)╶" "\ ╭^─^─^Insert glyph^^^^^─^─^───╮╭─╮╭^Rotate arrow^╮╭^Contour^╮╭^Text dir────^╮╭^─^─^─^─────────╮ │_a_,_A_rrow ▷ ▶ → ▹ ▸ ↔^^^^^^││-│╭^Tweak glyph─^╮│_c_ draw ││_C-_ ← ││_*_^^ customize │ │_s_,_S_quare □ ■ ◆ ◊ ^^^^^^││+││_S-_ ← ││_C_ ovwrt││_C-_ → ││_f_^^ font │ │_o_,_O_-shape · ● ◦ Ø ø^^^^^^││=││_S-_ → │╰^───────^╯│_C-_ ↑ ││_?_^^ info │ │_x_,_X_-cross ╳ ÷ × ± ¤^^^^^^││#││_S-_ ↑ │╭^─Fill──^╮│_C-_ ↓ ││_q_ _RET_ exit │ │_SPC_,_DEL_ grey ░▒▓█ ^^^^^^││~││_S-_ ↓ ││_i_ fill │╰^─^───────────╯╰^─^─^─^─────────╯ ╰^─^─^─^─^─^─^─^─^─^──────────╯╰─╯╰^────────────^╯╰^───────^╯")) ("a" uniline-insert-fw-arrow ) ("A" uniline-insert-bw-arrow ) ("s" uniline-insert-fw-square) ("S" uniline-insert-bw-square) ("o" uniline-insert-fw-oshape) ("O" uniline-insert-bw-oshape) ("x" uniline-insert-fw-cross ) ("X" uniline-insert-bw-cross ) ("SPC" uniline-insert-fw-grey) ("DEL" uniline-insert-bw-grey) ("S-" uniline-rotate-lf←) ("S-" uniline-rotate-ri→) ("S-" uniline-rotate-up↑) ("S-" uniline-rotate-dw↓) ("C-" uniline-text-direction-ri→ :exit t) ("C-" uniline-text-direction-lf← :exit t) ("C-" uniline-text-direction-up↑ :exit t) ("C-" uniline-text-direction-dw↓ :exit t) ("" uniline--self-insert--) ("" uniline--self-insert-+) ("-" self-insert-command) ("+" self-insert-command) ("=" self-insert-command) ("#" self-insert-command) ("~" self-insert-command) ("f" uniline-hydra-fonts/body :exit t) ("c" uniline-contour :exit t) ("C" (uniline-contour t) :exit t) ("i" uniline-fill :exit t) ("C-t" uniline-toggle-hints) ("TAB" uniline-toggle-hints) ("*" uniline-hydra-customize/body :exit t) ("?" (info "uniline") :exit t) ("q" () :exit t) ("RET" () :exit t)) (defhydra uniline-hydra-alt-styles (:pre (rectangle-mark-mode 1) :hint nil :exit nil) ;; Docstring MUST begin with an empty line to benefit from substitutions " ╭^Thickness^╮╭^─Alt styles^──╮╭^Base style^╮╭^─^─^─^──────────────╮ │_-_ thin ││_3_ 3x2 dots ││_0_ standard││_f_ ^^ choose font│ │_+_ thick ││_4_ 4x4 dots ││_a_ aa2u ││_C-t_ ^^ short hint │ │_=_ double ││_h_ hard corner│╰─^─^────────╯│_?_ ^^ info-mode │ ╰^─^────────╯╰^─^────────────╯ ^ ^ │_q_ _RET_ exit │ ^ ^ ^ ^ ^ ^ ╰^─^─^─^──────────────╯" ("3" uniline-change-style-dot-3-2) ("" uniline-change-style-dot-3-2) ("4" uniline-change-style-dot-4-4) ("" uniline-change-style-dot-4-4) ("h" uniline-change-style-hard-corners) ("0" uniline-change-style-standard) ("" uniline-change-style-standard) ("-" uniline-change-style-thin) ("" uniline-change-style-thin) ("+" uniline-change-style-thick) ("" uniline-change-style-thick) ("=" uniline-change-style-double) ("a" uniline-aa2u-rectangle) ;; copy here the bindings for handling rectangles ("" uniline-move-rect-ri→) ("" uniline-move-rect-lf←) ("" uniline-move-rect-up↑) ("" uniline-move-rect-dw↓) ("r" uniline-draw-inner-rectangle) ("R" uniline-draw-outer-rectangle) ("C-r" uniline-overwrite-inner-rectangle) ("C-S-R" uniline-overwrite-outer-rectangle) ("i" uniline-fill-rectangle) ("f" uniline-hydra-fonts/body :exit t) ("s" uniline-hydra-moverect/body :exit t) ;; misc. ("C-x C-x" rectangle-exchange-point-and-mark) ("C-t" uniline-toggle-hints) ("TAB" uniline-toggle-hints) ("?" (info "(uniline) Rectangular actions")) ("q" uniline--rect-quit :exit t) ("RET" uniline--rect-quit :exit t)) (defhydra uniline-hydra-moverect (:pre (rectangle-mark-mode 1) :hint nil :exit nil) ;; Docstring MUST begin with an empty line to benefit from substitutions " ╭^Move ^rect╮╭────^Draw^ rect────╮╭^─Rect^─╮╭^Brush^╮╭──^Misc^─────────╮ │__ →││_r_ trace inner││_c_ copy││_-_ ╭─╯││_s_ alt styles │ │__ ←││_R_ trace outer││_k_ kill││_+_ ┏━┛││_f_ choose font│ │__ ↑││_C-r_ ovewr inner││_y_ yank││_=_ ╔═╝││_C-t_ short hints│ │__ ↓││_C-S-R_ ovewr outer│╰^^──────╯│_#_ ▄▄▟││_?_ info │ ╰^─────^────╯│_i_ fill │ ^^╭──────╯_~_ ┄┄┄││_RET_ exit │ ^ ^ ╰^────^─────────────╯ ^^│__ DEL│╰^───^────────────╯ ^ ^ ^ ^ ^^╰^────────^────╯" ("" uniline-move-rect-ri→) ("" uniline-move-rect-lf←) ("" uniline-move-rect-up↑) ("" uniline-move-rect-dw↓) ("r" uniline-draw-inner-rectangle) ("R" uniline-draw-outer-rectangle) ("C-r" uniline-overwrite-inner-rectangle) ("C-S-R" uniline-overwrite-outer-rectangle) ("i" uniline-fill-rectangle) ("c" uniline-copy-rectangle :exit t) ("k" uniline-kill-rectangle :exit t) ("y" uniline-yank-rectangle) ("" uniline-set-brush-0) ("" uniline-set-brush-0) ("C-" uniline-set-brush-0) ("C-" uniline-set-brush-0) ("-" uniline-set-brush-1) ("" uniline-set-brush-1) ("+" uniline-set-brush-2) ("" uniline-set-brush-2) ("=" uniline-set-brush-3) ("#" uniline-set-brush-block) ("~" uniline-set-brush-dot-toggle) ("C-t" uniline-toggle-hints) ("TAB" uniline-toggle-hints) ("?" (info "(uniline) Rectangular actions")) ("f" uniline-hydra-fonts/body :exit t) ("s" uniline-hydra-alt-styles/body :exit t) ("C-x C-x" rectangle-exchange-point-and-mark) ("RET" uniline--rect-quit :exit t)) (defun uniline-launch-interface () "Choose between two Hydras based on selection. When selection is active, most likely user wants to act on a rectangle. Therefore the rectangle hydra is launched. Otherwise, the arrows & shapes hydra is invoked." (interactive) (let ((message-log-max)) ; avoid hint copied in *Messages* (if (region-active-p) (uniline-hydra-moverect/body) (uniline-hydra-arrows/body)))) (defhydra uniline-hydra-macro-exec (:hint nil :exit nil) ;; Docstring MUST begin with an empty line to benefit from substitutions " ╭^╴Call macro╶^───╮╭^^^^──────────────╮ │_e_ usual call ││_C-t_^^ short hint│ │__ call → ││_?_ ^^ info-mode │ │__ call ← ││_q_ _RET_ exit │ │__ call ↑ │╰^─^─^───^─────────╯ │__ call ↓ │ ╰^^───────────────╯" ("e" (kmacro-end-and-call-macro 1)) ("" uniline-call-macro-in-direction-ri→) ("" uniline-call-macro-in-direction-lf←) ("" uniline-call-macro-in-direction-up↑) ("" uniline-call-macro-in-direction-dw↓) ("C-t" uniline-toggle-hints) ("TAB" uniline-toggle-hints) ("?" (info "(uniline) Macros")) ("q" () :exit t) ("RET" () :exit t)) (defun uniline-macro-exec () (interactive) (uniline-hydra-macro-exec/body)) ;;;╭───────────────────╮ ;;;│Smaller hydra hints│ ;;;╰───────────────────╯ ;; Pack 2 hints in the usual uniline-hydra-*/hint variables ;; one is the standard hint created by `defhydra' ;; the other is a one-liner (setq uniline-hydra-arrows/hint `(if (eq uniline-hint-style t) ,uniline-hydra-arrows/hint ,(eval-when-compile (uniline--color-hint 'hydra-face-red "glyph:^aAsSoOxX SPC DEL-+=#~^ arr&tweak:^S-→←↑↓^ txt-dir:^C-→←↑↓^ ^c^ontour f^i^ll ^f^ont ^*^ ^C-t^"))) uniline-hydra-fonts/hint `(if (eq uniline-hint-style t) ,uniline-hydra-fonts/hint ,(eval-when-compile (uniline--color-hint 'hydra-face-red "font:^dhcjbfsiIpPua^ config:^*^ hint:^C-t^"))) uniline-hydra-customize/hint `(if (eq uniline-hint-style t) ,uniline-hydra-customize/hint ,(eval-when-compile (uniline--color-hint 'hydra-face-red "customize: ^t^ransient uniline-^g^roup .emacs:^H^ydra-^T^ransient ^l^ine-spc ^f^ont ^C-t^"))) uniline-hydra-moverect/hint `(if (eq uniline-hint-style t) ,uniline-hydra-moverect/hint ,(eval-when-compile (uniline--color-hint 'hydra-face-red "move:^→←↑↓^ trace:^rR C-rR^ copy-paste:^cky^ f^i^ll brush:^-+=# DEL^ ^s^tyle ^f^onts ^C-t^"))) uniline-hydra-macro-exec/hint `(if (eq uniline-hint-style t) ,uniline-hydra-macro-exec/hint ,(eval-when-compile (uniline--color-hint 'hydra-face-red "macro exec, usual:^e^ directional:^→←↑↓^ hint:^C-t^"))) uniline-hydra-alt-styles/hint `(if (eq uniline-hint-style t) ,uniline-hydra-alt-styles/hint ,(eval-when-compile (uniline--color-hint 'hydra-face-red "alt styles, thick:^-+=^ dashed:^34^ corners:^h^ standard:^0^ ^a^a2u ^C-t^")))) (defun uniline-toggle-hints (&optional notoggle) "Toggle between styles of hydra hints. When NOTOGGLE is t, do not toggle `uniline-hint-style', just put everything in sync." (interactive) (unless notoggle (setq uniline-hint-style (if (eq uniline-hint-style t) 1 t))) (cl-loop for hydra in '(uniline-hydra-arrows uniline-hydra-fonts uniline-hydra-customize uniline-hydra-moverect uniline-hydra-macro-exec uniline-hydra-alt-styles) do (hydra-set-property hydra :verbosity uniline-hint-style))) )) (defvar uniline--current-interface) (setq uniline--current-interface ?h) (provide 'uniline-hydra) ;;; uniline-hydra.el ends here ================================================ FILE: uniline-transient.el ================================================ ;;; uniline-transient.el --- Add▶ ■─UNICODE based diagrams─■ to▶ ■─text files─■ -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; Package-Requires: ((emacs "29.1") (transient "0.12.0")) ;; Keywords: convenience, text ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;;; Commentary: ;; ┏━━━━━━━┓ ;; ╭──────╮ ┃ thick ┣═◁═╗ ;; │ thin ┝◀━━━┫ box ┃ ║ ;; │ box │ ┗━━━━━━━┛ ║ ;; ╰───┬──╯ ╔══════╩═╗ ;; ↓ ║ double ║ ;; ╰────────────╢ box ║ ;; ╚════╤═══╝ ;; ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ │ ;; ▌quadrant-blocks▐─◁─╯ ;; ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ ;; ;;╭─Pure text────────────────□ ;;│ UNICODE characters are available to draw nice boxes and lines. ;;│ They come in 4 flavours: thin, thick, double, and quadrant-blocks. ;;│ Uniline makes it easy to draw and combine all 4 flavours. ;;│ Use the arrows on the keyboard to move around leaving a line behind. ;;╰──────────────────────────╮ ;;╭─Minor mode───────────────╯ ;;│ Uniline is a minor mode. Enter it with: ;;│ M-x uniline-mode ;;│ Leave it with: ;;│ C-c C-c ;;╰──────────────────────────╮ ;;╭─Fonts────────────────────╯ ;;│ A font able to displays the needed UNICODE characters have to ;;│ be used. It works well with the following families: ;;│ - DejaVu Sans Mono ;;│ - Unifont ;;│ - Hack ;;│ - JetBrains Mono ;;│ - Cascadia Mono ;;│ - Agave ;;│ - JuliaMono ;;│ - FreeMono ;;│ - Iosevka Comfy Fixed, Iosevka Comfy Wide Fixed ;;│ - Aporetic Sans Mono, Aporetic Serif Mono ;;│ - Source Code Pro ;;╰──────────────────────────╮ ;;╭─UTF-8────────────────────╯ ;;│ Also, the encoding of the file must support UNICODE. ;;│ One way to do that, is to add a line like this one ;;│ at the top of your file: ;;│ -*- coding:utf-8; -*- ;;╰──────────────────────────╮ ;;╭─Hydra or Transient───────╯ ;;│ Uniline comes with two flavours of user interfaces: ;;│ Hydra and Transient. ;;│ Both versions are compiled when installing the package. ;;│ ;;│ Then one or the other packages must be loaded (not both) ;;│ for example with: ;;│ (require 'uniline-hydra) ;;│ or ;;│ (use-package uniline-hydra ;;│ :bind ("C-" . uniline-mode)) ;;│ ;;│ This file, uniline-transient.el, implements the Transient interface ;;│ and calls the functions defined by uniline-core.el ;;╰──────────────────────────□ ;;; Requires: (require 'uniline-core) ;;; Code: ;;;╭───────────────────╮ ;;;│Transient interface│ ;;;╰───────────────────╯ (require 'transient) (defun uniline--self-insert-command (N) "To fool transient into thinking this is NOT self-insert-command." (interactive) (self-insert-command N)) (eval-and-compile (declare-function uniline-customize-hydra-or-transient (type))) (eval-and-compile ;; a kludge so that Uniline works both with transient 0.13.0 ;; or earlier, without generating warnings (if (and (boundp 'transient-show-popup) (not (boundp 'transient-show-menu))) (defvaralias 'transient-show-menu 'transient-show-popup))) ;; make this transient setting buffer local, so that Uniline can ;; tweak it without touching other usages like Magit for instance (make-variable-buffer-local 'transient-show-menu) (defun uniline-toggle-hints (&optional notoggle) "Toggle between styles of transient hints. When NOTOGGLE is t, do not toggle `uniline-hint-style', just put everything in sync." (interactive) (unless notoggle (setq transient-show-menu (cond ((eq transient-show-menu t) nil) ((eq transient-show-menu nil) t) ((numberp transient-show-menu) t)))) (setq uniline-hint-style (cond ((eq transient-show-menu t) t) ((eq transient-show-menu nil) 1) ((numberp transient-show-menu) 1)))) (transient-define-suffix uniline-toggle-transient-hints-suffix () "Toggle between full and one-liner menus. Associated with C-t, which does half the work natively in Transient: one-liner → full menu. Additionally, modify transient-show-menu so that the choice is remembered for later menu invocation in the same Uniline session." :transient 'transient--do-exit (interactive) (uniline-toggle-hints) (setq transient--showp nil) (eval-when-compile ;; suppress compilation warning "slot :command unknown" (put :command 'slot-name t)) (transient-setup (eieio-oref (transient-prefix-object) 'command ))) ;; Define common command classes to control state transitions (transient-define-suffix uniline--persistent-command (&rest args) "Base class for commands that should keep the transient state active." :transient t (interactive) args) ;; to avoid warnings (transient-define-suffix uniline--exit-command () "Base class for commands that should exit the transient state." :transient nil) (transient-define-prefix uniline-transient-customize () "Preferences." :info-manual "(uniline) Customization" :transient-non-suffix 'transient-quit-one [:class transient-columns :pad-keys t ["Current session" ("f" "choose font" uniline-transient-fonts :transient nil) ("h" "Hydra" (lambda () (interactive) (load-library "uniline-hydra") (declare-function uniline-hydra-customize/body "uniline-hydra" ()) (uniline-hydra-customize/body)) :transient nil) ("C-t" "large hints" uniline-toggle-transient-hints-suffix) ] ["Future sessions" ("g" "uniline group" (lambda () (interactive) (customize-group 'uniline))) ("H" "Hydra (change .emacs)" (lambda () (interactive) (uniline-customize-hydra-or-transient "hydra" )) :transient nil) ("T" "Transient (change .emacs)" (lambda () (interactive) (uniline-customize-hydra-or-transient "transient")) :transient nil) ("l" "line spacing" (lambda () (interactive) (customize-variable 'line-spacing))) ]] (interactive) (transient-setup 'uniline-transient-customize)) (transient-define-prefix uniline-transient-fonts () "Font selection menu." :info-manual "(uniline) Which fonts?" :transient-non-suffix 'transient-quit-one [:class transient-columns :pad-keys t ["Try a font" ("d" (lambda () (uniline--font-name-ticked ?d)) uniline--set-font-d :transient t) ("h" (lambda () (uniline--font-name-ticked ?h)) uniline--set-font-h :transient t) ("c" (lambda () (uniline--font-name-ticked ?c)) uniline--set-font-c :transient t) ("j" (lambda () (uniline--font-name-ticked ?j)) uniline--set-font-j :transient t) ("s" (lambda () (uniline--font-name-ticked ?s)) uniline--set-font-s :transient t)] ["" ("b" (lambda () (uniline--font-name-ticked ?b)) uniline--set-font-b :transient t) ("f" (lambda () (uniline--font-name-ticked ?f)) uniline--set-font-f :transient t) ("a" (lambda () (uniline--font-name-ticked ?a)) uniline--set-font-a :transient t) ("u" (lambda () (uniline--font-name-ticked ?u)) uniline--set-font-u :transient t)] ["" ("i" (lambda () (uniline--font-name-ticked ?i)) uniline--set-font-i :transient t) ("I" (lambda () (uniline--font-name-ticked ?I)) uniline--set-font-I :transient t) ("p" (lambda () (uniline--font-name-ticked ?p)) uniline--set-font-p :transient t) ("P" (lambda () (uniline--font-name-ticked ?P)) uniline--set-font-P :transient t)] ["Actions" ("*" "Configure" uniline-customize-face) ("C-t" "Togg hints" uniline-toggle-transient-hints-suffix) ("q" "Quit" transient-quit-one) ("RET" "Quit" (lambda () (interactive)) :transient nil)]] (interactive) (transient-setup 'uniline-transient-fonts)) (transient-define-prefix uniline-transient-arrows () "Arrows and shapes interface." :info-manual "uniline" :transient-suffix 'transient--do-leave :transient-non-suffix 'transient--do-leave [:class transient-columns :pad-keys t ["Insert" ("a" "▷▶→▹▸↔" uniline-insert-fw-arrow :transient t) ("s" "□■◆◊" uniline-insert-fw-square :transient t) ("o" "·●◦Øø" uniline-insert-fw-oshape :transient t) ("x" "╳╱╲÷×±¤" uniline-insert-fw-cross :transient t) ("SPC" " ░▒▓█" uniline-insert-fw-grey :transient t)] ["" ("A" "↔▸▹→▶▷" uniline-insert-bw-arrow :transient t) ("S" "◊◆■□" uniline-insert-bw-square :transient t) ("O" "øØ◦●·" uniline-insert-bw-oshape :transient t) ("X" "¤±×÷╲╱╳" uniline-insert-bw-cross :transient t) ("DEL" "█▓▒░ " uniline-insert-bw-grey :transient t)] ["" ("-" "" uniline--self-insert-- :transient t) ("+" "" uniline--self-insert-+ :transient t) ("=" "" self-insert-command :transient t) ("#" "" self-insert-command :transient t) ("~" "" self-insert-command :transient t)] ["Rotate,tweak" ("S-" "↑" uniline-rotate-up↑ :transient t) ("S-" "→" uniline-rotate-ri→ :transient t) ("S-" "↓" uniline-rotate-dw↓ :transient t) ("S-" "←" uniline-rotate-lf← :transient t)] ["Text dir" ("C-" "↑" uniline-text-direction-up↑ :transient nil) ("C-" "→" uniline-text-direction-ri→ :transient nil) ("C-" "↓" uniline-text-direction-dw↓ :transient nil) ("C-" "←" uniline-text-direction-lf← :transient nil)] ["Contour,fill" ("c" "Draw cnt" uniline-contour) ("C" "Ovwrt cnt" (lambda () (interactive) (uniline-contour t))) ("i" "Fill area" uniline-fill)] ["Navigation" ("*" "Customize" uniline-transient-customize) ("f" "Font" uniline-transient-fonts) ("C-t" "Hints" uniline-toggle-transient-hints-suffix) ("RET" "Quit" (lambda () (interactive)) :transient nil)]] (interactive) ;; the purpose of this keymap handling is to regain the basic behavior ;; of & ;; those keys were captured by Transient to navigate in the transient menu ;; the desired behavior is to exit this Transient menu and trace lines (let ((transient-popup-navigation-map (define-keymap "" #'transient-noop "C-r" #'transient-isearch-backward "C-s" #'transient-isearch-forward "M-RET" #'transient-push-button))) (transient-setup 'uniline-transient-arrows))) (transient-define-prefix uniline-transient-alt-styles () "Change lines style interface." :info-manual "(uniline) Rectangular actions" :transient-non-suffix 'transient-quit-one [:class transient-columns :pad-keys t ["Dashes" ("3" "3x2 dots" uniline-change-style-dot-3-2 :transient t) ("4" "4x4 dots" uniline-change-style-dot-4-4 :transient t) ("h" "hard corner" uniline-change-style-hard-corners :transient t)] ["Thickness" ("-" "thin" uniline-change-style-thin :transient t) ("+" "thick" uniline-change-style-thick :transient t) ("=" "double" uniline-change-style-double :transient t)] ["Base style" ("0" "standard" uniline-change-style-standard :transient t) ("a" "aa2u" uniline-aa2u-rectangle :transient t)] ;;["Move rectangle" ;; ("" "→" uniline-move-rect-ri→ :transient t) ;; ("" "←" uniline-move-rect-lf← :transient t) ;; ("" "↑" uniline-move-rect-up↑ :transient t) ;; ("" "↓" uniline-move-rect-dw↓ :transient t)] ["Misc" ("f" "fonts" uniline-transient-fonts) ("C-t" "Togg hints" uniline-toggle-transient-hints-suffix) ("s" "back" uniline-transient-moverect) ("RET" "exit" uniline--rect-quit)] ] (interactive) (rectangle-mark-mode 1) (transient-setup 'uniline-transient-alt-styles)) (transient-define-prefix uniline-transient-moverect () "Rectangle manipulation interface." :info-manual "(uniline) Rectangular actions" :transient-non-suffix 'transient-quit-one [:class transient-columns :pad-keys t ["Move" ("" "←" uniline-move-rect-lf← :transient t) ("" "→" uniline-move-rect-ri→ :transient t) ("" "↑" uniline-move-rect-up↑ :transient t) ("" "↓" uniline-move-rect-dw↓ :transient t)] ["Draw" ("r" "Trace inner" uniline-draw-inner-rectangle :transient t) ("R" "Trace outer" uniline-draw-outer-rectangle :transient t) ("C-r" "Ovwrt inner" uniline-overwrite-inner-rectangle :transient t) ("C-S-R" "Ovwrt outer" uniline-overwrite-outer-rectangle :transient t) ("i" "Fill" uniline-fill-rectangle :transient t)] ["Copy-paste" ("c" "Copy" uniline-copy-rectangle :transient nil) ("k" "Kill" uniline-kill-rectangle :transient nil) ("y" "Yank" uniline-yank-rectangle :transient t)] ["Brush" ("-" "╭─╯" uniline-set-brush-1 :transient t) ("+" "┏━┛" uniline-set-brush-2 :transient t) ("=" "╔═╝" uniline-set-brush-3 :transient t) ("#" "▄▄▟" uniline-set-brush-block :transient t) ("~" "┄┄┄" uniline-set-brush-dot-toggle :transient t) ("DEL" "DEL" uniline-set-brush-0 :transient t)] ["Misc" ("s" "Line styles" uniline-transient-alt-styles) ("f" "Choose font" uniline-transient-fonts) ;;("C-x C-x" "Exchg point-mark" rectangle-exchange-point-and-mark :transient t) ("C-t" "Togg hints" uniline-toggle-transient-hints-suffix) ("RET" "Exit" uniline--rect-quit)] ] (interactive) (rectangle-mark-mode 1) (transient-setup 'uniline-transient-moverect)) ;; those low-value helper-functions are needed because for an unknown reason ;; calling a macro exits a transient menu, so we have to re-enter it (defun uniline--transient-call-macro-in-direction-up↑ () (interactive) (uniline-call-macro-in-direction-up↑) (transient-setup 'uniline-transient-macro-exec)) (defun uniline--transient-call-macro-in-direction-ri→ () (interactive) (uniline-call-macro-in-direction-ri→) (transient-setup 'uniline-transient-macro-exec)) (defun uniline--transient-call-macro-in-direction-dw↓ () (interactive) (uniline-call-macro-in-direction-dw↓) (transient-setup 'uniline-transient-macro-exec)) (defun uniline--transient-call-macro-in-direction-lf← () (interactive) (uniline-call-macro-in-direction-lf←) (transient-setup 'uniline-transient-macro-exec)) (defun uniline--transient-call-macro () (interactive) (kmacro-end-and-call-macro 1) (transient-setup 'uniline-transient-macro-exec)) (defun uniline-macro-exec () (interactive) (transient-setup 'uniline-transient-macro-exec)) (transient-define-prefix uniline-transient-macro-exec () "Macro execution interface." :info-manual "(uniline) Macros" :transient-non-suffix 'transient-quit-one [:class transient-columns :pad-keys t ["Call macro in direction" ("" "→" uniline--transient-call-macro-in-direction-ri→) ("" "↑" uniline--transient-call-macro-in-direction-up↑) ("" "↓" uniline--transient-call-macro-in-direction-dw↓) ("" "←" uniline--transient-call-macro-in-direction-lf←)] ["" ("e" "Normal call" uniline--transient-call-macro) ("C-t" "Togg hints" uniline-toggle-transient-hints-suffix) ("RET" "Quit" transient-quit-one) ("q" "Quit" transient-quit-one)] ] (interactive) (transient-setup 'uniline-transient-macro-exec)) (eval-when-compile ;; this ugly patch removes dumb compilation warnings. ;; they appear when loading this file, then byte-compiling it. (dolist (s '(uniline-transient-moverect uniline-transient-arrows)) (plist-put (symbol-plist s) 'interactive-only nil))) (defun uniline-launch-interface () "Choose between rectangle and arrows interface based on selection." (interactive) (if (region-active-p) (uniline-transient-moverect) (uniline-transient-arrows))) (defvar uniline--current-interface) (setq uniline--current-interface ?t) (provide 'uniline-transient) ;;; uniline-transient.el ends here ================================================ FILE: uniline.el ================================================ ;;; uniline.el --- Add▶ ■─UNICODE based diagrams─■ to▶ ■─text files─■ -*- coding:utf-8; lexical-binding: t; -*- ;; Copyright (C) 2024-2026 Thierry Banel ;; Author: Thierry Banel tbanelwebmin at free dot fr ;; Version: 1.0 ;; Package-Requires: ((emacs "29.1") (hydra "0.15.0")) ;; Keywords: convenience, text ;; URL: https://github.com/tbanel/uniline ;; Uniline 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. ;; Uniline 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 . ;;; Commentary: ;; ┏━━━━━━━┓ ;; ╭──────╮ ┃ thick ┣═◁═╗ ;; │ thin ┝◀━━━┫ box ┃ ║ ;; │ box │ ┗━━━━━━━┛ ║ ;; ╰───┬──╯ ╔══════╩═╗ ;; ↓ ║ double ║ ;; ╰────────────╢ box ║ ;; ╚════╤═══╝ ;; ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ │ ;; ▌quadrant-blocks▐─◁─╯ ;; ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ ;; ;;╭─Pure text────────────────□ ;;│ UNICODE characters are available to draw nice boxes and lines. ;;│ They come in 4 flavours: thin, thick, double, and quadrant-blocks. ;;│ Uniline makes it easy to draw and combine all 4 flavours. ;;│ Use the arrows on the keyboard to move around leaving a line behind. ;;╰──────────────────────────╮ ;;╭─Minor mode───────────────╯ ;;│ Uniline is a minor mode. Enter it with: ;;│ M-x uniline-mode ;;│ Leave it with: ;;│ C-c C-c ;;╰──────────────────────────╮ ;;╭─Fonts────────────────────╯ ;;│ A font able to displays the needed UNICODE characters have to ;;│ be used. It works well with the following families: ;;│ - DejaVu Sans Mono ;;│ - Unifont ;;│ - Hack ;;│ - JetBrains Mono ;;│ - Cascadia Mono ;;│ - Agave ;;│ - JuliaMono ;;│ - FreeMono ;;│ - Iosevka Comfy Fixed, Iosevka Comfy Wide Fixed ;;│ - Aporetic Sans Mono, Aporetic Serif Mono ;;│ - Source Code Pro ;;╰──────────────────────────╮ ;;╭─UTF-8────────────────────╯ ;;│ Also, the encoding of the file must support UNICODE. ;;│ One way to do that, is to add a line like this one ;;│ at the top of your file: ;;│ -*- coding:utf-8; -*- ;;╰──────────────────────────╮ ;;╭─Hydra or Transient───────╯ ;;│ Uniline comes with two flavours of user interfaces: ;;│ Hydra and Transient. ;;│ Both versions are compiled when installing the package. ;;│ ;;│ Then one or the other packages must be loaded (not both) ;;│ for example with: ;;│ (require 'uniline-hydra) ;;│ or ;;│ (use-package uniline-hydra ;;│ :bind ("C-" . uniline-mode)) ;;│ ;;│ This file, uniline-core.el, is the largest one, the one ;;│ implementing all the core functions independent from ;;│ Hydra or Transient ;;╰──────────────────────────□ ;;; Requires: (require 'uniline-hydra) ;; this is an alias for uniline-hydra (provide 'uniline) ;;; uniline.el ends here ================================================ FILE: uniline.info ================================================ This is uniline.info, produced by makeinfo version 6.8 from uniline.texi. INFO-DIR-SECTION Emacs START-INFO-DIR-ENTRY * Uniline: (uniline). Draw UNICODE diagrams END-INFO-DIR-ENTRY INFO-DIR-SECTION Misc START-INFO-DIR-ENTRY * (uniline). Uniline. END-INFO-DIR-ENTRY  File: uniline.info, Node: Top, Next: Getting started in 10 seconds, Up: (dir) Uniline ******* * Menu: * Getting started in 10 seconds:: * New:: * Gallery pure UNICODE diagrams in Emacs:: * A minor mode for drawing:: * The key:: * Glyphs ▷ ▶ → □ ◆ ╮─ insertion & modification:: * Rectangular actions:: * Long range actions contour and flood-fill:: * Macros:: * Which fonts?:: * Hydra or Transient?:: * Customization:: * How Uniline behaves with its environment?:: * Lisp API:: * Mouse support:: * Installation:: * Related packages:: * Author, contributors: Author contributors. * License:: — The Detailed Node Listing — Gallery: pure UNICODE diagrams in Emacs * Document a command:: * Connect boxes with arrows:: * Explain decisions trees:: * Draw lines or blocks:: * Outline the General Relativity and the Schrödinger's equations:: * Explain the structure of a sentence in a foreign language:: * Draw electronic diagrams:: * Explain Lisp lists:: * Draw sketched objects:: * Pure text:: * Beware!:: A minor mode for drawing * Minor mode:: * Draw lines by moving the cursor:: * Infinite ∞ buffer:: * Brush style:: * Text direction:: Glyphs ‘▷ ▶ → □ ◆ ╮─’ insertion & modification * Arrows glyphs ▷ ▶ → ▹ ▸ ↔:: * Intersection glyphs ■ ◆ ●:: * Fine tweaking of lines:: Rectangular actions * Drawing a rectangle:: * Filling a rectangle:: * Moving a rectangular region:: * Copying, killing, yanking a rectangular region: Copying killing yanking a rectangular region. * Dashed lines and other styles:: * ASCII to UNICODE:: Long range actions: contour and flood-fill * Tracing a contour:: * Flood-fill:: Which fonts? * Recommended fonts:: * Use case mixing fonts:: Hydra or Transient? * Selecting Hydra or Transient:: * Instantly selecting Hydra or Transient:: * One-liner menus:: * The Hydra interface:: * The Transient interface:: Customization * Interface type:: * Insert key:: * Maximum steps when drawing a contour:: * Cursor type:: * Hint style:: * Welcome message visibility:: * Line spacing:: * Font:: * Upward infiniteness ∞:: How Uniline behaves with its environment? * Language environment:: * Compatibility with Picture-mode:: * Compatibility with Artist-mode:: * Compatibility with Whitespace-mode:: * Compatibility with Org Mode:: * Org Mode and LaTex:: * What about \t tabs?:: * What about ^L page separation?:: * Emacs on the Linux console:: * Emacs on a graphical terminal emulator:: * Emacs on Windows:: * Compatibility with ASCIIFlow:: Lisp API * Move the cursor:: * Brush:: * Example Lisp function to draw a plus sign:: * Long range actions (contour, flood-fill, rectangle): Long range actions (contour flood-fill rectangle). * Constants:: * Macro and text direction:: * Insert and tweak glyphs:: * Change to alternate styles:: Installation * use-package, the straightforward way: use-package the straightforward way. * Without use-package::  File: uniline.info, Node: Getting started in 10 seconds, Next: New, Prev: Top, Up: Top 1 Getting started in 10 seconds ******************************* • Type ‘M-x uniline-mode’ • Move cursor with the arrow-keys on the keyboard ‘→ ← ↑ ↓’ • Quit ‘C-c C-c’ ╷ ╭─────────╮ ╰───┤my first ├─╮ │drawing │ ╰───╮ ╰─────────╯ │ ╭────┬───────╯ ╰────╯  File: uniline.info, Node: New, Next: Gallery pure UNICODE diagrams in Emacs, Prev: Getting started in 10 seconds, Up: Top 2 New ***** Customization & settings now consistently available from the application menu, from Hydra, and from Transient. Type ‘ *’.  File: uniline.info, Node: Gallery pure UNICODE diagrams in Emacs, Next: A minor mode for drawing, Prev: New, Up: Top 3 Gallery: pure UNICODE diagrams in Emacs ***************************************** Draw diagrams like those: * Menu: * Document a command:: * Connect boxes with arrows:: * Explain decisions trees:: * Draw lines or blocks:: * Outline the General Relativity and the Schrödinger's equations:: * Explain the structure of a sentence in a foreign language:: * Draw electronic diagrams:: * Explain Lisp lists:: * Draw sketched objects:: * Pure text:: * Beware!::  File: uniline.info, Node: Document a command, Next: Connect boxes with arrows, Up: Gallery pure UNICODE diagrams in Emacs 3.1 Document a command ====================== pdfjam source.pdf 3-5,9 ╶─────▲────▲────────▲──▲╴ command╶╯ │ │ │ input file╶──╯ │ │ select pages 3,4,5╶───╯ │ and page 9╶──────────────╯  File: uniline.info, Node: Connect boxes with arrows, Next: Explain decisions trees, Prev: Document a command, Up: Gallery pure UNICODE diagrams in Emacs 3.2 Connect boxes with arrows ============================= ╭───────────────────────╮ ╷123╭────▶┤ hundred and something │ ╰───╯ ╰───────────────────────╯ ╭────▶──╮A╷ ╭───╮ ┏━━━┓ ╔═══╗ │ ╰─╯ 0╶─→┤ 1 ┝━━━▶┫ 2 ┣═══▷╣ 3 ╟──●────▶──╮B╷ ╰───╯ ┗━┯━┛ ╚═╤═╝ │ ╰─╯ ╰────←───╯ ╰────▶──╮C╷ ╰─╯ ╔══════════╗ ║ 1 ║ ▐▀▀▀▀▀▀▀▀▜ ║ ╭─────╫───╮ ◁──▷ ▐ 3 ▐ ╚════╪═════╝ 2 │ ▐▄▄▄▄▄▄▄▄▟ ╰─────────╯  File: uniline.info, Node: Explain decisions trees, Next: Draw lines or blocks, Prev: Connect boxes with arrows, Up: Gallery pure UNICODE diagrams in Emacs 3.3 Explain decisions trees =========================== ┏━━━━━━━━━━━━┓ ┃which color?┃ ┗━┯━━━━━━━━━━┛ │ ╭──────╮ │ ╭──┤yellow├─▷╮good─choice╭□ ▽ │ ╰──────╯ ╰═══════════╯ ╰──● ╭───╮ ┏━━━━━┓ ├──┤red├───▷┨dark?┠──╮ │ ╰───╯ ┗━━━━━┛ │ │ ╭───◁──────────────╯ │ │ ╭───╮ │ ╰─●─┤yes├▷╮regular─red╭─□ │ │ ╰───╯ ╰═══════════╯ │ │ ╭──╮ │ ╰─┤no├─▷╮pink╭────────□ │ ╰──╯ ╰════╯ │ ╭────╮ ├──┤blue├───▷╮next week╭──□ │ ╰────╯ ╰═════════╯ │ ╭─────╮ ╰──┤white├──▷╮available╭──□ ╰─────╯ ╰═════════╯  File: uniline.info, Node: Draw lines or blocks, Next: Outline the General Relativity and the Schrödinger's equations, Prev: Explain decisions trees, Up: Gallery pure UNICODE diagrams in Emacs 3.4 Draw lines or blocks ======================== ╭─╮←─╮ ╭╮ │ │ ╰──╴max 235 ╭╮││ ╭╯ │ │╰╯│╭─╯ │ ╭╮ │ ││ │ ╭─╮││╭╮ ╭──╮╭╮ │ ╰╯ ╰╮ ╭╯ ╰╯╰╯│ ╭╯ ╰╯╰─╮ │ │ ╭╮ ◁─╯ ╰──╯ ╰──╯ ╰─╯╰────▷ ◀════════════════════════════════════════▶ ╭────────╮ ▲ │all time│ ┃ ▄ ▗▟█ ←─┤highest │ Qdx █▌ ████ ╰────────╯ ┃ ▗▄█▌ █████▙ ┃ ▟███████▄█████████▄▄▄ ▗▄ ┃▐▄▄████████████████████████████▄▄▖ ╺━━━━━━━━━━╸time╺━━━━━━━━━━━━━━━━▶  File: uniline.info, Node: Outline the General Relativity and the Schrödinger's equations, Next: Explain the structure of a sentence in a foreign language, Prev: Draw lines or blocks, Up: Gallery pure UNICODE diagrams in Emacs 3.5 Outline the General Relativity and the Schrödinger’s equations ================================================================== ╭─────────────────────╴G: Einstein tensor │ ╭────╴κ: Gravitational coupling constant ╭──▽───╮ ╭───▽──╮ ┏━┷━━━━━━┷━━━━━━━━┷━━━━━━┷━━━┓ ┃ R - gR/2 + Λg = (8πG/c⁴)×T ┃◁╴General Relativity equation ┗━△━━━△△━━━━━△△━━━━━━△━△━━━△━┛ │ ││ ││ │ │ ╭╯ │ ││ ││ │ │ ╰╴Energy-impulsion tensor │ ││ ││ │ ╰───╴Speed of light │ ││ ││ ╰─────╴Gravitational constant │ ││ ╰┴────────────╴Cosmological constant │ │╰──────┴────────────╴Scalar curvature │ ╰───────╰────────────╴Metric tensor ╰────────────────────────╴Ricci tensor ╭─────────────────────╴Derivative over time │ ╭──────────╭────╴State of quantum system at time t │ │ │ (the square of its absolute value ╭▽─╮ ╭─▽──╮ ╭─▽──╮ is the probability density) ┏━━━━━┷━━┷━┷━━━━┷━━━━━┷━━━━┷━┓ ┃ i ħ d/dt |Ψ(t)> = Ĥ |Ψ(t)> ┃◁─╴Schrödinger's equation ┗━△━△━━━━△━━━━△━━━━━△━━━━△━━━┛ │ │ ╰────╰─────┤────╰───╴Time │ │ ╰────────╴Hamiltonian │ ╰────────────────────────╴Reduced Plank constant ╰──────────────────────────╴Imaginary number i²=-1  File: uniline.info, Node: Explain the structure of a sentence in a foreign language, Next: Draw electronic diagrams, Prev: Outline the General Relativity and the Schrödinger's equations, Up: Gallery pure UNICODE diagrams in Emacs 3.6 Explain the structure of a sentence in a foreign language ============================================================= (which language?) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ the pretty table is standing ┃ ┗┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ ╭────┬─────┬─────╴radicals ↕ ╭┴╮ ╭┴─╮ ╭┴─╮ ┏┷━━━┿━┿━━┿━━┿━━┿━━┿━━━┓ ┃ la bela tablo staras ┃ ┗━━━━┿━┿△━┿━━┿△━┿━━┿△━━┛ ╰─╯│ ╰──╯│ ╰──╯│ ┏━━━━━suffixes━━━━━┓ │ │ ╰──╂╴as: present tense┃ │ │ ┃ os: future tense ┃ │ │ ┃ is: past tense ┃ │ ╰────────╂╴ o: noun ┃ ╰──────────────╂╴ a: adjective ┃ ┃ e: adverb ┃ ┗━━━━━━━━━━━━━━━━━━┛  File: uniline.info, Node: Draw electronic diagrams, Next: Explain Lisp lists, Prev: Explain the structure of a sentence in a foreign language, Up: Gallery pure UNICODE diagrams in Emacs 3.7 Draw electronic diagrams ============================ ╭────────╭──────────╮ ┏━━┓ │ ╭┴╮ ╰─┨5V┃ ╭┴╮ │░│ ┗━━┛ │░│ │░│1KΩ │░│10KΩ ╰┬╯ 5μF ╰┬╯ ├─────────────● → ╷╷ │ ┠─╯ amplified → ●─────┤├───┼──────┨ output input ╵╵ │ ┠▶╮ 500μF signal signal ╭┴╮ │ ╷╷ │░│ ├───┤├──╮ │░│1KΩ ╭┴╮ ╵╵ │ ╰┬╯ │░│ │ ╭────╮ │ │░│470Ω │ │ ╺━━┷━━╸ │ ╰┬╯ │ │ ╺━━━╸ ╰────────╰───────╰────╯ ╺━╸  File: uniline.info, Node: Explain Lisp lists, Next: Draw sketched objects, Prev: Draw electronic diagrams, Up: Gallery pure UNICODE diagrams in Emacs 3.8 Explain Lisp lists ====================== '(a b c) ┏━━━┳━━━┓ ┏━━━┳━━━┓ ┏━━━┳━━━┓ ●━━━▶┫ ● ┃ ●─╂──▷┨ ● ┃ ●─╂──▷┨ ● ┃nil┃ ┗━┿━┻━━━┛ ┗━┿━┻━━━┛ ┗━┿━┻━━━┛ │ ╰──────────╮╰╮ │ ╭─────┬───────────╮ │ │ ╰─▷┤"a\0"│properties │ │ │ ├─────┼───────────┤ │ │ │"b\0"│properties ├◁╯ │ ├─────┼───────────┤ │ │"c\0"│properties ├◁──╯ ├─────┼───────────┤ │... │... │ ╵ ╵ ╵  File: uniline.info, Node: Draw sketched objects, Next: Pure text, Prev: Explain Lisp lists, Up: Gallery pure UNICODE diagrams in Emacs 3.9 Draw sketched objects ========================= ◀─(-)────────(+)──▶ ~╭──────╮~ ▗──────────────╮ ~~│ ╭~~╮ │~~ ▐ ╰╮ ~│ ╵ ╵ │~ ╭□▐ 1.5 volts ╭╯□╮ ╰─╖ ╓─╯ │ ▝▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘ │ ╠━━╣ │ ╰──────╯ │ ╰─────────────────────────────╯ ╶╮ ╭╴ ┏┳┥▒▒▒▒▒▒▒┝╸ ┃┃│▒▒eau▒▒│ ┃┃│▒▒▒▒▒▒▒│ ╔═════╗ ┃┃╰──╮▒╭──╯ ║ ╶╮ ▽ ╭╴ ┃┃ ▒ ║ │ ░ │ ┃┃ ▒ ║ │░░░░░░░░░░░░░░│ ┃┃ ╚═════╝ │░░░░░░░░░░░░░░╞════▷▒▒ ┃┃ │░░░░░akvo░░░░░│ ╶╮ ▒ ╭╴ ┃┃ │░░░░░░░░░░░░░░│ │ ▒ │ ┃┃ ╰─┲┳━━━━━━━━┳┱─╯ │▒▒▒▒▒▒▒▒▒▒▒│ ┃┃ ┃┃ ┃┃ │▒▒▒water▒▒▒│ ┃┃ ┃┃ ┃┃ │▒▒▒▒▒▒▒▒▒▒▒│ ┃┃ ┃┃ ┃┃ ╰───────────╯ ▝▀▀▀▀▀▀▘ ▝▀▘ ▝▀▘ ▀▀▀▀▀▀▀▀▀▀▀▀▀  File: uniline.info, Node: Pure text, Next: Beware!, Prev: Draw sketched objects, Up: Gallery pure UNICODE diagrams in Emacs 3.10 Pure text ============== Those diagrams are pure text. There is nothing graphic. They are achieved using UNICODE characters. Therefore they can be drawn within any text formatted document, like Org Mode, Markdown, txt, comments in any programming language source code (C++, Python, Rust, D, JavaScript, GnuPlot, LaTex, whatever). Most often, the text file will be encoded as UTF-8. This is becoming the de-facto standard for text and source code files. Creating such diagrams by hand is painfully slow. Use ‘Uniline’ to draw lines while you move the cursor with keyboard arrows.  File: uniline.info, Node: Beware!, Prev: Pure text, Up: Gallery pure UNICODE diagrams in Emacs 3.11 Beware! ============ If you see those diagrams miss-aligned, most likely the font used to display them does not support UNICODE block characters. See bellow the paragraph *note Which fonts?:: for details. If you get misalignment when drawing, this could come from too wide characters. Emojis are an example. Usual characters may also be considered twice as wide as normal under some "language environments". See the paragraph *note Language environment:: for details.  File: uniline.info, Node: A minor mode for drawing, Next: The key, Prev: Gallery pure UNICODE diagrams in Emacs, Up: Top 4 A minor mode for drawing ************************** * Menu: * Minor mode:: * Draw lines by moving the cursor:: * Infinite ∞ buffer:: * Brush style:: * Text direction::  File: uniline.info, Node: Minor mode, Next: Draw lines by moving the cursor, Up: A minor mode for drawing 4.1 Minor mode ============== ‘Uniline’ is a minor mode. Activate it temporarily: ‘M-x uniline-mode’ Exit it with: ‘C-c C-c’ The current major mode is still active underneath ‘uniline-mode’. While in ‘uniline-mode’, overwriting is active, as well as long lines truncation. Also, a hollow cursor is provided (customizable). Those settings are reset to their previous state when exiting ‘uniline-mode’.  File: uniline.info, Node: Draw lines by moving the cursor, Next: Infinite ∞ buffer, Prev: Minor mode, Up: A minor mode for drawing 4.2 Draw lines by moving the cursor =================================== Use keyboard arrows to draw lines. By default, drawing lines only happens over empty space or over other lines. If there is already text, it will not be erased. However, by hitting the control-key while moving, lines overwrite whatever there is. The usual numeric prefix is available. For instance, to draw a line 12 characters wide downward, type: ‘M-12 ’  File: uniline.info, Node: Infinite ∞ buffer, Next: Brush style, Prev: Draw lines by moving the cursor, Up: A minor mode for drawing 4.3 Infinite ∞ buffer ===================== The buffer is infinite ∞ in the south and east directions. Which means that when the cursor ends up outside the buffer, white space characters are automatically added. All algorithms also make use of the infiniteness of the buffer when needed. Those algorithms are: moving a rectangle, pasting a rectangle, drawing the external border of a rectangular region, or drawing the contour of a shape. The buffer is also infinite ∞ in the upward direction. That is customizable through the ‘uniline-infinite-up↑’ variable. If its value is ‘t’, then the buffer is actually infinite ∞ upward. If it is ‘nil’, then the upper border of the buffer is a hard limit. To customize, type: ‘M-x customize-variable uniline-infinite-up↑’ The buffer can be "narrowed", for instance with the ‘C-x n n’ or ‘M-x narrow-to-region’ command. In this case, the limits are those of the narrow region. When Uniline needs to bypass the up↑ or down↓ limits, it adds empty lines. When widening again the buffer, the region which was narrow will have increased.  File: uniline.info, Node: Brush style, Next: Text direction, Prev: Infinite ∞ buffer, Up: A minor mode for drawing 4.4 Brush style =============== Set the current brush with: • ‘-’ single thin line ‘╭─┬─╮’ • ‘+’ single thick line ‘┏━┳━┓’ • ‘=’ double line ‘╔═╦═╗’ • ‘#’ quarter block ‘▙▄▟▀’ • ‘~’ toggle dotted lines ‘┄┄┄┄’ • ‘’ eraser • ‘’ move without drawing anything The current brush and the current text direction (see *note Text direction::) are reflected in the mode-line (at the bottom of the ‘Emacs’ screen). It looks like this: current text current direction╶────╮ ╭───╴brush ▼ ▼ ══════════════════╧═══════╧══════════════ U:** buff (... →Uniline┼ ...) ═════════════════════════════════════════ The dotted toggle ‘~’ is a modifier for the single thin and thick lines. It circles along 3 styles: • plain lines, • 3 dots vertical, 2 dots horizontal, • 4 dots both vertical & horizontal, • back to plain line and so on. ║ thin ╷ thick ╷ ║ │ │ ══════════╬═════════╪═════════╡ ║ ╭╌╌╌╮ │ ┏╍╍╍┓ │ 3,2 dots ║ ┆ ┆ │ ┇ ┇ │ ║ ╰╌╌╌╯ │ ┗╍╍╍┛ │ ──────────╫─────────┼─────────┤ ║ ╭┈┈┈╮ │ ┏┉┉┉┓ │ 4,4 dots ║ ┊ ┊ │ ┋ ┋ │ ║ ╰┈┈┈╯ │ ┗┉┉┉┛ │ ──────────╨─────────┴─────────╯ Note that the UNICODE standard offers very limited support for dotted lines. Only vertical and horizontal lines are available. So, no crossing of line is possible. In case a line crosses a dotted line, Uniline falls back to a plain line crossing character (but still preserving thickness). There is no dotted versions of double lines either.  File: uniline.info, Node: Text direction, Prev: Brush style, Up: A minor mode for drawing 4.5 Text direction ================== Usually, inserting text in a buffer moves the cursor to the right. (And sometimes to the left for some locales). Any of the 4 directions can be selected under ‘Uniline’. Just type any of: • ‘ C-’ • ‘ C-’ • ‘ C-’ • ‘ C-’ The current direction is reflected in the mode-line, just before the word ‘"uniline"’.  File: uniline.info, Node: The key, Next: Glyphs ▷ ▶ → □ ◆ ╮─ insertion & modification, Prev: A minor mode for drawing, Up: Top 5 The ‘’ key ******************** The ‘’ key is a prefix for other keys: • for drawing arrows, squares, crosses, o-shapes glyphs, • for handling rectangles, • for inserting ‘# = - +’ which otherwise change the brush style, • for trying a choice of mono-spaced fonts. Why ‘’? Because: • ‘Uniline’ tries to leave their original meaning to as many keys as possible, • the standard meaning of ‘’ is to toggle the ‘overwrite-mode’; but ‘Uniline’ is already in ‘overwrite-mode’, and de-activating overwrite would break ‘Uniline’. So preempting ‘’ does not sacrifice anything. *Customization* Another key may be defined instead of ‘’. Type: M-x customize-variable uniline-key-insert  File: uniline.info, Node: Glyphs ▷ ▶ → □ ◆ ╮─ insertion & modification, Next: Rectangular actions, Prev: The key, Up: Top 6 Glyphs ‘▷ ▶ → □ ◆ ╮─’ insertion & modification ************************************************ Individual character glyphs may be inserted and changed. • Put the cursor where a glyphs should be edited or inserted. • Then press ‘’ (this key may be customized, see *note Insert key::). Arrows, squares, circles, crosses may be handled. Also lines may be fine tweaked a single character at a time. * Menu: * Arrows glyphs ▷ ▶ → ▹ ▸ ↔:: * Intersection glyphs ■ ◆ ●:: * Fine tweaking of lines::  File: uniline.info, Node: Arrows glyphs ▷ ▶ → ▹ ▸ ↔, Next: Intersection glyphs ■ ◆ ●, Up: Glyphs ▷ ▶ → □ ◆ ╮─ insertion & modification 6.1 Arrows glyphs ‘▷ ▶ → ▹ ▸ ↔’ =============================== When inserting an arrow, it points in the direction that the line drawing follows. ‘Uniline’ supports 6 arrows types: ‘▷ ▶ → ▹ ▸ ↔’ □ ╰─◁──▷─╮ □─╮ ╭─╮ ╭─╮ ╭─□ ╭─◀──▶─╯ △ ▲ ↑ ▵ ▴ ↕ ╰─←──→─╮ │ │ │ │ │ │ ╭─◃──▹─╯ ▽ ▼ ↓ ▿ ▾ ↕ ╰─◂──▸─╮ ╰─╯ ╰─╯ ╰─╯ ╭─↔──↔─╯ □ Actually, there are tons of arrows of all styles in the UNICODE standard. Unfortunately, support by fonts is weak. So ‘Uniline’ restrains itself to those six safe arrows. To insert an arrow, type: ‘ a’ or ‘ a a’ or ‘ a a a’. (‘a’ cycles through the 6 styles, ‘A’ cycles backward). ‘ 4 a’ is equivalent to ‘ a a a a’, which is also equivalent to ‘ A A A’. Those 3 shortcuts insert an arrow of this style: ‘▵▹▿◃’. The actual direction where the arrow points follows the last movement of the cursor. To change the direction of the arrow, use shift-arrow, for example: ‘S-’ will change from ‘→’ to ‘↑’.  File: uniline.info, Node: Intersection glyphs ■ ◆ ●, Next: Fine tweaking of lines, Prev: Arrows glyphs ▷ ▶ → ▹ ▸ ↔, Up: Glyphs ▷ ▶ → □ ◆ ╮─ insertion & modification 6.2 Intersection glyphs ‘■ ◆ ●’ =============================== There are a few UNICODE characters which are mono-space and symmetric in the 4 directions. They are great at line intersections: To insert a square ‘□ ■ ▫ ▪ ◆ ◊’ type: ‘ s s s…’ (‘s’ cycles, ‘S’ cycles backward). To insert a circular shape ‘· ∙ • ● ◦ Ø ø’ type: ‘ o o o…’ (‘o’ cycles, ‘O’ cycles backward). To insert a cross shape ‘╳ ╱ ╲ ÷ × ± ¤’ type: ‘ x x x…’ (‘x’ cycles, ‘X’ cycles backward). To insert a grey character ‘░▒▓█’ from pure white to pure black type: ‘ SPC SPC SPC…’ or ‘ DEL DEL DEL…’ (space key goes from white to black, back-space key goes from black to white) To insert a usual ASCII letter or symbol, just type it. As the keys ‘- + = # ~’ are preempted by ‘uniline-mode’, to type them, prefix them with ‘’. Example: ‘ -’ inserts a ‘-’ and ‘ +’ inserts a ‘+’. │ ├────────────────────────────╮ ▼ ╭─arrows──────╮ ▼ ╭───╮ ╰──▶─(a)─┤ ▷ ▶ → ▹ ▸ ↔ │ ╰──▶─(+)─┤ + │ │ ╰─────────────╯ │ ╰───╯ │ ╭─squares─────╮ │ ╭───╮ ╰──▶─(s)─┤ □ ■ ▫ ▪ ◆ ◊ │ ╰──▶─(-)─┤ - │ │ ╰─────────────╯ │ ╰───╯ │ ╭─circles───────╮ │ ╭───╮ ╰──▶─(o)─┤ · ∙ • ● ◦ Ø ø │ ╰──▶─(=)─┤ = │ │ ╰───────────────╯ │ ╰───╯ │ ╭─crosses───────╮ │ ╭───╮ ╰──▷─(x)─┤ ╳ ╱ ╲ ÷ × ± ¤ │ ╰──▶─(#)─┤ # │ │ ╰───────────────╯ │ ╰───╯ │ ╭───────╮ │ ╭───╮ ╰──▶─(SPC DEL)─┤ ░▒▓█ │ ╰──▶─(~)─┤ ~ │ ╰───────╯ ╰───╯  File: uniline.info, Node: Fine tweaking of lines, Prev: Intersection glyphs ■ ◆ ●, Up: Glyphs ▷ ▶ → □ ◆ ╮─ insertion & modification 6.3 Fine tweaking of lines ========================== convert this ═══▶ into that ╭───────────╮ ╭───────────╮ │╶───┬────▷ │ │╶───╮────▷ │ │ │ │ │ │ │ │ │ │ │ │ ▀▀▀ │ │ ▀▟▀ │ ╰───────────╯ ╰───────────╯ At the crossing of lines, it may be appealing to do small adjustments. In the above example, we removed a segment of line which occupies 1/4 of a character. This cannot be achieve with line tracing alone. We also modified a quarter-block line in a non-obvious way. • Put the point (the cursor) on the character where lines cross each other. • type ‘INS S- S-’ ‘’ here refers to the right part of the character under the point. The 1/4 line segment will cycle through all displayable forms. On the second stroke, no segment will be displayed, which is what we want. Caveat! The UNICODE standard does not define all possible combinations including double line segments. (It does for all combinations of thin and tick lines). So sometimes, when working with double lines, the process may be frustrating. This works also for lines made of quarter-blocks. There are 4 quarter-blocks in a character, either on or off. Each of the 4 shifted keyboard arrows flips a quarter-block on-and-off. In the above example, the effect was achieved with: ‘INS S- S- S-’  File: uniline.info, Node: Rectangular actions, Next: Long range actions contour and flood-fill, Prev: Glyphs ▷ ▶ → □ ◆ ╮─ insertion & modification, Up: Top 7 Rectangular actions ********************* • Drawing, • filling, • moving, • copying & yanking, • change line & glyph styles, those actions may be performed on a rectangular selection. Select a rectangular region with ‘C-SPC’ or ‘C-x SPC’ and move the cursor. You may also use ‘S-’ (‘’ being any of the 4 directions) to extend the selection. The buffer grows as needed with white spaces to accommodate the selection. Selection extension mode is active when ‘shift-select-mode’ is non-nil. Or you may use the mouse to highlight the desired region. All those region-highlighting are standard in ‘Emacs’, and unrelated to ‘Uniline’. Once you have a region highlighted, press ‘’ (this key can be customized, see *note Insert key::). The selection becomes rectangular if it was not. You are offered a menu of possible actions. * Menu: * Drawing a rectangle:: * Filling a rectangle:: * Moving a rectangular region:: * Copying, killing, yanking a rectangular region: Copying killing yanking a rectangular region. * Dashed lines and other styles:: * ASCII to UNICODE::  File: uniline.info, Node: Drawing a rectangle, Next: Filling a rectangle, Up: Rectangular actions 7.1 Drawing a rectangle ======================= To draw a rectangle in one shot, select a region, press ‘’, then hit: • ‘r’ to draw a rectangle inside the selection • ‘S-R’ to draw a rectangle outside the selection • ‘C-r’ to overwrite a rectangle inside the selection • ‘C-S-R’ to overwrite a rectangle outside the selection If needed, change the brush with any of ‘- + = # ’ ╭───────╮ r: inside╮╭───────╮ │ one │ ▗▄▄▄▄▄▄▖╭┤│▛▀▀▀▀▀▜│ │ ┏━━━━┿━━━━━━┓ ▐╭────╮▌│╰┼▌ ▐│ ╰──╂────╯ two ┃ ▐│ │▌│ │▙▄▄▄▄▄▟│ ┃ ╔═══════╋═╗ ▐│ ├▌╯ ╰─────┬─╯ ┗━━━╋━━━━━━━┛ ║ ▐╰────╯▌────────┴───╮ ║ three ║ ▝▀▀▀▀▀▀▘ R: outside╯ ╚═════════╝ ╭─────────╮ my text I │my text I│ want to ╶─R─▷ │want to │ box │box │ ╰─────────╯ The usual ‘C-_’ or ‘C-/’ keys may be hit to undo, even with the region still active visually.  File: uniline.info, Node: Filling a rectangle, Next: Moving a rectangular region, Prev: Drawing a rectangle, Up: Rectangular actions 7.2 Filling a rectangle ======================= While the rectangular mode is active, press ‘i’ to fill the rectangle. You will be asked to choose a character. You have those options: • for a regular character like ‘t’, just type it. • ‘SPC’ or ‘DEL’ for a shade of grey ‘" ░▒▓█"’ among the 5 available in UNICODE. ‘SPC’ to make it darker and darker. ‘DEL’ to make the rectangle lighter and lighter. • ‘C-y’ to chose the first character in the top of the kill ring. The above selection is the same as for the flood-fill action (see *note Flood-fill::).  File: uniline.info, Node: Moving a rectangular region, Next: Copying killing yanking a rectangular region, Prev: Filling a rectangle, Up: Rectangular actions 7.3 Moving a rectangular region =============================== Select a region, then press ‘’. Use arrow keys to move the rectangle around. A numeric prefix may be used to move the rectangle that many characters. • Under ‘Hydra’, be sure to specify the numeric prefix with just digits, without the ‘Alt’ key. Typing ‘15 ’ moves the rectangle 15 characters to the left. ‘M-15 ’ does not work. • Under ‘Transient’, use the ‘Alt’ key, like anywhere else in ‘Emacs’. Type ‘M-15 ’ to move the selected rectangle 15 characters to the left. Press ‘q’, ‘’, or ‘C-g’ to stop moving the rectangle. The ‘C-_’ key may also be used to undo the previous movements, even though the selection is still active. ▲ │ ╭─────┴──────╮ │this is │ │my rectangle│ ◀───┤I want to ├──▶ │move │ ╰─────┬──────╯ │ ▼ What is leakage? When moving a rectangular region, the rectangle leaves behind lines oriented in the movement direction. This is not a bug, but a feature. Leakage allows growing a drawing without breaking it in two parts. ┏┯━━━┓ ┏┯━━━┓ ┏━┛│ ┗━╦━━┓ with ┏━┛│ ┗━╦━━┓ ┃ │ ║ ┃╶──╮leak ┃ │ ║ ┃ leaked ┃ │ ║ ┃ ╰────▶ ┃ │ ║ ┃◀──────╴ ┗━━┷━━━━━╩━━┛ ┃ │ ║ ┃ lines │ ┗━━┷━━━━━╩━━┛ │without │leak ╰──────╮ ▼ ┏┯━━━┓ ┏━┛│ ┗━╦━━┓ ┃ │ ║ ┃ broken ◀───────╴ ┃ │ ║ ┃ drawing ┗━━┷━━━━━╩━━┛  File: uniline.info, Node: Copying killing yanking a rectangular region, Next: Dashed lines and other styles, Prev: Moving a rectangular region, Up: Rectangular actions 7.4 Copying, killing, yanking a rectangular region ================================================== A rectangle can be copied or killed, then yanked somewhere else. Select a region, press ‘’, then: • ‘c’ to copy • ‘k’ to kill • ‘y’ to yank (aka paste) This is similar to the ‘Emacs’ standard rectangle handling: • ‘C-x r r’ copy rectangle to register • ‘C-x r k’ kill rectangle • ‘C-x r y’ yank killed rectangle The first difference is that ‘Uniline’ rectangles, when killed and yanked, do not move surrounding characters. The second difference is that the white characters of the yanked rectangle are considered transparent. As a result, only non-blank parts of the yanked rectangle are over-printed. ‘Uniline’ and ‘Emacs’ standard rectangle share the same storage for copied and killed rectangles, namely the ‘killed-rectangle’ Lisp variable. So, a rectangle can be killed one way, and yanked another way.  File: uniline.info, Node: Dashed lines and other styles, Next: ASCII to UNICODE, Prev: Copying killing yanking a rectangular region, Up: Rectangular actions 7.5 Dashed lines and other styles ================================= ╭────▷───╮ ┏━━━━▶━━━┓ ╔════▶═══╗ │ ╭─□──╮ │ ┃ ┏━■━━┓ ┃ ║ ╔═■══╗ ║ △ │ │ ▽ ▲ ┃ ┃ ▼ ▲ ║ ║ ▼ │ ╰───◦╯ │ ┃ ┗━━━•┛ ┃ ║ ╚═══•╝ ║ ╰───◁────╯ ┗━━━◀━━━━┛ ╚═══◀════╝ ╭╌╌╌╌▷╌╌╌╮ ┏╍╍╍╍▶╍╍╍┓ ┆ ╭╌□╌╌╮ ┆ ┇ ┏╍■╍╍┓ ┇ △ ┆ ┆ ▽ ▲ ┇ ┇ ▼ ┆ ╰╌╌╌◦╯ ┆ ┇ ┗╍╍╍•┛ ┇ ╰╌╌╌◁╌╌╌╌╯ ┗╍╍╍◀╍╍╍╍┛ ╭┈┈┈┈▷┈┈┈╮ ┏┉┉┉┉▶┉┉┉┓ ┊ ╭┈□┈┈╮ ┊ ┋ ┏┉■┉┉┓ ┋ △ ┊ ┊ ▽ ▲ ┋ ┋ ▼ ┊ ╰┈┈┈◦╯ ┊ ┋ ┗┉┉┉•┛ ┋ ╰┈┈┈◁┈┈┈┈╯ ┗┉┉┉◀┉┉┉┉┛ A base drawing can be converted to dashed lines. Moreover, lines can be made either thin or thick. • Select the rectangular area you want to operate on (with mouse drag or ‘S-’, ‘S-’ and so on as described earlier). • Type ‘INS’, then ‘s’ (as "style"). You will be offered a choice of styles: • ‘3’: vertical lines will become 3 dashes per character, while horizontal ones will get 2 dashes per character. • ‘4’: vertical and horizontal lines will get 4 dashes per character. • ‘h’: thin lines corners, which are usually rounded, become hard angles. • ‘+’: thin lines and intersections become thick, empty glyphs get filled. • ‘-’: thick lines and intersections become thin, filled glyphs are emptied. • ‘=’: thick and thin lines become double lines. • ‘0’: come back to standard base-line ‘Uniline’ style: plain, not-dashed lines, thin corner rounded, ASCII art is converted to UNICODE. • ‘a’: apply the ‘aa2u-rectangle’ function from the unrelated ‘ascii-art-to-unicode’ package, to convert ASCII art to UNICODE (this only works if ‘ascii-art-to-unicode’ is already installed). Converting parts of a drawing from one style to another can produce nice looking sketches. ╭───╮ ╭───╮ ╭───╮ │░░░│ │░░░│ │░░░┝━▶┓ ╭╌╌╌╌╌╮ │░░░╰───╯░░░╰───╯░░░│ ┃ ┆░░░░░╰╌╌╌╌╌╮ □░░░░░░░░░░░░░░░░░░░│ ┗━┥░░░░░░░░░░░┆ │░░░╭───╮░░░╭───╮░░░│ ┆░░░░░╭╌╌╌╌╌╯ ╰───╯ ╰─┰─╯ ╰─┰─╯ ╰╌╌┰╌╌╯ ▲ ┃ ▼ ┗━━━━━━━┻━━━━━━━━━┛ ┏━━━┓ ┏━━━┓ ┏━━━┓ ┃░░░┃ ┃░░░┃ ┃░░░┠─▷╮ ┏╍╍╍╍╍┓ ┃░░░┗━━━┛░░░┗━━━┛░░░┃ │ ┇░░░░░┗╍╍╍╍╍┓ ■░░░░░░░░░░░░░░░░░░░┃ ╰─┨░░░░░░░░░░░┇ ┃░░░┏━━━┓░░░┏━━━┓░░░┃ ┇░░░░░┏╍╍╍╍╍┛ ┗━━━┛ ┗━┯━┛ ┗━┯━┛ ┗╍╍┯╍╍┛ △ │ ▽ ╰───────┴─────────╯  File: uniline.info, Node: ASCII to UNICODE, Prev: Dashed lines and other styles, Up: Rectangular actions 7.6 ASCII to UNICODE ==================== The standard base-line ‘Uniline’ (‘INS s 0’) or ‘aa2u-rectangle’ (‘INS s a’) conversions may be used to convert ASCII art to UNICODE. The original ASCII art may be drawn for instance by the ‘artist-mode’ or the ‘picture-mode’ packages. To use ‘aa2u-rectangle’, install the ‘ascii-art-to-unicode’ package by Thien-Thi Nguyen (RIP), available on ELPA. ‘Uniline’ does not requires a dependency on this package, by lazy evaluating any call to ‘aa2u-rectangle’. See +-------------+ +--+ | +-->-| +-----+ ASCII art | 1 +--------+--+ | 3 | made by +----+--------+ | +----+---+ Artist-mode | 2 +-<----+ +-----------+ ╭─────────────╮ ╭──╮ │ ├──▷─│ ╰─────╮ Converted to │ 1 ╭────────┼──╮ │ 3 │ Uniline base style ╰────┼────────╯ │ ╰────┬───╯ INS s 0 │ 2 ├─◁────╯ ╰───────────╯ ┌─────────────┐ ┌──┐ │ ├──>─│ └─────┐ Converted by │ 1 ┌────────┼──┐ │ 3 │ aa2u-rectangle └────┼────────┘ │ └────┬───┘ INS s a │ 2 ├─<────┘ └───────────┘ ‘INS s 0’ with selection active calls the ‘uniline-change-style-standard’ function. It converts what looks ASCII-art to UNICODE-art. Of course, there are ambiguities regarding whether a character is part of a sketch or not. The heuristic is to consider that a character is part of a sketch if it is surrounded by at least one other character which is part of a sketch. So, an isolated ‘-’ minus character will be left alone, while two such characters ‘--’ will be converted to UNICODE. Conversion will happens also for ‘<-’ for instance. Here is a fairly convoluted ASCII-art example, along with its conversion by ‘INS s 0’: ╭─↔--<-◁-◀--━+ +--->------==+ /----/ Rectangle1 |-----+-----+ Rectangle2 v v | | ^ " | "quote" +-\ ▼ ^^ \------------/ /-+-\ +------------+ " v | \--+------+--/ | | +----\----/--+ " >▷▶> \>--\ | | \---/ | | " v \==<===/ a=b 1=2 a-to-b +----+ ◁==/ >-> ╭─↔──◁─◁─◀──━┑ ╭───▷──────══╕ ╭────┤ Rectangle1 │─────╥─────┤ Rectangle2 ▽ ▽ │ │ △ ║ │ "quote" ├─╖ ▼ △^ ├────────────┤ ╭─╨─╮ ├────────────┤ ║ ▽ │ ╰──┬──────┬──╯ │ │ ╰────┬────┬──╯ ║ ▷▷▶▷ ╰▷──╮ │ │ ╰───╯ │ │ ║ ▽ ╘══◁═══╛ a=b 1=2 a-to-b ╰────╯ ◁══╝ ▷─▷  File: uniline.info, Node: Long range actions contour and flood-fill, Next: Macros, Prev: Rectangular actions, Up: Top 8 Long range actions: contour and flood-fill ******************************************** * Menu: * Tracing a contour:: * Flood-fill::  File: uniline.info, Node: Tracing a contour, Next: Flood-fill, Up: Long range actions contour and flood-fill 8.1 Tracing a contour ===================== ╭──────────────╮ ╭─╯A.written.text╰────────╮ │outlined by the.`contour'│ ╰─╮function.gets╶┬────────╯ ╰╮a.surrounding╰───────╮ ╰─╮line.in.the.current│ ╰─╮brush.style╭─────╯ ╰───────────╯ Choose or change the brush style with any of ‘-,+,=_,#,’. Put the cursor anywhere on the shape or outside but touching it. Then type: ‘ c’ A contour line is traced (or erased if brush style is ‘’) around the contiguous shape close to the cursor. When hitting capital letter: ‘ S-C’ the contour is overwritten. This means that if there was already a different style of line on the contour path, it is overwritten. The shape is distinguished because it floats in a blank characters ocean. For the shake of the contour function, blank characters are those containing lines as drawn by ‘Uniline’ (including true blank characters). Locations outside the buffer are also considered blank. The algorithm has an upper limit of ‘10000’ steps. This avoids an infinite loop in which the algorithm may end up in some rare cases. One of those cases is when the contour crosses a new-page character, displayed by ‘Emacs’ as ‘^L’. ‘10000’ steps require a fraction of a second to run. For shapes really huge, you may launch the contour command once again, at the point where the previous run ended. This ‘10000’ steps limit is customizable. Type: M-x customize-variable uniline-contour-max-steps  File: uniline.info, Node: Flood-fill, Prev: Tracing a contour, Up: Long range actions contour and flood-fill 8.2 Flood-fill ============== this.text.surrounds this.text.surrounds . / .▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒/ . //╶───▷╴.▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒// ... //// ...▒▒▒▒▒▒▒▒▒▒▒▒//// ...a.hole///// ...a.hole///// A hollow shape is a contiguous region of identical characters (not necessarily blank), surrounded by a boundary of different characters. The end of the buffer in any direction is also considered a boundary. Put the cursor anywhere in the hole. Then type: ‘ i’ Answer by giving a character to fill the hole. If instead of a character, ‘SPC’ or ‘DEL’ is typed, then a shade of grey character is picked. ‘SPC’ selects a darker grey than the one the point is on, while ‘DEL’ selects a lighter. There are 5 shades of grey in the UNICODE standard: ‘" ░▒▓█"’. Those grey characters are well supported by the suggested fonts. ‘C-y’ is also an option. The first character in the top of the kill ring will be chosen as the filling character. (The kill ring is filled by functions like ‘C-k’ or ‘M-w’, unrelated to ‘Uniline’). Typing ‘’ or ‘C-g’ aborts the filling operation. A rectangular shape may also be filled. • Mark a region • ‘ i’ • answer which character should be used to fill. There is no limit on the area to fill. Therefore, the filling operation may flood the entire buffer (but no more).  File: uniline.info, Node: Macros, Next: Which fonts?, Prev: Long range actions contour and flood-fill, Up: Top 9 Macros ******** ‘Uniline’ adds directional macros to the ‘Emacs’ standard macros. Record a macro as usual with ‘C-x (’ … ‘C-x )’. Then call it with the usual ‘C-x e’. But then, instead of executing the macro, a menu is offered to execute it in any of the 4 directions. When a macro is executed in a direction other than the one it was recorded, it is twisted in that direction. This means that recorded hits on the 4 keyboard arrows are rotated. It happens also for shift and control variations of those keys. Direction of text insertion is also rotated. There is still the classical ‘e’ option to call the last recorded macro. So instead of the usual ‘C-x e’, type ‘C-x e e’. And of course, the usual repetition typing repeatedly ‘e’ is available. Why are directional macros useful? To create fancy lines. For instance, if we want a doted-line instead of the continuous one, we record a macro for one step: C-x ( ;; begin recording INS o ;; insert a small dot ;; draw a line over 2 characters C-x ) ;; stop recording Then we call this macro repeatedly in any of the 4 directions: ·─·─·─·─· ╷ ·──· │ │ │ │ · · · · │ │ │ │ · ·─·─·─· · │ │ ·─·─·─·─·─·─· We can draw complex shapes by just drawing one step. Hereafter, we call a macro in 4 directions, closing a square: ╭╮╭╮╭╮╭╮╭╮╭╮ △ △ △ △ △ △ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╯╰╯╰╯╰╯╰╯╰╯│ ╶╯╶╯╶╯╶╯╶╯╶╯╷ ╭──╯∙╰─╯∙╰─╯∙╰─╯∙│ ▷┤□├▷┤□├▷┤□├▷┤□├▽ ╰╮ ╰╮ ◁╮ ╰▷ │∙ │ ╭┴┼─╯ ╰─╯ ╰─╯ ╰─┼┴╮ ╭╯ ╭╯ ╵ ╷ ╰╮ ╰╮ │□│ │□│ ╰╮ ╰╮ ◁╮ ╰▷ │ ∙│ ╰┬╯ ╰┬╯ ╭╯ ╭╯ ╵ ╷ ╭╯ ╭╯ △ ▽ ╰╮ ╰╮ ◁╮ ╰▷ │∙ │ ╭┴╮ ╭┴╮ ╭╯ ╭╯ ╵ ╷ ╰╮ ╰╮ │□│ │□│ ╰╮ ╰╮ ◁╮ ╰▷ │ ∙│ ╰┬┼─╮ ╭─╮ ╭─╮ ╭─┼┬╯ │╭╮╭╮╭╮╭╮╭╮╭─╯ ╵╭╴╭╴╭╴╭╴╭╴╭╴ │∙╭─╮∙╭─╮∙╭─╮∙╭──╯ △┤□├◁┤□├◁┤□├◁┤□├◁ ╰╯╰╯╰╯╰╯╰╯╰╯ ▽ ▽ ▽ ▽ ▽ ▽ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯  File: uniline.info, Node: Which fonts?, Next: Hydra or Transient?, Prev: Macros, Up: Top 10 Which fonts? *************** A mono-space character font must be used. It must also support UNICODE. * Menu: * Recommended fonts:: * Use case mixing fonts::  File: uniline.info, Node: Recommended fonts, Next: Use case mixing fonts, Up: Which fonts? 10.1 Recommended fonts ====================== Not all fonts are born equal. • ‘(set-frame-font "DejaVu Sans Mono" )’ • ‘(set-frame-font "Unifont" )’ • ‘(set-frame-font "Hack" )’ • ‘(set-frame-font "JetBrains Mono" )’ • ‘(set-frame-font "Cascadia Mono" )’ • ‘(set-frame-font "Agave" )’ • ‘(set-frame-font "JuliaMono" )’ • ‘(set-frame-font "FreeMono" )’ • ‘(set-frame-font "Iosevka Comfy Fixed" )’ • ‘(set-frame-font "Iosevka Comfy Wide Fixed")’ • ‘(set-frame-font "Aporetic Sans Mono" )’ • ‘(set-frame-font "Aporetic Serif Mono" )’ • ‘(set-frame-font "Source Code Pro" )’ Those fonts are known to support the required UNICODE characters, AND display them as mono-space. There are fonts advertised as mono-space which give arbitrary widths to non-ASCII characters. That is bad for the kind of drawings done by ‘Uniline’. You may want to try any of the suggested fonts. Just hit the corresponding entry in the ‘Uniline’ menu, or type ‘ f’. You may also execute the above Lisp commands like that: ‘M-: (set-frame-font "DejaVu Sans Mono")’ This setting is for the current session only. If you want to make it permanent, you may use the ‘Emacs’ customization: ‘ f *’ or ‘M-x customize-face default’ Beware that ‘Emacs’ tries to compensate for missing UNICODE support by the current font. ‘Emacs’ substitutes one font for another, character per character. The user may not notice until the drawings done under ‘Emacs’ are displayed on another text editor or on the Web. Of course, using the suggested fonts and the UNICODEs drawn by ‘Uniline’ keeps you away from those glitches. To know which font ‘Emacs’ has chosen for a given character, type: ‘C-u C-x =’ Note that none of those commands downloads a font from the Web. The font should already be available.  File: uniline.info, Node: Use case mixing fonts, Prev: Recommended fonts, Up: Which fonts? 10.2 Use case: mixing fonts =========================== A user on GitHub, dmullis, exposed his use-case. A source-code base is usually edited with a font not in the Uniline list of recommended fonts. However, it is desirable to document the source code with Uniline, either directly along the source or in separate files. How to achieve that without messing with the fonts in several Emacs buffers? Several solutions have emerged from the discussion. • ‘face-remap-add-relative’ A line like this at the top of the files reserved for Uniline drawings: -*- eval: (face-remap-add-relative 'default :family "DejaVu Sans Mono"); -*- This confines its effect to just the one single buffer. • ‘uniline-mode-hook’ Add a hook (a function called when entering ‘uniline-mode’): (add-hook 'uniline-mode-hook (lambda () (face-remap-add-relative 'default :family "DejaVu Sans Mono"))) There are also ‘uniline-mode-on-hook’ & ‘uniline-mode-off-hook’ which can be handy. • ‘font-lock-comment-face’ An alternative mean of limiting the scope of the font change is the Emacs standard font-lock mechanism. (customize-face '(font-lock-comment-face)) Then check ‘Font Family’, type in value ‘"DejaVu Sans Mono",’ and ‘C-x C-s’. Now any major mode that understands "comments" as distinct from other text can safely nest a Uniline drawing within its boundaries, all text outside the "comment" unaffected (except perhaps by spacing). Look also at the ‘font-lock-constant-face’ face. • Org Mode In Org Mode, the usable faces could be ‘org-block’, ‘org-quote’, ‘org-verse’. But first the ‘org-fontify-quote-and-verse-blocks’ variable must be set to ‘t’. • Markdown In Markdown mode, customize the ‘markdown-pre-face’ or ‘markdown-code-face’ faces.  File: uniline.info, Node: Hydra or Transient?, Next: Customization, Prev: Which fonts?, Up: Top 11 Hydra or Transient? ********************** The basic usage of ‘Uniline’ should be easy: just move the point, and lines are traced. Change brush to draw thicker lines. More complex actions are summoned by the ‘’ key, with or without selection. This is a single key to remember. Then a textual menu is displayed, giving the possible keys continuations and their meaning. All that is achieved by the ‘Hydra’ or ‘Transient’ libraries, which are now part of ‘Emacs’ (thanks!). The ‘Hydra’ and ‘Transient’ libraries offer similar features. Some users may prefer one or the other. ‘Uniline’ was developed from day one with ‘Hydra’. ‘Transient’ is a late addition. * Menu: * Selecting Hydra or Transient:: * Instantly selecting Hydra or Transient:: * One-liner menus:: * The Hydra interface:: * The Transient interface::  File: uniline.info, Node: Selecting Hydra or Transient, Next: Instantly selecting Hydra or Transient, Up: Hydra or Transient? 11.1 Selecting Hydra or Transient ================================= Two files are compiled when installing ‘Uniline’ • ‘uniline-hydra.el’ • ‘uniline-transient.el’ One of them should be loaded (but not both). There are several ways. The cleanest is ‘use-package’. Add those lines to your ‘~/.emacs’ file: (use-package uniline-hydra :bind ("C-" . uniline-mode)) or: (use-package uniline-transient :bind ("C-" . uniline-mode)) The following key sequences can assist in modifying the ‘.emacs’ file: • ‘ * H’ • ‘ * T’ Note: there used to be a customizable setting to switch between the two interfaces. This had many issues. One of them is that the native-compiler is blind to all user-customized settings. There is a third file, ‘uniline-code.elc’. Loading ‘uniline-hydra.elc’ or ‘uniline-transient.elc’ automatically loads ‘uniline-core.elc’.  File: uniline.info, Node: Instantly selecting Hydra or Transient, Next: One-liner menus, Prev: Selecting Hydra or Transient, Up: Hydra or Transient? 11.2 Instantly selecting Hydra or Transient =========================================== It is now possible to switch user interfaces on the fly. To do so, look at the "Customize" entry in the Uniline menu. This menu is available: • from the menu-bar at the top of the Emacs screen (if not made invisible), • by left-clicking on ‘"Uniline"’ in the mode-line, at the bottom of the Emacs screen. Note that the changes are for the current session only. To permanently choose Hydra or Transient, change your =~/.emacs=initialization file as describe in *note Selecting Hydra or Transient::. The actions performed by the menu are: • ‘(load-library "uniline-hydra")’ • ‘(load-library "uniline-transient")’ You can execute them directly or by other means.  File: uniline.info, Node: One-liner menus, Next: The Hydra interface, Prev: Instantly selecting Hydra or Transient, Up: Hydra or Transient? 11.3 One-liner menus ==================== The multi-lines menus in Hydra and Transient are quite useful for casual users. For seasoned users, those huge textual menus may distract them from their workflow. It is now possible to switch to less distracting textual menus. They are displayed in the echo-area on a single line. To do so, type: • ‘C-t’ within a sub-mode (glyph insertion mode, rectangle handling, etc.) • ‘C-h TAB’ at the top-level. This will flip between the two sizes of textual menus. It also affects the welcome message, the one displayed when entering the ‘Uniline’ minor mode. The current size is controlled by the ‘uniline-hint-style’ variable: • ‘t’ for full fledged messages over several lines • ‘1’ for one-liner messages • ‘0’ for no message at all The variable is "buffer-local", which means that it can take distinct values on distinct buffers. Its default value can be customized and saved for future sessions: ‘M-x customize-variable uniline-hint-style’ After customization it can be changed later, on a buffer per buffer basis, with the ‘C-t’ or ‘C-h TAB’ keys. Transient natively offers a similar setting: ‘transient-show-popup’. (There is no such variable in Hydra). It can be customized with ‘t’, ‘nil’, ‘0’ (zero), or a number. This is similar but not exactly the same as the Hydra behavior and the ‘uniline-hint-style’. the Transient setting stays in effect until the ‘C-t’ or ‘C-h TAB’ keys are not used, . As soon as one of those keys is invoked, ‘transient-show-popup’ is toggled (which does not happens in Transient alone). The change is kept in effect throughout the ‘Uniline’ session, but no longer.  File: uniline.info, Node: The Hydra interface, Next: The Transient interface, Prev: One-liner menus, Up: Hydra or Transient? 11.4 The Hydra interface ======================== Put that in your ‘~/.emacs’ file: (use-package uniline-hydra :bind ("C-" . uniline-mode)) It has been asked by ‘Transient’-only users to avoid installing the ‘Hydra’ package. Currently, it is not possible to make dependencies conditional in ‘Melpa’. And removing the ‘Hydra’ dependency would hurt ‘Hydra’ users. Therefore, for the time being, the ‘Hydra’ package is still installed when installing ‘Uniline’ through ‘Melpa’.  File: uniline.info, Node: The Transient interface, Prev: The Hydra interface, Up: Hydra or Transient? 11.5 The Transient interface ============================ Put that in your ‘~/.emacs’ file: (use-package uniline-transient :bind ("C-" . uniline-mode)) ‘Transient’ interface was added recently to ‘Uniline’. This leaded to the splitting of the single ‘uniline.el’ file into 4 source files. Hopefully, the added complexity remains hidden by the ‘Elpa’ - ‘Melpa’ packaging system.  File: uniline.info, Node: Customization, Next: How Uniline behaves with its environment?, Prev: Hydra or Transient?, Up: Top 12 Customization **************** Type: ‘M-x customize-group uniline’. Or ‘Menu bar ⟶ Options ⟶ Customize Emacs ⟶ Specific Group… ⟶ "uniline"’. This invokes the standard ‘Emacs’ customization system. Your settings will be saved in the file pointed to by the ‘custom-file’ variable if set, or your ‘~/.emacs’ file. (Along with all your other settings unrelated to ‘Uniline’). Two settings are special: interface type (obsolete) & the insert key. The other settings are self-explanatory * Menu: * Interface type:: * Insert key:: * Maximum steps when drawing a contour:: * Cursor type:: * Hint style:: * Welcome message visibility:: * Line spacing:: * Font:: * Upward infiniteness ∞::  File: uniline.info, Node: Interface type, Next: Insert key, Up: Customization 12.1 Interface type =================== The ‘uniline-interface’ variable is *obsolete*. Choosing between ‘Hydra’ or ‘Transient’ interface is done by loading one or the other sub-package. This is best done in the ‘.emacs’ initialization file. See *note Installation:: for details. Typing either of the following key sequences can assist in modifying the ‘.emacs’ file: • ‘ * H’ • ‘ * T’  File: uniline.info, Node: Insert key, Next: Maximum steps when drawing a contour, Prev: Interface type, Up: Customization 12.2 Insert key =============== By default, the ‘’ or ‘INS’ key is the prefix for most of the ‘Uniline’ actions. Some computers do not have an ‘INS’ key, or it is bound to some other command (Apple?). This can be changed temporarily or permanently. The customization allows to set several keys at the same time. Depending on whether ‘Emacs’ is run in a graphical environment or a text-only terminal, either the ‘’ or the ‘’ events are generated by the ‘INS’ key. Therefore, by default ‘Uniline’ defines both events as the ‘INS’ key. Variable ‘uniline-key-insert’.  File: uniline.info, Node: Maximum steps when drawing a contour, Next: Cursor type, Prev: Insert key, Up: Customization 12.3 Maximum steps when drawing a contour ========================================= Defaults to ‘10000’. To avoid an infinite loop in some rare cases. Variable ‘uniline-contour-max-steps’.  File: uniline.info, Node: Cursor type, Next: Hint style, Prev: Maximum steps when drawing a contour, Up: Customization 12.4 Cursor type ================ Hollow by default, so that what is under the cursor remains visible. There is the option to leave the cursor as it is. Variable ‘uniline-cursor-type.’  File: uniline.info, Node: Hint style, Next: Welcome message visibility, Prev: Cursor type, Up: Customization 12.5 Hint style =============== Currently only applicable to the ‘Hydra’. It defaults to "full fledged menus". Variable ‘uniline-hint-style’. ‘Transient’ offers a similar setting: ‘transient-show-popup’.  File: uniline.info, Node: Welcome message visibility, Next: Line spacing, Prev: Hint style, Up: Customization 12.6 Welcome message visibility =============================== Default is "on". Turn it "off" for less distraction. Even when turned of, the welcome message can still be displayed by pressing ‘C-h TAB’. Variable ‘uniline-show-welcome-message’.  File: uniline.info, Node: Line spacing, Next: Font, Prev: Welcome message visibility, Up: Customization 12.7 Line spacing ================= The ‘line-spacing’ setting in ‘Emacs’ can change the display of a sketch. (This setting is unrelated to ‘Uniline’). The best looking effect is given by: (setq line-spacing nil) You may want to change your current setting. ‘Uniline’ may handle this variable some day. Right now, ‘line-spacing’ is left as a matter of choice for everyone. ╭────┬────────┬────╮ ╺┯━━━━┯┯━━┯┯━┯┯━━━━━━━━┯┯━━━━━━━┯┯━━━━━━┯╸ │▒▒▒▒╰────────╯▒▒▒▒│ │ │╰is╯╰a╯│ ││ │╰around╯ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ ╰this╯ ╰sentence╯╰hanging╯ │▒▒▒╭─╮▒▒▒▒▒▒╭─╮▒▒▒│ △ │▒▒▒╰─╯▒▒▒▒▒▒╰─╯▒▒▒│ │ △ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ ╰─────────┬────────╯ ╰──────────────────╯ verbs (setq line-spacing nil)  File: uniline.info, Node: Font, Next: Upward infiniteness ∞, Prev: Line spacing, Up: Customization 12.8 Font ========= Face customization is unrelated to ‘Uniline’. However, ‘Uniline’ can assist in choosing a good font and customizing the ‘default’ face. See *note Which fonts?::. Type ‘ f’ to select a font just for the current ‘Uniline’ session. Type ‘*’ to enter the ‘Emacs’ customization of the ‘default’ face and retain your choice for future sessions.  File: uniline.info, Node: Upward infiniteness ∞, Prev: Font, Up: Customization 12.9 Upward infiniteness ∞ ========================== If the variable ‘uniline-infinite-up↑’ is: • ‘t’, then the buffer grows at the top of the buffer (or at the top of the narrowed region), by adding empty lines as needed. • ‘nil’, then the top of the buffer (or the top of the narrowed region) is a non-trespassable limit. This is the default, and the behaviour of previous versions of Uniline.  File: uniline.info, Node: How Uniline behaves with its environment?, Next: Lisp API, Prev: Customization, Up: Top 13 How Uniline behaves with its environment? ******************************************** * Menu: * Language environment:: * Compatibility with Picture-mode:: * Compatibility with Artist-mode:: * Compatibility with Whitespace-mode:: * Compatibility with Org Mode:: * Org Mode and LaTex:: * What about \t tabs?:: * What about ^L page separation?:: * Emacs on the Linux console:: * Emacs on a graphical terminal emulator:: * Emacs on Windows:: * Compatibility with ASCIIFlow::  File: uniline.info, Node: Language environment, Next: Compatibility with Picture-mode, Up: How Uniline behaves with its environment? 13.1 Language environment ========================= The so called "language environment" in Emacs can cause unwanted line breaks, like in this drawing: ╶───────────┬─────╮ │ │ │ │ │ │ ╰───────────╯ unexpected broken lines ╶─────┬───────────╮ │ │ │ │ │ │ ╰───────────╯ expected continuous lines The above example was drawn first with the ‘Chinese-BIG5’ language environment, then with the ‘English’ environment. There is nothing specific about ‘Chinese-BIG5’. It is just an instance picked out from more than 100 language environments. C-x RET l Chinese-BIG5 C-x RET l English In ‘Chinese-BIG5’, some characters are considered twice as wide as standard characters. Whereas in ‘English’, all characters needed by Uniline are 1 unit wide. Thanks to *rumengling* (GitHub) for discovering and diagnosing the issue! To workaround the issue, when entering ‘uniline-mode’, the width of all characters Uniline uses is checked. If some of them are more than 1, the ‘char-width-table’ variable is patched. What are the consequences of this patch? The ‘char-width-table’ variable is an Emacs global. Therefore the patch by Uniline will affect all buffers. As the characters touched by the patch are graphic, and have nothing to do with Chinese, it should not have any significance on text written in Chinese. It was pondered whether Uniline should put back ‘char-width-table’ at its original value upon exiting ‘uniline-mode’, or leaving the patch. For now, it has been decided to leave it. Because anyway, intertwining several ‘uniline-mode’ and changes to the language environment is intractable. In case of something, re-setting the language environment to its same value cancels the patch to ‘char-width-table’ by Uniline.  File: uniline.info, Node: Compatibility with Picture-mode, Next: Compatibility with Artist-mode, Prev: Language environment, Up: How Uniline behaves with its environment? 13.2 Compatibility with Picture-mode ==================================== ‘Picture-mode’ and ‘uniline-mode’ are compatible. Their features overlap somehow: • Both implement an unlimited buffer in east and south directions. • Both visually truncate long lines (actual text is not truncated). • Both set the overwrite mode (‘uniline-mode’ activates ‘overwrite-mode’, while ‘picture-mode’ re-implements it) • Both are able to draw rectangles (‘uniline-mode’ in UNICODE, ‘picture-mode’ in ASCII), copy and yank them. They also have features unique to each: • ‘Picture-mode’ writes in 8 possible directions • ‘Picture-mode’ handles TAB stops • ‘Uniline-mode’ draws lines and arrows  File: uniline.info, Node: Compatibility with Artist-mode, Next: Compatibility with Whitespace-mode, Prev: Compatibility with Picture-mode, Up: How Uniline behaves with its environment? 13.3 Compatibility with Artist-mode =================================== ‘Artist-mode’ and ‘uniline-mode’ are mostly incompatible. This is because ‘artist-mode’ preempts the arrow keys, which give access to a large part of ‘uniline-mode’ features. However, it is possible to use both one after the other.  File: uniline.info, Node: Compatibility with Whitespace-mode, Next: Compatibility with Org Mode, Prev: Compatibility with Artist-mode, Up: How Uniline behaves with its environment? 13.4 Compatibility with Whitespace-mode ======================================= ‘Whitespace-mode’ and ‘uniline-mode’ are mostly compatible. Why activate ‘whitespace-mode’ while in ‘uniline-mode’? Because ‘Uniline’ creates a lot of white-spaces to implement an infinite buffer. And it is funny to look at this activity. To make ‘uniline-mode’ and ‘whitespace-mode’ fully compatible, disable the newline visualization: • ‘M-x customize-variable whitespace-style’ • uncheck ‘(Mark) NEWLINEs’ This is due to a glitch in ‘move-to-column’ when a visual property is attached to newlines. And ‘uniline-mode’ makes heavy use of ‘move-to-column’.  File: uniline.info, Node: Compatibility with Org Mode, Next: Org Mode and LaTex, Prev: Compatibility with Whitespace-mode, Up: How Uniline behaves with its environment? 13.5 Compatibility with Org Mode ================================ You may want to customize the shift extension mode in ‘Org Mode’. This is because ‘Org Mode’ preempts ‘shift-select-mode’ for other useful purposes. Just type: M-x customize-variable org-support-shift-select and choose "when outside special context", which sets it to ‘t’. You then get the shift-selection from ‘Org Mode’, not from ‘Uniline’. The difference is that the ‘Uniline’’s one handles the infinite-ness of the buffer. Other than that, ‘Uniline’ is compatible with ‘Org Mode’ Thanks to jdtsmith (GitHub) for sharing a funny fact he discovered. If a source block is created with the ‘Uniline’ language (‘Uniline’ is *not* a language like ‘C++,’ ‘Python’, or ‘Bash’), then it can be edited (‘M-x org-edit-special’) with ‘uniline-mode’ automatically activated. #+begin_src uniline ╭───╮ ╭───╮ │ ╷ ╰───╯ ╷ │ │ ╰─ ╶─╯ │ ╰╮ ● ● ╭╯ │ ╷ │ ╰╮ ────╯ ╭╯ ╰───────╯ #+end_src  File: uniline.info, Node: Org Mode and LaTex, Next: What about \t tabs?, Prev: Compatibility with Org Mode, Up: How Uniline behaves with its environment? 13.6 Org Mode and LaTex ======================= Use the ‘pmboxdraw’ LaTex module. This gives limited support for "box drawing" characters in LaTex documents. Example: #+LATEX_HEADER: \usepackage{pmboxdraw} #+begin_src text this works: ┌─────┐ ┌────────────┐ │ ├───────┤ │ └─────┘ │ │ ┌─────┐ ┌────┤ │ │ ├──┘ │ │ └─────┘ ┌────┤ │ ┌─────┐ │ │ │ │ ├──┘ └────────────┘ └─────┘ this does not quite work: ┏━━━┓ ┏━━┓ ┏━━━━━┓ ┃ ┃ ┃ ┣━━━━━┫ ┃ ┃ ┗━━┛ ┃ ┏┛ ┃ ┗━━━━━━━━━┛ ┗━━━━━━┛ but that is OK: ┏━━━┓ ┃ ┃ ┗━━━┛ that is OK too: ╺════╦══╗ ╔════╗ ║ A║ ║ B ╚══╗ ╚══╝ ╚═══════╝ this works: ├── dev └┬┬ release │├── new │└── old ├── graph └── non-graph #+end_src Note that corners of thin lines should be sharp. There is no support for rounded corners. To export this Org Mode example to PDF through LaTex, type: ‘C-c C-E l o’  File: uniline.info, Node: What about \t tabs?, Next: What about ^L page separation?, Prev: Org Mode and LaTex, Up: How Uniline behaves with its environment? 13.7 What about ‘\t’ tabs? ========================== Some files may contain tabs (the character ‘\t’). Those include programming code (Python, Perl, C++, D, Rust, JavaScript and so on). When ‘Uniline’ draws something in the middle of a TAB, or right onto a TAB, it first converts it to spaces, then proceeds as usual. This process is invisible. So be cautious if TABs have a special meaning in the file. Also, rectangles are first untabified (if there are TABs) before moving them. This avoids some rare instances of misalignment. One way to see what is going on, is to activate the ‘whitespace-mode’.  File: uniline.info, Node: What about ^L page separation?, Next: Emacs on the Linux console, Prev: What about \t tabs?, Up: How Uniline behaves with its environment? 13.8 What about ‘^L’ page separation? ===================================== ‘Uniline’ does not work well with ‘^L’ (page separation) character. Nor with similar characters, like ‘^T’. When trying to draw a line over such a character, the cursor may get stuck. This is because those characters occupy twice the width of a normal character. Just try to get away from ‘^L’, ‘^T’ and such when drawing with ‘Uniline’.  File: uniline.info, Node: Emacs on the Linux console, Next: Emacs on a graphical terminal emulator, Prev: What about ^L page separation?, Up: How Uniline behaves with its environment? 13.9 Emacs on the Linux console =============================== Linux consoles are the 7 non-graphic screens which can be accessed usually typing ‘C-M-F1’, ‘C-M-F2’, and so on. Such a screen is also presented when connecting through ‘ssh’ or ‘tls’ into a non-graphical server. By default they use a font named "Fixed" with poor support for Unicode. However, it supports lines of the 3 types, mixing all of them in thin lines though. Another problem is that by default ‘S-’ and ‘C-’ are indistinguishable from ‘’. Same problem with ‘’, ‘’, ‘’ and ‘’. This has nothing to do with ‘Emacs’. A solution can be found here:  File: uniline.info, Node: Emacs on a graphical terminal emulator, Next: Emacs on Windows, Prev: Emacs on the Linux console, Up: How Uniline behaves with its environment? 13.10 Emacs on a graphical terminal emulator ============================================ This is the ‘Emacs’ launched from a terminal typing ‘emacs -nw’. In this environment, ‘’ does not exist. It is replaced by ‘’. This has already been taken into account by ‘Uniline’ by duplicating the key-bindings for the two flavors of this key. If you decide to bind globally ‘C-’ to the toggling of ‘Uniline’ minor mode as suggested, then you will have to do the same for ‘C-’, for example with ‘use-package’ in your ‘~/.emacs’ file: (use-package uniline :defer t :bind ("C-" . uniline-mode) :bind ("C-" . uniline-mode))  File: uniline.info, Node: Emacs on Windows, Next: Compatibility with ASCIIFlow, Prev: Emacs on a graphical terminal emulator, Up: How Uniline behaves with its environment? 13.11 Emacs on Windows ====================== On Windows the only native mono-spaced fonts are ‘Lucida Console’ and ‘Courier New’. They are not mono-spaced for the Unicodes used by ‘Uniline’. Often, the ‘Consolas’ font is present on Windows. It supports quite well the required Unicodes to draw lines. A few glyphs produce unaligned result though. They should be avoided under ‘Consolas’: ‘△▶▹◆’ Of course, other fonts may be installed. It is quite easy.  File: uniline.info, Node: Compatibility with ASCIIFlow, Prev: Emacs on Windows, Up: How Uniline behaves with its environment? 13.12 Compatibility with ASCIIFlow ================================== ASCIIFlow is a ASCII-UNICODE diagram drawing tool (as Uniline). It works on a web browser. Just open and start drawing. There is no server, ASCIIFlow operates locally on your PC. Your diagrams survive web browser sessions, as they are saved locally behind the scene. When your drawing is complete, you can export it to Emacs-Uniline: • Click on the download button • Select ‘"ASCII Extended"’ • Paste your diagram in Emacs with ‘C-y’ • Modify it with Uniline For the other way around, a Uniline drawing can be exported to ASCIIFlow: • Copy it from Emacs (with ‘M-w’ for instance). • In ASCIIFlow, choose ‘"Select & Move"’ • Type ‘C-v’ • Edit with ASCIIFlow  File: uniline.info, Node: Lisp API, Next: Mouse support, Prev: How Uniline behaves with its environment?, Up: Top 14 Lisp API *********** Could ‘Uniline’ be programmed (versus used interactively)? Yes! The API is usable programmatically: * Menu: * Move the cursor:: * Brush:: * Example Lisp function to draw a plus sign:: * Long range actions (contour, flood-fill, rectangle): Long range actions (contour flood-fill rectangle). * Constants:: * Macro and text direction:: * Insert and tweak glyphs:: * Change to alternate styles::  File: uniline.info, Node: Move the cursor, Next: Brush, Up: Lisp API 14.1 Move the cursor ==================== Move cursor while drawing lines by calling any of the 4 directions functions: • ‘uniline-write-up↑’ • ‘uniline-write-ri→’ • ‘uniline-write-dw↓’ • ‘uniline-write-lf←’ They expect a repeat ‘count’ (usually 1) and optionally ‘force=t’ to overwrite the buffer  File: uniline.info, Node: Brush, Next: Example Lisp function to draw a plus sign, Prev: Move the cursor, Up: Lisp API 14.2 Brush ========== Set the current brush by calling any of the following: • ‘uniline--set-brush-nil’ ;; write nothing • ‘uniline--set-brush-0’ ;; eraser • ‘uniline--set-brush-1’ ;; single thin line╶─╴ • ‘uniline--set-brush-2’ ;; single thick line╺━╸ • ‘uniline--set-brush-3’ ;; double line╺═╸ • ‘uniline--set-brush-block’ ;; blocks ▙▄▟▀ Those functions are equivalent to: • ‘(setq uniline--brush nil)’ • ‘(setq uniline--brush 0)’ • ‘(setq uniline--brush 1)’ • ‘(setq uniline--brush 2)’ • ‘(setq uniline--brush 3)’ • ‘(setq uniline--brush :block)’ except the functions also update the mode-line.  File: uniline.info, Node: Example Lisp function to draw a plus sign, Next: Long range actions (contour flood-fill rectangle), Prev: Brush, Up: Lisp API 14.3 Example: Lisp function to draw a plus sign =============================================== For instance, if we want to create a function to draw a "plus" sign, we can code it as follows: (defun uniline-draw-plus () (interactive) (uniline-write-ri→ 1) (uniline-write-dw↓ 1) (uniline-write-ri→ 1) (uniline-write-dw↓ 1) (uniline-write-lf← 1) (uniline-write-dw↓ 1) (uniline-write-lf← 1) (uniline-write-up↑ 1) (uniline-write-lf← 1) (uniline-write-up↑ 1) (uniline-write-ri→ 1) (uniline-write-up↑ 1)) Calling ‘M-x uniline-draw-plus’ will result in this nice little plus-shape: ╭╮ ╭╯╰╮ ╰╮╭╯ ╰╯ generated by M-x uniline-draw-plus We may modify the function to accept the size of the shape as a parameter: (defun uniline-draw-plus (size) (interactive "Nsize? ") (uniline-write-ri→ size) (uniline-write-dw↓ size) (uniline-write-ri→ size) (uniline-write-dw↓ size) (uniline-write-lf← size) (uniline-write-dw↓ size) (uniline-write-lf← size) (uniline-write-up↑ size) (uniline-write-lf← size) (uniline-write-up↑ size) (uniline-write-ri→ size) (uniline-write-up↑ size)) The ‘(interactive "Nsize? ")’ form prompts user for the size of the shape if not given as a parameter. This API works in any mode, not only in ‘Uniline’ minor mode. It takes care of the infiniteness of the buffer in the right and down directions.  File: uniline.info, Node: Long range actions (contour flood-fill rectangle), Next: Constants, Prev: Example Lisp function to draw a plus sign, Up: Lisp API 14.4 Long range actions (contour, flood-fill, rectangle) ======================================================== There are other useful functions operating on many characters at once. Contour tracing and flood-filling are among them: • ‘uniline-contour’ • ‘uniline-fill’ The following functions operate on a rectangular region, which must be active prior to calling them: • ‘uniline-draw-inner-rectangle’ • ‘uniline-draw-outer-rectangle’ • ‘uniline-copy-rectangle’ • ‘uniline-kill-rectangle’ • ‘uniline-yank-rectangle’ • ‘uniline-fill-rectangle’ • ‘uniline-move-rect-up↑’ • ‘uniline-move-rect-ri→’ • ‘uniline-move-rect-dw↓’ • ‘uniline-move-rect-lf←’  File: uniline.info, Node: Constants, Next: Macro and text direction, Prev: Long range actions (contour flood-fill rectangle), Up: Lisp API 14.5 Constants ============== Constants for the 4 directions: • ‘uniline-direction-up↑’ ;; constant 0 • ‘uniline-direction-ri→’ ;; constant 1 • ‘uniline-direction-dw↓’ ;; constant 2 • ‘uniline-direction-lf←’ ;; constant 3  File: uniline.info, Node: Macro and text direction, Next: Insert and tweak glyphs, Prev: Constants, Up: Lisp API 14.6 Macro and text direction ============================= Changing text direction: • ‘uniline-text-direction-up↑’ • ‘uniline-text-direction-ri→’ • ‘uniline-text-direction-dw↓’ • ‘uniline-text-direction-lf←’ or (in this case the mode-line is not updated): • ‘(setq uniline-text-direction uniline-direction-up↑)’ • ‘(setq uniline-text-direction uniline-direction-ri→)’ • ‘(setq uniline-text-direction uniline-direction-dw↓)’ • ‘(setq uniline-text-direction uniline-direction-lf←)’ Call macro in any direction: • ‘uniline-call-macro-in-direction-up↑’ • ‘uniline-call-macro-in-direction-ri→’ • ‘uniline-call-macro-in-direction-dw↓’ • ‘uniline-call-macro-in-direction-lf←’  File: uniline.info, Node: Insert and tweak glyphs, Next: Change to alternate styles, Prev: Macro and text direction, Up: Lisp API 14.7 Insert and tweak glyphs ============================ Insert and cycle intersection glyphs: • ‘uniline-insert-fw-arrow’ • ‘uniline-insert-fw-square’ • ‘uniline-insert-fw-oshape’ • ‘uniline-insert-fw-cross’ • ‘uniline-insert-fw-grey’ • ‘uniline-insert-bw-arrow’ • ‘uniline-insert-bw-square’ • ‘uniline-insert-bw-oshape’ • ‘uniline-insert-bw-cross’ • ‘uniline-insert-bw-grey’ Rotate arrow or tweak 4-half-lines or 4-block characters: • ‘uniline-rotate-up↑’ • ‘uniline-rotate-ri→’ • ‘uniline-rotate-dw↓’ • ‘uniline-rotate-lf←’ Here are the lowest level functions. Move point, possibly extending the buffer in right and bottom directions: • ‘uniline-move-to-column’ • ‘uniline-move-to-line’ • ‘uniline-move-to-lin-col’ • ‘uniline-move-to-delta-column’ • ‘uniline-move-to-delta-line’  File: uniline.info, Node: Change to alternate styles, Prev: Insert and tweak glyphs, Up: Lisp API 14.8 Change to alternate styles =============================== A drawing in a rectangular selection may have its style changed: • ‘uniline-change-style-dot-3-2’ ;; 3 dashes vert. ┆, 2 horiz. ╌ • ‘uniline-change-style-dot-4-4’ ;; 4 dashes vert. ┊ & horiz. ┈ • ‘uniline-change-style-standard’ ;; back to Uniline base style • ‘uniline-change-style-hard-corners’ ;; rounded corners╭╴become hard┌ • ‘uniline-change-style-thin’ ;; convert to ╭╴ thin lines • ‘uniline-change-style-thick’ ;; convert to ┏╸ thick lines • ‘uniline-change-style-double’ ;; convert to ╔═ thick lines • ‘uniline-aa2u-rectangle’ ;; call aa2u to convert ASCII to Unicode The above functions require a region to be marked.  File: uniline.info, Node: Mouse support, Next: Installation, Prev: Lisp API, Up: Top 15 Mouse support **************** The out-of-the-box mouse support of ‘Emacs’ works perfectly. Except when the mouse clicks on a position outside the buffer. This happens when clicking past the end of a too short line, or past the end of the buffer. To handle those cases, a few standard ‘Emacs’ functions have been extended to add blank characters or blank lines. Doing so, the mouse-click now falls on a valid part of the buffer. Of course, those extensions are only active on ‘uniline-mode’ activated buffers. Beware that when the window is at the same time zoomed with ‘C-x C-+ C--’ AND horizontally scrolled with ‘C-x <’, the cursor positioning is not accurate. This is due to ‘Emacs’ limitations and bugs. Just click twice to fix the inaccuracy.  File: uniline.info, Node: Installation, Next: Related packages, Prev: Mouse support, Up: Top 16 Installation *************** * Menu: * use-package, the straightforward way: use-package the straightforward way. * Without use-package::  File: uniline.info, Node: use-package the straightforward way, Next: Without use-package, Up: Installation 16.1 use-package, the straightforward way ========================================= The ‘use-package’ library became the de-facto standard to manage packages in your ‘.emacs’ initialization file. The ‘use-package’ library comes along with Emacs. It can (among other services) delay loading external packages until they are used, and bind keyboard shortcuts to the package’s entry points. Add the following lines to your ‘.emacs’ file, and reload it, if not already done. This says that the popular Melpa repository is one of the central store of third parties packages. To day, it provides almost 7000 packages to choose from. (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t) (package-initialize) Alternately you may customize this variable: M-x customize-variable package-archives Then add those lines in your Emacs initialization file (usually ‘~/.emacs’): (use-package uniline-hydra :bind ("C-" . uniline-mode)) or: (use-package uniline-transient :bind ("C-" . uniline-mode)) This tell Emacs: • Be prepared to load ‘uniline-mode’ when the user request it, but do not load it now. • Bind the ‘C-’ keys to the function ‘uniline-mode’. This shortens the longer ‘M-x uniline-mode’ command. Any other key combinations can be bound, as you prefer. ‘’ happens to also be the key used inside ‘Uniline’ (customizable). • Load either the ‘uniline-hydra’ or the ‘uniline-transient’ file, as you prefer. This gives Uniline one or the other flavour of user-interface. There is an alias to ‘uniline-hydra’: (use-package uniline :bind ("C-" . uniline-mode)) If you are using *note straight.el:: with ‘use-package’, and have ‘(setq straight-use-package-by-default t)’, you have the following options: ;; uniline-hydra using the alias (use-package uniline) ;;; uniline-hydra explicitly requested (use-package uniline-hydra :straight uniline) ;;; install and load uniline-transient (use-package uniline-transient :straight uniline)  File: uniline.info, Node: Without use-package, Prev: use-package the straightforward way, Up: Installation 16.2 Without use-package ======================== Download the package from Melpa: (package-install "uniline") Alternately, you can download the Lisp files, and load them manually: (load-file "uniline-hydra.el") ;; interpreted form (load-file "uniline-hydra.elc") ;; byte-compiled form (load-file "uniline-hydra.eln") ;; native-compiled form ;; this automatically ;; loads "uniline-core.el" ;; or "uniline-core.elc" ;; or "uniline-core.eln" or if you prefer the Transient interface over the Hydra one: (load-file "uniline-transient.el") ;; interpreted form (load-file "uniline-transient.elc") ;; byte-compiled form (load-file "uniline-transient.eln") ;; native-compiled form ;; this automatically ;; loads "uniline-core.el" ;; or "uniline-core.elc" ;; or "uniline-core.eln" You should prefer the byte-compiled or native-compiled forms over the interpreted forms, because there are a lot of optimizations performed at compile time. You may want to give ‘uniline-mode’ a key-binding. A way to do that without ‘use-package’ is to add those lines to your initialization file (usually ‘~/.emacs’): (require 'uniline-hydra) (bind-keys :package uniline-hydra ("C-" . uniline-mode)) The downside is that ‘Uniline’ will be loaded as soon as ‘Emacs’ is launched, rather than deferred until invoked.  File: uniline.info, Node: Related packages, Next: Author contributors, Prev: Installation, Up: Top 17 Related packages ******************* • ‘artist-mode’: the ASCII art mode built into ‘Emacs’. • ‘ascii-art-to-unicode’: as the name suggest, converts ASCII drawings to UNICODE, giving results similar to those of ‘Uniline’. • ‘picture-mode’: as in ‘Uniline’, the buffer is infinite in east & south directions. • ‘ascii-art-to-unicode’ ASCII art to UNICODE in ‘Emacs’. This is a standard ELPA package by Thien-Thi Nguyen (rest in peace). ‘Uniline’ may call it to convert ASCII art drawings to equivalent UNICODE. ‘Uniline’ arranges to not require a dependency on ‘ascii-art-to-unicode’ by lazy evaluating a call to ‘aa2u’. • ‘org-pretty-table’: Org Mode tables _appear_ to be drawn in UNICODE characters (actually they are still in ASCII). • ‘boxes’: draws artistic boxes around text, with nice looking unicorns, flowers, parchments, all in ASCII art. • ‘org-drawio’: a bridge between the Draw.Io editor and ‘Emacs’, producing drawing similar to those of ‘Uniline’, but in ‘.svg’. • ‘syntree’: draws ASCII trees on-the-fly from description. • ‘unicode-enbox’: create a UNICODE box around a text; input and output are strings. • ‘unicode-fonts’: in ‘Emacs’, helps alleviate the lack of full UNICODE coverage of most fonts. • ‘org-superstar’: prettify headings and plain lists in Org Mode, using UNICODE glyphs. • ‘charmap’: UNICODE table viewer for ‘Emacs’. • ‘insert-char-preview’: insert UNICODEs with character preview in completion prompt. • ‘list-unicode-display’: list all UNICODE characters, or a selection of them. • ‘show-font’: show font features in a buffer. • ‘ob-svgbob’: convert your ascii diagram scribbles into happy little SVG • ‘el-easydraw’: a full featured SVG editor right inside your ‘Emacs’ • ‘asciiflow’: (not ‘Emacs’) draw on the web, then copy-paste your UNICODE text • ‘ascii-draw’: like ‘asciiflow’ with Unicodes. • ‘dot-to-ascii.ggerganov.com:’ (not ‘Emacs’) describe your schema in the Graphviz language, and copy-past your UNICODE text. • ‘monosketch’: (not ‘Emacs’) draw on the web, then copy-paste your UNICODE text. • ‘ibm-box-drawing-hydra.el’: keyboard interface to insert UNICODE box-drawing characters one at a time. • ‘excalidraw.com’: inline drawing, but not in Unicode or Ascii. • ‘org-excalidraw’: integrate SVG images generated by excalidraw into Org Mode. • ‘rcd-box’: create tables surrounded by box-drawing characters from Lisp descriptions. • ‘ob-diagram’: generate various diagrams using diagrams backend. • ‘ob-mermaid’: generate Mermaid diagrams within org-mode babel. • ‘quail-boxdrawing.el’: input method for box drawing characters. • ‘make-box.el’: box around part of a buffer. • ‘vim drawit ascii diagrams’: in Vim, in ASCII. • ‘MarkDeep’: (Casual Effects): write in Markdown, render on the Web on the fly. Uniline may be used to author part of the Markdown source. • ‘org-utf-to-xetex’: export Org-Mode utf-8 documents to various formats preserving smileys and other Unicode characters. • ‘image-to-ascii’: turns a photo into ASCII. • ‘ascii-maze-generator’: web-inline generator of mazes, with the same lines drawn by Uniline. • ‘diagon.arthursonzogni.com’: web-inline drawing of diagrams similar to those that Unline enables. • ‘cascii.html’: a single Html-JavaScript file to draw Unicode diagrams. • ‘elm-svgbob’: think of Ditaa for converting Ascii to SVG. • ‘rasciigraph’: script to plot time-series in Unicode. • ‘asciichart-sharp’: another script to plot time-series in Unicode. • ‘d2’, ‘ob-d2’: think of Mermaid to convert diagram textual descriptions to SVG. • ‘dag-draw.el’: in Emacs program diagrams using Lisp, the output is Unicode. • ‘https://mbork.pl/2025-11-10_ASCII_art_timeline_diagrams’: not a package, just an example of how easy is to draw time-series in Ascii with Emacs. • ‘pikchr’: describe objects and their relationship in Markdown, render in Unicode. • ‘ob-pikchr.el’: integration of ‘pikchr’ in Emacs Org-Mode. • ‘GoAT’: Go-based Text-Art to SVG refinement. • ‘SvgBob’: another script to convert Ascii to SVG (like Ditaa). Written in Rust. • ‘Durdraw’: an Ascii, Unicode and Ansi art editor for Unix-like systems. With colors. • ‘figlet’: makes large letters out of ordinary text, in text. There is an Emacs integration by J. Kotta.  File: uniline.info, Node: Author contributors, Next: License, Prev: Related packages, Up: Top 18 Author, contributors *********************** • Thierry Banel, author Feedback: • Chris Rayner (@riscy), gave recommendations prior to insertion in MELPA • Adam Porter (@alphapapa), suggested submitting ‘Uniline’ to ‘ELPA’; should I? • Joost Kremers found a bug in the minor-mode key-binding definitions, and incompatibility with • DogLooksGood gave feedback on inserting usual characters not moving the cursor • LuciusChen & lhindir on GitHub, arthurno1 & karthink on Reddit, pushed toward ‘Transient’ as the default interface instead of ‘Hydra’ • karthink noted that ‘Transient’ was now built into ‘Emacs’, loosening the dependencies conundrum, arthurno1 participated in the ‘Hydra’ - ‘Transient’ discussion • karthink pointed to the new ‘Aporetic’ font family, which was then added to the ‘Uniline’ supported fonts • rumengling on GitHub found and diagnosed the misaligned lines issue produced by some "language environments" (see *note Language environment::). • tpapp documented the installation using Straight, and fixed some typos. • tskinner-oppfi (GitHub) repoted Elpaca packaging break due to a bad version number. Contributors: • JD Smith (jdtsmith on GitHub) rewrote the ‘:lighter’ for added flexibility (the information in the mode-line about the state of ‘Uniline’) • JD Smith also pointed to ‘#+begin_src uniline’ Org Mode block suprising behavior (editing its content automatically switches to ‘uniline-mode’) Utilities: • Oleh Krehel alias abo-abo for his package ‘Hydra’ • The ‘Magit’ team for the ‘Transient’ library • Thien-Thi Nguyen (RIP) for his package ‘ascii-art-to-unicode’  File: uniline.info, Node: License, Prev: Author contributors, Up: Top 19 License ********** Copyright (C) 2024-2026 Thierry Banel Uniline 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. Uniline 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 .  Tag Table: Node: Top171 Node: Getting started in 10 seconds3126 Node: New3732 Node: Gallery pure UNICODE diagrams in Emacs4010 Node: Document a command4596 Node: Connect boxes with arrows5129 Node: Explain decisions trees6449 Node: Draw lines or blocks7955 Node: Outline the General Relativity and the Schrödinger's equations9497 Node: Explain the structure of a sentence in a foreign language12186 Node: Draw electronic diagrams13772 Node: Explain Lisp lists15177 Node: Draw sketched objects16360 Node: Pure text18325 Node: Beware!19059 Node: A minor mode for drawing19642 Node: Minor mode19954 Node: Draw lines by moving the cursor20516 Node: Infinite ∞ buffer21108 Node: Brush style22389 Node: Text direction24974 Node: The key25524 Node: Glyphs ▷ ▶ → □ ◆ ╮─ insertion & modification26524 Node: Arrows glyphs ▷ ▶ → ▹ ▸ ↔27241 Node: Intersection glyphs ■ ◆ ●28813 Node: Fine tweaking of lines31643 Node: Rectangular actions33526 Node: Drawing a rectangle34876 Node: Filling a rectangle36527 Node: Moving a rectangular region37295 Node: Copying killing yanking a rectangular region39864 Node: Dashed lines and other styles41059 Node: ASCII to UNICODE45184 Node: Long range actions contour and flood-fill48867 Node: Tracing a contour49130 Node: Flood-fill51044 Node: Macros52818 Node: Which fonts?56209 Node: Recommended fonts56470 Node: Use case mixing fonts58560 Node: Hydra or Transient?60557 Node: Selecting Hydra or Transient61546 Node: Instantly selecting Hydra or Transient62664 Node: One-liner menus63630 Node: The Hydra interface65568 Node: The Transient interface66241 Node: Customization66779 Node: Interface type67647 Node: Insert key68173 Node: Maximum steps when drawing a contour68955 Node: Cursor type69285 Node: Hint style69610 Node: Welcome message visibility69957 Node: Line spacing70338 Node: Font71804 Node: Upward infiniteness ∞72320 Node: How Uniline behaves with its environment?72850 Node: Language environment73449 Node: Compatibility with Picture-mode75854 Node: Compatibility with Artist-mode76801 Node: Compatibility with Whitespace-mode77320 Node: Compatibility with Org Mode78220 Node: Org Mode and LaTex79641 Node: What about \t tabs?81501 Node: What about ^L page separation?82302 Node: Emacs on the Linux console82925 Node: Emacs on a graphical terminal emulator83885 Node: Emacs on Windows84815 Node: Compatibility with ASCIIFlow85496 Node: Lisp API86452 Node: Move the cursor87002 Node: Brush87433 Node: Example Lisp function to draw a plus sign88302 Node: Long range actions (contour flood-fill rectangle)90138 Node: Constants91076 Node: Macro and text direction91491 Node: Insert and tweak glyphs92426 Node: Change to alternate styles93539 Node: Mouse support94448 Node: Installation95332 Node: use-package the straightforward way95576 Node: Without use-package97961 Node: Related packages99522 Node: Author contributors104585 Node: License106622  End Tag Table  Local Variables: coding: utf-8 End: