├── COPYING ├── LOG ├── Makefile ├── README ├── all.h ├── aoeui.1.m4 ├── aoeui.txt ├── asdfg ├── bookmark.c ├── buffer.c ├── buffer.h ├── child.c ├── child.h ├── clip.c ├── clip.h ├── die.c ├── die.h ├── display-test.c ├── display.c ├── display.h ├── file.c ├── find.c ├── fold.c ├── help.c ├── help.m4 ├── keyword.c ├── locus.c ├── locus.h ├── macro.c ├── macro.h ├── main.c ├── make-TAGS ├── mem.c ├── mem.h ├── mode.c ├── mode.h ├── notes.txt ├── rgba.h ├── search.c ├── tab.c ├── tags.c ├── text.c ├── text.h ├── types.h ├── undo.c ├── unicode.c ├── utf8.c ├── utf8.h ├── util.c ├── util.h ├── window.c └── window.h /COPYING: -------------------------------------------------------------------------------- 1 | The aoeui text editor is copyright 2007 Peter Klausler and 2 | released under the GNU General Public License version 2 only. 3 | Any change to a later version of the license, or to another 4 | open source license, will be explicitly stated in later editions 5 | of this file. 6 | 7 | A copy of this license is appended, whose text itself is copyright 8 | by the good folks at the Free Software Foundation. 9 | 10 | ---------------------------------------- 11 | 12 | GNU GENERAL PUBLIC LICENSE 13 | Version 2, June 1991 14 | 15 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 16 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 17 | Everyone is permitted to copy and distribute verbatim copies 18 | of this license document, but changing it is not allowed. 19 | 20 | Preamble 21 | 22 | The licenses for most software are designed to take away your 23 | freedom to share and change it. By contrast, the GNU General Public 24 | License is intended to guarantee your freedom to share and change free 25 | software--to make sure the software is free for all its users. This 26 | General Public License applies to most of the Free Software 27 | Foundation's software and to any other program whose authors commit to 28 | using it. (Some other Free Software Foundation software is covered by 29 | the GNU Library General Public License instead.) You can apply it to 30 | your programs, too. 31 | 32 | When we speak of free software, we are referring to freedom, not 33 | price. Our General Public Licenses are designed to make sure that you 34 | have the freedom to distribute copies of free software (and charge for 35 | this service if you wish), that you receive source code or can get it 36 | if you want it, that you can change the software or use pieces of it 37 | in new free programs; and that you know you can do these things. 38 | 39 | To protect your rights, we need to make restrictions that forbid 40 | anyone to deny you these rights or to ask you to surrender the rights. 41 | These restrictions translate to certain responsibilities for you if you 42 | distribute copies of the software, or if you modify it. 43 | 44 | For example, if you distribute copies of such a program, whether 45 | gratis or for a fee, you must give the recipients all the rights that 46 | you have. You must make sure that they, too, receive or can get the 47 | source code. And you must show them these terms so they know their 48 | rights. 49 | 50 | We protect your rights with two steps: (1) copyright the software, and 51 | (2) offer you this license which gives you legal permission to copy, 52 | distribute and/or modify the software. 53 | 54 | Also, for each author's protection and ours, we want to make certain 55 | that everyone understands that there is no warranty for this free 56 | software. If the software is modified by someone else and passed on, we 57 | want its recipients to know that what they have is not the original, so 58 | that any problems introduced by others will not reflect on the original 59 | authors' reputations. 60 | 61 | Finally, any free program is threatened constantly by software 62 | patents. We wish to avoid the danger that redistributors of a free 63 | program will individually obtain patent licenses, in effect making the 64 | program proprietary. To prevent this, we have made it clear that any 65 | patent must be licensed for everyone's free use or not licensed at all. 66 | 67 | The precise terms and conditions for copying, distribution and 68 | modification follow. 69 | 70 | GNU GENERAL PUBLIC LICENSE 71 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 72 | 73 | 0. This License applies to any program or other work which contains 74 | a notice placed by the copyright holder saying it may be distributed 75 | under the terms of this General Public License. The "Program", below, 76 | refers to any such program or work, and a "work based on the Program" 77 | means either the Program or any derivative work under copyright law: 78 | that is to say, a work containing the Program or a portion of it, 79 | either verbatim or with modifications and/or translated into another 80 | language. (Hereinafter, translation is included without limitation in 81 | the term "modification".) Each licensee is addressed as "you". 82 | 83 | Activities other than copying, distribution and modification are not 84 | covered by this License; they are outside its scope. The act of 85 | running the Program is not restricted, and the output from the Program 86 | is covered only if its contents constitute a work based on the 87 | Program (independent of having been made by running the Program). 88 | Whether that is true depends on what the Program does. 89 | 90 | 1. You may copy and distribute verbatim copies of the Program's 91 | source code as you receive it, in any medium, provided that you 92 | conspicuously and appropriately publish on each copy an appropriate 93 | copyright notice and disclaimer of warranty; keep intact all the 94 | notices that refer to this License and to the absence of any warranty; 95 | and give any other recipients of the Program a copy of this License 96 | along with the Program. 97 | 98 | You may charge a fee for the physical act of transferring a copy, and 99 | you may at your option offer warranty protection in exchange for a fee. 100 | 101 | 2. You may modify your copy or copies of the Program or any portion 102 | of it, thus forming a work based on the Program, and copy and 103 | distribute such modifications or work under the terms of Section 1 104 | above, provided that you also meet all of these conditions: 105 | 106 | a) You must cause the modified files to carry prominent notices 107 | stating that you changed the files and the date of any change. 108 | 109 | b) You must cause any work that you distribute or publish, that in 110 | whole or in part contains or is derived from the Program or any 111 | part thereof, to be licensed as a whole at no charge to all third 112 | parties under the terms of this License. 113 | 114 | c) If the modified program normally reads commands interactively 115 | when run, you must cause it, when started running for such 116 | interactive use in the most ordinary way, to print or display an 117 | announcement including an appropriate copyright notice and a 118 | notice that there is no warranty (or else, saying that you provide 119 | a warranty) and that users may redistribute the program under 120 | these conditions, and telling the user how to view a copy of this 121 | License. (Exception: if the Program itself is interactive but 122 | does not normally print such an announcement, your work based on 123 | the Program is not required to print an announcement.) 124 | 125 | These requirements apply to the modified work as a whole. If 126 | identifiable sections of that work are not derived from the Program, 127 | and can be reasonably considered independent and separate works in 128 | themselves, then this License, and its terms, do not apply to those 129 | sections when you distribute them as separate works. But when you 130 | distribute the same sections as part of a whole which is a work based 131 | on the Program, the distribution of the whole must be on the terms of 132 | this License, whose permissions for other licensees extend to the 133 | entire whole, and thus to each and every part regardless of who wrote it. 134 | 135 | Thus, it is not the intent of this section to claim rights or contest 136 | your rights to work written entirely by you; rather, the intent is to 137 | exercise the right to control the distribution of derivative or 138 | collective works based on the Program. 139 | 140 | In addition, mere aggregation of another work not based on the Program 141 | with the Program (or with a work based on the Program) on a volume of 142 | a storage or distribution medium does not bring the other work under 143 | the scope of this License. 144 | 145 | 3. You may copy and distribute the Program (or a work based on it, 146 | under Section 2) in object code or executable form under the terms of 147 | Sections 1 and 2 above provided that you also do one of the following: 148 | 149 | a) Accompany it with the complete corresponding machine-readable 150 | source code, which must be distributed under the terms of Sections 151 | 1 and 2 above on a medium customarily used for software interchange; or, 152 | 153 | b) Accompany it with a written offer, valid for at least three 154 | years, to give any third party, for a charge no more than your 155 | cost of physically performing source distribution, a complete 156 | machine-readable copy of the corresponding source code, to be 157 | distributed under the terms of Sections 1 and 2 above on a medium 158 | customarily used for software interchange; or, 159 | 160 | c) Accompany it with the information you received as to the offer 161 | to distribute corresponding source code. (This alternative is 162 | allowed only for noncommercial distribution and only if you 163 | received the program in object code or executable form with such 164 | an offer, in accord with Subsection b above.) 165 | 166 | The source code for a work means the preferred form of the work for 167 | making modifications to it. For an executable work, complete source 168 | code means all the source code for all modules it contains, plus any 169 | associated interface definition files, plus the scripts used to 170 | control compilation and installation of the executable. However, as a 171 | special exception, the source code distributed need not include 172 | anything that is normally distributed (in either source or binary 173 | form) with the major components (compiler, kernel, and so on) of the 174 | operating system on which the executable runs, unless that component 175 | itself accompanies the executable. 176 | 177 | If distribution of executable or object code is made by offering 178 | access to copy from a designated place, then offering equivalent 179 | access to copy the source code from the same place counts as 180 | distribution of the source code, even though third parties are not 181 | compelled to copy the source along with the object code. 182 | 183 | 4. You may not copy, modify, sublicense, or distribute the Program 184 | except as expressly provided under this License. Any attempt 185 | otherwise to copy, modify, sublicense or distribute the Program is 186 | void, and will automatically terminate your rights under this License. 187 | However, parties who have received copies, or rights, from you under 188 | this License will not have their licenses terminated so long as such 189 | parties remain in full compliance. 190 | 191 | 5. You are not required to accept this License, since you have not 192 | signed it. However, nothing else grants you permission to modify or 193 | distribute the Program or its derivative works. These actions are 194 | prohibited by law if you do not accept this License. Therefore, by 195 | modifying or distributing the Program (or any work based on the 196 | Program), you indicate your acceptance of this License to do so, and 197 | all its terms and conditions for copying, distributing or modifying 198 | the Program or works based on it. 199 | 200 | 6. Each time you redistribute the Program (or any work based on the 201 | Program), the recipient automatically receives a license from the 202 | original licensor to copy, distribute or modify the Program subject to 203 | these terms and conditions. You may not impose any further 204 | restrictions on the recipients' exercise of the rights granted herein. 205 | You are not responsible for enforcing compliance by third parties to 206 | this License. 207 | 208 | 7. If, as a consequence of a court judgment or allegation of patent 209 | infringement or for any other reason (not limited to patent issues), 210 | conditions are imposed on you (whether by court order, agreement or 211 | otherwise) that contradict the conditions of this License, they do not 212 | excuse you from the conditions of this License. If you cannot 213 | distribute so as to satisfy simultaneously your obligations under this 214 | License and any other pertinent obligations, then as a consequence you 215 | may not distribute the Program at all. For example, if a patent 216 | license would not permit royalty-free redistribution of the Program by 217 | all those who receive copies directly or indirectly through you, then 218 | the only way you could satisfy both it and this License would be to 219 | refrain entirely from distribution of the Program. 220 | 221 | If any portion of this section is held invalid or unenforceable under 222 | any particular circumstance, the balance of the section is intended to 223 | apply and the section as a whole is intended to apply in other 224 | circumstances. 225 | 226 | It is not the purpose of this section to induce you to infringe any 227 | patents or other property right claims or to contest validity of any 228 | such claims; this section has the sole purpose of protecting the 229 | integrity of the free software distribution system, which is 230 | implemented by public license practices. Many people have made 231 | generous contributions to the wide range of software distributed 232 | through that system in reliance on consistent application of that 233 | system; it is up to the author/donor to decide if he or she is willing 234 | to distribute software through any other system and a licensee cannot 235 | impose that choice. 236 | 237 | This section is intended to make thoroughly clear what is believed to 238 | be a consequence of the rest of this License. 239 | 240 | 8. If the distribution and/or use of the Program is restricted in 241 | certain countries either by patents or by copyrighted interfaces, the 242 | original copyright holder who places the Program under this License 243 | may add an explicit geographical distribution limitation excluding 244 | those countries, so that distribution is permitted only in or among 245 | countries not thus excluded. In such case, this License incorporates 246 | the limitation as if written in the body of this License. 247 | 248 | 9. The Free Software Foundation may publish revised and/or new versions 249 | of the General Public License from time to time. Such new versions will 250 | be similar in spirit to the present version, but may differ in detail to 251 | address new problems or concerns. 252 | 253 | Each version is given a distinguishing version number. If the Program 254 | specifies a version number of this License which applies to it and "any 255 | later version", you have the option of following the terms and conditions 256 | either of that version or of any later version published by the Free 257 | Software Foundation. If the Program does not specify a version number of 258 | this License, you may choose any version ever published by the Free Software 259 | Foundation. 260 | 261 | 10. If you wish to incorporate parts of the Program into other free 262 | programs whose distribution conditions are different, write to the author 263 | to ask for permission. For software which is copyrighted by the Free 264 | Software Foundation, write to the Free Software Foundation; we sometimes 265 | make exceptions for this. Our decision will be guided by the two goals 266 | of preserving the free status of all derivatives of our free software and 267 | of promoting the sharing and reuse of software generally. 268 | 269 | NO WARRANTY 270 | 271 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 272 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 273 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 274 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 275 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 276 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 277 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 278 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 279 | REPAIR OR CORRECTION. 280 | 281 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 282 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 283 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 284 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 285 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 286 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 287 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 288 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 289 | POSSIBILITY OF SUCH DAMAGES. 290 | 291 | END OF TERMS AND CONDITIONS 292 | 293 | How to Apply These Terms to Your New Programs 294 | 295 | If you develop a new program, and you want it to be of the greatest 296 | possible use to the public, the best way to achieve this is to make it 297 | free software which everyone can redistribute and change under these terms. 298 | 299 | To do so, attach the following notices to the program. It is safest 300 | to attach them to the start of each source file to most effectively 301 | convey the exclusion of warranty; and each file should have at least 302 | the "copyright" line and a pointer to where the full notice is found. 303 | 304 | 305 | Copyright (C) 306 | 307 | This program is free software; you can redistribute it and/or modify 308 | it under the terms of the GNU General Public License as published by 309 | the Free Software Foundation; either version 2 of the License, or 310 | (at your option) any later version. 311 | 312 | This program is distributed in the hope that it will be useful, 313 | but WITHOUT ANY WARRANTY; without even the implied warranty of 314 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 315 | GNU General Public License for more details. 316 | 317 | You should have received a copy of the GNU General Public License 318 | along with this program; if not, write to the Free Software 319 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 320 | 321 | 322 | Also add information on how to contact you by electronic and paper mail. 323 | 324 | If the program is interactive, make it output a short notice like this 325 | when it starts in an interactive mode: 326 | 327 | Gnomovision version 69, Copyright (C) year name of author 328 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 329 | This is free software, and you are welcome to redistribute it 330 | under certain conditions; type `show c' for details. 331 | 332 | The hypothetical commands `show w' and `show c' should show the appropriate 333 | parts of the General Public License. Of course, the commands you use may 334 | be called something other than `show w' and `show c'; they could even be 335 | mouse-clicks or menu items--whatever suits your program. 336 | 337 | You should also get your employer (if you work as a programmer) or your 338 | school, if any, to sign a "copyright disclaimer" for the program, if 339 | necessary. Here is a sample; alter the names: 340 | 341 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 342 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 343 | 344 | , 1 April 1989 345 | Ty Coon, President of Vice 346 | 347 | This General Public License does not permit incorporating your program into 348 | proprietary programs. If your program is a subroutine library, you may 349 | consider it more useful to permit linking proprietary applications with the 350 | library. If this is what you want to do, use the GNU Library General 351 | Public License instead of this License. 352 | -------------------------------------------------------------------------------- /LOG: -------------------------------------------------------------------------------- 1 | 2016 2 | 03-02 pmk: rehosting on github 3 | 2015 4 | 08-06 1.7 release 5 | 2014 6 | 08-26 pmk: C++11 keywords 7 | 03-01 pmk: "find tag" now opens view as search 8 | 2013 9 | 11-25 pmk: fix ESC-'@' (ALT+'@') and ESC-RET 10 | 11-25 pmk: fix typos in man page 11 | 09-25 pmk: erase and reset Xterm alt screen when done 12 | 09-25 pmk: suppress pointer alignment warning in undo.c 13 | 06-26 pmk: Go language syntax 14 | 05-22 pmk: fix needless column erasures in window repaints 15 | 01-31 pmk: s/~0/~(size_t)0/ in several sites 16 | 2012 17 | 11-30 pmk: set colors to default before erase/insert/delete 18 | 11-10 1.6 release 19 | 10-27 pmk: automatic detection of tabbing and tab stop 20 | 10-26 pmk: fix off-by-one error in macro repetition 21 | 08-17 pmk: fix cursor position after xterm REGSCREEN 22 | 05-04 pmk: fix window destruction display error 23 | 04-11 pmk: fix line insert/delete bug 24 | 03-25 pmk: Haskell support 25 | 03-17 pmk: feature requests, line numbers 26 | 03-16 pmk: AOEUI_OVERLAP and comment highlighting 27 | 03-15 pmk: fix display issues on non-Apple Xterms, Linux console 28 | 03-11 pmk: optimize screen erasure again 29 | 02-01 pmk: fix raw hex character insertion (encode to UTF-8 unless -U) 30 | 2011 31 | 10-14 1.5 release 32 | 10-14 pmk: display debugging output, fix display errors 33 | 04-21 pmk: tweak colors, code cleanup, add display test 34 | 03-17 pmk: optimize screen erasure (background color problem) 35 | 02-16 pmk: AOEUI_WRITABLE environment variable 36 | 01-11 pmk: fix crash due to stale image pointer after resizing 37 | 01-08 pmk: work around Apple Terminal for OS X 10.5 38 | 2010 39 | 09-10 pmk: tweak ^J after { again to NOT add automatic } if one exists 40 | 08-07 pmk: tweak ^J after { to add correct automatic } more often 41 | 07-21 pmk: -o (do not save original) and -r modes (read-only mode) 42 | 06-16 pmk: ^V in search mode retains target as selection 43 | 06-10 pmk: use mode-specific background colors for selections 44 | 04-05 pmk: fix searching bug with CURSOR==0 45 | 04-05 pmk: ^P with number 46 | 04-03 pmk: -w 'p4 edit %s' (command to make file writable) 47 | 02-21 pmk: detect and support screen(1) 48 | 02-15 1.4 release 49 | 02-09 pmk: SVN check-in 50 | 01-05 pmk: auto-closing '}' for ^J after '{' 51 | 2009 52 | 12-11 pmk: multi-line code alignment 53 | 12-10 pmk: rewrite online help 54 | 12-03 1.3 release 55 | 11-19 pmk: add copyright notices 56 | 11-11 pmk: removed useless ^Space^M and ^Space^J 57 | 11-11 pmk: swap ^J and ^M again so that cut-and-paste text doesn't align 58 | 11-07 pmk: spot improvements to code alignment 59 | 10-04 1.2 release 60 | 09-09 pmk: display regular expressions during searches 61 | 09-09 pmk: swap ^J and ^M, so that Enter causes automatic alignment 62 | 09-05 pmk: fix original~ file saving and bad writes to read-only files 63 | 09-05 pmk: fix TAGS with absolute paths 64 | 09-05 pmk: fix folding, tweak motion by words and identifiers 65 | 07-04 1.1.2 release 66 | 04-18 pmk: shell mode via ptys 67 | 04-16 pmk: basic keyword highlighting for C/C++ 68 | 04-11 pmk: multiple TAGS files in directory hierarchy, multiple hits 69 | 04-07 pmk: true UP and DOWN arrow movement keys 70 | 04-04 pmk: make default ^O macro a global value, not per-view 71 | 04-04 pmk: make last search match a global string, not per-view 72 | 04-04 pmk: lots of code cleanup 73 | 04-03 1.1.1 release 74 | 04-02 pmk: snap buffers on abort (^\) 75 | 04-01 pmk: make DELETE key synonymous with BACKSPACE (for screen(1)) 76 | 02-29 pmk: -s (no_tabs) 77 | 01-21 pmk: Mac OS X port 78 | 2008 79 | 12-26 pmk: support a repeat count on macros, abort macros on failed search 80 | 10-29 pmk: fix ^ and $ in regex searches by adding REG_NEWLINE to regcomp 81 | 08-29 pmk: support output from exuberant-ctags 82 | 08-13 pmk: add -u and -U options to force and disable automatic UTF-8 detection 83 | 07-12 pmk: fix non-UTF-8 detection when UTF-8 sequence spans first page boundary 84 | 07-12 1.1.0 release 85 | 07-11 pmk: fix partial escape sequence parsing, support $ROWS/$COLUMNS 86 | 07-10 pmk: erase display after display size change 87 | 07-06 pmk: disable UTF-8 and accept DOS CR-NL line ends automatically 88 | 07-02 pmk: fix looping bug after redefined macro 89 | 07-02 pmk: fix hex literal characters 90 | 06-29 1.0.3 release 91 | 06-28 pmk: handle all Escape sequences and conversions in display.c 92 | 06-27 pmk: allow multibyte characters in macros and display input 93 | 06-27 pmk: fix buffered input bug 94 | 06-27 pmk: modularized macro facility, function key definitions 95 | 06-27 pmk: fix misspellings in documentation 96 | 06-27 pmk: remove -Werror and TAGS default build from Makefile 97 | 06-26 pmk: preserve original mark when search ends with ^V 98 | 06-26 pmk: fixes, work again on serial port 99 | 06-25 pmk: decode function keys and command responses in display.c, not mode.c 100 | 06-25 pmk: get display size from terminal as backup to ioctl 101 | 06-24 pmk: red cursor on read-only text, green cursor on dirty text 102 | 06-24 pmk: fix cursor color bug 103 | 06-23 1.0.2 release 104 | 06-22 pmk: fun animated scrolling effects 105 | 06-22 pmk: fix fg/bg colors so everything works on console 106 | 06-22 pmk: use insert/delete of spaces and lines in display 107 | 06-21 pmk: improve folding/unfolding commands 108 | 06-21 pmk: remove needless erasures and cursor motion from display 109 | 06-18 1.0.1 release 110 | 06-18 pmk: nicer background colors 111 | 06-07 pmk: "asdfg" QWERTY variant 112 | 06-06 pmk: fix spurious "file may have changed" warning after renaming 113 | 06-01 pmk: some code cleaning 114 | 05-30 1.0.0 release! yay 115 | 05-24 pmk: tweak automatic alignment 116 | 05-22 pmk: some minor BSD porting issues in buffer.c and file.c 117 | 05-16 pmk: tweak ebuild according to gentoo developer's comments 118 | 04-18 pmk: fix path name tab completion (shortest common continuation) 119 | 04-18 1.0_alpha5 120 | 04-17 pmk: rewrote alignment 121 | 04-15 pmk: generalized tab completion 122 | 04-15 pmk: don't set mark automatically with ^] 123 | 04-13 pmk: color-cue paired brackets 124 | 04-13 pmk: clean up scratch files better 125 | 04-13 pmk: ^A (macros) is now ^O, so that ^A is available to screen(1) 126 | 04-13 pmk: registers for ^B and regexs 127 | 04-12 pmk: regex searches begun 128 | 04-11 pmk: somewhat less ugly colors in xterms 129 | 04-09 1.0_alpha4 130 | 04-09 pmk: code folding 131 | 04-07 pmk: code buffing 132 | 04-06 commit 133 | 04-06 pmk: improve tag command 134 | 04-06 pmk: create new files 644, not 600 135 | 04-06 pmk: pull basic help text into program 136 | 04-05 pmk: fix cursor placement at end of full line, make it smart 137 | 04-04 commit 138 | 04-02 pmk: TAGS support (^Space.) 139 | 04-01 pmk: shell mode (^E without selection) 140 | 03-31 1.0_alpha3 141 | 03-30 pmk: generalize the asynchronous child I/O implementation 142 | 03-30 pmk: change emergency exit from ^\ to ^Space^\ 143 | 03-29 pmk: some optimization of repainting 144 | 03-29 pmk: debug behavior when xterm window size changes 145 | 03-28 pmk: support -t tabstop 146 | 03-28 pmk: special-case 'cd' in ^E 147 | 03-28 pmk: tab completion when cursor is at the end of a selection 148 | 03-28 pmk: hexadecimal arguments and Unicode ^^ literals 149 | 03-28 pmk: bookmarks 150 | 03-28 pmk: *remove* bogus DOS/Mac newline handling 151 | 03-28 pmk: ^Space # TAB sets tab stop 152 | 03-28 pmk: reap terminated children 153 | 03-28 pmk: asynchronous child I/O 154 | 03-27 1.0_alpha2 155 | 03-27 pmk: Make ^V with a selection unset it, ^Space^V exchange 156 | 03-27 pmk: Clean up makefile, write ebuild 157 | 03-26 pmk: Put newly-created texts in $HOME/.aoeui, etc. 158 | 03-25 pmk: Keep active window background set to default color 159 | 03-25 pmk: Keep file gap buffers in file# for recovery 160 | 03-24 pmk: Save original~ copy of file at write time, not open time 161 | 03-24 pmk: Memory-map clean images of files 162 | 03-23 pmk: Create new empty texts when view_next() finds no others 163 | 03-23 pmk: Make variant ^D with no selection select surrounding white space 164 | 03-22 pmk: Initial Subversion check in at sourceforge.net 165 | 2007 166 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = 1.7 2 | PACKAGE = aoeui-$(VERSION) 3 | SRCS = main.c mem.c die.c display.c text.c file.c locus.c buffer.c \ 4 | undo.c utf8.c window.c util.c clip.c mode.c search.c \ 5 | child.c bookmark.c help.c find.c tags.c tab.c fold.c macro.c \ 6 | keyword.c 7 | HDRS = all.h buffer.h child.h mode.h text.h locus.h utf8.h display.h \ 8 | window.h util.h clip.h macro.h mem.h die.h types.h rgba.h 9 | RELS = $(SRCS:.c=.o) 10 | LIBS = -lutil 11 | INST_DIR = $(DESTDIR)/usr 12 | CFLAGS = -Wall -Wno-parentheses \ 13 | -Wpointer-arith -Wcast-align -Wwrite-strings -Wstrict-prototypes \ 14 | -Wmissing-prototypes -Wmissing-declarations 15 | # -Werror 16 | 17 | # Uncomment this line if you want to develop aoeui yourself with exuberant-ctags 18 | # CTAGS = exuberant-ctags 19 | CTAGS = ctags 20 | 21 | STRINGIFY = sed 's/\\/\\\\/g;s/"/\\"/g;s/^/"/;s/$$/\\n"/' 22 | 23 | default: optimized display-test aoeui.1 asdfg.1 24 | 25 | aoeui: $(RELS) 26 | $(CC) $(CFLAGS) -o $@ $(RELS) $(LIBS) 27 | $(RELS): $(HDRS) 28 | help.o: aoeui.help asdfg.help 29 | aoeui.help: help.m4 30 | m4 help.m4 | $(STRINGIFY) >$@ 31 | asdfg.help: help.m4 32 | m4 -D ASDFG help.m4 | $(STRINGIFY) >$@ 33 | 34 | display-test: display-test.o display.o mem.o utf8.o 35 | $(CC) $(CFLAGS) -o $@ display-test.o display.o mem.o utf8.o 36 | display-test.o: types.h utf8.h display.h 37 | 38 | aoeui.1.gz: aoeui.1 39 | gzip -9 -c aoeui.1 >$@ 40 | asdfg.1.gz: asdfg.1 41 | gzip -9 -c asdfg.1 >$@ 42 | aoeui.1.html: aoeui.1 43 | man2html aoeui.1 >$@ 44 | asdfg.1.html: asdfg.1 45 | man2html asdfg.1 >$@ 46 | aoeui.1: aoeui.1.m4 47 | m4 aoeui.1.m4 >$@ 48 | asdfg.1: aoeui.1.m4 49 | m4 -D ASDFG aoeui.1.m4 >$@ 50 | 51 | optimized: 52 | $(MAKE) CFLAGS="-O3 $(CFLAGS)" aoeui 53 | static: 54 | $(MAKE) CFLAGS="-O3 $(CFLAGS) -static" aoeui 55 | debug: clean 56 | $(MAKE) CFLAGS="-g -O0 -DDEBUG $(CFLAGS)" aoeui 57 | profile: clean 58 | $(MAKE) CFLAGS="-pg $(CFLAGS)" aoeui 59 | 60 | TAGS: $(SRCS) $(HDRS) 61 | $(CTAGS) -x $(SRCS) $(HDRS) >$@ 62 | 63 | install: aoeui aoeui.1.gz asdfg.1.gz 64 | install -d $(INST_DIR)/bin 65 | install -d $(INST_DIR)/share/aoeui 66 | install -d $(INST_DIR)/share/man/man1 67 | install aoeui $(INST_DIR)/bin 68 | ln -nf $(INST_DIR)/bin/aoeui $(INST_DIR)/bin/asdfg 69 | install *.txt $(INST_DIR)/share/aoeui 70 | install *.1.gz $(INST_DIR)/share/man/man1 71 | clean: 72 | rm -f *.o *.help core gmon.out screenlog.* 73 | clobber: clean 74 | rm -f aoeui display-test unicode TAGS *.1 *.1.gz *.1.html 75 | spotless: clobber 76 | rm -f *~ *.tgz 77 | release: spotless 78 | rm -rf .tar $(PACKAGE) $(PACKAGE).tgz 79 | mkdir .tar 80 | find . | egrep -v '/\.' | egrep -v '[~#]' | cpio -o | (cd .tar; cpio -id) 81 | mv .tar $(PACKAGE) 82 | ls -CF $(PACKAGE) 83 | tar czf $(PACKAGE).tgz $(PACKAGE) 84 | rm -rf $(PACKAGE) 85 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | These are the sources for the aoeui, a lightweight and unobtrusive 2 | text editing program that is optimized for fast editing by users of 3 | the Dvorak keyboard layout. It was written in March 2007 by 4 | pmklausler@gmail.com. 5 | 6 | The file COPYING contains the license under which aoeui is released; 7 | please review it so that you understand your rights and responsibilities. 8 | In particular, be aware that aoeui comes with ABSOLUTELY NO WARRANTY 9 | and your use of the program is entirely at your own risk. 10 | 11 | To build, edit first few lines of the Makefile to reflect your system. 12 | 13 | aoeui's repository is https://github.com/pklausler/aoeui 14 | 15 | Enjoy! 16 | -------------------------------------------------------------------------------- /all.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | /* All editor sources #include this file. */ 3 | 4 | /* Standard and system headers */ 5 | #define _GNU_SOURCE /* for mremap */ 6 | #define _POSIX_C_SOURCE_199309 /* for nanosleep */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #if defined __APPLE__ || defined BSD 27 | # include 28 | #else 29 | # include 30 | #endif 31 | 32 | #ifndef NAME_MAX 33 | # define NAME_MAX 256 34 | #endif 35 | #ifndef S_IRUSR 36 | # define S_IRUSR 0400 37 | #endif 38 | #ifndef S_IWUSR 39 | # define S_IWUSR 0200 40 | #endif 41 | 42 | #ifndef INLINE 43 | # ifdef __GNUC__ 44 | # define INLINE static __inline__ 45 | # else 46 | # define INLINE static 47 | # endif 48 | #endif 49 | 50 | /* Module headers */ 51 | #include "types.h" 52 | #include "utf8.h" 53 | #include "buffer.h" 54 | #include "locus.h" 55 | #include "text.h" 56 | #include "window.h" 57 | #include "util.h" 58 | #include "clip.h" 59 | #include "macro.h" 60 | #include "display.h" 61 | #include "mode.h" 62 | #include "mem.h" 63 | #include "die.h" 64 | #include "child.h" 65 | -------------------------------------------------------------------------------- /aoeui.txt: -------------------------------------------------------------------------------- 1 | 2 | I have written a text editor. This is a silly thing to do, since 3 | there is certainly no shortage of interactive display editors in 4 | the world of free software, and some are excellent. But now I 5 | have an editor that works the way that I want my text editor to 6 | work. I contend that I have every capability of modern large 7 | editors that is actually useful for word processing and computer 8 | programming packaged into a small command set and tiny memory 9 | footprint. 10 | 11 | It is called aoeui because it's optimized for users, like myself, 12 | of the Dvorak keyboard layout. QWERTY key bindings could be found 13 | that make sense, I suppose, but the entire point of this project was 14 | to construct a tool for extremely fast and efficient text editing, 15 | and anybody still using QWERTY is not serious enough about their 16 | tools to want to pick up a new editor. Really. 17 | 18 | ' , . P Y F G C R L / 19 | A O E U I D H T N S - 20 | ; Q J K X B M W V Z 21 | 22 | What do I mean when I contend that aoeui is optimized for the Dvorak 23 | layout? I mean that all of the most heavily-used command keys are 24 | located under the fingers of the right hand, so that they may be 25 | accessed while the left pinky is holding down the Control or Alt key. 26 | (Control, Alt, and an Escape prefix all mean the same thing to aoeui. 27 | The user does not have to worry about using the right one or using 28 | them to alternate between, say, character and word units of motion.) 29 | In the QWERTY layout, too much of the right-hand real estate is occupied 30 | by punctuation keys that don't have Control capability, so a 31 | straightforward remapping of the command set is not obvious. 32 | 33 | The editor itself is lightweight and utterly unobtrusive. It has no 34 | mode line, minibuffer, scroll bar, or status indicators. Just a 35 | cursor, some background coloration cues, and the xterm window title. 36 | 37 | Editing with aoeui is intuitive and rather like working with a mouse- 38 | based word processing program, except of course that you don't have to 39 | take your hands off the keyboard in order to navigate and manipulate 40 | your text. New text is simply typed in place. Changes to text are 41 | typically by means of creating a selection and then copying, cutting, 42 | or replacing it. All effects are immediately visible, retractable, 43 | and repeatable. Selections are delimited by the cursor on one end 44 | and the "mark" at the other, and are highlighted in blue. 45 | 46 | Commands, as mentioned above, are control keys. The full command set 47 | fits in the available control keys because they are sensitive to the 48 | context in intuitive ways, changing their behavior in the presence or 49 | absence of a current selection, and because their behavior can be 50 | modified with a variant prefix, ^Space. This means that your thumb 51 | gets into the action too. 52 | 53 | For example, ^V is the key that creates and modifies the selection. 54 | When no selection exists, ^V starts one by placing the mark at the 55 | current cursor position. When a selection has already been started, 56 | ^V unsets the mark. The variant form ^Space^V selects the entire 57 | current line in the absence of a selection, or exchanges the cursor 58 | with the mark if one already exists. 59 | 60 | Typing new text in the presence of a selection will add the new text 61 | to the selection if the cursor is at its end, or cut the selection 62 | and begin its replacement if the cursor is at its start. Unlike 63 | Emacs, you may start a selection by dropping a mark with ^V and 64 | then define its content by typing it. This is useful because some 65 | commands take a textual argument, like a file name, from the selection. 66 | 67 | Cutting the selection, apart from the automatic cut that takes place 68 | when new text is entered with the cursor at its beginning, is done 69 | with ^D. The variant ^Space^D adds the selection to the old content 70 | of the clip buffer, which is normally replaced. ^F copies the selection 71 | to the clip buffer, with the same variation possibility. And ^B 72 | is a generalize form of pasting. It does a simple pasting of the content 73 | of the clip buffer when no selection exists, but when ^B is hit with 74 | a selection defined, it exchanges the clip buffer with the selection. 75 | This speeds up rearrangement of multiple blocks of text, and also 76 | forms the method of interactive searching and replacing. 77 | 78 | Note that ^F, ^D, and ^B constitute a column on the Dvorak keyboard. 79 | This is intentional; they are related functions, but highly unlikely 80 | to immediately follow one another. 81 | 82 | The upper right hand keys are all concerned with navigation in both 83 | directions by different units. ^H and ^T move backward and forward, 84 | respectively, in units of single characters. ^N and ^S do the same 85 | by words; their ^Space variants move by punctuated sentences. The 86 | vast majority of editing takes place with these home row commands, 87 | with ^V to create and manipulate the selection. They are combinable 88 | in many intuitive ways to accomplish tasks that constitute distinct 89 | commands in Emacs and Vi. For example, when a misspelled word has 90 | just been entered, it's often fastest to retype it than to fix it. 91 | The aoeui user does so with ^V to place the mark at the end of the 92 | bad word, ^N to put the cursor at its beginning, thereby selecting 93 | it, and then typing its correct spelling. 94 | 95 | The other navigation keys are on the upper right hand row. 96 | ^G and ^C move backward and forward, respectively, to the beginnings 97 | and endings of lines. ^R and ^L move in units of screenfuls. 98 | Their variants, ^Space^R and ^Space^L, move to the start and end of 99 | the entire view. ^] finds the closest bracket character, or the 100 | corresponding bracket character, and is the fastest way to select 101 | entire subexpressions or statement blocks. 102 | 103 | What's a view? It's usually an entire file in a buffer, but it can be 104 | just a part of a file. The aoeui editor supports multiple open files, 105 | multiple views to them, and multiple windows showing some subset of 106 | the views. ^Z recenters the cursor so that it is on the middle line 107 | of the current window. ^W finds an invisible view and puts it up in 108 | the window, making the old view invisible. (^Space^W closes the 109 | old view.) 110 | 111 | To bring new files into the editor and to rearrange the windows 112 | require some commands that are under the fingers of the left hand. 113 | First, ^X opens the file whose name is in the selection (or raises 114 | it up if it is invisible). With no selection, ^X inserts the full 115 | path name of the current file as the new selection in the current 116 | view. The variant ^Space^X replaces the current view's path name 117 | with the selection. To rename a text, one typically acquires its 118 | current name with ^X, modifies it in place within the selection, and 119 | establishes it with ^Space^X. 120 | 121 | ^Y splits the current window horizontally into two and makes another 122 | view visible in the lower half. ^Space^Y does the same thing vertically, 123 | which is quite handy in a large xterm. ^P transfers the cursor to another 124 | window; ^Space^P closes the old window afterwards. 125 | 126 | On the lower left hand row are the commands for writing files and 127 | leaving aoeui. ^K saves all modified files; its variant saves just 128 | the current view. ^Q suspends aoeui and returns you to the shell 129 | so that you can run a compiler. ^Space^Q saves all modified files 130 | and shuts the editor down. For emergencies, ^\ aborts the editor 131 | without saving anything. 132 | 133 | Now to the home row on the left hand side of the Dvorak keyboard. 134 | ^U undoes the most recent modification to the file; ^Space^U redoes it. 135 | The editor has infinite undo capability, of course. 136 | 137 | ^E brings all the power of UNIX text processing commands into your 138 | editing session, including your own programs and scripts. This is 139 | the best way to customize your editor. ^E executes the shell command 140 | pipeline that is the current selection, using the content of the clip 141 | buffer as its standard input. The selection is replaced with the command's 142 | standard output. 143 | 144 | ^Space^A begins the recording of keystrokes in the current view as 145 | a new macro. ^A ends recording, if it is active, otherwise it 146 | replays the macro as if its keystrokes had been typed again. 147 | 148 | ^I is the same as TAB. Their variants cause the current line to be 149 | aligned with the previous one. ^J is the same as ^Enter and it 150 | inserts a new line with alignment whitespace already in place. 151 | ^M is the same as Enter; their variants open a new blank line after 152 | the current one. 153 | 154 | Last is the incremental search mode, which is entered with ^/ or ^_. 155 | Case is not significant to aoeui's incremental search mode. 156 | In search mode, each character that you type is appended to a 157 | search target string and the selection is moved to its next occurrence 158 | in the view. Hitting ^/ twice restarts a search using the same 159 | target string as last time. In search mode, most commands end 160 | the search implicitly before invoking their usual behavior. The 161 | differences are: Enter simply ends the search, and ^H and ^T cause 162 | motion backwards and forwards, respectively, to other occurrences 163 | of the search target string. 164 | 165 | The aoeui editor does not have an incremental search-and-replace 166 | command, because there are at least three ways of doing that with 167 | the facilities that already exist. First, the replacement string 168 | may be cut or copied into the clip buffer, and then exchanged and 169 | recopied with the string occurrences with ^B^F^/^/. Second, the 170 | replacement string may be saved as a macro (^Space^A^Vnew text^A) 171 | the first time. And third, the entire view can be selected, cut, 172 | and then run through "sed s/OLD/NEW/g" with ^E for a global replacement. 173 | 174 | Only a few other commands exist. The aoeui editor is, of course, 175 | fully aware of UTF-8 Unicode encodings, and is capable of displaying 176 | and entering control characters. They appear in the display with 177 | a red background and a leading caret (^). To enter a control 178 | character, use ^^ (Control-^) and a valid letter or punctuation 179 | symbol. Raw Unicode code points in decimal and hexadecimal are 180 | also supported. 181 | 182 | It should go without saying that the arrow, Home/End, and Page 183 | Up/Down keys on your keyboard are also supported, as is Backspace, 184 | but that they usually are not as fast to use as the standard aoeui 185 | control character commands. 186 | 187 | And that's the end of the full story. No "line mode", interactive 188 | on-line help, mail reading client, or IRC interface. Those 189 | capabilities exist with excellent implementations elsewhere in the 190 | free software ecosystem, and with the use of ^Q and ^E are always 191 | handy to the aoeui user. Further, the source code for aoeui is 192 | freely available for your modification, and is itself smaller than 193 | many Emacs local customization files. (It's ironically about one 194 | eighth the size of the "nano" editor's source!) 195 | 196 | Let me conclude with this position statement. Free software works best 197 | when programs acknowledge each other's existence and interoperate 198 | smoothly, without cluttering themselves with capabilities that are 199 | redundant in the rich user environment that surrounds them. There is 200 | a strength that comes from having a well-written program that does 201 | a few things well that can be fully understood, appreciated, and 202 | internalized. Such programs become invisible to their users. 203 | I sincerely hope that the aoeui editor can fill that role for you 204 | as well as it has for me, and look forward to hearing whether or 205 | not it has. Thanks for giving it a look. 206 | 207 | (aoeui source code is now located at http://aoeui.sourceforge.net) 208 | 209 | Peter Klausler, 3-18-2007 210 | -------------------------------------------------------------------------------- /asdfg: -------------------------------------------------------------------------------- 1 | aoeui -------------------------------------------------------------------------------- /bookmark.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | static struct bookmark { 5 | unsigned id; 6 | struct view *view; 7 | locus_t locus[2]; 8 | struct bookmark *next; 9 | } *bookmarks; 10 | 11 | void bookmark_set(unsigned id, struct view *view, 12 | position_t cursor, position_t mark) 13 | { 14 | struct bookmark *bm = allocate0(sizeof *bm); 15 | 16 | bookmark_unset(id); 17 | bm->id = id; 18 | bm->view = view; 19 | bm->locus[0] = locus_create(view, cursor); 20 | bm->locus[1] = locus_create(view, mark); 21 | bm->next = bookmarks; 22 | bookmarks = bm; 23 | } 24 | 25 | Boolean_t bookmark_get(struct view **view, position_t *cursor, position_t *mark, 26 | unsigned id) 27 | { 28 | struct bookmark *bm; 29 | for (bm = bookmarks; bm; bm = bm->next) 30 | if (bm->id == id) { 31 | *view = bm->view; 32 | *cursor = locus_get(bm->view, bm->locus[0]); 33 | if (*cursor == UNSET) 34 | *cursor = 0; 35 | *mark = locus_get(bm->view, bm->locus[1]); 36 | return TRUE; 37 | } 38 | *view = NULL; 39 | *cursor = *mark = 0; 40 | return FALSE; 41 | } 42 | 43 | void bookmark_unset(unsigned id) 44 | { 45 | struct bookmark *bm, *prev = NULL; 46 | 47 | for (bm = bookmarks; bm; bm = bm->next) 48 | if (bm->id == id) { 49 | locus_destroy(bm->view, bm->locus[0]); 50 | locus_destroy(bm->view, bm->locus[1]); 51 | if (prev) 52 | prev->next = bm->next; 53 | else 54 | bookmarks = bm->next; 55 | return; 56 | } 57 | } 58 | 59 | void bookmark_unset_view(struct view *view) 60 | { 61 | struct bookmark *bm, *prev = NULL, *next; 62 | 63 | for (bm = bookmarks; bm; bm = next) { 64 | next = bm->next; 65 | if (bm->view != view) { 66 | prev = bm; 67 | continue; 68 | } 69 | locus_destroy(view, bm->locus[0]); 70 | locus_destroy(view, bm->locus[1]); 71 | if (prev) 72 | prev->next = next; 73 | else 74 | bookmarks = next; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /buffer.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | /* 5 | * Buffers contain payload bytes and a "gap" of unused space. 6 | * The gap is contiguous, and may appear in the midst of the 7 | * payload. Editing operations shift the gap around in order 8 | * to enlarge it (when bytes are to be deleted) or to add new 9 | * bytes to its boundaries. 10 | * 11 | * The gap comprises all of the free space in a buffer. 12 | * Since the cost of moving data does not depend on the 13 | * distance by which it is moved, there is no point to 14 | * using any gap size other than the full amount of unused 15 | * storage. 16 | * 17 | * Buffers are represented by pages mmap'ed from the file's 18 | * temporary# file, or anonymous storage for things that aren't 19 | * file texts. 20 | * 21 | * Besides being used to hold the content of files, buffers 22 | * are used for cut/copied text (the "clip buffer"), macros, 23 | * and for undo histories. 24 | */ 25 | 26 | struct buffer *buffer_create(char *path) 27 | { 28 | struct buffer *buffer = allocate0(sizeof *buffer); 29 | if (path && *path) { 30 | buffer->path = allocate(strlen(path) + 2); 31 | sprintf(buffer->path, "%s#", path); 32 | errno = 0; 33 | buffer->fd = open(buffer->path, O_CREAT|O_TRUNC|O_RDWR, 34 | S_IRUSR|S_IWUSR); 35 | if (buffer->fd < 0) 36 | message("could not create temporary file %s", 37 | buffer->path); 38 | } else 39 | buffer->fd = -1; 40 | return buffer; 41 | } 42 | 43 | void buffer_destroy(struct buffer *buffer) 44 | { 45 | if (buffer) { 46 | munmap(buffer->data, buffer->mapped); 47 | if (buffer->fd >= 0) { 48 | close(buffer->fd); 49 | unlink(buffer->path); 50 | } 51 | RELEASE(buffer->path); 52 | RELEASE(buffer); 53 | } 54 | } 55 | 56 | static void place_gap(struct buffer *buffer, position_t offset) 57 | { 58 | size_t gapsize = buffer_gap_bytes(buffer); 59 | 60 | if (offset > buffer->payload) 61 | offset = buffer->payload; 62 | if (offset <= buffer->gap) 63 | memmove(buffer->data + offset + gapsize, buffer->data + offset, 64 | buffer->gap - offset); 65 | else 66 | memmove(buffer->data + buffer->gap, 67 | buffer->data + buffer->gap + gapsize, 68 | offset - buffer->gap); 69 | buffer->gap = offset; 70 | if (buffer->fd >= 0 && gapsize) 71 | memset(buffer->data + buffer->gap, ' ', gapsize); 72 | } 73 | 74 | static void resize(struct buffer *buffer, size_t payload_bytes) 75 | { 76 | void *p; 77 | char *old = buffer->data; 78 | fd_t fd; 79 | int mapflags = 0; 80 | size_t map_bytes = payload_bytes; 81 | 82 | static size_t pagesize; 83 | if (!pagesize) 84 | pagesize = getpagesize(); 85 | 86 | /* Whole pages, with extras as size increases */ 87 | map_bytes += pagesize-1; 88 | map_bytes /= pagesize; 89 | map_bytes *= 11; 90 | map_bytes /= 10; 91 | map_bytes *= pagesize; 92 | 93 | if (map_bytes < buffer->mapped) 94 | munmap(old + map_bytes, buffer->mapped - map_bytes); 95 | if (buffer->fd >= 0 && map_bytes != buffer->mapped) { 96 | errno = 0; 97 | if (ftruncate(buffer->fd, map_bytes)) 98 | die("could not adjust %s from %lu to %lu bytes", 99 | buffer->path, (long) buffer->mapped, 100 | (long) map_bytes); 101 | } 102 | if (map_bytes <= buffer->mapped) { 103 | buffer->mapped = map_bytes; 104 | return; 105 | } 106 | #ifdef MREMAP_MAYMOVE 107 | if (old) { 108 | /* attempt extension */ 109 | errno = 0; 110 | p = mremap(old, buffer->mapped, map_bytes, MREMAP_MAYMOVE); 111 | if (p != MAP_FAILED) 112 | goto done; 113 | #define NEED_DONE_LABEL 114 | } 115 | #endif 116 | 117 | /* new/replacement allocation */ 118 | if ((fd = buffer->fd) >= 0) { 119 | mapflags |= MAP_SHARED; 120 | if (old) { 121 | munmap(old, buffer->mapped); 122 | old = NULL; 123 | } 124 | } else { 125 | #ifdef MAP_ANONYMOUS 126 | mapflags |= MAP_ANONYMOUS; 127 | #elif defined MAP_ANON 128 | mapflags |= MAP_ANON; 129 | #else 130 | static fd_t anonymous_fd = -1; 131 | if (anonymous_fd < 0) { 132 | errno = 0; 133 | anonymous_fd = open("/dev/zero", O_RDWR); 134 | if (anonymous_fd < 0) 135 | die("could not open /dev/zero for " 136 | "anonymous mappings"); 137 | } 138 | fd = anonymous_fd; 139 | #endif 140 | mapflags |= MAP_PRIVATE; 141 | } 142 | 143 | errno = 0; 144 | p = mmap(0, map_bytes, PROT_READ|PROT_WRITE, mapflags, fd, 0); 145 | if (p == MAP_FAILED) 146 | die("mmap(%lu bytes, fd %d) failed", (long) map_bytes, fd); 147 | 148 | if (old) { 149 | memcpy(p, old, buffer->payload); 150 | munmap(old, buffer->mapped); 151 | } 152 | 153 | #ifdef NEED_DONE_LABEL 154 | done: 155 | #endif 156 | buffer->data = p; 157 | buffer->mapped = map_bytes; 158 | } 159 | 160 | size_t buffer_raw(struct buffer *buffer, char **out, 161 | position_t offset, size_t bytes) 162 | { 163 | if (!buffer) { 164 | *out = NULL; 165 | return 0; 166 | } 167 | if (offset >= buffer->payload) 168 | offset = buffer->payload; 169 | if (offset + bytes > buffer->payload) 170 | bytes = buffer->payload - offset; 171 | if (!bytes) { 172 | *out = NULL; 173 | return 0; 174 | } 175 | 176 | if (offset < buffer->gap && offset + bytes > buffer->gap) 177 | place_gap(buffer, offset + bytes); 178 | *out = buffer->data + offset; 179 | if (offset >= buffer->gap) 180 | *out += buffer_gap_bytes(buffer); 181 | return bytes; 182 | } 183 | 184 | size_t buffer_get(struct buffer *buffer, void *out, 185 | position_t offset, size_t bytes) 186 | { 187 | size_t left; 188 | 189 | if (!buffer) 190 | return 0; 191 | if (offset >= buffer->payload) 192 | offset = buffer->payload; 193 | if (offset + bytes > buffer->payload) 194 | bytes = buffer->payload - offset; 195 | if (!bytes) 196 | return 0; 197 | left = bytes; 198 | if (offset < buffer->gap) { 199 | unsigned before = buffer->gap - offset; 200 | if (before > bytes) 201 | before = bytes; 202 | memcpy(out, buffer->data + offset, before); 203 | out = (char *) out + before; 204 | offset += before; 205 | left -= before; 206 | if (!left) 207 | return bytes; 208 | } 209 | offset += buffer_gap_bytes(buffer); 210 | memcpy(out, buffer->data + offset, left); 211 | return bytes; 212 | } 213 | 214 | size_t buffer_delete(struct buffer *buffer, 215 | position_t offset, size_t bytes) 216 | { 217 | if (!buffer) 218 | return 0; 219 | if (offset > buffer->payload) 220 | offset = buffer->payload; 221 | if (offset + bytes > buffer->payload) 222 | bytes = buffer->payload - offset; 223 | place_gap(buffer, offset); 224 | buffer->payload -= bytes; 225 | return bytes; 226 | } 227 | 228 | size_t buffer_insert(struct buffer *buffer, const void *in, 229 | position_t offset, size_t bytes) 230 | { 231 | if (!buffer) 232 | return 0; 233 | if (offset > buffer->payload) 234 | offset = buffer->payload; 235 | if (bytes > buffer_gap_bytes(buffer)) { 236 | place_gap(buffer, buffer->payload); 237 | resize(buffer, buffer->payload + bytes); 238 | } 239 | place_gap(buffer, offset); 240 | if (in) 241 | memcpy(buffer->data + offset, in, bytes); 242 | else 243 | memset(buffer->data + offset, 0, bytes); 244 | buffer->gap += bytes; 245 | buffer->payload += bytes; 246 | return bytes; 247 | } 248 | 249 | size_t buffer_move(struct buffer *to, position_t to_offset, 250 | struct buffer *from, position_t from_offset, 251 | size_t bytes) 252 | { 253 | char *raw = NULL; 254 | bytes = buffer_raw(from, &raw, from_offset, bytes); 255 | buffer_insert(to, raw, to_offset, bytes); 256 | return buffer_delete(from, from_offset, bytes); 257 | } 258 | 259 | void buffer_snap(struct buffer *buffer) 260 | { 261 | if (buffer && buffer->fd >= 0) { 262 | place_gap(buffer, buffer->payload); 263 | if (ftruncate(buffer->fd, buffer->payload)) { 264 | /* don't care */ 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /buffer.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef BUFFER_H 3 | #define BUFFER_H 4 | 5 | /* Gap buffers */ 6 | 7 | struct buffer; 8 | 9 | struct buffer *buffer_create(char *path); 10 | void buffer_destroy(struct buffer *); 11 | size_t buffer_raw(struct buffer *, char **, position_t, size_t); 12 | size_t buffer_get(struct buffer *, void *, position_t, size_t); 13 | size_t buffer_delete(struct buffer *, position_t, size_t); 14 | size_t buffer_insert(struct buffer *, const void *, position_t, size_t); 15 | size_t buffer_move(struct buffer *dest, position_t, 16 | struct buffer *src, position_t, size_t); 17 | void buffer_snap(struct buffer *); 18 | 19 | /* do *not* use directly; this definition is here 20 | * just for the inline functions. 21 | */ 22 | struct buffer { 23 | char *data; 24 | size_t payload, mapped; 25 | position_t gap; 26 | fd_t fd; 27 | char *path; 28 | }; 29 | 30 | INLINE size_t buffer_bytes(struct buffer *buffer) 31 | { 32 | return buffer ? buffer->payload : 0; 33 | } 34 | 35 | INLINE size_t buffer_gap_bytes(struct buffer *buffer) 36 | { 37 | return buffer->mapped - buffer->payload; 38 | } 39 | 40 | INLINE int buffer_byte(struct buffer *buffer, size_t offset) 41 | { 42 | if (!buffer || offset >= buffer->payload) 43 | return -1; 44 | if (offset >= buffer->gap) 45 | offset += buffer_gap_bytes(buffer); 46 | return offset[(Byte_t *) buffer->data]; 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /child.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | /* 5 | * Handle the ^E command, which runs the shell command pipeline 6 | * in the selection using the current buffer content as 7 | * the standard input, replacing the selection with the 8 | * standard output of the child process. 9 | * 10 | * This all works asynchronously. The standard output and 11 | * error streams of the child are monitored with select() 12 | * and whenever new child output is available, it's captured 13 | * and inserted into the original view. 14 | */ 15 | 16 | static struct stream *streams; 17 | 18 | typedef Boolean_t (*activity)(struct stream *, char *received, ssize_t bytes); 19 | 20 | struct stream { 21 | struct stream *next; 22 | fd_t fd; 23 | Boolean_t retain; 24 | activity activity; 25 | struct view *view; 26 | locus_t locus; 27 | const char *data; 28 | size_t bytes, writ; 29 | }; 30 | 31 | static Boolean_t insertion_activity(struct stream *stream, 32 | char *received, ssize_t bytes) 33 | { 34 | position_t offset; 35 | if (bytes <= 0) 36 | return FALSE; 37 | offset = locus_get(stream->view, stream->locus); 38 | if (offset == UNSET) 39 | return FALSE; 40 | view_insert(stream->view, received, offset, bytes); 41 | locus_set(stream->view, stream->locus, offset + bytes); 42 | return TRUE; 43 | } 44 | 45 | static Boolean_t shell_output_activity(struct stream *stream, char *received, 46 | ssize_t bytes) 47 | { 48 | position_t offset; 49 | struct view *view = stream->view; 50 | locus_t locus = view->shell_out_locus; 51 | 52 | if (bytes <= 0) 53 | return FALSE; 54 | offset = locus_get(view, locus); 55 | if (offset == UNSET) 56 | offset = view->bytes; 57 | else 58 | offset++; 59 | locus_set(view, locus, offset); 60 | 61 | while (bytes--) { 62 | char ch = *received++; 63 | if (ch != '\r' && ch != CONTROL('H')) { 64 | if (ch == '\n') 65 | offset = locus_get(view, locus); 66 | view_insert(view, &ch, offset++, 1); 67 | } 68 | } 69 | 70 | offset = locus_get(view, locus); 71 | locus_set(view, locus, offset - !!offset); 72 | return TRUE; 73 | } 74 | 75 | static Boolean_t error_activity(struct stream *stream, char *received, 76 | ssize_t bytes) 77 | { 78 | if (bytes <= 0) 79 | return FALSE; 80 | received[bytes] = '\0'; 81 | message("%s", received); 82 | return TRUE; 83 | } 84 | 85 | static Boolean_t out_activity(struct stream *stream, char *x, ssize_t bytes) 86 | { 87 | ssize_t chunk = stream->bytes - stream->writ; 88 | 89 | if (chunk <= 0) 90 | return 0; 91 | do { 92 | errno = 0; 93 | bytes = write(stream->fd, stream->data + stream->writ, 94 | chunk); 95 | } while (bytes < 0 && (errno == EAGAIN || errno == EINTR)); 96 | 97 | if (bytes < 0 && (errno == EPIPE || errno == EIO)) { 98 | message("write failed (child terminated?)"); 99 | return FALSE; 100 | } 101 | if (bytes <= 0) 102 | die("write of %d bytes failed", chunk); 103 | stream->writ += bytes; 104 | return bytes > 0; 105 | } 106 | 107 | static struct stream *stream_create(fd_t fd) 108 | { 109 | struct stream *stream = allocate0(sizeof *stream); 110 | 111 | stream->fd = fd; 112 | stream->locus = NO_LOCUS; 113 | if (!streams) 114 | streams = stream; 115 | else { 116 | struct stream *prev = streams; 117 | while (prev->next) 118 | prev = prev->next; 119 | prev->next = stream; 120 | } 121 | return stream; 122 | } 123 | 124 | static void stream_destroy(struct stream *stream, struct stream *prev) 125 | { 126 | if (!stream->retain) 127 | close(stream->fd); 128 | if (stream->view) 129 | locus_destroy(stream->view, stream->locus); 130 | if (prev) 131 | prev->next = stream->next; 132 | else 133 | streams = stream->next; 134 | RELEASE(stream->data); 135 | RELEASE(stream); 136 | } 137 | 138 | Boolean_t multiplexor(Boolean_t block) 139 | { 140 | struct timeval tv, *tvp = NULL; 141 | int j; 142 | fd_t maxfd = 0; 143 | ssize_t bytes; 144 | struct stream *stream, *prev, *next; 145 | fd_set fds[3]; 146 | char *rdbuff = NULL; 147 | 148 | for (j = 0; j < 3; j++) 149 | FD_ZERO(&fds[j]); 150 | FD_SET(0, &fds[0]); 151 | FD_SET(0, &fds[2]); 152 | for (stream = streams; stream; stream = stream->next) { 153 | FD_SET(stream->fd, &fds[!!stream->data]); 154 | FD_SET(stream->fd, &fds[2]); 155 | if (stream->fd > maxfd) 156 | maxfd = stream->fd; 157 | } 158 | if (block) 159 | tvp = NULL; 160 | else 161 | memset(tvp = &tv, 0, sizeof tv); 162 | 163 | errno = 0; 164 | if (select(maxfd + 1, &fds[0], &fds[1], &fds[2], tvp) < 0) 165 | return errno != EAGAIN && errno != EINTR; 166 | 167 | for (prev = NULL, stream = streams; stream; stream = next) { 168 | next = stream->next; 169 | if (!FD_ISSET(stream->fd, &fds[!!stream->data]) && 170 | !FD_ISSET(stream->fd, &fds[2])) { 171 | prev = stream; 172 | continue; 173 | } 174 | if (stream->data) 175 | bytes = 0; 176 | else { 177 | if (!rdbuff) 178 | rdbuff = allocate(1024); 179 | errno = 0; 180 | bytes = read(stream->fd, rdbuff, 1023); 181 | } 182 | if (stream->activity(stream, rdbuff, bytes)) 183 | prev = stream; 184 | else 185 | stream_destroy(stream, prev); 186 | } 187 | 188 | RELEASE(rdbuff); 189 | return FD_ISSET(0, &fds[0]) || FD_ISSET(0, &fds[2]); 190 | }; 191 | 192 | static void child_close(struct view *view) 193 | { 194 | if (view->shell_std_in >= 0) { 195 | close(view->shell_std_in); 196 | view->shell_std_in = -1; 197 | } 198 | if (view->shell_pg >= 0) { 199 | killpg(view->shell_pg, SIGHUP); 200 | view->shell_pg = -1; 201 | } 202 | locus_destroy(view, view->shell_out_locus); 203 | view->shell_out_locus = NO_LOCUS; 204 | } 205 | 206 | void demultiplex_view(struct view *view) 207 | { 208 | struct stream *stream, *prev = NULL, *next; 209 | 210 | for (stream = streams; stream; stream = next) { 211 | next = stream->next; 212 | if (stream->view == view) 213 | stream_destroy(stream, prev); 214 | else 215 | prev = stream; 216 | } 217 | child_close(view); 218 | } 219 | 220 | void multiplex_write(fd_t fd, const char *data, ssize_t bytes, Boolean_t retain) 221 | { 222 | struct stream *stream; 223 | 224 | if (bytes < 0) 225 | bytes = data ? strlen(data) : 0; 226 | if (!bytes) { 227 | if (!retain) 228 | close(fd); 229 | return; 230 | } 231 | stream = stream_create(fd); 232 | stream->retain = retain; 233 | stream->activity = out_activity; 234 | stream->data = data; 235 | stream->bytes = bytes; 236 | } 237 | 238 | static void single_write(fd_t fd, Unicode_t ch) 239 | { 240 | char buf[8]; 241 | size_t len = unicode_utf8(buf, ch); 242 | char *single = allocate(len); 243 | 244 | memcpy(single, buf, len); 245 | multiplex_write(fd, single, len, TRUE /*retain*/); 246 | } 247 | 248 | static Boolean_t pipes(fd_t fd[3][2], unsigned stdfds) 249 | { 250 | int j, k; 251 | static Boolean_t use_ptys = TRUE; 252 | 253 | /* pipe(2) creates two file descriptors: [0] read, [1] write; 254 | * this code transposes some to produce: [0] parent, [1] child 255 | */ 256 | 257 | if (stdfds == 2 && use_ptys) { 258 | struct termios termios = original_termios; 259 | termios.c_oflag &= ~ONLCR; 260 | termios.c_lflag &= ~(ECHO|ECHOE| ECHOKE); 261 | termios.c_lflag |= ECHOK; 262 | errno = 0; 263 | if (!openpty(&fd[0][0], &fd[0][1], NULL, &termios, NULL)) { 264 | for (j = 1; j < 3; j++) 265 | for (k = 0; k < 2; k++) 266 | fd[j][k] = dup(fd[0][k]); 267 | return TRUE; 268 | } 269 | message("could not create pty"); 270 | use_ptys = FALSE; 271 | } 272 | 273 | /* use pipes */ 274 | errno = 0; 275 | for (j = 0; j < stdfds; j++) { 276 | if (pipe(fd[j])) { 277 | message("could not create pipes"); 278 | return 0; 279 | } 280 | } 281 | j = fd[0][0], fd[0][0] = fd[0][1], fd[0][1] = j; 282 | return TRUE; 283 | } 284 | 285 | static pid_t child(fd_t stdfd[3][2], unsigned stdfds, const char *argv[]) 286 | { 287 | int j; 288 | pid_t pid; 289 | 290 | if (!pipes(stdfd, stdfds)) 291 | return -1; 292 | fflush(NULL); 293 | errno = 0; 294 | if ((pid = fork()) < 0) { 295 | message("could not fork"); 296 | return -1; 297 | } 298 | 299 | if (pid) 300 | return pid; /* parent */ 301 | 302 | /* child */ 303 | for (j = 0; j < 3; j++) { 304 | close(stdfd[j][0]); 305 | errno = 0; 306 | if (dup2(stdfd[j][1], j) != j) { 307 | fprintf(stderr, "dup2(%d,%d) failed: %s\n", 308 | stdfd[j][1], j, strerror(errno)); 309 | exit(EXIT_FAILURE); 310 | } 311 | close(stdfd[j][1]); 312 | } 313 | 314 | if (isatty(0)) { 315 | pid = setsid(); /* new session */ 316 | ioctl(0, TIOCSCTTY); /* set controlling terminal */ 317 | ioctl(0, TIOCSPGRP, &pid); /* set process group */ 318 | } 319 | setenv("TERM", "network", TRUE); 320 | unsetenv("LS_COLORS"); 321 | 322 | errno = 0; 323 | execvp(argv[0], (char *const *) argv); 324 | 325 | fprintf(stderr, "could not execute %s: %s\n", 326 | argv[0], strerror(errno)); 327 | exit(EXIT_FAILURE); 328 | } 329 | 330 | static const char *shell_name(void) 331 | { 332 | const char *shell = getenv("SHELL"); 333 | if (access(shell, X_OK)) 334 | shell = "/bin/sh"; 335 | return shell; 336 | } 337 | 338 | void mode_child(struct view *view) 339 | { 340 | char *command = view_extract_selection(view); 341 | char *wrbuff = NULL; 342 | position_t cursor; 343 | size_t to_write; 344 | fd_t stdfd[3][2]; 345 | const char *argv[4]; 346 | struct stream *std_out, *std_err; 347 | int j; 348 | 349 | if (!command) { 350 | window_beep(view); 351 | return; 352 | } 353 | 354 | if (view->shell_std_in >= 0) { 355 | locus_set(view, MARK, UNSET); 356 | multiplex_write(view->shell_std_in, command, strlen(command), 357 | TRUE /*retain*/); 358 | single_write(view->shell_std_in, '\n'); 359 | return; 360 | } 361 | 362 | view_delete_selection(view); 363 | 364 | if (command[0] == 'c' && command[1] == 'd' && 365 | (!command[2] || command[2] == ' ')) { 366 | const char *dir = command + 2; 367 | while (*dir == ' ') 368 | dir++; 369 | if (!*dir && !(dir = getenv("HOME"))) 370 | window_beep(view); 371 | else { 372 | errno = 0; 373 | if (chdir(dir)) 374 | message("%s failed", command); 375 | } 376 | return; 377 | } 378 | cursor = locus_get(view, CURSOR); 379 | to_write = clip_paste(view, cursor, 0); 380 | if (to_write) { 381 | locus_set(view, MARK, cursor); 382 | wrbuff = view_extract_selection(view); 383 | view_delete_selection(view); 384 | } 385 | 386 | argv[0] = shell_name(); 387 | argv[1] = "-c"; 388 | argv[2] = command; 389 | argv[3] = NULL; 390 | if (child(stdfd, 3, argv) < 0) 391 | return; 392 | 393 | for (j = 0; j < 3; j++) 394 | close(stdfd[j][1]); 395 | 396 | multiplex_write(stdfd[0][0], wrbuff, to_write, FALSE /*don't retain*/); 397 | std_out = stream_create(stdfd[1][0]); 398 | std_out->activity = insertion_activity; 399 | std_out->view = view; 400 | std_out->locus = locus_create(view, cursor); 401 | std_err = stream_create(stdfd[2][0]); 402 | std_err->activity = error_activity; 403 | } 404 | 405 | void mode_shell_pipe(struct view *view) 406 | { 407 | fd_t stdfd[3][2]; 408 | const char *shell, *p, *argv[8]; 409 | struct stream *output; 410 | int j, ai = 0; 411 | pid_t pg; 412 | 413 | argv[ai++] = shell = shell_name(); 414 | if ((p = strrchr(shell, '/')) && !strcmp(p+1, "bash")) 415 | argv[ai++] = "--noediting"; 416 | argv[ai++] = NULL; 417 | 418 | if ((pg = child(stdfd, 2, argv)) < 0) 419 | return; 420 | 421 | close(stdfd[2][0]); 422 | for (j = 0; j < 3; j++) 423 | close(stdfd[j][1]); 424 | 425 | child_close(view); 426 | view->shell_out_locus = locus_create(view, locus_get(view, CURSOR)); 427 | view->shell_pg = pg; 428 | output = stream_create(stdfd[1][0]); 429 | output->activity = shell_output_activity; 430 | output->view = view; 431 | view->shell_std_in = stdfd[0][0]; 432 | } 433 | 434 | void shell_command(struct view *view, Unicode_t ch) 435 | { 436 | position_t offset, linestart, cursor; 437 | char *command; 438 | 439 | if (ch >= ' ' || ch == '\t') 440 | return; 441 | 442 | cursor = locus_get(view, CURSOR); 443 | linestart = cursor ? find_line_start(view, cursor-1) : 0; 444 | offset = locus_get(view, view->shell_out_locus) + 1; 445 | if (offset < linestart || offset >= cursor) 446 | offset = linestart; 447 | command = view_extract(view, offset, cursor - offset); 448 | if (command) 449 | multiplex_write(view->shell_std_in, command, 450 | -1, TRUE /*retain*/); 451 | locus_set(view, view->shell_out_locus, 452 | view->bytes ? view->bytes-1 : UNSET); 453 | locus_set(view, CURSOR, view->bytes); 454 | } 455 | 456 | void background_command(const char *command) 457 | { 458 | int j; 459 | pid_t pg; 460 | fd_t stdfd[3][2]; 461 | const char *argv[4]; 462 | struct stream *std_out, *std_err; 463 | 464 | argv[0] = shell_name(); 465 | argv[1] = "-c"; 466 | argv[2] = command; 467 | argv[3] = NULL; 468 | if ((pg = child(stdfd, 3, argv)) < 0) 469 | return; 470 | for (j = 0; j < 3; j++) 471 | close(stdfd[j][1]); 472 | close(stdfd[0][0]); 473 | std_out = stream_create(stdfd[1][0]); 474 | std_out->activity = error_activity; 475 | std_err = stream_create(stdfd[2][0]); 476 | std_err->activity = error_activity; 477 | waitpid(pg, NULL, 0); 478 | } 479 | -------------------------------------------------------------------------------- /child.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef CHILD_H 3 | #define CHILD_H 4 | 5 | void mode_child(struct view *); 6 | void mode_shell_pipe(struct view *); 7 | void shell_command(struct view *, Unicode_t); 8 | void background_command(const char *command); 9 | 10 | Boolean_t multiplexor(Boolean_t block); 11 | void multiplex_write(fd_t fd, const char *, ssize_t bytes, Boolean_t retain); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /clip.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | static struct buffer **clip_buffer; 5 | static unsigned clip_buffers; 6 | 7 | void clip_init(unsigned reg) 8 | { 9 | if (reg < clip_buffers) 10 | buffer_delete(clip_buffer[reg], 0, ~(size_t)0); 11 | } 12 | 13 | size_t clip(unsigned reg, struct view *view, position_t offset, 14 | size_t bytes, Boolean_t append) 15 | { 16 | char *raw; 17 | 18 | if (reg >= clip_buffers) { 19 | clip_buffer = reallocate(clip_buffer, 20 | (reg+1) * sizeof *clip_buffer); 21 | memset(clip_buffer + clip_buffers, 0, 22 | (reg + 1 - clip_buffers) * sizeof *clip_buffer); 23 | clip_buffers = reg + 1; 24 | } 25 | 26 | if (!clip_buffer[reg]) 27 | clip_buffer[reg] = buffer_create(NULL); 28 | bytes = view_raw(view, &raw, offset, bytes); 29 | return buffer_insert(clip_buffer[reg], raw, 30 | append ? buffer_bytes(clip_buffer[reg]) : 0, 31 | bytes); 32 | } 33 | 34 | size_t clip_paste(struct view *view, position_t offset, unsigned reg) 35 | { 36 | char *raw; 37 | size_t bytes; 38 | 39 | if (reg >= clip_buffers) 40 | return 0; 41 | bytes = buffer_raw(clip_buffer[reg], &raw, 0, 42 | buffer_bytes(clip_buffer[reg])); 43 | return view_insert(view, raw, offset, bytes); 44 | } 45 | -------------------------------------------------------------------------------- /clip.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | struct view; 3 | void clip_init(unsigned reg); 4 | size_t clip(unsigned reg, struct view *, position_t, size_t, Boolean_t append); 5 | size_t clip_paste(struct view *, position_t, unsigned reg); 6 | -------------------------------------------------------------------------------- /die.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | /* Error messaging */ 5 | 6 | void die(const char *msg, ...) 7 | { 8 | int err = errno; 9 | va_list ap; 10 | 11 | tcsetattr(1, TCSANOW, &original_termios); 12 | fputs("\afatal editor error: ", stderr); 13 | va_start(ap, msg); 14 | vfprintf(stderr, msg, ap); 15 | va_end(ap); 16 | if (err) 17 | fprintf(stderr, ": %s", strerror(err)); 18 | fputc('\n', stderr); 19 | exit(EXIT_FAILURE); 20 | } 21 | 22 | void message(const char *msg, ...) 23 | { 24 | int err = errno; 25 | va_list ap; 26 | struct view *view = view_find("* ATTENTION *"); 27 | position_t start; 28 | 29 | if (!view) 30 | view = text_create("* ATTENTION *", TEXT_EDITOR); 31 | view->text->flags &= ~TEXT_RDONLY; 32 | view_insert(view, "\n", view->bytes, 1); 33 | start = view->bytes; 34 | va_start(ap, msg); 35 | view_vprintf(view, msg, ap); 36 | va_end(ap); 37 | if (err) 38 | view_printf(view, "\n(System error code: %s)", strerror(err)); 39 | view_insert(view, " ", view->bytes, 1); 40 | view->text->flags |= TEXT_RDONLY; 41 | locus_set(view, CURSOR, start); 42 | window_below(NULL, view, 3 + !!err); 43 | } 44 | 45 | static struct view *status_view; 46 | 47 | void status(const char *msg, ...) 48 | { 49 | va_list ap; 50 | 51 | if (!status_view) 52 | status_view = text_create("* STATUS *", TEXT_EDITOR); 53 | status_view->text->flags &= ~TEXT_RDONLY; 54 | view_delete(status_view, 0, status_view->bytes); 55 | va_start(ap, msg); 56 | view_vprintf(status_view, msg, ap); 57 | va_end(ap); 58 | view_insert(status_view, " ", status_view->bytes, 1); 59 | locus_set(status_view, CURSOR, status_view->bytes-1); 60 | status_view->text->flags |= TEXT_RDONLY; 61 | window_below(NULL, status_view, 3); 62 | } 63 | 64 | void status_hide(void) 65 | { 66 | if (status_view) 67 | window_destroy(status_view->window); 68 | } 69 | -------------------------------------------------------------------------------- /die.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef DIE_H 3 | #define DIE_H 4 | 5 | void die(const char *, ...); 6 | void message(const char *, ...); 7 | void status(const char *, ...); 8 | void status_hide(void); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /display-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "types.h" 7 | #include "utf8.h" 8 | #include "display.h" 9 | 10 | void die(const char *, ...); 11 | Boolean_t multiplexor(Boolean_t); 12 | 13 | static struct display *D; 14 | static int rows, columns; 15 | 16 | static void dfill(int row, int rows, int col, int cols, 17 | int ch, rgba_t fgrgba, rgba_t bgrgba) 18 | { 19 | int j, k; 20 | for (j = 0; j < rows; j++) 21 | for (k = 0; k < cols; k++) 22 | display_put(D, row+j, col+k, ch, fgrgba, bgrgba); 23 | } 24 | 25 | static void dprint(int row, int col, rgba_t fgrgba, rgba_t bgrgba, 26 | const char *format, ...) 27 | { 28 | char buffer[256], *p; 29 | va_list ap; 30 | va_start(ap, format); 31 | vsnprintf(buffer, sizeof buffer, format, ap); 32 | va_end(ap); 33 | for (p = buffer; *p; p++) 34 | display_put(D, row, col++, *p, fgrgba, bgrgba); 35 | } 36 | 37 | static void dpause(void) 38 | { 39 | int ch; 40 | dfill(rows-1, 1, 0, columns, ' ', RED_RGBA, WHITE_RGBA); 41 | dprint(rows-1, 0, RED_RGBA, WHITE_RGBA, "Hit Q to quit, or any other key to continue..."); 42 | while ((ch = display_getch(D, 1)) == ERROR_CHANGED) 43 | display_get_geometry(D, &rows, &columns); 44 | if (ch == 'q' || ch == 'Q') { 45 | display_end(D); 46 | exit(EXIT_SUCCESS); 47 | } 48 | } 49 | 50 | void die(const char *msg, ...) 51 | { 52 | va_list ap; 53 | display_end(D); 54 | va_start(ap, msg); 55 | vfprintf(stderr, msg, ap); 56 | va_end(ap); 57 | exit(EXIT_FAILURE); 58 | } 59 | 60 | Boolean_t multiplexor(Boolean_t block) 61 | { 62 | return TRUE; 63 | } 64 | 65 | int main(void) 66 | { 67 | if (tcgetattr(1, &original_termios)) 68 | die("not running in a terminal"); 69 | D = display_init(); 70 | display_get_geometry(D, &rows, &columns); 71 | display_title(D, "display-test"); 72 | dprint(0, 0, BLACK_RGBA, WHITE_RGBA, "geometry: %d rows, %d columns", rows, columns); 73 | dprint(1, 0, DEFAULT_FGRGBA, DEFAULT_BGRGBA, "default foreground and background"); 74 | dprint(2, 0, WHITE_RGBA, BLACK_RGBA, "white foreground on black background"); 75 | dprint(3, 0, BLACK_RGBA, WHITE_RGBA, "black foreground on white background"); 76 | dprint(4, 0, RED_RGBA, BLUE_RGBA, "red foreground on blue background"); 77 | dprint(5, 0, BLUE_RGBA, RED_RGBA, "blue foreground on red background"); 78 | dpause(); 79 | 80 | dfill(0, rows, 0, columns, '.', RED_RGBA, GREEN_RGBA); 81 | dprint(0, 0, BLUE_RGBA, RED_RGBA, "filled with red dots on green"); 82 | dpause(); 83 | 84 | display_erase(D, 0, 0, rows, columns); 85 | dprint(0, 0, BLUE_RGBA, RED_RGBA, "after ERASEALL display_erase()"); 86 | dpause(); 87 | 88 | dfill(0, rows, 0, columns, '.', RED_RGBA, GREEN_RGBA); 89 | display_erase(D, rows/2, 0, rows - rows/2, columns); 90 | dprint(0, 0, BLUE_RGBA, RED_RGBA, "after ERASETOEND display_erase()"); 91 | dpause(); 92 | 93 | dfill(0, rows, 0, columns, '.', RED_RGBA, GREEN_RGBA); 94 | display_erase(D, 1, columns/2, 1, columns); 95 | dprint(0, 0, BLUE_RGBA, RED_RGBA, "after ERASELINE display_erase()"); 96 | dpause(); 97 | 98 | dfill(0, rows, 0, columns, '.', RED_RGBA, GREEN_RGBA); 99 | display_erase(D, 1, 0, 1, columns/2); 100 | dprint(0, 0, BLUE_RGBA, RED_RGBA, "after ERASECOLS display_erase()"); 101 | dpause(); 102 | 103 | dfill(0, rows, 0, columns, '.', RED_RGBA, GREEN_RGBA); 104 | display_insert_lines(D, 2, 0, rows/2, rows-2, columns); 105 | dprint(0, 0, BLUE_RGBA, RED_RGBA, "after display_insert_lines()"); 106 | dpause(); 107 | 108 | dfill(0, rows, 0, columns, '.', RED_RGBA, GREEN_RGBA); 109 | dfill(rows-1, 1, 0, columns, '*', RED_RGBA, GREEN_RGBA); 110 | display_delete_lines(D, 2, 0, rows/2, rows-2, columns); 111 | dprint(0, 0, BLUE_RGBA, RED_RGBA, "after display_delete_lines()"); 112 | dpause(); 113 | 114 | dfill(0, rows, 0, columns, '.', RED_RGBA, GREEN_RGBA); 115 | display_insert_spaces(D, 2, 0, columns/2, columns); 116 | display_insert_spaces(D, 3, columns/2, columns - (columns/2), columns); 117 | dprint(0, 0, BLUE_RGBA, RED_RGBA, "after display_insert_spaces()"); 118 | dpause(); 119 | 120 | dfill(0, rows, 0, columns, '.', RED_RGBA, GREEN_RGBA); 121 | display_delete_chars(D, 2, 0, columns/2, columns); 122 | display_delete_chars(D, 3, columns/2, columns - (columns/2), columns); 123 | dprint(0, 0, BLUE_RGBA, RED_RGBA, "after display_delete_chars()"); 124 | dpause(); 125 | 126 | display_end(D); 127 | printf("display-test done\n"); 128 | return EXIT_SUCCESS; 129 | } 130 | -------------------------------------------------------------------------------- /display.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef DISPLAY_H 3 | #define DISPLAY_H 4 | 5 | #include "rgba.h" 6 | 7 | extern struct termios original_termios; 8 | 9 | struct display; 10 | 11 | struct display *display_init(void); 12 | void display_reset(struct display *); 13 | void display_end(struct display *); 14 | void display_get_geometry(struct display *, int *rows, int *columns); 15 | Boolean_t display_title(struct display *, const char *); 16 | void display_cursor(struct display *, int row, int column); 17 | Boolean_t display_cursor_color(struct display *, rgba_t rgba); 18 | void display_put(struct display *, int row, int column, 19 | Unicode_t unicode, rgba_t fgRGBA, rgba_t bgRGBA); 20 | void display_beep(struct display *); 21 | void display_sync(struct display *); 22 | 23 | /* display_getch() implies a display_sync(). 24 | * 25 | * Once ERROR_CHANGED is returned after a window size change, 26 | * it will continue to be returned until display_get_geometry() is called 27 | */ 28 | Unicode_t display_getch(struct display *, Boolean_t block); 29 | 30 | /* hints */ 31 | void display_erase(struct display *, int row, int column, 32 | int rows, int columns); 33 | void display_insert_spaces(struct display *, int row, int column, 34 | int spaces, int columns); 35 | void display_delete_chars(struct display *, int row, int column, 36 | int chars, int columns); 37 | void display_insert_lines(struct display *, int row, int column, 38 | int lines, int rows, int columns); 39 | void display_delete_lines(struct display *, int row, int column, 40 | int lines, int rows, int columns); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /file.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | enum utf8_mode utf8_mode = UTF8_AUTO; 5 | const char *make_writable; 6 | Boolean_t no_save_originals; 7 | Boolean_t read_only; 8 | unsigned default_tab_stop = 8; /* the only correct value :-) */ 9 | Boolean_t default_no_tabs; 10 | Boolean_t default_tabs; 11 | 12 | const char *path_format(const char *path) 13 | { 14 | char *cwdbuf, *cwd; 15 | const char *slash; 16 | 17 | if (!path || *path != '/') 18 | return path; 19 | cwdbuf = allocate(1024); 20 | cwd = getcwd(cwdbuf, 1024); 21 | while ((slash = strchr(path, '/')) && 22 | !strncmp(cwd, path, slash - path)) { 23 | cwd += slash++ - path; 24 | if (*cwd && *cwd++ != '/') 25 | break; 26 | path = slash; 27 | } 28 | RELEASE(cwdbuf); 29 | return path; 30 | } 31 | 32 | static ssize_t old_fashioned_read(struct text *text) 33 | { 34 | char *raw; 35 | ssize_t got, total = 0; 36 | size_t max; 37 | #define CHUNK 1024 38 | 39 | do { 40 | buffer_insert(text->buffer, NULL, total, CHUNK); 41 | max = buffer_raw(text->buffer, &raw, total, CHUNK); 42 | errno = 0; 43 | got = read(text->fd, raw, max); 44 | if (got < 0) { 45 | message("%s: can't read", 46 | path_format(text->path)); 47 | buffer_delete(text->buffer, 0, total + CHUNK); 48 | return -1; 49 | } 50 | buffer_delete(text->buffer, total + got, CHUNK - got); 51 | total += got; 52 | } while (got); 53 | 54 | return total; 55 | } 56 | 57 | static char *fix_path(const char *path) 58 | { 59 | char *fpath; 60 | const char *freepath = NULL, *home; 61 | size_t pathlen; 62 | 63 | if (!path) 64 | return NULL; 65 | while (isspace(*path)) 66 | path++; 67 | while (*path == '.' && path[1] == '/') 68 | path += 2; 69 | if (!(pathlen = strlen(path))) 70 | return NULL; 71 | if (isspace(path[pathlen-1])) { 72 | char *apath; 73 | while (pathlen && isspace(path[--pathlen])) 74 | ; 75 | if (!pathlen) 76 | return NULL; 77 | apath = allocate(pathlen+1); 78 | memcpy(apath, path, pathlen); 79 | apath[pathlen] = '\0'; 80 | freepath = path = apath; 81 | } 82 | if (!strncmp(path, "~/", 2) && (home = getenv("HOME"))) { 83 | char *apath = allocate(strlen(home) + pathlen); 84 | sprintf(apath, "%s%s", home, path + 1); 85 | RELEASE(freepath); 86 | freepath = path = apath; 87 | } else if (*path != '/') { 88 | char *cwdbuf = allocate(1024); 89 | char *cwd = getcwd(cwdbuf, 1024); 90 | char *apath = allocate(strlen(cwd) + pathlen + 2); 91 | sprintf(apath, "%s/%s", cwd, path); 92 | RELEASE(freepath); 93 | RELEASE(cwdbuf); 94 | freepath = path = apath; 95 | } 96 | fpath = strdup(path); 97 | RELEASE(freepath); 98 | return fpath; 99 | } 100 | 101 | static void clean_mmap(struct text *text, size_t bytes, int flags) 102 | { 103 | void *p; 104 | size_t pagesize = getpagesize(); 105 | unsigned pages = (bytes + pagesize - 1) / pagesize; 106 | 107 | if (text->clean) 108 | munmap(text->clean, text->clean_bytes); 109 | text->clean_bytes = bytes; 110 | text->clean = NULL; 111 | if (!pages) 112 | return; 113 | p = mmap(0, pages * pagesize, flags, MAP_SHARED, text->fd, 0); 114 | if (p != MAP_FAILED) 115 | text->clean = p; 116 | } 117 | 118 | static void grab_mtime(struct text *text) 119 | { 120 | struct stat statbuf; 121 | 122 | if (text->fd >= 0 && !fstat(text->fd, &statbuf)) 123 | text->mtime = statbuf.st_mtime; 124 | else 125 | text->mtime = 0; 126 | } 127 | 128 | static void scan(struct view *view) 129 | { 130 | char *raw, scratch[8]; 131 | position_t at; 132 | size_t bytes = view_raw(view, &raw, 0, getpagesize()); 133 | size_t chop = bytes < view->bytes ? 8 : 0; 134 | size_t chlen, check; 135 | Unicode_t ch, lastch = 0; 136 | int crnl = 0, nl = 0; 137 | Boolean_t any_tab = default_tabs; 138 | int tabstop = default_tab_stop; 139 | 140 | /* Reset state */ 141 | view->text->flags &= ~(TEXT_NO_UTF8 | TEXT_CRNL | TEXT_NO_TABS); 142 | view->text->tabstop = default_tab_stop; 143 | 144 | if (utf8_mode == UTF8_NO) 145 | view->text->flags |= TEXT_NO_UTF8; 146 | else if (utf8_mode == UTF8_AUTO) 147 | for (at = 0; at + chop < bytes; at += chlen) { 148 | chlen = utf8_length(raw + at, bytes - at); 149 | ch = utf8_unicode(raw + at, chlen); 150 | check = unicode_utf8(scratch, ch); 151 | if (chlen != check) { 152 | view->text->flags |= TEXT_NO_UTF8; 153 | break; 154 | } 155 | } 156 | 157 | for (at = 0; at + chop < bytes; lastch = ch) 158 | if ((ch = view_unicode(view, at, &at)) == '\n') { 159 | nl++; 160 | crnl += lastch == '\r'; 161 | } 162 | if (nl && crnl == nl) 163 | view->text->flags |= TEXT_CRNL; 164 | 165 | for (at = 0; at + chop < bytes; at = find_line_end(view, at) + 1) { 166 | int spaces = 0; 167 | while ((ch = view_unicode(view, at, &at)) == ' ') 168 | spaces++; 169 | if (ch == '\t') 170 | any_tab = TRUE; 171 | if (spaces > 1 && spaces < tabstop) 172 | tabstop = spaces; 173 | } 174 | if (default_no_tabs || !any_tab) { 175 | view->text->flags |= TEXT_NO_TABS; 176 | view->text->tabstop = tabstop; 177 | } 178 | } 179 | 180 | struct view *view_open(const char *path0) 181 | { 182 | struct view *view; 183 | struct text *text; 184 | struct stat statbuf; 185 | char *path = fix_path(path0); 186 | 187 | if (!path) 188 | return NULL; 189 | 190 | for (text = text_list; text; text = text->next) 191 | if (text->path && !strcmp(text->path, path)) { 192 | for (view = text->views; view; view = view->next) 193 | if (!view->window) 194 | goto done; 195 | view = view_create(text); 196 | goto done; 197 | } 198 | 199 | view = text_create(path, 0); 200 | text = view->text; 201 | 202 | errno = 0; 203 | if (stat(path, &statbuf)) { 204 | if (errno != ENOENT) { 205 | message("%s: can't stat", path_format(path)); 206 | goto fail; 207 | } 208 | if (read_only) { 209 | message("%s: can't create in read-only mode", 210 | path_format(path)); 211 | goto fail; 212 | } 213 | errno = 0; 214 | text->fd = open(path, O_CREAT|O_TRUNC|O_RDWR, 215 | S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 216 | if (text->fd < 0) { 217 | message("%s: can't create", path_format(path)); 218 | goto fail; 219 | } 220 | text->flags |= TEXT_CREATED; 221 | } else { 222 | if (!S_ISREG(statbuf.st_mode)) { 223 | message("%s: not a regular file", path_format(path)); 224 | goto fail; 225 | } 226 | if (!read_only) 227 | text->fd = open(path, O_RDWR); 228 | if (text->fd < 0) { 229 | errno = 0; 230 | text->flags |= TEXT_RDONLY; 231 | text->fd = open(path, O_RDONLY); 232 | if (text->fd < 0) { 233 | message("%s: can't open", path_format(path)); 234 | goto fail; 235 | } 236 | } 237 | clean_mmap(text, statbuf.st_size, PROT_READ); 238 | if (!text->clean) { 239 | text->buffer = buffer_create(path); 240 | if (old_fashioned_read(text) < 0) 241 | goto fail; 242 | grab_mtime(text); 243 | } 244 | view->bytes = text->buffer ? buffer_bytes(text->buffer) : 245 | text->clean_bytes; 246 | scan(view); 247 | text_forget_undo(text); 248 | } 249 | goto done; 250 | 251 | fail: view_close(view); 252 | view = NULL; 253 | 254 | done: RELEASE(path); 255 | return view; 256 | } 257 | 258 | static fd_t try_dir(char *path, const char *dir, const struct tm *gmt) 259 | { 260 | struct stat statbuf; 261 | 262 | errno = 0; 263 | if (stat(dir, &statbuf)) { 264 | if (errno != ENOENT) 265 | return -1; 266 | if (mkdir(dir, S_IRUSR|S_IWUSR|S_IXUSR)) 267 | return -1; 268 | if (stat(dir, &statbuf)) 269 | return -1; 270 | } 271 | if (!S_ISDIR(statbuf.st_mode)) 272 | return -1; 273 | sprintf(path, "%s/%02d-%02d-%02d.%02d%02d%02d", dir, 274 | gmt->tm_year+1900, gmt->tm_mon+1, gmt->tm_mday, 275 | gmt->tm_hour, gmt->tm_min, gmt->tm_sec); 276 | return open(path, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR); 277 | } 278 | 279 | struct view *text_new(void) 280 | { 281 | char dir[128], path[128]; 282 | const char *me, *home; 283 | time_t now = time(NULL); 284 | struct tm *gmt = gmtime(&now); 285 | fd_t fd = -1; 286 | struct view *view; 287 | 288 | if ((home = getenv("HOME"))) { 289 | sprintf(dir, "%s/.aoeui", home); 290 | fd = try_dir(path, dir, gmt); 291 | } 292 | if (fd < 0 && (me = getenv("LOGNAME"))) { 293 | sprintf(dir, "/tmp/aoeui-%s", me); 294 | fd = try_dir(path, dir, gmt); 295 | } 296 | #if !defined __APPLE__ && !defined BSD 297 | if (fd < 0 && (me = cuserid(NULL))) { 298 | sprintf(dir, "/tmp/aoeui-%s", me); 299 | fd = try_dir(path, dir, gmt); 300 | } 301 | #endif 302 | if (fd < 0) 303 | fd = try_dir(path, "/tmp/aoeui", gmt); 304 | if (fd < 0) 305 | fd = try_dir(path, "./aoeui", gmt); 306 | 307 | if (fd < 0) 308 | view = text_create("* New *", TEXT_EDITOR); 309 | else { 310 | view = text_create(path, TEXT_CREATED | TEXT_SCRATCH); 311 | view->text->fd = fd; 312 | } 313 | return view; 314 | } 315 | 316 | Boolean_t text_rename(struct text *text, const char *path0) 317 | { 318 | char *path = fix_path(path0); 319 | struct text *b; 320 | struct view *view; 321 | fd_t fd; 322 | 323 | if (!path) 324 | return FALSE; 325 | for (b = text; b; b = b->next) 326 | if (b->path && !strcmp(b->path, path)) 327 | return FALSE; 328 | 329 | errno = 0; 330 | if ((fd = open(path, O_CREAT|O_RDWR, 331 | S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) { 332 | message("%s: can't create", path_format(path)); 333 | RELEASE(path); 334 | return FALSE; 335 | } 336 | 337 | if (text->flags & TEXT_CREATED) { 338 | unlink(text->path); 339 | text->flags &= ~(TEXT_CREATED | TEXT_SCRATCH); 340 | } 341 | 342 | /* Do not truncate or overwrite yet. */ 343 | text->flags |= TEXT_SAVED_ORIGINAL; 344 | text->flags &= ~TEXT_RDONLY; 345 | text_dirty(text); 346 | close(text->fd); 347 | if (text->clean) { 348 | munmap(text->clean, text->clean_bytes); 349 | text->clean = NULL; 350 | } 351 | text->fd = fd; 352 | grab_mtime(text); 353 | RELEASE(text->path); 354 | text->path = path; 355 | keyword_init(text); 356 | for (view = text->views; view; view = view->next) 357 | view_name(view); 358 | return TRUE; 359 | } 360 | 361 | void text_dirty(struct text *text) 362 | { 363 | if (text->path && !text->dirties && text->flags & TEXT_RDONLY) 364 | message("%s: read-only, %s", 365 | path_format(text->path), 366 | make_writable ? "will be made writable" 367 | : "changes won't be saved here"); 368 | text->dirties++; 369 | if (!text->buffer) { 370 | text->buffer = buffer_create(text->fd >= 0 ? text->path : NULL); 371 | if (text->clean) 372 | buffer_insert(text->buffer, text->clean, 0, 373 | text->clean_bytes); 374 | grab_mtime(text); 375 | } 376 | } 377 | 378 | static void save_original(struct text *text) 379 | { 380 | char *save_path; 381 | fd_t fd; 382 | ssize_t wrote = -1; 383 | 384 | if (no_save_originals || 385 | !text->clean || 386 | !text->path || 387 | text->flags & (TEXT_SAVED_ORIGINAL | 388 | TEXT_RDONLY | 389 | TEXT_CREATED | 390 | TEXT_EDITOR)) 391 | return; 392 | 393 | save_path = allocate(strlen(text->path)+2); 394 | sprintf(save_path, "%s~", text->path); 395 | errno = 0; 396 | fd = creat(save_path, S_IRUSR|S_IWUSR); 397 | if (fd >= 0) { 398 | wrote = write(fd, text->clean, text->clean_bytes); 399 | if (close(fd)) 400 | wrote = -1; 401 | } 402 | if (wrote != text->clean_bytes) 403 | message("%s: can't save original text", path_format(save_path)); 404 | RELEASE(save_path); 405 | text->flags |= TEXT_SAVED_ORIGINAL; 406 | } 407 | 408 | Boolean_t text_is_dirty(struct text *text) 409 | { 410 | return text->preserved != text->dirties && 411 | text->fd >= 0 && 412 | text->buffer; 413 | } 414 | 415 | void text_preserve(struct text *text) 416 | { 417 | char *raw; 418 | size_t bytes; 419 | struct stat statbuf; 420 | 421 | if (text->preserved == text->dirties || 422 | text->fd < 0 || 423 | !text->buffer) 424 | return; 425 | text->preserved = ++text->dirties; 426 | if (read_only) 427 | return; 428 | text_unfold_all(text); 429 | if (text->clean) { 430 | save_original(text); 431 | bytes = buffer_raw(text->buffer, &raw, 0, ~(size_t)0); 432 | if (bytes == text->clean_bytes && 433 | !memcmp(text->clean, raw, bytes)) 434 | return; 435 | munmap(text->clean, text->clean_bytes); 436 | text->clean = NULL; 437 | } 438 | if (text->mtime && 439 | text->path && 440 | !fstat(text->fd, &statbuf) && 441 | text->mtime < statbuf.st_mtime) 442 | message("%s: modified since read into the " 443 | "editor, changes may have been overwritten.", 444 | path_format(text->path)); 445 | text->preserved = ++text->dirties; 446 | if (text->flags & TEXT_RDONLY && text->path && make_writable) { 447 | char cmd[128]; 448 | int newfd; 449 | snprintf(cmd, sizeof cmd, make_writable, text->path); 450 | background_command(cmd); 451 | newfd = open(text->path, O_RDWR); 452 | if (newfd >= 0) { 453 | close(text->fd); 454 | text->fd = newfd; 455 | text->flags &= ~TEXT_RDONLY; 456 | } 457 | } 458 | if (text->flags & TEXT_RDONLY && text->path) { 459 | int newfd; 460 | char *new_path = allocate(strlen(text->path) + 2); 461 | sprintf(new_path, "%s@", text->path); 462 | errno = 0; 463 | newfd = open(new_path, O_CREAT|O_TRUNC|O_RDWR, 464 | S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 465 | if (newfd < 0) { 466 | message("%s: can't create", path_format(new_path)); 467 | RELEASE(new_path); 468 | return; 469 | } 470 | message("%s: read-only, new version saved to %s@", 471 | text->path, text->path); 472 | text->flags &= ~TEXT_RDONLY; 473 | close(text->fd); 474 | RELEASE(text->path); 475 | text->fd = newfd; 476 | text->path = new_path; 477 | } 478 | text->flags &= ~TEXT_CREATED; 479 | bytes = buffer_raw(text->buffer, &raw, 0, ~(size_t)0); 480 | if (ftruncate(text->fd, bytes)) 481 | message("%s: truncation failed", path_format(text->path)); 482 | clean_mmap(text, bytes, PROT_READ|PROT_WRITE); 483 | if (text->clean) { 484 | memcpy(text->clean, raw, bytes); 485 | msync(text->clean, bytes, MS_SYNC); 486 | } else { 487 | ssize_t wrote; 488 | lseek(text->fd, 0, SEEK_SET); 489 | errno = 0; 490 | wrote = write(text->fd, raw, bytes); 491 | if (wrote != bytes) 492 | message("%s: write failed", path_format(text->path)); 493 | } 494 | grab_mtime(text); 495 | } 496 | 497 | void texts_preserve(void) 498 | { 499 | struct text *text; 500 | for (text = text_list; text; text = text->next) 501 | text_preserve(text); 502 | } 503 | 504 | void texts_uncreate(void) 505 | { 506 | struct text *text; 507 | for (text = text_list; text; text = text->next) 508 | if (text->flags & TEXT_CREATED) 509 | unlink(text->path); 510 | } 511 | -------------------------------------------------------------------------------- /find.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | /* Routines that scan characters in views */ 5 | 6 | position_t find_line_start(struct view *view, position_t offset) 7 | { 8 | Unicode_t ch; 9 | position_t prev; 10 | 11 | while (IS_UNICODE((ch = view_char_prior(view, offset, &prev))) && 12 | ch != '\n') 13 | offset = prev; 14 | return offset; 15 | } 16 | 17 | position_t find_line_end(struct view *view, position_t offset) 18 | { 19 | Unicode_t ch; 20 | position_t next; 21 | 22 | while (IS_UNICODE((ch = view_char(view, offset, &next))) && 23 | ch != '\n') 24 | offset = next; 25 | return offset; 26 | } 27 | 28 | position_t find_paragraph_start(struct view *view, position_t offset) 29 | { 30 | Unicode_t ch, nch = UNICODE_BAD, nnch = UNICODE_BAD; 31 | position_t prev; 32 | 33 | while (IS_UNICODE((ch = view_char_prior(view, offset, &prev)))) { 34 | if (ch == '\n' && nch == '\n' && IS_UNICODE(nnch)) 35 | return offset + 1; 36 | offset = prev, nnch = nch, nch = ch; 37 | } 38 | return offset; 39 | } 40 | 41 | position_t find_paragraph_end(struct view *view, position_t offset) 42 | { 43 | Unicode_t ch, pch = UNICODE_BAD, ppch = UNICODE_BAD; 44 | position_t next; 45 | 46 | while (IS_UNICODE((ch = view_char(view, offset, &next))) && 47 | (ch == '\n' || pch != '\n' || ppch != '\n')) 48 | offset = next, ppch = pch, pch = ch; 49 | return offset; 50 | } 51 | 52 | static void updown_goal(struct view *view, position_t at) 53 | { 54 | if (at == view->goal.cursor) 55 | ; 56 | else if (view_byte(view, at) == '\n') 57 | view->goal.row = ~0; 58 | else { 59 | view->goal.row = 0; 60 | view->goal.column = find_column(&view->goal.row, view, 61 | find_line_start(view, at), 62 | at, 0); 63 | } 64 | } 65 | 66 | static position_t same_column(struct view *view, position_t at, position_t fail) 67 | { 68 | unsigned r = 0, c = 0; 69 | unsigned tabstop = view->text->tabstop; 70 | unsigned columns = window_columns(view->window); 71 | position_t next; 72 | 73 | for (; r < view->goal.row || c < view->goal.column; at = next) { 74 | Unicode_t ch; 75 | ch = view_char(view, at, &next); 76 | if (!IS_UNICODE(ch) || ch == '\n') { 77 | at = fail; 78 | break; 79 | } 80 | if ((c += char_columns(ch, c, tabstop)) > view->goal.column && 81 | r == view->goal.row) 82 | break; 83 | if (c > columns) 84 | r++, c = char_columns(ch, 0, tabstop); 85 | } 86 | return view->goal.cursor = at; 87 | } 88 | 89 | position_t find_line_up(struct view *view, position_t at) 90 | { 91 | position_t linestart = find_line_start(view, at); 92 | 93 | updown_goal(view, at); 94 | if (!linestart) 95 | return 0; 96 | return same_column(view, find_line_start(view, linestart-1), 97 | linestart-1); 98 | } 99 | 100 | position_t find_line_down(struct view *view, position_t at) 101 | { 102 | position_t nextstart = find_line_end(view, at) + 1; 103 | 104 | updown_goal(view, at); 105 | if (nextstart >= view->bytes) 106 | return view->bytes; 107 | return same_column(view, nextstart, find_line_end(view, nextstart)); 108 | } 109 | 110 | typedef Unicode_t (*stepper_t)(struct view *, position_t, position_t *); 111 | 112 | static position_t find_not(struct view *view, position_t offset, 113 | stepper_t stepper, 114 | Boolean_t (*test)(Unicode_t)) 115 | { 116 | position_t next; 117 | 118 | while (test(stepper(view, offset, &next))) 119 | offset = next; 120 | return offset; 121 | } 122 | 123 | static Boolean_t space_test(Unicode_t ch) 124 | { 125 | return IS_CODEPOINT(ch) && isspace(ch); 126 | } 127 | 128 | static Boolean_t nonspace_test(Unicode_t ch) 129 | { 130 | return IS_UNICODE(ch) && !space_test(ch); 131 | } 132 | 133 | position_t find_space(struct view *view, position_t offset) 134 | { 135 | return find_not(view, offset, view_char, nonspace_test); 136 | } 137 | 138 | position_t find_space_prior(struct view *view, position_t offset) 139 | { 140 | return find_not(view, offset, view_char_prior, nonspace_test); 141 | } 142 | 143 | position_t find_nonspace(struct view *view, position_t offset) 144 | { 145 | return find_not(view, offset, view_char, space_test); 146 | } 147 | 148 | position_t find_nonspace_prior(struct view *view, position_t offset) 149 | { 150 | return find_not(view, offset, view_char_prior, space_test); 151 | } 152 | 153 | 154 | static position_t find_contiguous(struct view *view, position_t offset, 155 | stepper_t stepper, 156 | Boolean_t (*test)(Unicode_t, struct view *, 157 | position_t *, stepper_t)) 158 | { 159 | Unicode_t ch; 160 | position_t next; 161 | Boolean_t in_region = FALSE; 162 | 163 | for (; IS_UNICODE((ch = stepper(view, offset, &next))); offset = next) 164 | if (test(ch, view, &next, stepper)) 165 | in_region = TRUE; 166 | else if (in_region) 167 | break; 168 | return offset; 169 | } 170 | 171 | static Boolean_t word_test(Unicode_t ch, struct view *view, position_t *next, 172 | stepper_t stepper) 173 | { 174 | return is_wordch(ch); 175 | } 176 | 177 | position_t find_word_start(struct view *view, position_t offset) 178 | { 179 | return find_contiguous(view, offset, view_char_prior, word_test); 180 | } 181 | 182 | position_t find_word_end(struct view *view, position_t offset) 183 | { 184 | return find_contiguous(view, offset, view_char, word_test); 185 | } 186 | 187 | static Boolean_t id_test(Unicode_t ch, struct view *view, position_t *next, 188 | stepper_t stepper) 189 | { 190 | return is_idch(ch) || 191 | ch == ':' && stepper(view, *next, next) == ':'; 192 | } 193 | 194 | position_t find_id_start(struct view *view, position_t offset) 195 | { 196 | return find_contiguous(view, offset, view_char_prior, id_test); 197 | } 198 | 199 | position_t find_id_end(struct view *view, position_t offset) 200 | { 201 | return find_contiguous(view, offset, view_char, id_test); 202 | } 203 | 204 | position_t find_sentence_start(struct view *view, position_t offset) 205 | { 206 | position_t prev; 207 | Unicode_t ch, next = view_char_prior(view, offset, &prev); 208 | 209 | if (!IS_UNICODE(next)) 210 | return offset; 211 | while (IS_UNICODE(ch = view_char_prior(view, offset = prev, &prev)) && 212 | ch != '.' && ch != ',' && ch != ';' && ch != ':' && 213 | ch != '!' && ch != '?' && 214 | ch != '(' && ch != '[' && ch != '{' && 215 | (ch != '\n' || ch != next)) 216 | next = ch; 217 | return offset; 218 | } 219 | 220 | position_t find_sentence_end(struct view *view, position_t offset) 221 | { 222 | position_t next; 223 | Unicode_t ch, last = view_char(view, offset, &next); 224 | 225 | if (!IS_UNICODE(last)) 226 | return offset; 227 | while (IS_UNICODE(ch = view_char(view, offset = next, &next)) && 228 | ch != '.' && ch != ',' && ch != ';' && ch != ':' && 229 | ch != '!' && ch != '?' && 230 | ch != ')' && ch != ']' && ch != '}' && 231 | (ch != '\n' || ch != last)) 232 | last = ch; 233 | return offset; 234 | } 235 | 236 | sposition_t find_corresponding_bracket(struct view *view, position_t offset) 237 | { 238 | static signed char peer[256], updown[256]; 239 | const char *p = view->text->brackets; 240 | position_t next; 241 | Unicode_t ch = view_char(view, offset, &next); 242 | Byte_t stack[32]; 243 | int stackptr = 0, dir; 244 | 245 | if (!p) 246 | return -1; 247 | memset(peer, 0, sizeof peer); 248 | memset(updown, 0, sizeof updown); 249 | for (; *p; p += 2) { 250 | int L = (unsigned char) p[0], R = (unsigned char) p[1]; 251 | peer[L] = R; 252 | peer[R] = L; 253 | updown[L] = 1; 254 | updown[R] = -1; 255 | } 256 | 257 | if (ch >= sizeof updown || !(dir = updown[ch])) { 258 | position_t back = offset, ahead, next = offset; 259 | while (IS_UNICODE(ch = view_char_prior(view, back, &back))) { 260 | if (ch >= sizeof updown) 261 | continue; 262 | if (updown[ch] < 0) 263 | if (stackptr == sizeof stack) 264 | break; 265 | else 266 | stack[stackptr++] = ch; 267 | else if (updown[ch] > 0 && 268 | (!stackptr || ch != peer[stack[--stackptr]])) 269 | break; 270 | } 271 | if (!IS_UNICODE(ch)) 272 | back = offset+1; 273 | while (IS_UNICODE(ch = view_char(view, ahead = next, &next))) { 274 | if (ch >= sizeof updown) 275 | continue; 276 | if (updown[ch] > 0) 277 | if (stackptr == sizeof stack) 278 | break; 279 | else 280 | stack[stackptr++] = ch; 281 | else if (updown[ch] < 0 && 282 | (!stackptr || ch != peer[stack[--stackptr]])) 283 | break; 284 | } 285 | if (back < offset && 286 | (offset - back <= ahead - offset || !IS_UNICODE(ch))) 287 | return back; 288 | return IS_UNICODE(ch) ? ahead : -1; 289 | } 290 | 291 | stack[stackptr++] = ch; 292 | if (dir > 0) 293 | offset = next; 294 | while (stackptr) { 295 | ch = (dir > 0 ? view_char : view_char_prior) 296 | (view, offset, &next); 297 | if (ch >= sizeof updown) 298 | return -1; 299 | if (updown[ch] == dir) { 300 | if (stackptr == sizeof stack) 301 | return -1; 302 | stack[stackptr++] = ch; 303 | } else if (updown[ch] == -dir) 304 | if (ch != peer[stack[--stackptr]] || 305 | !stackptr && dir > 0) 306 | break; 307 | offset = next; 308 | } 309 | 310 | return offset; 311 | } 312 | 313 | position_t find_line_number(struct view *view, unsigned line) 314 | { 315 | position_t offset, next, next2; 316 | sposition_t fold_start = -1, fold_end = -1; 317 | 318 | if (line-- <= 1) 319 | return 0; 320 | for (offset = 0; offset < view->bytes; offset = next) { 321 | Unicode_t ch = view_unicode(view, offset, &next); 322 | if (offset >= fold_end) 323 | fold_end = -1; 324 | if (ch == '\n' && !--line) 325 | break; 326 | if (fold_end < 0 && IS_FOLDED(ch)) { 327 | size_t fbytes = FOLDED_BYTES(ch); 328 | if (view_unicode(view, next + fbytes, &next2) == 329 | FOLD_END + fbytes) { 330 | fold_start = offset; 331 | fold_end = next2; 332 | } 333 | } 334 | } 335 | if (fold_end >= 0) 336 | return fold_start; 337 | return offset + 1; 338 | } 339 | 340 | unsigned current_line_number(struct view *view, position_t offset) 341 | { 342 | int line = 1; 343 | position_t at; 344 | unsigned last = '\n'; 345 | 346 | if (offset >= view->bytes) 347 | offset = view->bytes; 348 | for (at = 0; at < offset; at++) { 349 | last = view_byte(view, at); 350 | if (last == '\n') 351 | line++; 352 | } 353 | if (at == view->bytes && last == '\n') 354 | line--; 355 | return line; 356 | } 357 | 358 | position_t find_row_bytes(struct view *view, position_t offset0, 359 | unsigned column, unsigned columns) 360 | { 361 | position_t offset = offset0, next; 362 | unsigned tabstop = view->text->tabstop; 363 | Unicode_t ch = 0; 364 | int charcols; 365 | 366 | while (column < columns) { 367 | if (!IS_UNICODE(ch = view_char(view, offset, &next))) 368 | break; 369 | if (ch == '\n') { 370 | offset = next; 371 | break; 372 | } 373 | charcols = char_columns(ch, column, tabstop); 374 | if (column+charcols > columns) 375 | break; 376 | column += charcols; 377 | offset = next; 378 | } 379 | 380 | if (column == columns && 381 | offset != locus_get(view, CURSOR) && 382 | view_char(view, offset, NULL) == '\n') 383 | offset++; 384 | return offset - offset0; 385 | } 386 | 387 | unsigned find_column(unsigned *row, struct view *view, position_t at, 388 | position_t offset, unsigned column) 389 | { 390 | unsigned tabstop = view->text->tabstop; 391 | unsigned columns = window_columns(view->window); 392 | position_t next; 393 | 394 | for (; at < offset; at = next) { 395 | Unicode_t ch = view_char(view, at, &next); 396 | if (!IS_UNICODE(ch)) 397 | break; 398 | if (ch == '\n') { 399 | if (row) 400 | ++*row; 401 | column = 0; 402 | } else { 403 | column += char_columns(ch, column, tabstop); 404 | if (column >= columns) { 405 | if (row) 406 | ++*row; 407 | column = char_columns(ch, 0, tabstop); 408 | } 409 | } 410 | } 411 | return column; 412 | } 413 | 414 | sposition_t find_string(struct view *view, const char *string, 415 | position_t offset) 416 | { 417 | const Byte_t *ustring = (const Byte_t *) string; 418 | unsigned first = *ustring, j; 419 | Unicode_t ch; 420 | 421 | for (; IS_UNICODE(ch = view_byte(view, offset)); offset++) { 422 | if (ch != first) 423 | continue; 424 | for (j = 1; (ch = ustring[j]); j++) 425 | if (ch != view_byte(view, offset+j)) 426 | break; 427 | if (!ch) 428 | return offset; 429 | } 430 | return -1; 431 | } 432 | -------------------------------------------------------------------------------- /fold.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | void view_fold(struct view *view, position_t cursor, position_t mark) 5 | { 6 | size_t bytes = mark - cursor; 7 | char buf[8]; 8 | 9 | if (mark < cursor) 10 | bytes = -bytes, cursor = mark; 11 | if (cursor > view->bytes) 12 | return; 13 | if (cursor + bytes > view->bytes) 14 | bytes = view->bytes - cursor; 15 | if (!bytes) 16 | return; 17 | view_insert(view, buf, cursor + bytes, unicode_utf8(buf, FOLD_END+bytes)); 18 | view_insert(view, buf, cursor, unicode_utf8(buf, FOLD_START+bytes)); 19 | view->text->foldings++; 20 | } 21 | 22 | sposition_t view_unfold(struct view *view, position_t offset) 23 | { 24 | position_t next, next2; 25 | size_t fbytes; 26 | Unicode_t ch = view_unicode(view, offset, &next); 27 | 28 | if (ch < FOLD_START || ch >= FOLD_END) 29 | return -1; 30 | fbytes = FOLDED_BYTES(ch); 31 | if (view_unicode(view, next + fbytes, &next2) != 32 | FOLD_END + fbytes) 33 | return -1; 34 | view_delete(view, next + fbytes, next2 - (next + fbytes)); 35 | view_delete(view, offset, next - offset); 36 | view->text->foldings--; 37 | return offset + fbytes; 38 | } 39 | 40 | void view_unfold_selection(struct view *view) 41 | { 42 | position_t offset = locus_get(view, CURSOR); 43 | position_t end = locus_get(view, MARK); 44 | 45 | if (end == UNSET) 46 | return; 47 | if (end < offset) { 48 | position_t t = end; 49 | end = offset; 50 | offset = t; 51 | } 52 | 53 | while (offset < end) { 54 | position_t next, next2; 55 | size_t fbytes; 56 | Unicode_t ch = view_unicode(view, offset, &next); 57 | if (!IS_UNICODE(ch)) 58 | break; 59 | if (ch < FOLD_START || ch >= FOLD_END) { 60 | offset = next; 61 | continue; 62 | } 63 | fbytes = FOLDED_BYTES(ch); 64 | if (view_unicode(view, next + fbytes, &next2) != 65 | FOLD_END + fbytes) { 66 | offset = next; 67 | continue; 68 | } 69 | view_delete(view, next + fbytes, next2 - (next + fbytes)); 70 | view_delete(view, offset, next - offset); 71 | view->text->foldings--; 72 | offset += fbytes; 73 | } 74 | } 75 | 76 | static int indentation(struct view *view, position_t offset) 77 | { 78 | unsigned indent = 0, tabstop = view->text->tabstop; 79 | Unicode_t ch; 80 | tabstop |= !tabstop; 81 | for (;;) 82 | if ((ch = view_char(view, offset, &offset)) == ' ') 83 | indent++; 84 | else if (ch == '\t') 85 | indent = (indent / tabstop + 1) * tabstop; 86 | else if (ch == '\n') 87 | return -1; 88 | else 89 | break; 90 | return indent; 91 | } 92 | 93 | static unsigned max_indentation(struct view *view) 94 | { 95 | position_t offset, next; 96 | int maxindent = 0; 97 | 98 | for (offset = 0; offset < view->bytes; offset = next) { 99 | int indent = indentation(view, offset); 100 | if (indent > maxindent) 101 | maxindent = indent; 102 | next = find_line_end(view, offset) + 1; 103 | } 104 | return maxindent; 105 | } 106 | 107 | void view_fold_indented(struct view *view, unsigned minindent) 108 | { 109 | unsigned maxindent; 110 | 111 | minindent |= !minindent; 112 | while ((maxindent = max_indentation(view)) >= minindent) { 113 | position_t offset, next; 114 | sposition_t start = -1; 115 | for (offset = 0; offset < view->bytes; offset = next) { 116 | next = find_line_end(view, offset) + 1; 117 | if (indentation(view, offset) < maxindent) { 118 | if (start >= 0) { 119 | view_fold(view, next = start, offset-1); 120 | start = -1; 121 | } 122 | } else if (start < 0) 123 | start = offset - !!offset; 124 | } 125 | if (start >= 0) 126 | view_fold(view, start, offset-1); 127 | } 128 | } 129 | 130 | void view_unfold_all(struct view *view) 131 | { 132 | position_t offset, next; 133 | if (!view->text->foldings) 134 | return; 135 | for (offset = 0; 136 | IS_UNICODE(view_unicode(view, offset, &next)); 137 | offset = next) 138 | if (view_unfold(view, offset) >= 0) { 139 | if (!view->text->foldings) 140 | break; 141 | next = offset; 142 | } 143 | } 144 | 145 | void text_unfold_all(struct text *text) 146 | { 147 | struct view *view; 148 | if (!text->foldings) 149 | return; 150 | view_unfold_all(view = view_create(text)); 151 | view_close(view); 152 | } 153 | -------------------------------------------------------------------------------- /help.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | static const char *help[2] = { 5 | #include "aoeui.help" 6 | , 7 | #include "asdfg.help" 8 | }; 9 | 10 | struct view *view_help(void) 11 | { 12 | struct view *view = text_create("* Help *", TEXT_EDITOR); 13 | view_insert(view, help[is_asdfg], 0, strlen(help[is_asdfg])); 14 | locus_set(view, CURSOR, 0); 15 | view->text->flags |= TEXT_RDONLY; 16 | return view; 17 | } 18 | -------------------------------------------------------------------------------- /help.m4: -------------------------------------------------------------------------------- 1 | ifdef(`ASDFG',`define(`AOEUI',`asdfg')define(`cmd',`$2')',`define(`AOEUI',`aoeui')define(`cmd',`$1')')dnl 2 | Welcome to AOEUI 1.7! Here are some clues to help you use the editor. 3 | 4 | - The up/down/left/right "arrow" keys, page up/down keys, and Delete key 5 | all work fine. You can use AOEUI as a simple notepad if you like. 6 | - To insert text into a document, just type it. 7 | - In this documentation, the notation ^A means the command A, which you 8 | access by holding down Control or Alt while hitting A, or by pressing 9 | Escape and then A. All of these modifiers mean the same thing in AOEUI. 10 | - The space bar, when used as a command (^Sp), is a prefix that distinguishes 11 | command variants and numeric arguments. 12 | 13 | Command summary: 14 | 15 | ^Sp? display this help again 16 | ^Q pause the editor and return to the shell; return with "fg" 17 | ^Sp^Q save all files and quit 18 | ^Sp^\ quit immediately without saving 19 | ^cmd(U,Z) undo ^Sp^cmd(U,Z) redo 20 | ^cmd(K,W) save all files ^Sp^cmd(K,W) save one file 21 | 22 | ^cmd(H,G) backward ^cmd(T,H) forward 23 | ^Sp^cmd(H,G) up ^Sp^cmd(T,H) down 24 | ^cmd(N,K) previous word ^cmd(S,L) next word 25 | ^Sp^cmd(N,K) previous sentence ^Sp^cmd(S,L) next sentence 26 | ^cmd(G,T) previous beginning of line ^cmd(C,Y) next end of line 27 | ^Sp^cmd(G,T) paragraph start ^Sp^cmd(C,Y) paragraph end 28 | ^cmd(R,O) previous page ^cmd(L,P) next page 29 | ^Sp^cmd(R,O) go to beginning ^Sp^cmd(L,P) go to end 30 | ^] go to nearest ([{bracket}]), or to corresponding bracket 31 | ^Sp n ^cmd(Z,N) go to line number n 32 | 33 | ^cmd(V,U) begin a selection if none, forgets selection otherwise 34 | ^Sp^cmd(V,U) go to opposite end of selection, or select whole line if none 35 | ^cmd(D,X) cut, replacing clip buffer 36 | (typing new text at the start of the selection also cuts) 37 | ^Sp^cmd(D,X) with selection: cut, adding to clip buffer 38 | ^Sp^cmd(D,X) without selection: select all white space surrounding cursor 39 | ^cmd(F,C) copy, replacing clip buffer 40 | ^Sp^cmd(F,C) copy, adding to clip buffer 41 | ^cmd(B,V) exchange clip buffer with selection, if any; else paste 42 | 43 | ^J insert new line with automatic alignment (^Return may also work) 44 | Tab and ^I attempt tab completion if no selection is present 45 | ^SpTab align current line (^Sp^I also works) 46 | ^^ insert next character as raw or control character 47 | ^Sp n ^^ insert Unicode character n -- use leading 0x for hexadecimal 48 | 49 | ^_ incremental search mode (^-, ^/, and ^A may also work) 50 | ^Sp^_ incremental regular expression search mode with POSIX regexps. 51 | In search mode, use ^cmd(H,G) and ^cmd(T,H) to move from one 52 | instance of the search target to another and any other command, 53 | or Return, to resume editing. 54 | 55 | ^cmd(X,E) open file named by selection in new window 56 | insert current path as selection if none 57 | ^Sp^cmd(X,E) rename current text with path in selection 58 | ^cmd(W,F) display another open view in this window 59 | ^Sp^cmd(W,F) close this window and its text 60 | ^cmd(Y,D) split current window horizontally 61 | ^Sp^cmd(Y,D) split current window vertically 62 | ^cmd(P,S) switch to another window 63 | ^Sp^cmd(P,S) close current window 64 | 65 | ^Sp^cmd(O,B) begin recording default macro (end with ^cmd(O,B)) 66 | ^cmd(O,B) run default macro, or end macro/function key recording 67 | ^SpF1-F12 begin recording function key macro (end with ^cmd(O,B)) 68 | F1-F12 execute function key macro 69 | 70 | ^cmd(E,R) run shell command in selection with clip buffer as input, 71 | or open new shell interaction window if no selection 72 | ^Sp^cmd(E,R) cancel all pending background commands 73 | 74 | Parting words: 75 | - Many commands support a repeat count; ^Sp9^cmd(T,H) advances nine characters. 76 | - AOEUI has bookmarks, registers, tags, folding, and other features. 77 | Read the manual page for the full story and lots of useful tips. 78 | - Send me a note at pmklausler@gmail.com and say hi! 79 | -------------------------------------------------------------------------------- /keyword.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | Boolean_t no_keywords; 5 | 6 | static const char *C_keyword_list[] = { 7 | "#define", "#elif", "#else", "#endif", "#if", "#ifdef", "#ifndef", 8 | "#include", "#pragma", "#undef", 9 | "asm", "auto", "break", "case", "char", "const", "continue", 10 | "default", "do", "double", "else", "enum", "extern", "float", 11 | "for", "goto", "if", "int", "long", "register", "return", 12 | "short", "signed", "sizeof", "static", "struct", "switch", 13 | "typedef", "union", "unsigned", "void", "volatile", "while" 14 | }; 15 | 16 | static const char *Cpp_keyword_list[] = { 17 | "#define", "#elif", "#else", "#endif", "#if", "#ifdef", "#ifndef", 18 | "#include", "#pragma", "#undef", 19 | "asm", "auto", "bool", "break", "case", "catch", "char", "class", 20 | "const", "constexpr", "const_cast", "continue", "default", "delete", 21 | "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", 22 | "extern", "false", "final", "float", "for", "friend", "goto", "if", 23 | "inline", "int", "long", "mutable", "namespace", "new", "operator", 24 | "override", "private", "protected", 25 | "public", "register", "reinterpret_cast", "return", "short", "signed", 26 | "sizeof", "static", "static_cast", "struct", "switch", "template", 27 | "this", "throw", "true", "try", "typedef", "typeid", "typename", 28 | "union", "unsigned", "using", "virtual", "void", "volatile", 29 | "wchar_t", "while" 30 | }; 31 | 32 | static const char *go_keyword_list[] = { 33 | "break", "case", "chan", "const", "continue", "default", "defer", 34 | "else", "fallthrough", "for", "func", "go", "goto", "if", "import", 35 | "interface", "map", "package", "range", "return", "select", "struct", 36 | "switch", "type", "var" 37 | }; 38 | 39 | static const char *Haskell_keyword_list[] = { 40 | "_", "case", "class", "data", "default", "deriving", "do", "else", 41 | "foreign", "if", "import", "in", "infix", "infixl", "infixr", 42 | "instance", "let", "module", "newtype", "of", "then", "type", 43 | "where" 44 | }; 45 | 46 | static sposition_t C_comment_start(struct view *view, position_t offset) 47 | { 48 | Unicode_t ch, nch = 0; 49 | int newlines = 0; 50 | 51 | while (IS_UNICODE((ch = view_char_prior(view, offset, &offset)))) { 52 | if (ch == '\n') { 53 | if (newlines++ == 100) 54 | break; 55 | } else if (ch == '/') { 56 | if (nch == '*') 57 | return offset; 58 | if (!newlines && nch == '/') 59 | return offset; 60 | } else if (ch == '*' && nch == '/') 61 | break; 62 | nch = ch; 63 | } 64 | return -1; 65 | } 66 | 67 | static sposition_t C_comment_end(struct view *view, position_t offset) 68 | { 69 | Unicode_t ch, lch = 0; 70 | position_t next; 71 | 72 | if (view_char(view, offset, &offset) != '/') 73 | return -1; 74 | ch = view_char(view, offset, &offset); 75 | if (ch == '/') 76 | return find_line_end(view, offset); 77 | if (ch != '*') 78 | return -1; 79 | while (IS_UNICODE((ch = view_char(view, offset, &next)))) { 80 | if (lch == '*' && ch == '/') 81 | return offset; 82 | lch = ch; 83 | offset = next; 84 | } 85 | return -1; 86 | } 87 | 88 | static sposition_t C_string_end(struct view *view, position_t offset) 89 | { 90 | Unicode_t ch, lch = 0, ch0 = view_char(view, offset, &offset); 91 | position_t next; 92 | 93 | if (ch0 != '\'' && ch0 != '"') 94 | return -1; 95 | while (IS_UNICODE((ch = view_char(view, offset, &next)))) { 96 | if (ch == ch0 && lch != '\\') 97 | return offset; 98 | if (ch == '\n') 99 | break; 100 | if (ch == '\\' && lch == '\\') 101 | lch = 0; 102 | else 103 | lch = ch; 104 | offset = next; 105 | } 106 | return -1; 107 | } 108 | 109 | static sposition_t Haskell_comment_start(struct view *view, position_t offset) 110 | { 111 | Unicode_t ch, nch = 0; 112 | int newlines = 0; 113 | 114 | while (IS_UNICODE((ch = view_char_prior(view, offset, &offset)))) { 115 | if (ch == '\n') { 116 | if (newlines++ == 100) 117 | break; 118 | } else if (ch == '{' && nch == '-') 119 | return offset; 120 | else if (ch == '-') { 121 | if (!newlines && nch == '-') 122 | return offset; 123 | if (nch == '}') 124 | break; 125 | } 126 | nch = ch; 127 | } 128 | return -1; 129 | } 130 | 131 | static sposition_t Haskell_comment_end(struct view *view, position_t offset) 132 | { 133 | Unicode_t ch, lch = 0; 134 | position_t next; 135 | 136 | ch = view_char(view, offset, &offset); 137 | if (ch != '-' && ch != '{') 138 | return -1; 139 | if (view_char(view, offset, &offset) != '-') 140 | return -1; 141 | if (ch == '-') 142 | return find_line_end(view, offset); 143 | while (IS_UNICODE((ch = view_char(view, offset, &next)))) { 144 | if (lch == '-' && ch == '}') 145 | return offset; 146 | lch = ch; 147 | offset = next; 148 | } 149 | return -1; 150 | } 151 | 152 | static sposition_t Haskell_string_end(struct view *view, position_t offset) 153 | { 154 | position_t next; 155 | Unicode_t ch, lch = 0, ch0 = view_char(view, offset, &next); 156 | 157 | if (ch0 == '\'') { 158 | ch = view_char_prior(view, offset, NULL); 159 | if (isalnum(ch) || ch == '_' || ch == '\'') 160 | return -1; 161 | } else if (ch0 != '"') 162 | return -1; 163 | while (IS_UNICODE((ch = view_char(view, offset = next, &next)))) { 164 | if (ch == ch0 && lch != '\\') 165 | return offset; 166 | if (ch == '\n') 167 | break; 168 | if (ch == '\\' && lch == '\\') 169 | lch = 0; 170 | else 171 | lch = ch; 172 | } 173 | return -1; 174 | } 175 | 176 | #define KW(lang) { sizeof lang##_keyword_list / sizeof *lang##_keyword_list, \ 177 | lang##_keyword_list } 178 | 179 | static struct file_keywords { 180 | const char *suffix; 181 | struct keywords keywords; 182 | const char *brackets; 183 | sposition_t (*comment_start)(struct view *, position_t); 184 | sposition_t (*comment_end)(struct view *, position_t); 185 | sposition_t (*string_end)(struct view *, position_t); 186 | } kwmap [] = { 187 | { ".c", KW(C), "()[]{}", C_comment_start, C_comment_end, C_string_end }, 188 | { ".C", KW(C), "()[]{}", C_comment_start, C_comment_end, C_string_end }, 189 | { ".cc", KW(Cpp), "()[]{}", C_comment_start, C_comment_end, C_string_end }, 190 | { ".cpp", KW(Cpp), "()[]{}", C_comment_start, C_comment_end, C_string_end }, 191 | { ".cxx", KW(Cpp), "()[]{}", C_comment_start, C_comment_end, C_string_end }, 192 | { ".go", KW(go), "()[]{}", C_comment_start, C_comment_end, C_string_end }, 193 | { ".h", KW(Cpp), "()[]{}", C_comment_start, C_comment_end, C_string_end }, 194 | { ".hs", KW(Haskell), "()[]{}", Haskell_comment_start, Haskell_comment_end, 195 | Haskell_string_end }, 196 | { ".html", { 0, NULL }, "<>" }, 197 | { } 198 | }; 199 | 200 | void keyword_init(struct text *text) 201 | { 202 | if (text->path) { 203 | size_t pathlen = strlen(text->path); 204 | int j; 205 | for (j = 0; kwmap[j].suffix; j++) { 206 | size_t suffixlen = strlen(kwmap[j].suffix); 207 | if (pathlen > suffixlen && 208 | !strcmp(text->path + pathlen - suffixlen, 209 | kwmap[j].suffix)) { 210 | text->brackets = kwmap[j].brackets; 211 | if (!no_keywords) { 212 | text->keywords = &kwmap[j].keywords; 213 | text->comment_start = kwmap[j].comment_start; 214 | text->comment_end = kwmap[j].comment_end; 215 | text->string_end = kwmap[j].string_end; 216 | } 217 | return; 218 | } 219 | } 220 | } 221 | text->brackets = "()[]{}"; 222 | text->keywords = NULL; 223 | } 224 | 225 | Boolean_t is_keyword(struct view *view, position_t offset) 226 | { 227 | unsigned n; 228 | const char **tab; 229 | char *word; 230 | size_t bytes; 231 | 232 | if (!view->text->keywords) 233 | return FALSE; 234 | bytes = find_id_end(view, offset) - offset; 235 | if (view_raw(view, &word, offset, bytes) < bytes) 236 | return FALSE; 237 | for (n = view->text->keywords->count, 238 | tab = view->text->keywords->word; n; ) { 239 | unsigned mid = n / 2; 240 | int cmp = strncmp(word, tab[mid], bytes); 241 | if (!cmp) 242 | if (tab[mid][bytes]) 243 | cmp = -1; 244 | else 245 | return TRUE; 246 | if (cmp < 0) 247 | n = mid; 248 | else { 249 | n -= mid + 1; 250 | tab += mid + 1; 251 | } 252 | } 253 | return FALSE; 254 | } 255 | -------------------------------------------------------------------------------- /locus.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | /* 5 | * A locus is a position in a text. Insertions and 6 | * deletions in the text prior to a locus cause 7 | * automatic adjustments to the byte offset of a locus. 8 | */ 9 | 10 | #define DELETED (UNSET-1) 11 | 12 | locus_t locus_create(struct view *view, position_t offset) 13 | { 14 | locus_t locus; 15 | 16 | for (locus = 0; locus < view->loci; locus++) 17 | if (view->locus[locus] == DELETED) 18 | break; 19 | if (locus == view->loci) { 20 | view->locus = reallocate(view->locus, 21 | (locus+1) * sizeof *view->locus); 22 | view->loci++; 23 | } 24 | locus_set(view, locus, offset); 25 | return locus; 26 | } 27 | 28 | void locus_destroy(struct view *view, locus_t locus) 29 | { 30 | if (locus < view->loci) 31 | view->locus[locus] = DELETED; 32 | } 33 | 34 | position_t locus_get(struct view *view, locus_t locus) 35 | { 36 | position_t offset; 37 | 38 | if (!view || locus >= view->loci) 39 | return UNSET; 40 | offset = view->locus[locus]; 41 | if (offset == UNSET) 42 | return UNSET; 43 | if ((int) offset < 0) 44 | offset = 0; 45 | else if (offset > view->bytes) 46 | offset = view->bytes; 47 | return offset; 48 | } 49 | 50 | position_t locus_set(struct view *view, locus_t locus, position_t offset) 51 | { 52 | if (offset != UNSET && offset > view->bytes) 53 | offset = view->bytes; 54 | if (locus < view->loci) 55 | view->locus[locus] = offset; 56 | return offset; 57 | } 58 | 59 | void loci_adjust(struct view *view, position_t offset, int delta) 60 | { 61 | int j; 62 | 63 | if (delta < 0) { 64 | position_t limit = offset - delta; 65 | for (j = 0; j < view->loci; j++) { 66 | position_t locus = view->locus[j]; 67 | if (locus == DELETED || locus == UNSET) 68 | continue; 69 | if (limit <= locus) 70 | locus += delta; 71 | else if (offset < locus) 72 | locus = j == CURSOR ? offset : UNSET; 73 | view->locus[j] = locus; 74 | } 75 | } else 76 | for (j = 0; j < view->loci; j++) { 77 | position_t locus = view->locus[j]; 78 | if (locus == DELETED || locus == UNSET) 79 | continue; 80 | if (offset <= locus) 81 | view->locus[j] = locus + delta; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /locus.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef LOCUS_H 3 | #define LOCUS_H 4 | 5 | /* A locus is a fixed point in a view, an offset that get adjusted 6 | * when insertions and deletions occur before its position. 7 | */ 8 | 9 | typedef unsigned locus_t; 10 | 11 | /* loci in all views */ 12 | #define CURSOR 0 13 | #define MARK 1 14 | #define NO_LOCUS (~0u) 15 | #define DEFAULT_LOCI (MARK+1) 16 | 17 | #define UNSET (~0) 18 | 19 | struct view; 20 | 21 | locus_t locus_create(struct view *, position_t); 22 | void locus_destroy(struct view *, locus_t); 23 | position_t locus_get(struct view *, locus_t); 24 | position_t locus_set(struct view *, locus_t, position_t); 25 | void loci_adjust(struct view *, position_t, int delta); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /macro.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | struct macro { 5 | position_t start, at; 6 | size_t bytes; 7 | int repeat; 8 | struct macro *next, *suspended; 9 | }; 10 | 11 | static struct buffer *macbuf; 12 | static struct macro *macros; 13 | static struct macro *recording, *playing; 14 | 15 | struct macro *macro_record(void) 16 | { 17 | struct macro *new; 18 | if (!macbuf) 19 | macbuf = buffer_create(NULL); 20 | new = allocate0(sizeof *new); 21 | new->next = macros; 22 | new->start = buffer_bytes(macbuf); 23 | return macros = recording = new; 24 | } 25 | 26 | Boolean_t macro_end_recording(Unicode_t chop) 27 | { 28 | char *raw; 29 | size_t n; 30 | 31 | if (!recording) 32 | return FALSE; 33 | n = buffer_raw(macbuf, &raw, recording->start, 34 | recording->bytes); 35 | while (n) { 36 | size_t lastlen = utf8_length_backwards(raw+n-1, n); 37 | n -= lastlen; 38 | if (utf8_unicode(raw+n, lastlen) == chop) 39 | break; 40 | } 41 | buffer_delete(macbuf, recording->start + n, recording->bytes - n); 42 | recording->bytes = n; 43 | recording->at = recording->bytes; /* not playing */ 44 | recording = NULL; 45 | return TRUE; 46 | } 47 | 48 | static Boolean_t macro_is_playing(struct macro *macro) 49 | { 50 | return macro && macro->at < macro->bytes; 51 | } 52 | 53 | Boolean_t macro_play(struct macro *macro, int repeat) 54 | { 55 | if (!macro || 56 | macro_is_playing(macro) || 57 | !macro->bytes) 58 | return FALSE; 59 | macro->suspended = playing; 60 | macro->at = 0; 61 | macro->repeat = repeat; 62 | playing = macro; 63 | return TRUE; 64 | } 65 | 66 | void macros_abort(void) 67 | { 68 | for (; playing; playing = playing->suspended) 69 | playing->at = playing->bytes; 70 | } 71 | 72 | void macro_free(struct macro *macro) 73 | { 74 | struct macro *previous = NULL, *mac, *next; 75 | if (!macro) 76 | return; 77 | if (recording) 78 | recording = NULL; 79 | else if (macro_is_playing(macro)) 80 | macros_abort(); 81 | for (mac = macros; mac != macro; previous = mac, mac = next) { 82 | next = mac->next; 83 | if (mac == macro) 84 | if (previous) 85 | previous->next = next; 86 | else 87 | macros = next; 88 | else if (mac->start > macro->start) 89 | mac->start -= macro->bytes; 90 | } 91 | buffer_delete(macbuf, macro->start, macro->bytes); 92 | RELEASE(macro); 93 | } 94 | 95 | Unicode_t macro_getch(void) 96 | { 97 | Unicode_t ch; 98 | 99 | if (playing) { 100 | char *p; 101 | size_t n = buffer_raw(macbuf, &p, playing->start + playing->at, 102 | playing->bytes - playing->at); 103 | ch = utf8_unicode(p, n = utf8_length(p, n)); 104 | if ((playing->at += n) == playing->bytes) 105 | if (playing->repeat-- > 1) 106 | playing->at = 0; 107 | else 108 | playing = playing->suspended; 109 | } else { 110 | ch = window_getch(); 111 | if (!IS_ERROR_CODE(ch) && recording) { 112 | char buf[8]; 113 | size_t n = unicode_utf8(buf, ch); 114 | buffer_insert(macbuf, buf, 115 | recording->start + recording->bytes, 116 | n); 117 | recording->bytes += n; 118 | } 119 | } 120 | return ch; 121 | } 122 | -------------------------------------------------------------------------------- /macro.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef MACRO_H 3 | #define MACRO_H 4 | 5 | struct macro *macro_record(void); 6 | Boolean_t macro_end_recording(Unicode_t chop); 7 | Boolean_t macro_play(struct macro *, int repeat); 8 | void macros_abort(void); 9 | void macro_free(struct macro *); 10 | Unicode_t macro_getch(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | static void sighandler(int signo) 5 | { 6 | if (signo == SIGCHLD) { 7 | while (waitpid(-1, NULL, WNOHANG) > 0) 8 | ; 9 | } else { 10 | errno = 0; 11 | die("fatal signal %d", signo); 12 | } 13 | } 14 | 15 | static void signals(void) 16 | { 17 | static int sig[] = { SIGHUP, SIGINT, SIGQUIT, 18 | SIGTERM, SIGCHLD, 19 | #ifndef DEBUG 20 | SIGFPE, SIGSEGV, 21 | #endif 22 | #ifdef SIGPWR 23 | SIGPWR, 24 | #endif 25 | 0 }; 26 | static int sigig[] = { 27 | #ifdef SIGTTIN 28 | SIGTTIN, 29 | #endif 30 | #ifdef SIGTTOUT 31 | SIGTTOUT, 32 | #endif 33 | #ifdef SIGPIPE 34 | SIGPIPE, 35 | #endif 36 | 0 }; 37 | int j; 38 | 39 | for (j = 0; sig[j]; j++) 40 | signal(sig[j], sighandler); 41 | for (j = 0; sigig[j]; j++) 42 | signal(sigig[j], SIG_IGN); 43 | } 44 | 45 | static void save_all(void) 46 | { 47 | struct text *text; 48 | Boolean_t msg = FALSE; 49 | char *raw; 50 | 51 | for (text = text_list; text; text = text->next) { 52 | if (!text->path || !text->buffer || !text->buffer->path) 53 | continue; 54 | text_unfold_all(text); 55 | if (text->clean && 56 | buffer_raw(text->buffer, &raw, 0, ~(size_t)0) == 57 | text->clean_bytes && 58 | !memcmp(text->clean, raw, text->clean_bytes)) { 59 | unlink(text->buffer->path); 60 | continue; 61 | } 62 | if (!msg) { 63 | fprintf(stderr, "\ncheck working files for " 64 | "current unsaved data\n"); 65 | msg = TRUE; 66 | } 67 | fprintf(stderr, "\t%s\n", text->buffer->path); 68 | buffer_snap(text->buffer); 69 | } 70 | } 71 | 72 | int main(int argc, char *const *argv) 73 | { 74 | int ch, value; 75 | struct view *view; 76 | Unicode_t unicode; 77 | 78 | errno = 0; 79 | if (tcgetattr(1, &original_termios)) 80 | die("not running in a terminal"); 81 | signals(); 82 | atexit(save_all); 83 | 84 | is_asdfg = argc && argv[0] && strstr(argv[0], "asdfg"); 85 | if (!make_writable) 86 | make_writable = getenv("AOEUI_WRITABLE"); 87 | 88 | while ((ch = getopt(argc, argv, "dkoqrsSt:uUw:")) >= 0) 89 | switch (ch) { 90 | case 'd': 91 | is_asdfg = FALSE; 92 | break; 93 | case 'k': 94 | no_keywords = TRUE; 95 | break; 96 | case 'o': 97 | no_save_originals = TRUE; 98 | break; 99 | case 'q': 100 | is_asdfg = TRUE; 101 | break; 102 | case 'r': 103 | read_only = TRUE; 104 | break; 105 | case 's': 106 | default_no_tabs = TRUE; 107 | break; 108 | case 'S': 109 | default_tabs = TRUE; 110 | break; 111 | case 't': 112 | value = atoi(optarg); 113 | if (value >= 1 && value <= 20) 114 | default_tab_stop = value; 115 | else 116 | message("bad tab stop setting: %s", optarg); 117 | break; 118 | case 'u': 119 | utf8_mode = UTF8_YES; 120 | break; 121 | case 'U': 122 | utf8_mode = UTF8_NO; 123 | break; 124 | case 'w': 125 | make_writable = optarg; 126 | break; 127 | default: 128 | die("unknown flag"); 129 | } 130 | 131 | for (; optind < argc; optind++) 132 | view_open(argv[optind]); 133 | 134 | /* Main loop */ 135 | while ((view = window_current_view()) && 136 | ((unicode = macro_getch()), 137 | !IS_ERROR_CODE(unicode))) 138 | view->mode->command(view, unicode); 139 | 140 | die("error in input"); 141 | return EXIT_FAILURE; 142 | } 143 | -------------------------------------------------------------------------------- /make-TAGS: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ ".$1" = . ] 3 | then set . 4 | fi 5 | for path 6 | do find -L $path -type f | egrep '\.(h|c|cc|C)$' 7 | done | xargs ctags -x \ 8 | | tee TAGS.tmp1 \ 9 | | grep '::' \ 10 | | sed 's/[^:]*:://' >TAGS.tmp2 11 | sort TAGS.tmp[12] >TAGS 12 | rm TAGS.tmp[12] 13 | -------------------------------------------------------------------------------- /mem.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include 3 | #include 4 | #include "die.h" 5 | #include "mem.h" 6 | 7 | /* Error-checking wrappers for memory management */ 8 | 9 | void *reallocate(const void *old, size_t bytes) 10 | { 11 | void *new = realloc((void *) old, bytes); 12 | if (!new && bytes) 13 | die("could not allocate %lu bytes", (long) bytes); 14 | return new; 15 | } 16 | 17 | void *allocate0(size_t bytes) 18 | { 19 | void *new = allocate(bytes); 20 | if (new) 21 | memset(new, 0, bytes); 22 | return new; 23 | } 24 | -------------------------------------------------------------------------------- /mem.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef MEM_H 3 | #define MEM_H 4 | 5 | void *reallocate(const void *, size_t); 6 | #define allocate(sz) (reallocate(NULL, (sz))) 7 | void *allocate0(size_t); 8 | #define RELEASE(p) (reallocate((p), 0), (p) = NULL) 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /mode.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | /* 5 | * This is the default command mode. 6 | */ 7 | 8 | Boolean_t is_asdfg; 9 | 10 | static struct macro *default_macro, *function_key[FUNCTION_FKEYS+1]; 11 | 12 | struct mode_default { 13 | command command; 14 | rgba_t selection_bgrgba; 15 | Boolean_t variant, is_hex; 16 | int value; 17 | }; 18 | 19 | static void command_handler(struct view *, Unicode_t); 20 | 21 | 22 | static position_t cut(struct view *view, Boolean_t delete) 23 | { 24 | struct mode_default *mode = (struct mode_default *) view->mode; 25 | position_t offset; 26 | Boolean_t append; 27 | size_t bytes = view_get_selection(view, &offset, &append); 28 | int copies = mode->value ? mode->value : 1; 29 | 30 | if (!mode->variant || mode->value) 31 | clip_init(0); 32 | while (copies--) 33 | clip(0, view, offset, bytes, append); 34 | if (delete) 35 | view_delete(view, offset, bytes); 36 | locus_set(view, MARK, UNSET); 37 | return offset; 38 | } 39 | 40 | static void paste(struct view *view) 41 | { 42 | struct mode_default *mode = (struct mode_default *) view->mode; 43 | position_t cursor = locus_get(view, CURSOR); 44 | clip_paste(view, cursor, mode->value); 45 | locus_set(view, MARK, /*old*/ cursor); 46 | } 47 | 48 | static void forward_lines(struct view *view) 49 | { 50 | struct mode_default *mode = (struct mode_default *) view->mode; 51 | unsigned count = mode->variant ? mode->value : 1; 52 | position_t cursor = locus_get(view, CURSOR); 53 | 54 | if (!count) 55 | cursor = find_paragraph_end(view, cursor); 56 | else 57 | for (; count && cursor < view->bytes; count--) 58 | cursor = find_line_end(view, cursor+1); 59 | if (count) 60 | macros_abort(); 61 | locus_set(view, CURSOR, cursor); 62 | } 63 | 64 | static void down_lines(struct view *view) 65 | { 66 | struct mode_default *mode = (struct mode_default *) view->mode; 67 | unsigned count = mode->variant ? mode->value : 1; 68 | position_t cursor = locus_get(view, CURSOR); 69 | 70 | for (; count && cursor < view->bytes; count--) 71 | cursor = find_line_down(view, cursor); 72 | if (count) 73 | macros_abort(); 74 | locus_set(view, CURSOR, cursor); 75 | } 76 | 77 | static void backward_lines(struct view *view) 78 | { 79 | struct mode_default *mode = (struct mode_default *) view->mode; 80 | unsigned count = mode->variant ? mode->value : 1; 81 | position_t cursor = locus_get(view, CURSOR); 82 | 83 | if (!count) 84 | cursor = find_paragraph_start(view, cursor); 85 | else 86 | for (; count && cursor; count--) 87 | cursor = find_line_start(view, cursor-1); 88 | if (count) 89 | macros_abort(); 90 | locus_set(view, CURSOR, cursor); 91 | } 92 | 93 | static void up_lines(struct view *view) 94 | { 95 | struct mode_default *mode = (struct mode_default *) view->mode; 96 | unsigned count = mode->variant ? mode->value : 1; 97 | position_t cursor = locus_get(view, CURSOR); 98 | 99 | for (; count && cursor; count--) 100 | cursor = find_line_up(view, cursor); 101 | if (count && !cursor) 102 | macros_abort(); 103 | locus_set(view, CURSOR, cursor); 104 | } 105 | 106 | static void forward_chars(struct view *view) 107 | { 108 | struct mode_default *mode = (struct mode_default *) view->mode; 109 | unsigned count = mode->variant ? mode->value : 1; 110 | position_t cursor = locus_get(view, CURSOR), next; 111 | 112 | for (; count && IS_UNICODE(view_char(view, cursor, &next)); count--) 113 | cursor = next; 114 | if (count) 115 | macros_abort(); 116 | locus_set(view, CURSOR, cursor); 117 | } 118 | 119 | static void backward_chars(struct view *view) 120 | { 121 | struct mode_default *mode = (struct mode_default *) view->mode; 122 | unsigned count = mode->variant ? mode->value : 1; 123 | position_t cursor = locus_get(view, CURSOR); 124 | 125 | for (; count && cursor; count--) 126 | view_char_prior(view, cursor, &cursor); 127 | if (count) 128 | macros_abort(); 129 | locus_set(view, CURSOR, cursor); 130 | } 131 | 132 | static Boolean_t funckey(struct view *view, int Fk) 133 | { 134 | struct mode_default *mode = (struct mode_default *) view->mode; 135 | 136 | if (Fk > FUNCTION_FKEYS) 137 | return FALSE; 138 | if (mode->variant && !mode->value) { 139 | macro_end_recording(CONTROL('@')); 140 | macro_free(function_key[Fk]); 141 | function_key[Fk] = macro_record(); 142 | return TRUE; 143 | } 144 | return macro_play(function_key[Fk], mode->value); 145 | } 146 | 147 | static position_t self_insert(struct view *view, Unicode_t ch, 148 | position_t mark, position_t old_cursor) 149 | { 150 | char cbuf[16], *p = cbuf; 151 | position_t cursor = old_cursor; 152 | size_t len = 0; 153 | 154 | if (mark != UNSET && mark > cursor) { 155 | cursor = cut(view, 1); 156 | mark = UNSET; 157 | } 158 | if (ch == '\n' && view->text->flags & TEXT_CRNL) { 159 | memcpy(p, "\r\n", len = 2); 160 | } else if (view->text->flags & TEXT_NO_UTF8) { 161 | for (p = cbuf + sizeof cbuf; ch; ch >>= 8) 162 | *--p = ch, len++; 163 | if (!len) 164 | *--p = 0, len++; 165 | } else { 166 | len = unicode_utf8(p, ch); 167 | } 168 | view_insert(view, p, cursor, len); 169 | if (mark == old_cursor) 170 | locus_set(view, MARK, old_cursor); 171 | if (view->shell_std_in >= 0) 172 | shell_command(view, ch); 173 | return cursor + len; 174 | } 175 | 176 | static void command_handler(struct view *view, Unicode_t ch0) 177 | { 178 | struct mode_default *mode = (struct mode_default *) view->mode; 179 | Unicode_t ch = ch0; 180 | position_t cursor = locus_get(view, CURSOR); 181 | position_t mark = locus_get(view, MARK); 182 | position_t offset; 183 | Boolean_t ok = TRUE; 184 | struct view *new_view; 185 | char *select; 186 | 187 | status_hide(); 188 | 189 | /* Backspace always deletes the character before cursor. */ 190 | if (ch == 0x7f /*BCK*/) { 191 | delete: if (IS_UNICODE(view_char_prior(view, cursor, &mark))) 192 | view_delete(view, mark, cursor-mark); 193 | else 194 | window_beep(view); 195 | goto done; 196 | } 197 | 198 | /* Decode function-key sequences */ 199 | if (IS_FUNCTION_KEY(ch)) { 200 | 201 | /* Forget the up/down goal column if not moving up/down */ 202 | if (ch != FUNCTION_UP && ch != FUNCTION_DOWN) 203 | view->goal.cursor = UNSET; 204 | 205 | switch (ch) { 206 | case FUNCTION_DOWN: 207 | down_lines(view); 208 | break; 209 | case FUNCTION_UP: 210 | up_lines(view); 211 | break; 212 | case FUNCTION_LEFT: 213 | backward_chars(view); 214 | break; 215 | case FUNCTION_RIGHT: 216 | forward_chars(view); 217 | break; 218 | case FUNCTION_PGUP: 219 | window_page_up(view); 220 | break; 221 | case FUNCTION_PGDOWN: 222 | window_page_down(view); 223 | break; 224 | case FUNCTION_HOME: 225 | locus_set(view, CURSOR, 0); 226 | break; 227 | case FUNCTION_END: 228 | locus_set(view, CURSOR, view->bytes); 229 | break; 230 | case FUNCTION_INSERT: 231 | paste(view); 232 | break; 233 | case FUNCTION_DELETE: 234 | goto delete; 235 | default: 236 | if (ch < FUNCTION_F(1) || 237 | !funckey(view, ch - FUNCTION_F(1) + 1)) 238 | ok = FALSE; 239 | } 240 | goto done; 241 | } 242 | 243 | 244 | /* 245 | * Non-control characters are self-inserted, with a prior 246 | * automatic cut of the selection if one exists and the 247 | * cursor is at its beginning. But if we're in a variant, 248 | * some characters may contribute to the value, or be 249 | * non-Control commands. 250 | */ 251 | 252 | if (ch >= ' ' /*0x20*/) { 253 | 254 | view->goal.cursor = UNSET; 255 | 256 | if (mode->variant) { 257 | if (mode->is_hex && isxdigit(ch)) { 258 | mode->value *= 16; 259 | if (isdigit(ch)) 260 | mode->value += ch - '0'; 261 | else 262 | mode->value += tolower(ch) - 'a' + 10; 263 | return; 264 | } 265 | if (isdigit(ch)) { 266 | mode->value *= 10; 267 | mode->value += ch - '0'; 268 | return; 269 | } 270 | if (!mode->value && (ch == 'x' || ch == 'X')) { 271 | mode->is_hex = 1; 272 | return; 273 | } 274 | switch (ch) { 275 | case '=': 276 | bookmark_set(mode->value, view, cursor, mark); 277 | goto done; 278 | case '-': 279 | if (bookmark_get(&new_view, &cursor, &mark, 280 | mode->value)) { 281 | locus_set(new_view, CURSOR, cursor); 282 | if (mark != UNSET) 283 | locus_set(new_view, MARK, mark); 284 | window_activate(new_view); 285 | } else 286 | ok = FALSE; 287 | goto done; 288 | case ';': 289 | window_after(view, text_new(), -1); 290 | goto done; 291 | case '\'': 292 | find_tag(view); 293 | goto done; 294 | case ',': 295 | if (mark == UNSET) 296 | view_fold_indented(view, mode->value); 297 | else { 298 | view_fold(view, cursor, mark); 299 | locus_set(view, MARK, UNSET); 300 | } 301 | goto done; 302 | case '.': 303 | if (mode->value) 304 | view_unfold_all(view); 305 | else if (mark != UNSET) 306 | view_unfold_selection(view); 307 | else { 308 | mark = view_unfold(view, cursor); 309 | if ((signed) mark < 0) 310 | view_unfold_all(view); 311 | else 312 | locus_set(view, MARK, mark); 313 | } 314 | goto done; 315 | case '#': 316 | status("%s line %d", view->text->path, 317 | current_line_number(view, cursor)); 318 | goto done; 319 | case '?': 320 | window_after(view, view_help(), -1 /*auto*/); 321 | goto done; 322 | } 323 | } 324 | 325 | self_insert(view, ch, mark, cursor); 326 | goto done; 327 | } 328 | 329 | /* 330 | * Control character commands 331 | */ 332 | 333 | ch += '@'; 334 | if (is_asdfg && ch >= 'A' && ch <= 'Z') { 335 | static char asdfg_to_aoeui[26] = { 336 | 'A', 'O', 'F', 'Y', 'X', 'W', 'H', 'T', 337 | 'I', 'J', 'N', 'S', 'M', 'Z', 'R', 'L', 338 | 'Q', 'E', 'P', 'G', 'V', 'B', 'K', 'D', 339 | 'C', 'U' 340 | }; 341 | ch = asdfg_to_aoeui[ch-'A']; 342 | } 343 | 344 | if (ch != 'G' && ch != 'C' && 345 | (ch != 'H' && ch != 'T' || 346 | !mode->variant || mode->value)) 347 | view->goal.cursor = UNSET; 348 | 349 | switch (ch) { 350 | case '@': /* (^Space) */ 351 | if (mode->variant) 352 | break; /* unset variant */ 353 | mode->variant = 1; 354 | return; 355 | case 'A': /* synonym */ 356 | case '_': /* ^/, ^_: search */ 357 | mode_search(view, mode->variant); 358 | break; 359 | case 'B': /* exchange clip buffer and selection, if any, else paste */ 360 | if (mark != UNSET) { 361 | size_t outbytes = view_get_selection(view, &offset, NULL); 362 | unsigned reg = mode->value; 363 | size_t inbytes = clip_paste(view, offset + outbytes, reg); 364 | clip_init(reg); 365 | clip(reg, view, offset, outbytes, 0); 366 | view_delete(view, offset, outbytes); 367 | locus_set(view, CURSOR, offset); 368 | locus_set(view, MARK, offset + inbytes); 369 | } else 370 | paste(view); 371 | break; 372 | case 'C': 373 | forward_lines(view); 374 | break; 375 | case 'D': /* [select whitespace] / cut [pre/appending] */ 376 | if (mark == UNSET && mode->variant) { 377 | locus_set(view, MARK, find_nonspace(view, cursor)); 378 | locus_set(view, CURSOR, 379 | find_nonspace_prior(view, cursor)); 380 | } else 381 | cut(view, TRUE); 382 | break; 383 | case 'E': 384 | if (mark != UNSET) 385 | mode_child(view); 386 | else if (mode->variant) 387 | demultiplex_view(view); 388 | else { 389 | new_view = text_new(); 390 | window_after(view, new_view, -1); 391 | mode_shell_pipe(new_view); 392 | } 393 | break; 394 | case 'F': /* copy [pre/appending] */ 395 | cut(view, FALSE); 396 | break; 397 | case 'G': 398 | backward_lines(view); 399 | break; 400 | case 'H': 401 | if (mode->variant && !mode->value) { 402 | mode->variant = FALSE; 403 | up_lines(view); 404 | } else 405 | backward_chars(view); 406 | break; 407 | case 'I': /* (TAB) tab / tab completion [align; set tab stop] */ 408 | if (!mode->variant) { 409 | if (!tab_completion_command(view)) 410 | insert_tab(view); 411 | } else if (mode->value) 412 | if (mode->value == 1) 413 | view->text->flags ^= TEXT_NO_TABS; 414 | else if (mode->value > 1 && mode->value <= 20) 415 | view->text->tabstop = 416 | default_tab_stop = mode->value; 417 | else 418 | window_beep(view); 419 | else 420 | align(view); 421 | break; 422 | case 'J': /* line feed: new line */ 423 | insert_newline(view); 424 | break; 425 | case 'M': /* (ENTER) new line with alignment */ 426 | self_insert(view, '\n', mark, cursor); 427 | break; 428 | case 'K': /* save all [single] */ 429 | if (mode->variant) 430 | text_preserve(view->text); 431 | else 432 | texts_preserve(); 433 | break; 434 | case 'L': /* forward screen [end of view] */ 435 | if (mode->variant) 436 | locus_set(view, CURSOR, view->bytes); 437 | else 438 | window_page_down(view); 439 | break; 440 | case 'N': /* backward word(s) [sentence] */ 441 | if (mode->value) 442 | while (mode->value-- && cursor) 443 | cursor = find_word_start(view, cursor); 444 | else if (mode->variant) 445 | cursor = find_sentence_start(view, cursor); 446 | else 447 | cursor = find_word_start(view, cursor); 448 | locus_set(view, CURSOR, cursor); 449 | break; 450 | case 'O': /* macro end/execute [start] */ 451 | if (mode->variant && !mode->value) { 452 | macro_end_recording(CONTROL('@')); 453 | macro_free(default_macro); 454 | default_macro = macro_record(); 455 | } else if (!macro_end_recording(ch0) && 456 | !macro_play(default_macro, mode->value)) 457 | window_beep(view); 458 | break; 459 | case 'P': /* select other window [closing current] */ 460 | if (!mode->variant) 461 | window_next(view); 462 | else if (mode->value) 463 | window_index(mode->value); 464 | else 465 | window_destroy(view->window); 466 | break; 467 | case 'Q': /* suspend [quit] */ 468 | if (mode->variant) { 469 | windows_end(); 470 | texts_preserve(); 471 | while (text_list) 472 | view_close(text_list->views); 473 | exit(EXIT_SUCCESS); 474 | } 475 | windows_end_display(); 476 | kill(getpid(), SIGSTOP); 477 | window_recenter(view); 478 | break; 479 | case 'R': /* backward screen [beginning of view] */ 480 | if (mode->variant) 481 | locus_set(view, CURSOR, 0); 482 | else 483 | window_page_up(view); 484 | break; 485 | case 'S': /* forward word(s) [sentence] */ 486 | if (mode->value) 487 | while (mode->value--) 488 | cursor = find_word_end(view, cursor); 489 | else if (mode->variant) 490 | cursor = find_sentence_end(view, cursor); 491 | else 492 | cursor = find_word_end(view, cursor); 493 | locus_set(view, CURSOR, cursor); 494 | break; 495 | case 'T': 496 | if (mode->variant && !mode->value) { 497 | mode->variant = FALSE; 498 | down_lines(view); 499 | } else 500 | forward_chars(view); 501 | break; 502 | case 'U': /* undo [redo] */ 503 | offset = (mode->variant ? text_redo : text_undo)(view->text); 504 | if ((offset -= view->start) <= view->bytes) 505 | locus_set(view, CURSOR, offset); 506 | locus_set(view, MARK, UNSET); 507 | break; 508 | case 'V': /* set/unset mark [exchange, or select line; force unset] */ 509 | if (!mode->variant) 510 | locus_set(view, MARK, mark == UNSET ? cursor : UNSET); 511 | else if (mode->value) 512 | locus_set(view, MARK, UNSET); 513 | else if (mark == UNSET) { 514 | locus_set(view, MARK, find_line_end(view, cursor) + 1); 515 | locus_set(view, CURSOR, find_line_start(view, cursor)); 516 | } else { 517 | locus_set(view, MARK, cursor); 518 | locus_set(view, CURSOR, mark); 519 | } 520 | break; 521 | case 'W': /* select other view [closing current] */ 522 | if (mode->variant) 523 | view_close(view); 524 | else if (!window_replace(view, view_next(view))) 525 | window_beep(view); 526 | break; 527 | case 'X': /* get path / visit file [set path] */ 528 | if (mark == UNSET) { 529 | view_insert(view, view->text->path, cursor, -1); 530 | locus_set(view, MARK, /*old*/ cursor); 531 | } else if ((select = view_extract_selection(view))) { 532 | if (mode->variant) { 533 | if ((ok = text_rename(view->text, select))) 534 | window_activate(view); 535 | new_view = NULL; 536 | } else 537 | ok = !!(new_view = view_open(select)); 538 | RELEASE(select); 539 | if (ok) 540 | view_delete_selection(view); 541 | if (new_view) 542 | window_after(view, new_view, -1 /*auto*/); 543 | } 544 | break; 545 | case 'Y': /* split window [vertically] */ 546 | if (mark != UNSET) { 547 | position_t offset = mark < cursor ? mark : cursor; 548 | size_t bytes = mark < cursor ? cursor - mark : 549 | mark - cursor; 550 | new_view = view_selection(view, offset, bytes); 551 | } else 552 | new_view = view_next(view); 553 | if (new_view) 554 | window_after(view, new_view, mode->variant); 555 | else 556 | window_beep(view); 557 | break; 558 | case 'Z': /* recenter/goto */ 559 | if (mode->value) 560 | locus_set(view, CURSOR, 561 | find_line_number(view, mode->value)); 562 | else if (mode->variant) 563 | window_raise(view); 564 | window_recenter(view); 565 | break; 566 | case '\\': /* quit */ 567 | if (mode->variant) { 568 | windows_end(); 569 | texts_uncreate(); 570 | exit(EXIT_SUCCESS); 571 | } 572 | break; 573 | case ']': /* move to corresponding bracket */ 574 | cursor = find_corresponding_bracket(view, cursor); 575 | if ((signed) cursor < 0) 576 | window_beep(view); 577 | else 578 | locus_set(view, CURSOR, cursor); 579 | break; 580 | case '^': /* literal [; unicode] */ 581 | if (mode->value) 582 | self_insert(view, mode->value, mark, cursor); 583 | else if (IS_UNICODE(ch = macro_getch())) { 584 | if (ch >= '@' && ch <= '_') 585 | ch = CONTROL(ch); 586 | else if (ch >= 'a' && ch <= 'z') 587 | ch = CONTROL(ch-'a'+'A'); 588 | else if (ch == '?') 589 | ch = 0x7f; 590 | self_insert(view, ch, mark, cursor); 591 | } else 592 | ok = FALSE; 593 | break; 594 | default: 595 | ok = FALSE; 596 | break; 597 | } 598 | 599 | done: mode->variant = mode->is_hex = FALSE; 600 | mode->value = 0; 601 | if (!ok) 602 | window_beep(view); 603 | } 604 | 605 | struct mode *mode_default(void) 606 | { 607 | struct mode_default *dft = allocate0(sizeof *dft); 608 | dft->command = command_handler; 609 | dft->selection_bgrgba = SELECTION_BGRGBA; 610 | return (struct mode *) dft; 611 | } 612 | -------------------------------------------------------------------------------- /mode.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef MODE_H 3 | #define MODE_H 4 | 5 | struct view; 6 | typedef void (*command)(struct view *, Unicode_t); 7 | 8 | /* All modes start with this header. */ 9 | struct mode { 10 | command command; 11 | rgba_t selection_bgrgba; 12 | }; 13 | 14 | extern Boolean_t is_asdfg; 15 | struct mode *mode_default(void); 16 | void mode_search(struct view *, Boolean_t regex); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | The Dvorak Simplified Keyboard layout 2 | 3 | ESC FFFFF FFFFF DEL 4 | ` 12345 67890 [] BCK 5 | TAB ',.py fgcrl /= \ 6 | CTL AoeuI dhtns - ENT 7 | SHF ;qJkx bMwvz SHF 8 | LCK ALT SPC ALT CTL arrows 9 | 10 | Commands are denoted here in these notes by ^, which signifies use 11 | of Control, Alt, or a leading Escape before a key. (Some Control 12 | keys don't work the same in a Linux console; Escape always works. 13 | Control-[ is the same as Escape.) 14 | 15 | Non-command characters are inserted or searched for. 16 | Self-insertion automatically cuts when cursor < mark. 17 | 18 | There are multiple windows, views, and texts. Every window has a view, 19 | but not every view has a window. Every view has a text and every text 20 | has at least one view. Some texts have files. The cursor, mark, and 21 | macro are local to their view. 22 | 23 | * below means "must be this character". 24 | () indicate synonyms 25 | [;] indicate variants activated by ^Space, possibly with value 26 | / indicates behavior with mark unset / set 27 | 28 | Command characters 29 | * ESC function keys and query responses, ALT 30 | * Fk global macro execute [start; repeat] 31 | ^@ (^Space) 32 | ^^ literal, control [; unicode] 33 | * ^[ (ESC or "smaller font") 34 | ^] move to corresponding or nearest bracket [AVAILABLE] 35 | * TAB tab / tab completion [align; set tab stop] 36 | ^P select another window [closing current; by index] 37 | ^Y split window [vertically] / narrow to selection 38 | ^F AVAILABLE / copy [pre/append; replicate] 39 | ^G backward to line start [paragraph start; multiple lines] 40 | ^C forward to line end [paragraph end; multiple lines] 41 | ^R backward screen(s) [beginning of view] 42 | ^L forward screen(s) [end of view] 43 | ^/ (^_) 44 | * ^? (BCK) delete character before cursor 45 | * ^+ -- larger font 46 | ^\ [quit without saving] 47 | * ^A -- reserved by screen(1), synonym for ^/(^_) 48 | ^O macro end, macro execute [macro start; repeat] 49 | ^E shell [end children] / pipe clipbuffer to command 50 | ^U undo [redo] 51 | * ^I (TAB) 52 | ^D cut char [select whitespace] / cut [pre/append; replicate] 53 | ^H backward char(s) [up line; multiple chars] 54 | ^T forward char(s) [down line; multiple chars] 55 | ^N backward word(s) [sentence; multiple words] 56 | ^S forward word(s) [sentence; multiple words] 57 | * ^- -- smaller font in some WMs, otherwise (^_) 58 | * ^_ (^/) incremental search mode [regexp] 59 | * ENT (^M) newline 60 | ^ENT (^J) 61 | ^Q suspend editor [quit] 62 | * ^J (^ENT) newline with automatic alignment 63 | ^K save all [single] 64 | ^X get path / visit file [set path] 65 | ^B paste / exchange with clip buffer [; register] 66 | * ^M (ENT) 67 | ^W select other view [closing current] 68 | ^V set/unset mark [select line / exchange mark with cursor; force unset] 69 | ^Z recenter view [single window, reset display; go to line] 70 | ^SP (^@) variant, beginning of value 71 | 72 | Non-control characters -- commands must be [variants] 73 | = [; set bookmark] 74 | - [; go to bookmark] 75 | ; [new anonymous text] 76 | ' [go to tag] 77 | , [fold view on indentation] / [fold selection] 78 | . [unfold; unfold entire view] / [unfold selection once] 79 | # [get current position] 80 | ? [help] 81 | 0-9 decimal argument 82 | x 0-9 a-f A-F hexadecimal argument 83 | unused: ` ~ ! @ $ % & ( ) { } " < > | : 84 | 85 | Missing features: 86 | multiple column characters (using wcwidth) 87 | preserving window layouts 88 | moving to prior window 89 | saving, restoring, rewriting F-key definitions 90 | 91 | Idioms 92 | - search and replace: cut replacement text, search for first occurrence, 93 | then repeat ^B^F^/^/ to exchange target with replacement, restore 94 | replacement text in the clip buffer, and proceed to the next hit 95 | - also, can pipe text through sed 's/X/Y/g' for global replacement 96 | - also, can use a macro 97 | - exchanging cursor and mark and then retyping with automatic cut 98 | is often faster than an explicit cut. 99 | - going forward or back a half screenful: use ^R/^L, then ^Z 100 | - inserting with a repeat count: cut with repeat, then paste 101 | 102 | 103 | QWERTY->Dvorak command mapping 104 | 105 | qwert yuIop 106 | Asdfg hJkl; 107 | zxcvb nM,./ 108 | 109 | navigation: G:t C:y R:o L:p 110 | H:g T:h N:k S:l 111 | Z:n 112 | selection: V:u F:c D:x B:v 113 | windows: P:s Y:d W:f 114 | files: Q:q K:w X:e 115 | others: O:b E:r U:z 116 | 117 | 118 | Unicode tips: 119 | 0x2000-206f general punctuation 120 | 0x2100-22ff math 121 | 0x2300-23ff misc. technical 122 | 0x2400-24ff printable control chars, boxed numbers and letters 123 | 0x2500-25ff block graphics 124 | 0x2600-26b2 misc. symbols 125 | 0x2700-27ff dingbats 126 | 127 | Compose key sequences for Latin-1: 128 | 129 | 0xa1 (¡) !! 0xa2 (¢) c/ 0xa3 (£) l- 130 | 0xa4 (¤) ox 0xa5 (¥) Y= 0xa6 (¦) 0xa7 (§) so 131 | 0xa8 (¨) "" 0xa9 (©) OC 0xaa (ª) a_ 0xab («) << 132 | 0xac (¬) ,- 0xad (­) --- 0xae (®) OR 0xaf (¯) ^- 133 | 0xb0 (°) 0xb1 (±) +- 0xb2 (²) ^2 0xb3 (³) ^3 134 | 0xb4 (´) '' 0xb5 (µ) u/ 0xb6 (¶) p! 0xb7 (·) ^. 135 | 0xb8 (¸) ,, 0xb9 (¹) ^1 0xba (º) o_ 0xbb (») >> 136 | 0xbc (¼) 0xbd (½) 0xbe (¾) 0xbf (¿) ?? 137 | 0xc6 (Æ) AE 138 | 0xd0 (Ð) D- 139 | 0xd7 (×) xx 140 | 0xde (Þ) TH 0xdf (ß) ss 141 | 0xe6 (æ) ae 142 | 0xf0 (ð) 143 | 0xf7 (÷) :- 144 | 0xfe (þ) th 145 | (€) C= 146 | 147 | 0x203b ※ 0x2230 ∰ 0x237e ⍾ 0x2615 ☕ 0x2620 ☠ 0x2767 ❧ 148 | 149 | 150 | ---- no mark ---- ------ mark ------ 151 | raw ^Space arg raw ^Space arg 152 | Q suspend quit 153 | U Z undo redo 154 | K W save save 1 155 | \ AVAIL abort 156 | X E (get path) visit (set path) 157 | 158 | H G <-ch up <-chs 159 | T H ch-> down chs-> 160 | N K <-wd <-sent <-wds 161 | S L wd-> sent-> wds-> 162 | G T <-ln <-pp <-lns 163 | C Y ln-> pp-> lns-> 164 | R O <-pg home 165 | L P pg-> end 166 | ] [] 167 | Z N center reset ->line# 168 | _ search regexp 169 | 170 | J (auto-align new line) 171 | Tab (tab complete) Tab 172 | ^ (literal/ctl) unicode 173 | 174 | V U mark selline unmark unmark swap unmark 175 | F C AVAIL copy append repl 176 | D X cutch selwhite cut append repl 177 | B V paste exch register 178 | 179 | W F view close 180 | Y D split vsplit narrow 181 | P S window close ->window# 182 | 183 | O B macro macstart macros 184 | E R shell endchildren pipe 185 | = bookmk bookmk# 186 | - gotomk gotomk# 187 | ; (new anon text) 188 | ' (go to tag) 189 | , (fold view) (fold selection) 190 | . unfold unfoldall (unfold selection) 191 | # where 192 | ? help 193 | -------------------------------------------------------------------------------- /rgba.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 Peter Klausler. See COPYING for license. */ 2 | #ifndef RGBA_H 3 | #define RGBA_H 4 | 5 | typedef unsigned rgba_t; 6 | 7 | #define DEFAULT_FGRGBA 0xff 8 | #define DEFAULT_BGRGBA (~0) 9 | #define PALE_RGBA(rgba) ((rgba) & 0x7f7f7f00) 10 | 11 | #define RED_RGBA 0xff000000 12 | #define GREEN_RGBA 0x00ff0000 13 | #define BLUE_RGBA 0x0000ff00 14 | #define YELLOW_RGBA 0xffff0000 15 | #define MAGENTA_RGBA 0xff00ff00 16 | #define CYAN_RGBA 0x00ffff00 17 | #define WHITE_RGBA 0xffffff00 18 | #define BLACK_RGBA 0x00000000 19 | 20 | #define DEFAULT_CURSORRGBA GREEN_RGBA 21 | #define RDONLY_RGBA RED_RGBA 22 | #define DIRTY_RGBA MAGENTA_RGBA 23 | #define SELECTING_RGBA BLUE_RGBA 24 | 25 | #define SELECTION_FGRGBA RED_RGBA 26 | #define BRACKET_FGRGBA BLUE_RGBA 27 | #define COMMENT_FGRGBA MAGENTA_RGBA 28 | #define STRING_FGRGBA RED_RGBA 29 | #define KEYWORD_FGRGBA BLUE_RGBA 30 | #define FOLDED_FGRGBA WHITE_RGBA 31 | 32 | #define FOLDED_BGRGBA RED_RGBA 33 | #define SELECTION_BGRGBA CYAN_RGBA 34 | #define LAMESPACE_BGRGBA MAGENTA_RGBA 35 | #define BADCHAR_BGRGBA MAGENTA_RGBA 36 | #define SEARCH_BGRGBA YELLOW_RGBA 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /search.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | /* 5 | * Incremental search mode 6 | */ 7 | 8 | struct mode_search { 9 | command command; 10 | rgba_t selection_bgrgba; 11 | struct mode *previous; 12 | Byte_t *pattern; 13 | size_t bytes, alloc, last_bytes; 14 | Boolean_t backward; 15 | position_t start, mark; 16 | regex_t *regex; 17 | Boolean_t regex_ready; 18 | }; 19 | 20 | static Boolean_t match_char(Unicode_t x, Unicode_t y) 21 | { 22 | if (!IS_UNICODE(x) || !IS_UNICODE(y)) 23 | return FALSE; 24 | if (x >= 'a' && x <= 'z') 25 | x += 'A' - 'a'; 26 | if (y >= 'a' && y <= 'z') 27 | y += 'A' - 'a'; 28 | return x == y; 29 | } 30 | 31 | static size_t match_pattern(struct view *view, position_t offset) 32 | { 33 | struct mode_search *mode = (struct mode_search *) view->mode; 34 | size_t j; 35 | 36 | for (j = 0; j < mode->bytes; j++) 37 | if (!match_char(mode->pattern[j], 38 | view_byte(view, offset++))) 39 | return 0; 40 | return mode->bytes; 41 | } 42 | 43 | static int match_regex(struct view *view, position_t *offset, Boolean_t advance) 44 | { 45 | int j, err; 46 | char *raw; 47 | size_t bytes = view_raw(view, &raw, *offset, ~(size_t)0); 48 | unsigned flags = 0; 49 | regmatch_t match[10]; 50 | struct mode_search *mode = (struct mode_search *) view->mode; 51 | 52 | if (view_char_prior(view, *offset, NULL) != '\n') 53 | flags |= REG_NOTBOL; 54 | err = regexec(mode->regex, raw, 10, match, flags); 55 | if (err && err != REG_NOMATCH) 56 | window_beep(view); 57 | if (err) 58 | return 0; 59 | if (!advance && match[0].rm_so) 60 | return 0; 61 | if (match[0].rm_so >= bytes) 62 | return 0; 63 | if (match[0].rm_eo > bytes) 64 | match[0].rm_eo = bytes; 65 | for (j = 1; j < 10; j++) { 66 | if (match[j].rm_so < 0 || 67 | match[j].rm_so >= bytes) 68 | continue; 69 | if (match[j].rm_eo > bytes) 70 | match[j].rm_eo = bytes; 71 | clip_init(j); 72 | clip(j, view, *offset + match[j].rm_so, 73 | match[j].rm_eo - match[j].rm_so, 0); 74 | } 75 | *offset += match[0].rm_so; 76 | return match[0].rm_eo - match[0].rm_so; 77 | } 78 | 79 | static int scan_forward(struct view *view, size_t *length, 80 | position_t offset, position_t max_offset) 81 | { 82 | struct mode_search *mode = (struct mode_search *) view->mode; 83 | 84 | if (mode->bytes > view->bytes) 85 | return -1; 86 | if (max_offset > view->bytes - mode->bytes) 87 | max_offset = view->bytes - mode->bytes; 88 | if (offset + mode->bytes > max_offset) 89 | return -1; 90 | if (mode->regex) { 91 | if ((*length = match_regex(view, &offset, 1)) && 92 | offset < max_offset) 93 | return offset; 94 | } else 95 | for (; offset < max_offset; offset++) 96 | if ((*length = match_pattern(view, offset))) 97 | return offset; 98 | return -1; 99 | } 100 | 101 | static int scan_backward(struct view *view, size_t *length, 102 | position_t offset, position_t min_offset) 103 | { 104 | struct mode_search *mode = (struct mode_search *) view->mode; 105 | 106 | if (min_offset + mode->bytes > view->bytes) 107 | return -1; 108 | if (offset + mode->bytes > view->bytes) 109 | offset = view->bytes - mode->bytes; 110 | if (mode->regex) { 111 | for (; offset+1 > min_offset; offset--) 112 | if ((*length = match_regex(view, &offset, 0))) 113 | return offset; 114 | } else 115 | for (; offset+1 > min_offset; offset--) 116 | if ((*length = match_pattern(view, offset))) 117 | return offset; 118 | return -1; 119 | } 120 | 121 | static Boolean_t search(struct view *view, int backward, int new) 122 | { 123 | struct mode_search *mode = (struct mode_search *) view->mode; 124 | position_t mark; 125 | size_t length = 0; 126 | int at; 127 | 128 | if (!mode->bytes) { 129 | locus_set(view, CURSOR, mode->start); 130 | locus_set(view, MARK, UNSET); 131 | return TRUE; 132 | } 133 | 134 | if (mode->regex) { 135 | int err; 136 | if (new && mode->regex_ready) { 137 | regfree(mode->regex); 138 | mode->regex_ready = FALSE; 139 | } 140 | if (!mode->regex_ready) { 141 | mode->pattern[mode->bytes] = '\0'; 142 | status("regular expression: %s", mode->pattern); 143 | err = regcomp(mode->regex, (char *) mode->pattern, 144 | REG_EXTENDED | REG_ICASE | REG_NEWLINE); 145 | if (err) 146 | return FALSE; 147 | mode->regex_ready = TRUE; 148 | } 149 | } 150 | 151 | mark = locus_get(view, MARK); 152 | if (mark == UNSET) 153 | mark = locus_get(view, CURSOR); 154 | 155 | if (backward) { 156 | at = scan_backward(view, &length, mark - !new, 0); 157 | if (at < 0) 158 | at = scan_backward(view, &length, 159 | view->bytes, mark+1); 160 | } else { 161 | at = scan_forward(view, &length, 162 | mark + !new, view->bytes); 163 | if (at < 0) 164 | at = scan_forward(view, &length, 0, mark-1); 165 | } 166 | if (at < 0) { 167 | window_beep(view); 168 | macros_abort(); 169 | return 0; 170 | } 171 | 172 | /* A hit! */ 173 | locus_set(view, MARK, at); 174 | locus_set(view, CURSOR, at + length); 175 | mode->last_bytes = mode->bytes; 176 | return TRUE; 177 | } 178 | 179 | static void command_handler(struct view *view, Unicode_t ch) 180 | { 181 | struct mode_search *mode = (struct mode_search *) view->mode; 182 | static char cmdchar[][2] = { 183 | { CONTROL('H'), CONTROL('G') }, 184 | { CONTROL('T'), CONTROL('H') }, 185 | { CONTROL('V'), CONTROL('U') } 186 | }; 187 | 188 | static char *last_search; 189 | 190 | /* Backspace removes the last character from the search target and 191 | * returns to the previous hit 192 | */ 193 | if (ch == 0x7f /*BCK*/) 194 | if (!mode->bytes) { 195 | window_beep(view); 196 | goto done; 197 | } else { 198 | mode->bytes--; 199 | search(view, !mode->backward, 1); 200 | return; 201 | } 202 | 203 | /* Non-control characters are appended to the search target and 204 | * we proceed to the next hit if the current position does not 205 | * match the extended target. 206 | */ 207 | if (ch >= ' ' || ch == '\t') { 208 | size_t new; 209 | if (mode->bytes + 8 > mode->alloc) { 210 | mode->alloc = mode->bytes + 64; 211 | mode->pattern = reallocate(mode->pattern, mode->alloc); 212 | } 213 | mode->bytes += new = unicode_utf8((char *) mode->pattern + 214 | mode->bytes, ch); 215 | mode->pattern[mode->bytes] = '\0'; 216 | if (!search(view, mode->backward, new) && 217 | !mode->regex) 218 | mode->bytes -= new; 219 | return; 220 | } 221 | 222 | /* ^H moves to a hit that is earlier in the text. 223 | * ^T and ^/ (^A, ^_) proceed to a later hit. 224 | */ 225 | if (mode->last_bytes && 226 | (ch == cmdchar[0][is_asdfg] || 227 | ch == cmdchar[1][is_asdfg] || 228 | ch == CONTROL('A') || 229 | ch == CONTROL('_'))) { 230 | mode->bytes = mode->last_bytes; 231 | search(view, mode->backward = (ch == cmdchar[0][is_asdfg]), 0); 232 | return; 233 | } 234 | 235 | /* Hitting ^H/^T or ^/ (^A, ^_) with an empty search pattern causes 236 | * the last successful search target to be reused. 237 | */ 238 | if ((ch == cmdchar[0][is_asdfg] || 239 | ch == cmdchar[1][is_asdfg] || 240 | ch == CONTROL('A') || 241 | ch == CONTROL('_')) && 242 | !mode->bytes && last_search) { 243 | mode->bytes = strlen(last_search); 244 | mode->alloc = mode->bytes + 8; 245 | mode->pattern = reallocate(mode->pattern, mode->alloc); 246 | memcpy(mode->pattern, last_search, mode->bytes+1); 247 | if (ch == cmdchar[0][is_asdfg]) 248 | mode->backward = 1; 249 | else if (ch == cmdchar[1][is_asdfg]) 250 | mode->backward = 0; 251 | search(view, mode->backward, 0); 252 | return; 253 | } 254 | 255 | /* Search is done */ 256 | done: if (mode->bytes) { 257 | last_search = reallocate(last_search, mode->bytes+1); 258 | memcpy(last_search, mode->pattern, mode->bytes); 259 | last_search[mode->bytes] = '\0'; 260 | } 261 | 262 | view->mode = mode->previous; 263 | status_hide(); 264 | 265 | if (ch == cmdchar[2][is_asdfg]) { 266 | /* ^V: keep target as selection */ 267 | } else { 268 | /* restore mark, if any */ 269 | locus_set(view, MARK, mode->mark); 270 | if (ch != '\r' && 271 | ch != CONTROL('A') && 272 | ch != CONTROL('_') && 273 | ch != 0x7f /*BCK*/) 274 | view->mode->command(view, ch); 275 | } 276 | 277 | /* Release search mode resources */ 278 | RELEASE(mode->pattern); 279 | if (mode->regex_ready) 280 | regfree(mode->regex); 281 | RELEASE(mode->regex); 282 | RELEASE(mode); 283 | } 284 | 285 | void mode_search(struct view *view, Boolean_t regex) 286 | { 287 | struct mode_search *mode = allocate0(sizeof *mode); 288 | mode->previous = view->mode; 289 | mode->command = command_handler; 290 | mode->selection_bgrgba = SEARCH_BGRGBA; 291 | mode->start = locus_get(view, CURSOR); 292 | mode->mark = locus_get(view, MARK); 293 | if (mode->mark != UNSET && mode->start < mode->mark && !regex) { 294 | mode->bytes = mode->mark - mode->start; 295 | mode->alloc = mode->bytes + 8; 296 | mode->pattern = allocate(mode->alloc); 297 | view_get(view, mode->pattern, mode->start, mode->bytes); 298 | mode->last_bytes = mode->bytes; 299 | locus_set(view, MARK, mode->start); 300 | locus_set(view, CURSOR, mode->mark); 301 | } 302 | if (regex) 303 | mode->regex = allocate0(sizeof *mode->regex); 304 | view->mode = (struct mode *) mode; 305 | } 306 | -------------------------------------------------------------------------------- /tab.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | static char *path_complete(const char *string) 5 | { 6 | const char *home; 7 | size_t length; 8 | char *new = NULL, *p; 9 | DIR *dir; 10 | char *freestring = NULL; 11 | 12 | while (isspace(*string)) 13 | string++; 14 | if (!strncmp(string, "~/", 2) && 15 | (home = getenv("HOME"))) { 16 | freestring = allocate(strlen(home) + strlen(string)); 17 | sprintf(freestring, "%s%s", home, string+1); 18 | string = freestring; 19 | } 20 | 21 | length = strlen(string); 22 | new = allocate(length + NAME_MAX); 23 | memcpy(new, string, length+1); 24 | p = strrchr(new, '/'); 25 | if (p) { 26 | *p = '\0'; 27 | dir = opendir(new); 28 | *p++ = '/'; 29 | } else { 30 | dir = opendir("."); 31 | p = new; 32 | } 33 | if (dir) { 34 | struct dirent *dent; 35 | size_t prefix_len = new + length - p; 36 | size_t best_len = 0; 37 | while ((dent = readdir(dir))) { 38 | size_t dent_len = strlen(dent->d_name); 39 | if (dent_len <= prefix_len) 40 | continue; 41 | if (strncmp(p, dent->d_name, prefix_len)) 42 | continue; 43 | if (!best_len) { 44 | strcpy(new + length, dent->d_name + prefix_len); 45 | best_len = dent_len - prefix_len; 46 | } else { 47 | unsigned old_best_len = best_len; 48 | for (best_len = 0; 49 | best_len < old_best_len; 50 | best_len++) 51 | if (new[length+best_len] != 52 | dent->d_name[prefix_len+best_len]) 53 | break; 54 | if (!best_len) 55 | break; 56 | } 57 | } 58 | new[length+best_len] = '\0'; 59 | closedir(dir); 60 | if (best_len) 61 | goto done; 62 | } 63 | 64 | RELEASE(new); /* sets new = NULL */ 65 | done: RELEASE(freestring); 66 | return new; 67 | } 68 | 69 | static char *word_complete(const char *string) 70 | { 71 | struct text *text; 72 | struct view *hit_view = NULL; 73 | size_t hit_offset = 0, hit_length = 0; 74 | size_t length = strlen(string); 75 | 76 | if (!length) 77 | return NULL; 78 | for (text = text_list; text; text = text->next) { 79 | struct view *view = text->views; 80 | sposition_t offset; 81 | if (!view) 82 | continue; 83 | for (offset = 0; 84 | (offset = find_string(view, string, offset)) >= 0; 85 | offset++) { 86 | size_t old_hit_length; 87 | position_t last = offset + length - 1; 88 | size_t this_length = 89 | find_word_end(view, last) - last - 1; 90 | if (!this_length) 91 | continue; 92 | if (!(old_hit_length = hit_length)) { 93 | hit_view = view; 94 | hit_offset = offset; 95 | hit_length = this_length; 96 | } else { 97 | for (hit_length = 0; 98 | hit_length < old_hit_length; 99 | hit_length++) 100 | if (view_byte(hit_view, 101 | hit_offset + length + 102 | hit_length) != 103 | view_byte(view, offset + length + 104 | hit_length)) 105 | break; 106 | if (!hit_length) 107 | return NULL; 108 | } 109 | } 110 | } 111 | 112 | if (!hit_length) 113 | return NULL; 114 | return view_extract(hit_view, hit_offset, length + hit_length); 115 | } 116 | 117 | char *tab_complete(const char *string, Boolean_t selection) 118 | { 119 | char *new = NULL; 120 | while (isspace(*string)) 121 | string++; 122 | if (!*string) 123 | return NULL; 124 | if (selection) 125 | new = path_complete(string); 126 | if (!new) 127 | new = word_complete(string); 128 | if (new && !strcmp(new, string)) 129 | RELEASE(new); /* assigns new = NULL; */ 130 | return new; 131 | } 132 | 133 | Boolean_t tab_completion_command(struct view *view) 134 | { 135 | position_t cursor = locus_get(view, CURSOR); 136 | position_t mark = locus_get(view, MARK); 137 | char *completed = NULL, *select = NULL; 138 | Boolean_t selection = mark < cursor; 139 | Unicode_t ch; 140 | Boolean_t result = FALSE; 141 | 142 | if (selection) 143 | select = view_extract_selection(view); 144 | else if (mark == UNSET && 145 | IS_CODEPOINT((ch = view_char_prior(view, cursor, NULL))) && 146 | (isalnum(ch) || ch == '_')) { 147 | mark = find_word_start(view, cursor); 148 | if (mark < cursor) 149 | select = view_extract(view, mark, cursor-mark); 150 | } 151 | if (select && (completed = tab_complete(select, selection))) { 152 | view_delete(view, mark, cursor-mark); 153 | view_insert(view, completed, locus_get(view, CURSOR), -1); 154 | if (!selection) 155 | mark = cursor; 156 | locus_set(view, MARK, mark); 157 | RELEASE(completed); 158 | result = TRUE; 159 | } 160 | RELEASE(select); 161 | return result; 162 | } 163 | 164 | void insert_tab(struct view *view) 165 | { 166 | position_t cursor = locus_get(view, CURSOR); 167 | position_t mark = locus_get(view, MARK); 168 | 169 | if (mark != UNSET && mark > cursor) { 170 | view_delete(view, cursor, mark - cursor); 171 | locus_set(view, MARK, mark = UNSET); 172 | } 173 | if (view->text->flags & TEXT_NO_TABS) { 174 | int tabstop = view->text->tabstop; 175 | sposition_t offset = 0; 176 | position_t at = find_line_start(view, cursor); 177 | if (at) 178 | while (at != cursor) { 179 | Unicode_t ch = view_char(view, at, &at); 180 | if (ch == '\t') 181 | offset = (offset / tabstop + 1) * 182 | tabstop; 183 | else 184 | offset++; 185 | } 186 | for (offset %= tabstop; offset++ < tabstop; ) 187 | view_insert(view, " ", cursor, 1); 188 | } else 189 | view_insert(view, "\t", cursor, 1); 190 | if (mark == cursor) 191 | locus_set(view, MARK, /*old*/ cursor); 192 | } 193 | 194 | static void indent_line(struct view *view, 195 | position_t lnstart, 196 | unsigned indentation, 197 | Boolean_t no_blank_line) 198 | { 199 | char *indent; 200 | unsigned indent_bytes; 201 | position_t nonspace, next; 202 | Unicode_t ch; 203 | 204 | for (nonspace = lnstart; 205 | IS_UNICODE(ch = view_char(view, nonspace, &next)); 206 | nonspace = next) 207 | if (ch == '\n' || !IS_CODEPOINT(ch) || !isspace(ch)) 208 | break; 209 | view_delete(view, lnstart, nonspace - lnstart); 210 | if (no_blank_line && ch == '\n') 211 | return; 212 | 213 | if (view->text->flags & TEXT_NO_TABS) { 214 | indent_bytes = indentation; 215 | indent = allocate(indent_bytes); 216 | memset(indent, ' ', indentation); 217 | } else { 218 | unsigned tabstop = view->text->tabstop; 219 | tabstop |= !tabstop; 220 | indent_bytes = indentation/tabstop + indentation%tabstop; 221 | indent = allocate(indent_bytes); 222 | memset(indent, '\t', indentation / tabstop); 223 | memset(indent + indentation/tabstop, ' ', 224 | indentation % tabstop); 225 | } 226 | 227 | view_insert(view, indent, lnstart, indent_bytes); 228 | RELEASE(indent); 229 | } 230 | 231 | static int current_line_indentation(struct view *view, position_t *at) 232 | { 233 | int indent = 0, spaces = 0, chars = 0; 234 | position_t offset = *at; 235 | Unicode_t ch; 236 | unsigned tabstop = view->text->tabstop; 237 | 238 | while (IS_UNICODE(ch = view_char(view, offset, &offset)) && ch != '\n') 239 | if (ch == ' ') 240 | spaces += !chars; 241 | else if (ch == '\t') { 242 | indent = ((indent + spaces + chars) / 243 | tabstop + 1) * tabstop; 244 | spaces = chars = 0; 245 | *at = offset; 246 | } else 247 | chars++; 248 | *at += spaces; 249 | return indent + spaces; 250 | } 251 | 252 | static int line_alignment(struct view *view, position_t lnstart0) 253 | { 254 | position_t lnstart, offset, at, corr; 255 | Unicode_t ch; 256 | unsigned indent, brindent; 257 | unsigned tabstop = view->text->tabstop; 258 | Boolean_t is_nested; 259 | 260 | if (!lnstart0) 261 | return 0; 262 | lnstart = lnstart0 - 1; 263 | tabstop |= !tabstop; 264 | 265 | /* Find previous non-blank line */ 266 | again: lnstart = find_line_start(view, lnstart); 267 | while (lnstart && view_char(view, lnstart, NULL) == '\n') 268 | lnstart = find_line_start(view, lnstart-1); 269 | 270 | /* Determine its indentation */ 271 | at = lnstart; 272 | indent = current_line_indentation(view, &at); 273 | 274 | /* Adjust for nesting */ 275 | brindent = indent + 1; 276 | is_nested = FALSE; 277 | for (ch = view_char(view, at, &offset); 278 | IS_UNICODE(ch) && ch != '\n'; 279 | ch = view_char(view, at = offset, &offset), brindent++) { 280 | switch (ch) { 281 | case '(': 282 | case '[': 283 | corr = find_corresponding_bracket(view, at); 284 | if (corr >= lnstart0) { 285 | indent = brindent; 286 | is_nested = TRUE; 287 | } 288 | break; 289 | case ')': 290 | case ']': 291 | corr = find_corresponding_bracket(view, at); 292 | if (corr < lnstart) { 293 | lnstart = corr; 294 | goto again; 295 | } 296 | break; 297 | case '\t': 298 | brindent = (brindent/tabstop + 1) * tabstop; 299 | break; 300 | } 301 | } 302 | 303 | /* Adjust for clues at the end of the previous line */ 304 | if (!is_nested && lnstart0) { 305 | ch = view_char_prior(view, lnstart0 - 1, NULL); 306 | if (ch == '{' || ch == ')' || ch == ':' || ch == /*els*/'e') 307 | indent = (indent/tabstop + 1) * tabstop; 308 | else if (indent >= tabstop) { 309 | offset = lnstart; 310 | do ch = view_char_prior(view, offset, &offset); 311 | while (ch == ' ' || ch == '\t' || ch == '\n'); 312 | if (ch == ')' /* || ch == '}' */) 313 | indent = (indent/tabstop - 1) * tabstop; 314 | } 315 | } 316 | 317 | /* Adjust for leading character on current line */ 318 | if (indent) { 319 | offset = lnstart0; 320 | do ch = view_char(view, offset, &offset); 321 | while (ch == ' ' || ch == '\t'); 322 | if (ch == '}') 323 | indent = (indent/tabstop - 1) * tabstop; 324 | } 325 | 326 | return indent; 327 | } 328 | 329 | void align(struct view *view) 330 | { 331 | position_t cursor = locus_get(view, CURSOR); 332 | position_t mark = locus_get(view, MARK); 333 | if (mark == UNSET) { 334 | position_t line_start = find_line_start(view, cursor); 335 | indent_line(view, line_start, line_alignment(view, line_start), 336 | FALSE); 337 | } else { 338 | position_t top, bottom, line_start; 339 | locus_t end; 340 | if (cursor <= mark) 341 | top = cursor, bottom = mark; 342 | else 343 | top = mark, bottom = cursor; 344 | end = locus_create(view, bottom); 345 | line_start = find_line_start(view, top); 346 | do { 347 | int align = line_alignment(view, line_start); 348 | indent_line(view, line_start, align, TRUE); 349 | line_start = find_line_end(view, line_start) + 1; 350 | } while (line_start < locus_get(view, end)); 351 | locus_destroy(view, end); 352 | } 353 | } 354 | 355 | void insert_newline(struct view *view) 356 | { 357 | sposition_t cursor = locus_get(view, CURSOR); 358 | position_t mark = locus_get(view, MARK); 359 | if (mark != UNSET && mark > cursor) { 360 | view_delete(view, cursor, mark - cursor); 361 | locus_set(view, MARK, mark = UNSET); 362 | } 363 | view_insert(view, "\n", cursor, 1); 364 | locus_set(view, CURSOR, ++cursor); 365 | if (view_byte(view, cursor-2) == '{') { 366 | position_t at = cursor+1; 367 | int la, cla; 368 | if (cursor == view->bytes || 369 | (la = line_alignment(view, cursor)) > 370 | (cla = current_line_indentation(view, &at) + 371 | view->text->tabstop) || 372 | la == cla && view_byte(view, at) != '}') { 373 | view_insert(view, "}\n", cursor, 374 | 1 + (cursor == view->bytes)); 375 | align(view); 376 | view_insert(view, "\n", cursor, 1); 377 | locus_set(view, CURSOR, cursor); 378 | } 379 | } 380 | align(view); 381 | } 382 | -------------------------------------------------------------------------------- /tags.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | /* TAGS file searching */ 5 | 6 | static char *extract_id(struct view *view) 7 | { 8 | Unicode_t ch; 9 | position_t at = locus_get(view, CURSOR), next; 10 | char *id; 11 | 12 | if (is_idch((ch = view_char(view, at, &next))) || 13 | ch == ':' && view_char(view, next, NULL) == ':') 14 | locus_set(view, CURSOR, at = find_id_end(view, at)); 15 | locus_set(view, MARK, find_id_start(view, at)); 16 | id = view_extract_selection(view); 17 | locus_set(view, MARK, UNSET); 18 | return id; 19 | } 20 | 21 | static struct view *find_TAGS(struct view *view, struct view *tags) 22 | { 23 | const char *currpath = (tags ? tags : view)->text->path; 24 | char *path = allocate(strlen(currpath) + 8); 25 | char *slash; 26 | 27 | strcpy(path, currpath); 28 | if (tags) { 29 | view_close(tags); 30 | slash = strrchr(path, '/'); 31 | if (slash) 32 | *slash = '\0'; 33 | } 34 | for (; (slash = strrchr(path, '/')); *slash = '\0') { 35 | strcpy(slash+1, "TAGS"); 36 | if (!access(path, R_OK)) { 37 | tags = view_open(path); 38 | if (tags && !(tags->text->flags & TEXT_CREATED)) { 39 | RELEASE(path); 40 | return tags; 41 | } 42 | view_close(tags); 43 | } 44 | } 45 | RELEASE(path); 46 | return NULL; 47 | } 48 | 49 | static sposition_t find_id_in_TAGS(struct view *tags, const char *id) 50 | { 51 | position_t first = 0; 52 | position_t last = tags->bytes; 53 | position_t at, wordstart, wordend; 54 | sposition_t result = -1; 55 | char *this; 56 | int cmp; 57 | 58 | while (first < last) { 59 | at = find_line_start(tags, first + last >> 1); 60 | if (at < first) 61 | at = find_line_end(tags, at) + 1; 62 | if (at >= last) 63 | break; 64 | wordstart = find_nonspace(tags, at); 65 | wordend = find_space(tags, wordstart); 66 | this = view_extract(tags, wordstart, wordend - wordstart); 67 | if (!this) 68 | break; 69 | cmp = strcmp(id, this); 70 | RELEASE(this); 71 | if (!cmp) 72 | result = wordend; 73 | if (cmp <= 0) 74 | last = at; 75 | else 76 | first = find_line_end(tags, at) + 1; 77 | } 78 | return result; /* failure */ 79 | } 80 | 81 | static sposition_t find_next_id_in_TAGS(struct view *tags, const char *id, 82 | position_t prevend) 83 | { 84 | position_t wordstart = find_nonspace(tags, 85 | find_line_end(tags, prevend) + 1); 86 | position_t wordend = find_space(tags, wordstart); 87 | char *this = view_extract(tags, wordstart, wordend - wordstart); 88 | int cmp; 89 | 90 | if (!this) 91 | return -1; 92 | cmp = strcmp(id, this); 93 | RELEASE(this); 94 | return cmp ? -1 : wordend; 95 | } 96 | 97 | static struct view *show_tag(struct view *tags, sposition_t wordend, 98 | const char *id) 99 | { 100 | position_t wordstart, linestart; 101 | char *this, *path, *slash; 102 | int line; 103 | struct view *view; 104 | sposition_t at; 105 | 106 | if (wordend < 0) 107 | return NULL; 108 | wordstart = find_nonspace(tags, wordend); /* line number */ 109 | wordend = find_space(tags, wordstart); 110 | this = view_extract(tags, wordstart, wordend - wordstart); 111 | if (!isdigit(*this)) { 112 | /* exuberant-ctags puts a classifier before the line number */ 113 | RELEASE(this); 114 | wordstart = find_nonspace(tags, wordend); /* line number */ 115 | wordend = find_space(tags, wordstart); 116 | this = view_extract(tags, wordstart, wordend - wordstart); 117 | if (!isdigit(*this)) { 118 | RELEASE(this); 119 | return NULL; 120 | } 121 | } 122 | line = atoi(this); 123 | RELEASE(this); 124 | wordstart = find_nonspace(tags, wordend); /* file name */ 125 | wordend = find_space(tags, wordstart); 126 | this = view_extract(tags, wordstart, wordend - wordstart); 127 | 128 | if (*this == '/') 129 | path = this; 130 | else { 131 | path = allocate(strlen(tags->text->path) + strlen(this) + 8); 132 | strcpy(path, tags->text->path); 133 | if (!(slash = strrchr(path, '/'))) 134 | *(slash = path + strlen(path)) = '/'; 135 | strcpy(slash + 1, this); 136 | RELEASE(this); 137 | } 138 | 139 | view = view_open(path); 140 | if (!view) 141 | message("Could not open %s from TAGS", path); 142 | RELEASE(path); 143 | if (!view || view->text->flags & TEXT_CREATED) { 144 | view_close(view); 145 | return FALSE; 146 | } 147 | linestart = find_line_number(view, line); 148 | at = find_string(view, id, linestart); 149 | if (at >= 0) { 150 | locus_set(view, CURSOR, at); 151 | locus_set(view, MARK, at + strlen(id)); 152 | mode_search(view, FALSE); 153 | } else { 154 | locus_set(view, CURSOR, linestart); 155 | locus_set(view, MARK, UNSET); 156 | } 157 | return view; 158 | } 159 | 160 | static Boolean_t show_tags(struct view *tags, struct view *view, 161 | const char *id) 162 | { 163 | sposition_t wordend = find_id_in_TAGS(tags, id); 164 | struct view *new_view = show_tag(tags, wordend, id); 165 | struct view *top_view; 166 | 167 | if (!new_view) 168 | return FALSE; 169 | 170 | window_below(view, top_view = new_view, 4); 171 | while ((wordend = find_next_id_in_TAGS(tags, id, wordend)) >= 0) 172 | if ((new_view = show_tag(tags, wordend, id))) 173 | window_below(view, top_view = new_view, 4); 174 | window_activate(top_view); 175 | return TRUE; 176 | } 177 | 178 | void find_tag(struct view *view) 179 | { 180 | struct view *tags = NULL, *prior_tags; 181 | char *id; 182 | 183 | if (locus_get(view, MARK) != UNSET) { 184 | id = view_extract_selection(view); 185 | view_delete_selection(view); 186 | } else 187 | id = extract_id(view); 188 | if (!id) { 189 | window_beep(view); 190 | return; 191 | } 192 | 193 | while ((tags = find_TAGS(view, prior_tags = tags))) 194 | if (show_tags(tags, view, id)) 195 | break; 196 | 197 | if (tags) 198 | view_close(tags); 199 | else if (!prior_tags) 200 | message("No readable TAGS file found."); 201 | else { 202 | errno = 0; 203 | message("couldn't find tag %s", id); 204 | } 205 | 206 | RELEASE(id); 207 | } 208 | -------------------------------------------------------------------------------- /text.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | /* 5 | * A text is a container for the content of a file or 6 | * an internal scratch buffer. It has a buffer, which 7 | * contains the actual bytes of the text, an "undo" history 8 | * buffer, and one or more views, which each present part or 9 | * all of the text to the user for editing. The positions of 10 | * the cursor, mark, and other "loci" are all local to a view. 11 | */ 12 | 13 | struct text *text_list; 14 | 15 | struct view *view_find(const char *name) 16 | { 17 | struct view *view; 18 | struct text *text; 19 | 20 | if (!name) 21 | name = ""; 22 | for (text = text_list; text; text = text->next) 23 | for (view = text->views; view; view = view->next) 24 | if (view->name && !strcmp(view->name, name)) 25 | return view; 26 | return NULL; 27 | } 28 | 29 | void view_name(struct view *view) 30 | { 31 | int j, len; 32 | const char *name = view->text->path; 33 | char *new, *p; 34 | 35 | if (!name) 36 | name = ""; 37 | len = strlen(name); 38 | new = allocate(len + 8); 39 | if (view->text->flags & TEXT_EDITOR) { 40 | memcpy(new, name, len+1); 41 | p = new + len; 42 | } else { 43 | for (j = 0, p = new; j < len; j++) { 44 | unsigned ch = name[j]; 45 | if (ch >= 'a' && ch <= 'z' || 46 | ch >= 'A' && ch <= 'Z' || 47 | ch >= '0' && ch <= '9' || 48 | ch == '_' || ch == '-' || ch == '+' || 49 | ch == '.' || ch == ',' || 50 | ch >= 0x80) 51 | *p++ = ch; 52 | else if (ch == '/' && j < len-1) 53 | p = new; 54 | } 55 | *p = '\0'; 56 | } 57 | 58 | if (view_find(new)) 59 | for (j = 2; ; j++) { 60 | sprintf(p, "<%d>", j); 61 | if (!view_find(new)) 62 | break; 63 | } 64 | 65 | RELEASE(view->name); 66 | view->name = new; 67 | } 68 | 69 | struct view *view_create(struct text *text) 70 | { 71 | struct view *view = allocate0(sizeof *view); 72 | view->loci = DEFAULT_LOCI; 73 | view->locus = allocate(view->loci * sizeof *view->locus); 74 | memset(view->locus, UNSET, view->loci * sizeof *view->locus); 75 | view->locus[CURSOR] = 0; 76 | view->text = text; 77 | view->next = text->views; 78 | text->views = view; 79 | view->bytes = text->buffer ? buffer_bytes(text->buffer) : 80 | text->clean ? text->clean_bytes :0; 81 | view->mode = mode_default(); 82 | view->shell_std_in = -1; 83 | view->shell_pg = -1; 84 | view->shell_out_locus = NO_LOCUS; 85 | view_name(view); 86 | return view; 87 | } 88 | 89 | struct view *text_create(const char *path, unsigned flags) 90 | { 91 | struct text *text = allocate0(sizeof *text), *prev, *bp; 92 | 93 | text->fd = -1; 94 | text->flags = flags; 95 | text->path = strdup(path); 96 | if (utf8_mode == UTF8_NO) 97 | text->flags |= TEXT_NO_UTF8; 98 | if (default_no_tabs) 99 | text->flags |= TEXT_NO_TABS; 100 | text->tabstop = default_tab_stop; 101 | keyword_init(text); 102 | 103 | for (prev = NULL, bp = text_list; bp; prev = bp, bp = bp->next) 104 | ; 105 | if (prev) 106 | prev->next = text; 107 | else 108 | text_list = text; 109 | return view_create(text); 110 | } 111 | 112 | static void text_close(struct text *text) 113 | { 114 | struct text *prev = NULL, *bp; 115 | 116 | for (bp = text_list; bp; prev = bp, bp = bp->next) 117 | if (bp == text) { 118 | if (prev) 119 | prev->next = text->next; 120 | else 121 | text_list = text->next; 122 | break; 123 | } 124 | 125 | if (text->clean) 126 | munmap(text->clean, text->clean_bytes); 127 | buffer_destroy(text->buffer); 128 | text_forget_undo(text); 129 | if (text->fd >= 0) 130 | close(text->fd); 131 | if (text->flags & (TEXT_SCRATCH | TEXT_CREATED)) 132 | unlink(text->path); 133 | RELEASE(text->path); 134 | RELEASE(text); 135 | } 136 | 137 | void view_close(struct view *view) 138 | { 139 | struct view *vp, *prev = NULL; 140 | struct text *text; 141 | 142 | if (!view) 143 | return; 144 | text = view->text; 145 | window_destroy(view->window); 146 | demultiplex_view(view); 147 | bookmark_unset_view(view); 148 | 149 | if (text) 150 | for (vp = text->views; vp; prev = vp, vp = vp->next) 151 | if (vp == view) { 152 | if (prev) 153 | prev->next = vp->next; 154 | else if (!(text->views = vp->next)) 155 | text_close(text); 156 | break; 157 | } 158 | 159 | RELEASE(view->name); 160 | RELEASE(view); 161 | } 162 | 163 | struct view *view_selection(struct view *current, position_t offset, 164 | size_t bytes) 165 | { 166 | struct view *view; 167 | 168 | if (!current) 169 | return NULL; 170 | view = view_create(current->text); 171 | 172 | if (offset > current->bytes) 173 | offset = current->bytes; 174 | if (offset + bytes > current->bytes) 175 | bytes = current->bytes - offset; 176 | view->start = current->start + offset; 177 | view->bytes = bytes; 178 | locus_set(view, CURSOR, 0); 179 | return view; 180 | } 181 | 182 | void text_adjust_loci(struct text *text, position_t offset, int delta) 183 | { 184 | struct view *view; 185 | 186 | if (!delta) 187 | return; 188 | 189 | if (delta < 0) { 190 | position_t limit = offset - delta; 191 | for (view = text->views; view; view = view->next) 192 | if (limit < view->start) 193 | view->start += delta; 194 | else if (offset < view->start) { 195 | size_t loss = limit - view->start; 196 | if (loss > view->bytes) 197 | loss = view->bytes; 198 | view->start = offset; 199 | view->bytes -= loss; 200 | loci_adjust(view, 0, -loss); 201 | } else if (offset < view->start + view->bytes) { 202 | size_t loss = view->start + view->bytes - 203 | offset; 204 | if (loss > -delta) 205 | loss = -delta; 206 | view->bytes -= loss; 207 | loci_adjust(view, offset - view->start, -loss); 208 | } 209 | } else 210 | for (view = text->views; view; view = view->next) 211 | if (offset < view->start) 212 | view->start += delta; 213 | else if (offset <= view->start + view->bytes) { 214 | view->bytes += delta; 215 | loci_adjust(view, offset - view->start, delta); 216 | } 217 | } 218 | 219 | static size_t text_get(struct text *text, void *out, position_t offset, 220 | size_t bytes) 221 | { 222 | if (text->buffer) 223 | return buffer_get(text->buffer, out, offset, bytes); 224 | if (!text->clean || offset >= text->clean_bytes) 225 | return 0; 226 | if (offset + bytes > text->clean_bytes) 227 | bytes = text->clean_bytes - offset; 228 | memcpy(out, text->clean + offset, bytes); 229 | return bytes; 230 | } 231 | 232 | size_t view_get(struct view *view, void *out, position_t offset, size_t bytes) 233 | { 234 | if (offset >= view->bytes) 235 | return 0; 236 | if (offset + bytes > view->bytes) 237 | bytes = view->bytes - offset; 238 | return text_get(view->text, out, view->start + offset, bytes); 239 | } 240 | 241 | static size_t text_raw(struct text *text, char **out, position_t offset, 242 | size_t bytes) 243 | { 244 | if (text->buffer) 245 | return buffer_raw(text->buffer, out, offset, bytes); 246 | if (!text->clean) { 247 | *out = NULL; 248 | return 0; 249 | } 250 | if (offset + bytes > text->clean_bytes) 251 | bytes = text->clean_bytes - offset; 252 | *out = bytes ? text->clean + offset : NULL; 253 | return bytes; 254 | } 255 | 256 | size_t view_raw(struct view *view, char **out, position_t offset, size_t bytes) 257 | { 258 | if (offset >= view->bytes) { 259 | *out = NULL; 260 | return 0; 261 | } 262 | if (offset + bytes > view->bytes) 263 | bytes = view->bytes - offset; 264 | return text_raw(view->text, out, view->start + offset, bytes); 265 | } 266 | 267 | size_t view_delete(struct view *view, position_t offset, size_t bytes) 268 | { 269 | return text_delete(view->text, view->start + offset, bytes); 270 | } 271 | 272 | size_t view_insert(struct view *view, const void *in, 273 | position_t offset, ssize_t bytes) 274 | { 275 | if (bytes < 0) 276 | bytes = in ? strlen(in) : 0; 277 | return text_insert(view->text, in, view->start + offset, bytes); 278 | } 279 | -------------------------------------------------------------------------------- /text.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef TEXT_H 3 | #define TEXT_H 4 | 5 | /* Texts and views */ 6 | 7 | struct text { 8 | struct text *next; 9 | struct view *views; /* list of views into this text */ 10 | char *clean; /* unmodified content, mmap'ed */ 11 | size_t clean_bytes; 12 | fd_t fd; 13 | struct buffer *buffer; /* modified content */ 14 | struct undo *undo; /* undo/redo state */ 15 | char *path; 16 | unsigned dirties; /* number of modifications */ 17 | unsigned preserved; /* "dirties" at last save */ 18 | time_t mtime; 19 | unsigned tabstop; 20 | struct keywords *keywords; 21 | sposition_t (*comment_start)(struct view *, position_t); 22 | sposition_t (*comment_end)(struct view *, position_t); 23 | sposition_t (*string_end)(struct view *, position_t); 24 | const char *brackets; 25 | unsigned foldings; 26 | unsigned flags; 27 | #define TEXT_SAVED_ORIGINAL (1<<0) 28 | #define TEXT_RDONLY (1<<1) 29 | #define TEXT_EDITOR (1<<2) 30 | #define TEXT_CREATED (1<<3) 31 | #define TEXT_SCRATCH (1<<4) 32 | #define TEXT_NO_TABS (1<<5) 33 | #define TEXT_NO_UTF8 (1<<6) 34 | #define TEXT_CRNL (1<<7) 35 | }; 36 | 37 | struct view { 38 | struct view *next; 39 | struct text *text; 40 | struct window *window; 41 | char *name; 42 | position_t start; /* offset in text */ 43 | size_t bytes; 44 | unsigned loci; 45 | position_t *locus; 46 | struct mode *mode; 47 | fd_t shell_std_in; 48 | locus_t shell_out_locus; 49 | pid_t shell_pg; 50 | struct goal_column { 51 | position_t cursor; 52 | unsigned row, column; 53 | } goal; 54 | }; 55 | 56 | struct keywords { 57 | int count; 58 | const char **word; 59 | }; 60 | 61 | extern struct text *text_list; 62 | extern unsigned default_tab_stop; /* -t 8 */ 63 | extern Boolean_t default_no_tabs; /* -s */ 64 | extern Boolean_t default_tabs; /* -S */ 65 | extern Boolean_t no_keywords; /* -k */ 66 | extern Boolean_t no_save_originals; /* -o */ 67 | extern Boolean_t read_only; /* -r */ 68 | extern enum utf8_mode { UTF8_NO, UTF8_YES, UTF8_AUTO } utf8_mode; 69 | extern const char *make_writable; 70 | 71 | /* text.c */ 72 | struct view *view_find(const char *name); 73 | void view_name(struct view *); 74 | struct view *view_create(struct text *); 75 | struct view *text_create(const char *name, unsigned flags); 76 | struct view *text_new(void); 77 | void view_close(struct view *); 78 | struct view *view_selection(struct view *, position_t, size_t); 79 | void text_adjust_loci(struct text *, position_t, int delta); 80 | size_t view_get(struct view *, void *, position_t, size_t); 81 | size_t view_raw(struct view *, char **, position_t, size_t); 82 | size_t view_delete(struct view *, position_t, size_t); 83 | size_t view_insert(struct view *, const void *, position_t, ssize_t); 84 | 85 | /* Use only for raw bytes. See util.h for general folded and Unicode 86 | * character access with view_char[_prior](). 87 | */ 88 | INLINE Unicode_t text_byte(struct text *text, position_t offset) 89 | { 90 | if (text->buffer) 91 | return buffer_byte(text->buffer, offset); 92 | if (text->clean) 93 | return (Byte_t) text->clean[offset]; 94 | return UNICODE_BAD; 95 | } 96 | 97 | INLINE Unicode_t view_byte(struct view *view, position_t offset) 98 | { 99 | if (offset >= view->bytes) 100 | return UNICODE_BAD; 101 | return text_byte(view->text, view->start + offset); 102 | } 103 | 104 | /* file.c */ 105 | struct view *view_open(const char *path); 106 | Boolean_t text_rename(struct text *, const char *path); 107 | void text_dirty(struct text *); 108 | Boolean_t text_is_dirty(struct text *); 109 | void text_preserve(struct text *); 110 | void texts_preserve(void); 111 | void texts_uncreate(void); 112 | 113 | /* undo.c */ 114 | size_t text_delete(struct text *, position_t, size_t); 115 | size_t text_insert(struct text *, const void *, position_t, size_t); 116 | sposition_t text_undo(struct text *); 117 | sposition_t text_redo(struct text *); 118 | void text_forget_undo(struct text *); 119 | 120 | /* bookmark.c */ 121 | void bookmark_set(unsigned, struct view *, position_t cursor, position_t mark); 122 | Boolean_t bookmark_get(struct view **, position_t *cursor, position_t *mark, 123 | unsigned id); 124 | void bookmark_unset(unsigned); 125 | void bookmark_unset_view(struct view *); 126 | 127 | void demultiplex_view(struct view *); /* child.c */ 128 | struct view *view_help(void); /* help.c */ 129 | char *tab_complete(const char *, Boolean_t); /* tab.c */ 130 | Boolean_t tab_completion_command(struct view *); 131 | void insert_tab(struct view *); 132 | void insert_newline(struct view *); 133 | void align(struct view *); 134 | 135 | /* fold.c */ 136 | void view_fold(struct view *, position_t, position_t); 137 | sposition_t view_unfold(struct view *, position_t); 138 | void view_unfold_selection(struct view *); 139 | void view_fold_indented(struct view *, unsigned); 140 | void view_unfold_all(struct view *); 141 | void text_unfold_all(struct text *); 142 | 143 | /* see also util.h */ 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /types.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef TYPES_H 3 | #define TYPES_H 4 | 5 | #include /* for ssize_t */ 6 | 7 | typedef unsigned Unicode_t; 8 | typedef unsigned char Byte_t; 9 | typedef enum Boolean_t { FALSE = 0!=0, TRUE = 0==0 } Boolean_t; 10 | typedef size_t position_t; 11 | typedef ssize_t sposition_t; 12 | typedef int fd_t; 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /undo.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | struct edit { 5 | position_t offset; 6 | ssize_t bytes; /* negative means "inserted" */ 7 | }; 8 | 9 | struct undo { 10 | struct buffer *edits, *deleted; 11 | position_t redo, saved; 12 | }; 13 | 14 | static struct edit *get_raw_edit(void *raw) 15 | { 16 | /* The intermediate cast to void* suppresses an 17 | * alignment warning otherwise emitted by some 18 | * compilers. 19 | */ 20 | return raw; 21 | } 22 | 23 | static struct edit *last_edit(struct text *text) 24 | { 25 | char *raw = NULL; 26 | 27 | if (text->undo && 28 | text->undo->redo && 29 | text->undo->redo == buffer_bytes(text->undo->edits)) 30 | buffer_raw(text->undo->edits, &raw, 31 | text->undo->redo - sizeof(struct edit), 32 | sizeof(struct edit)); 33 | return get_raw_edit(raw); 34 | } 35 | 36 | static void resume_editing(struct text *text) 37 | { 38 | if (!text->undo) { 39 | text->undo = allocate0(sizeof *text->undo); 40 | text->undo->edits = buffer_create(NULL); 41 | text->undo->deleted = buffer_create(NULL); 42 | } 43 | buffer_delete(text->undo->edits, text->undo->redo, 44 | buffer_bytes(text->undo->edits) - text->undo->redo); 45 | buffer_delete(text->undo->deleted, text->undo->saved, 46 | buffer_bytes(text->undo->deleted) - text->undo->saved); 47 | } 48 | 49 | static Boolean_t in_view(struct view *view, position_t *offset, size_t *bytes) 50 | { 51 | if (*offset < view->start) { 52 | if (*offset + *bytes <= view->start) 53 | return FALSE; 54 | *bytes -= view->start - *offset; 55 | *offset = FALSE; 56 | } else { 57 | *offset -= view->start; 58 | if (*offset >= view->bytes) 59 | return FALSE; 60 | if (*offset + *bytes > view->bytes) 61 | *bytes = view->bytes - *offset; 62 | } 63 | return TRUE; 64 | } 65 | 66 | static void view_hint_deleting(struct view *view, position_t offset, 67 | size_t bytes) 68 | { 69 | if (view->window && in_view(view, &offset, &bytes)) 70 | window_hint_deleting(view->window, offset, bytes); 71 | } 72 | 73 | static void view_hint_inserted(struct view *view, position_t offset, 74 | size_t bytes) 75 | { 76 | if (view->window && in_view(view, &offset, &bytes)) 77 | window_hint_inserted(view->window, offset, bytes); 78 | } 79 | 80 | size_t text_delete(struct text *text, position_t offset, size_t bytes) 81 | { 82 | char *old; 83 | struct edit edit, *last; 84 | struct view *view; 85 | 86 | if (!bytes) 87 | return 0; 88 | text_dirty(text); 89 | edit.offset = offset; 90 | edit.bytes = buffer_raw(text->buffer, &old, offset, bytes); 91 | bytes = edit.bytes; 92 | 93 | if ((last = last_edit(text)) && 94 | last->bytes >= 0 && 95 | last->offset == offset) 96 | last->bytes += bytes; 97 | else { 98 | resume_editing(text); 99 | buffer_insert(text->undo->edits, &edit, 100 | text->undo->redo, sizeof edit); 101 | text->undo->redo += sizeof edit; 102 | } 103 | for (view = text->views; view; view = view->next) 104 | view_hint_deleting(view, offset, bytes); 105 | buffer_move(text->undo->deleted, text->undo->saved, 106 | text->buffer, offset, bytes); 107 | text->undo->saved += bytes; 108 | text_adjust_loci(text, offset, -bytes); 109 | return bytes; 110 | } 111 | 112 | size_t text_insert(struct text *text, const void *in, 113 | position_t offset, size_t bytes) 114 | { 115 | struct edit edit, *last; 116 | struct view *view; 117 | 118 | if (!bytes) 119 | return 0; 120 | text_dirty(text); 121 | bytes = buffer_insert(text->buffer, in, offset, bytes); 122 | if ((last = last_edit(text)) && 123 | last->bytes < 0 && 124 | last->offset - last->bytes == offset) 125 | last->bytes -= bytes; 126 | else { 127 | resume_editing(text); 128 | edit.offset = offset; 129 | edit.bytes = -bytes; 130 | buffer_insert(text->undo->edits, &edit, text->undo->redo, 131 | sizeof edit); 132 | text->undo->redo += sizeof edit; 133 | } 134 | text_adjust_loci(text, offset, bytes); 135 | for (view = text->views; view; view = view->next) 136 | view_hint_inserted(view, offset, bytes); 137 | return bytes; 138 | } 139 | 140 | sposition_t text_undo(struct text *text) 141 | { 142 | char *raw; 143 | struct edit *edit; 144 | 145 | if (!text->undo || !text->undo->redo) 146 | return -1; 147 | text_dirty(text); 148 | buffer_raw(text->undo->edits, &raw, text->undo->redo -= sizeof *edit, 149 | sizeof *edit); 150 | edit = get_raw_edit(raw); 151 | if (edit->bytes >= 0) 152 | buffer_move(text->buffer, edit->offset, text->undo->deleted, 153 | text->undo->saved -= edit->bytes, edit->bytes); 154 | else 155 | buffer_move(text->undo->deleted, text->undo->saved, 156 | text->buffer, edit->offset, -edit->bytes); 157 | text_adjust_loci(text, edit->offset, edit->bytes); 158 | return edit->offset; 159 | } 160 | 161 | sposition_t text_redo(struct text *text) 162 | { 163 | char *raw; 164 | struct edit *edit; 165 | 166 | if (!text->undo || 167 | text->undo->redo == buffer_bytes(text->undo->edits)) 168 | return -1; 169 | text_dirty(text); 170 | buffer_raw(text->undo->edits, &raw, text->undo->redo, sizeof *edit); 171 | edit = get_raw_edit(raw); 172 | text->undo->redo += sizeof *edit; 173 | if (edit->bytes >= 0) { 174 | buffer_move(text->undo->deleted, text->undo->saved, 175 | text->buffer, edit->offset, edit->bytes); 176 | text->undo->saved += edit->bytes; 177 | } else 178 | buffer_move(text->buffer, edit->offset, text->undo->deleted, 179 | text->undo->saved, -edit->bytes); 180 | text_adjust_loci(text, edit->offset, -edit->bytes); 181 | return edit->offset; 182 | } 183 | 184 | void text_forget_undo(struct text *text) 185 | { 186 | if (text->undo) { 187 | buffer_destroy(text->undo->edits); 188 | buffer_destroy(text->undo->deleted); 189 | RELEASE(text->undo); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /unicode.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "types.h" 8 | #include "utf8.h" 9 | 10 | int main(int argc, const char *argv[]) 11 | { 12 | Unicode_t ch = 0x203b; 13 | unsigned num = 1, at = 0; 14 | char buf[8]; 15 | if (argc > 1) 16 | ch = strtoul(argv[1], NULL, 16); 17 | if (argc > 2) 18 | num = strtoul(argv[2], NULL, 0); 19 | while (num--) { 20 | if (!at) 21 | printf("0x%04x", ch); 22 | buf[unicode_utf8(buf, ch++)] = '\0'; 23 | printf("\t%s", buf); 24 | if (++at == 8) { 25 | at = 0; 26 | putchar('\n'); 27 | } 28 | } 29 | if (at) 30 | putchar('\n'); 31 | return EXIT_SUCCESS; 32 | } 33 | -------------------------------------------------------------------------------- /utf8.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include 3 | #include 4 | #include "types.h" 5 | #include "utf8.h" 6 | 7 | /* Encode a Unicode code point in UTF-8; return the encoding length in bytes. */ 8 | size_t unicode_utf8(char *out, Unicode_t unicode) 9 | { 10 | char *p = out; 11 | 12 | if (!(unicode >> 7)) 13 | *p++ = unicode; 14 | else { 15 | int n; 16 | for (n = 1; n < 5; n++) 17 | if (!(unicode >> 6 + 5*n)) 18 | break; 19 | *p++ = 0xfc << 5-n | unicode >> 6*n; 20 | while (n--) 21 | *p++ = 0x80 | unicode >> 6*n & 0x3f; 22 | } 23 | 24 | return p - out; 25 | } 26 | 27 | /* The first byte of a UTF-8 encoding reveals its length. */ 28 | Byte_t utf8_bytes[0x100] = { 29 | /* 00-7f are themselves */ 30 | /*00*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31 | /*10*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 32 | /*20*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 33 | /*30*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34 | /*40*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 35 | /*50*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 36 | /*60*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 37 | /*70*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 38 | /* 80-bf are later bytes, out-of-sync if first */ 39 | /*80*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 40 | /*90*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 41 | /*a0*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 42 | /*b0*/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43 | /* c0-df are first byte of two-byte sequences (5+6=11 bits) */ 44 | /* c0-c1 are noncanonical */ 45 | /*c0*/ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 46 | /*d0*/ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 47 | /* e0-ef are first byte of three-byte (4+6+6=16 bits) */ 48 | /* e0 80-9f are noncanonical */ 49 | /*e0*/ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 50 | /* f0-f7 are first byte of four-byte (3+6+6+6=21 bits) */ 51 | /* f0 80-8f are noncanonical */ 52 | /*f0*/ 4, 4, 4, 4, 4, 4, 4, 4, 53 | /* f8-fb are first byte of five-byte (2+6+6+6+6=26 bits) */ 54 | /* f8 80-87 are noncanonical */ 55 | /*f8*/ 5, 5, 5, 5, 56 | /* fc-fd are first byte of six-byte (1+6+6+6+6+6=31 bits) */ 57 | /* fc 80-83 are noncanonical */ 58 | /*fc*/ 6, 6, 59 | /* fe and ff are not part of valid UTF-8 so they stand alone */ 60 | /*fe*/ 1, 1 61 | }; 62 | 63 | /* 64 | * Validate a UTF-8 encoding and return its length. 65 | * Invalid encodings are expressed as single bytes. 66 | */ 67 | size_t utf8_length(const char *in, size_t max) 68 | { 69 | const Byte_t *p = (const Byte_t *) in; 70 | size_t n = utf8_bytes[*p]; 71 | 72 | if (max > n) 73 | max = n; 74 | if (max < n) 75 | return 1; 76 | for (n = 1; n < max; n++) 77 | if ((p[n] & 0xc0) != 0x80) 78 | return 1; 79 | return max; 80 | } 81 | 82 | /* Find the length of a UTF-8 encoding in reverse. */ 83 | size_t utf8_length_backwards(const char *in, size_t max) 84 | { 85 | int n; 86 | const Byte_t *p = (const Byte_t *) in; 87 | 88 | if ((*p & 0xc0) != 0x80) 89 | return 1; 90 | if (max > 6) 91 | max = 6; 92 | for (n = 1; n < max; n++) 93 | if ((p[-n] & 0xc0) != 0x80) 94 | break; 95 | if (utf8_bytes[p[-n]] == n+1) 96 | return n+1; 97 | return 1; 98 | } 99 | 100 | /* Decode UTF-8 to Unicode. */ 101 | Unicode_t utf8_unicode(const char *in, size_t length) 102 | { 103 | const Byte_t *p = (const Byte_t *) in; 104 | Unicode_t unicode; 105 | 106 | if (length <= 1 || length > 6) 107 | return *p; 108 | unicode = *p & (1 << 7-length)-1; 109 | while (--length) 110 | unicode <<= 6, unicode |= *++p & 0x3f; 111 | return unicode; 112 | } 113 | -------------------------------------------------------------------------------- /utf8.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef UTF8_H 3 | #define UTF8_H 4 | 5 | /* 6 | * UTF-8 encoding is used to represent actual Unicode characters 7 | * as well as the artificial values used as the delimiters of folded 8 | * blocks. One of these extended characters can be held in a Unicode_t 9 | * and passes IS_UNICODE(). True Unicode code points also satisfy 10 | * IS_CODEPOINT(). 11 | */ 12 | 13 | #define UNICODE_BAD (1u << 31) 14 | #define IS_UNICODE(u) ((u) < UNICODE_BAD) 15 | #define IS_CODEPOINT(u) ((u) < 0x10000) 16 | 17 | /* Some non-Unicode code points are used to represent function keys 18 | * and input errors. 19 | */ 20 | #define FUNCTION_KEY(x) (UNICODE_BAD + 1 + (x)) 21 | #define IS_FUNCTION_KEY(x) ((x) - FUNCTION_KEY(0) < 256) 22 | #define FUNCTION_UP FUNCTION_KEY(1) 23 | #define FUNCTION_DOWN FUNCTION_KEY(2) 24 | #define FUNCTION_RIGHT FUNCTION_KEY(3) 25 | #define FUNCTION_LEFT FUNCTION_KEY(4) 26 | #define FUNCTION_PGUP FUNCTION_KEY(5) 27 | #define FUNCTION_PGDOWN FUNCTION_KEY(6) 28 | #define FUNCTION_HOME FUNCTION_KEY(7) 29 | #define FUNCTION_END FUNCTION_KEY(8) 30 | #define FUNCTION_INSERT FUNCTION_KEY(9) 31 | #define FUNCTION_DELETE FUNCTION_KEY(10) 32 | #define FUNCTION_F(k) FUNCTION_KEY(20+(k)) 33 | #define FUNCTION_FKEYS 12 34 | 35 | #define ERROR_CODE(e) (FUNCTION_KEY(256) + (e)) 36 | #define IS_ERROR_CODE(e) ((e) >= ERROR_CODE(0)) 37 | #define ERROR_EOF ERROR_CODE(1) 38 | #define ERROR_CHANGED ERROR_CODE(2) /* must call _get_geometry()! */ 39 | #define ERROR_INPUT ERROR_CODE(3) 40 | #define ERROR_EMPTY ERROR_CODE(4) /* no input */ 41 | 42 | size_t unicode_utf8(char *, Unicode_t); 43 | size_t utf8_length(const char *, size_t max); 44 | size_t utf8_length_backwards(const char *, size_t max); 45 | Unicode_t utf8_unicode(const char *, size_t length); 46 | 47 | #define CONTROL(x) ((x)-'@') 48 | 49 | /* Huge code point values are used to bracket folded sections. */ 50 | #define FOLD_START 0x40000000 51 | #define FOLD_END 0x60000000 52 | #define IS_FOLDED(ch) ((ch) - FOLD_START < FOLD_END-FOLD_START) 53 | #define FOLDED_BYTES(ch) ((ch) & 0x1fffffff) 54 | 55 | extern Byte_t utf8_bytes[0x100]; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #include "all.h" 3 | 4 | ssize_t view_vprintf(struct view *view, const char *msg, va_list ap) 5 | { 6 | char buff[1024]; 7 | vsnprintf(buff, sizeof buff, msg, ap); 8 | return view_insert(view, buff, view->bytes, -1); 9 | } 10 | 11 | ssize_t view_printf(struct view *view, const char *msg, ...) 12 | { 13 | va_list ap; 14 | int result; 15 | 16 | va_start(ap, msg); 17 | result = view_vprintf(view, msg, ap); 18 | va_end(ap); 19 | return result; 20 | } 21 | 22 | size_t view_get_selection(struct view *view, position_t *offset, 23 | Boolean_t *append) 24 | { 25 | position_t cursor = locus_get(view, CURSOR); 26 | position_t mark = locus_get(view, MARK); 27 | 28 | if (mark == UNSET) 29 | if ((mark = cursor) < view->bytes) 30 | view_char(view, mark, &mark); 31 | if (append) 32 | *append = cursor >= mark; 33 | if (mark <= cursor) 34 | return cursor - (*offset = mark); 35 | return mark - (*offset = cursor); 36 | } 37 | 38 | char *view_extract(struct view *view, position_t offset, unsigned bytes) 39 | { 40 | char *str; 41 | 42 | if (!view) 43 | return NULL; 44 | if (offset > view->bytes) 45 | return NULL; 46 | if (offset + bytes > view->bytes) 47 | bytes = view->bytes - offset; 48 | if (!bytes) 49 | return NULL; 50 | str = allocate(bytes+1); 51 | str[view_get(view, str, offset, bytes)] = '\0'; 52 | return str; 53 | } 54 | 55 | char *view_extract_selection(struct view *view) 56 | { 57 | position_t offset; 58 | size_t bytes = view_get_selection(view, &offset, NULL); 59 | return view_extract(view, offset, bytes); 60 | } 61 | 62 | size_t view_delete_selection(struct view *view) 63 | { 64 | position_t offset; 65 | size_t bytes = view_get_selection(view, &offset, NULL); 66 | view_delete(view, offset, bytes); 67 | locus_set(view, MARK, UNSET); 68 | return bytes; 69 | } 70 | 71 | struct view *view_next(struct view *view) 72 | { 73 | struct view *new = view; 74 | do { 75 | if (new->next) 76 | new = new->next; 77 | else if (new->text->next) 78 | new = new->text->next->views; 79 | else 80 | new = text_list->views; 81 | } while (new != view && new->window); 82 | return new == view ? text_new() : new; 83 | } 84 | 85 | Unicode_t view_unicode(struct view *view, position_t offset, position_t *next) 86 | { 87 | Unicode_t ch = view_byte(view, offset); 88 | char *raw; 89 | size_t length; 90 | 91 | if (!IS_UNICODE(ch) || 92 | ch < 0x80 || 93 | view->text->flags & TEXT_NO_UTF8) { 94 | if (ch == '\r' && 95 | view->text->flags & TEXT_CRNL && 96 | view_byte(view, offset + 1) == '\n') { 97 | if (next) 98 | *next = offset + 2; 99 | return '\n'; 100 | } 101 | if (next) 102 | *next = offset + IS_UNICODE(ch); 103 | return ch; 104 | } 105 | 106 | length = view_raw(view, &raw, offset, 8); 107 | length = utf8_length(raw, length); 108 | if (next) 109 | *next = offset + length; 110 | return utf8_unicode(raw, length); 111 | } 112 | 113 | Unicode_t view_unicode_prior(struct view *view, position_t offset, 114 | position_t *prev) 115 | { 116 | Unicode_t ch = UNICODE_BAD; 117 | char *raw; 118 | 119 | if (offset) { 120 | ch = view_byte(view, --offset); 121 | if (IS_UNICODE(ch) && 122 | ch >= 0x80 && 123 | !(view->text->flags & TEXT_NO_UTF8)) { 124 | unsigned at = offset >= 7 ? offset-7 : 0; 125 | view_raw(view, &raw, at, offset-at+1); 126 | offset -= utf8_length_backwards(raw+offset-at, 127 | offset-at+1) - 1; 128 | ch = view_unicode(view, offset, NULL); 129 | } else if (ch == '\n' && 130 | view->text->flags & TEXT_CRNL && 131 | offset && 132 | view_byte(view, offset-1) == '\r') 133 | offset--; 134 | } 135 | if (prev) 136 | *prev = offset; 137 | return ch; 138 | } 139 | 140 | Unicode_t view_char(struct view *view, position_t offset, position_t *next) 141 | { 142 | position_t next0; 143 | Unicode_t ch = view_unicode(view, offset, &next0); 144 | 145 | if (!next) 146 | return ch; 147 | *next = next0; 148 | if (IS_FOLDED(ch)) { 149 | size_t fbytes = FOLDED_BYTES(ch); 150 | position_t next2; 151 | if (view_unicode(view, next0 + fbytes, &next2) == 152 | FOLD_END + fbytes) 153 | *next = next2; 154 | } 155 | return ch; 156 | } 157 | 158 | Unicode_t view_char_prior(struct view *view, position_t offset, 159 | position_t *prev) 160 | { 161 | Unicode_t ch = view_unicode_prior(view, offset, &offset), ch0; 162 | size_t fbytes; 163 | position_t offset0; 164 | 165 | if (ch >= FOLD_END && 166 | (fbytes = FOLDED_BYTES(ch)) <= offset && 167 | IS_FOLDED(ch0 = view_unicode_prior(view, offset - fbytes, 168 | &offset0)) && 169 | FOLDED_BYTES(ch0) == fbytes) { 170 | ch = ch0; 171 | offset = offset0; 172 | } 173 | if (prev) 174 | *prev = offset; 175 | return ch; 176 | } 177 | 178 | Boolean_t is_open_bracket(const char *brackets, Unicode_t ch) 179 | { 180 | if (ch >= 0x80) 181 | return FALSE; 182 | for (; *brackets; brackets += 2) 183 | if (*brackets == ch) 184 | return TRUE; 185 | return FALSE; 186 | } 187 | 188 | Boolean_t is_close_bracket(const char *brackets, Unicode_t ch) 189 | { 190 | if (ch >= 0x80) 191 | return FALSE; 192 | for (brackets++; *brackets; brackets += 2) 193 | if (*brackets == ch) 194 | return TRUE; 195 | return FALSE; 196 | } 197 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef UTIL_H 3 | #define UTIL_H 4 | 5 | /* Utilities */ 6 | 7 | struct view; 8 | 9 | /* find.c */ 10 | position_t find_line_start(struct view *, position_t); 11 | position_t find_line_end(struct view *, position_t); 12 | position_t find_paragraph_start(struct view *, position_t); 13 | position_t find_paragraph_end(struct view *, position_t); 14 | position_t find_line_up(struct view *, position_t); 15 | position_t find_line_down(struct view *, position_t); 16 | position_t find_space(struct view *, position_t); 17 | position_t find_space_prior(struct view *, position_t); 18 | position_t find_nonspace(struct view *, position_t); 19 | position_t find_nonspace_prior(struct view *, position_t); 20 | position_t find_word_start(struct view *, position_t); 21 | position_t find_word_end(struct view *, position_t); 22 | position_t find_id_start(struct view *, position_t); 23 | position_t find_id_end(struct view *, position_t); 24 | position_t find_sentence_start(struct view *, position_t); 25 | position_t find_sentence_end(struct view *, position_t); 26 | sposition_t find_corresponding_bracket(struct view *, position_t); 27 | position_t find_line_number(struct view *, unsigned line); 28 | unsigned current_line_number(struct view *, position_t); 29 | position_t find_row_bytes(struct view *, position_t, 30 | unsigned column, unsigned columns); 31 | unsigned find_column(unsigned *row, struct view *, position_t linestart, 32 | position_t offset, unsigned start_column); 33 | sposition_t find_string(struct view *, const char *, position_t); 34 | 35 | const char *path_format(const char *); /* file.c */ 36 | 37 | void find_tag(struct view *); /* tags.c */ 38 | 39 | ssize_t view_vprintf(struct view *, const char *, va_list); 40 | ssize_t view_printf(struct view *, const char *, ...); 41 | size_t view_get_selection(struct view *, position_t *offset, Boolean_t *append); 42 | char *view_extract(struct view *, position_t, unsigned bytes); 43 | char *view_extract_selection(struct view *); 44 | size_t view_delete_selection(struct view *); 45 | struct view *view_next(struct view *); 46 | 47 | Unicode_t view_unicode(struct view *, position_t, size_t *); 48 | Unicode_t view_unicode_prior(struct view *, position_t, position_t *prev); 49 | Unicode_t view_char(struct view *, position_t, size_t *); 50 | Unicode_t view_char_prior(struct view *, position_t, position_t *prev); 51 | Boolean_t is_open_bracket(const char *, Unicode_t); 52 | Boolean_t is_close_bracket(const char *, Unicode_t); 53 | 54 | void keyword_init(struct text *); /* keyword.c */ 55 | Boolean_t is_keyword(struct view *, position_t); 56 | 57 | INLINE Boolean_t is_wordch(Unicode_t ch) 58 | { 59 | return ch > 0x100 && ch < FOLD_START || IS_CODEPOINT(ch) && isalnum(ch); 60 | } 61 | 62 | INLINE Boolean_t is_idch(Unicode_t ch) 63 | { 64 | return ch == '_' || is_wordch(ch); 65 | } 66 | 67 | INLINE unsigned char_columns(Unicode_t ch, unsigned column, unsigned tabstop) 68 | { 69 | if (ch == '\t') 70 | return tabstop - column % tabstop; 71 | if (ch < ' ' || ch == 0x7f || IS_FOLDED(ch)) 72 | return 2; /* ^X or folded <> */ 73 | return 1; 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /window.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2007, 2008 Peter Klausler. See COPYING for license. */ 2 | #ifndef WINDOW_H 3 | #define WINDOW_H 4 | 5 | /* Windows */ 6 | 7 | struct window *window_raise(struct view *); 8 | struct window *window_activate(struct view *); 9 | struct window *window_after(struct view *, struct view *, int vertical); 10 | struct window *window_below(struct view *, struct view *, unsigned rows); 11 | struct window *window_replace(struct view *, struct view *); 12 | void window_destroy(struct window *); 13 | void window_next(struct view *); 14 | void window_index(int); 15 | void window_hint_deleting(struct window *, position_t, size_t); 16 | void window_hint_inserted(struct window *, position_t, size_t); 17 | struct window *window_recenter(struct view *); 18 | void window_page_up(struct view *); 19 | void window_page_down(struct view *); 20 | void window_beep(struct view *); 21 | Unicode_t window_getch(void); 22 | struct view *window_current_view(void); 23 | unsigned window_columns(struct window *); 24 | void windows_end(void); 25 | void windows_end_display(void); 26 | 27 | #endif 28 | --------------------------------------------------------------------------------