├── LICENSE ├── README.md ├── README_ZH.md ├── SConscript ├── editors ├── vi.c ├── vi_utils.c └── vi_utils.h └── vi-doc.md /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vi for RT-Thread 2 | 3 | [中文页](README_ZH.md) | English 4 | 5 | ## 1. Introduction 6 | 7 | vi is a well-known text editor under Unix-like platforms. This software package is a port of vi to RT-Thread operating system, derived from the vi editor in busybox, and only implements basic editing functions. 8 | 9 | ### 1.1. File structure 10 | 11 | | Name | Description | 12 | | ---- | ---- | 13 | | vi.c | vi editor source file | 14 | 15 | ### 1.2 License 16 | 17 | vi follows the LGPLv2.1 license, see the `LICENSE` file for details. 18 | 19 | ## 2. How to Obtain 20 | 21 | RT-Thread online packages ---> 22 | miscellaneous packages ---> 23 | [*] vi: A screen-oriented text editor 24 | 25 | ## 3. Contact information 26 | 27 | Maintainer: [Meco Man](https://github.com/mysterywolf) 28 | 29 | Homepage: 30 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # vi for RT-Thread 2 | 3 | 中文页 | [English](README.md) 4 | 5 | ## 1. 简介 ## 6 | 7 | vi 是类 unix 平台下著名的的文本编辑器。本软件包是 vi 对 RT-Thread 操作系统的移植,源自 busybox 中的 vi 编辑器,仅实现基本编辑功能。 8 | 9 | 官方仓库:https://github.com/mirror/busybox/blob/master/editors/vi.c 10 | 11 | 维护参考:https://gitee.com/mysterywolf/busybox/blob/master/editors/vi.c 12 | 13 | 教学视频:https://www.bilibili.com/video/BV18f4y1S7St 14 | 15 | ### 1.1. 文件结构 ### 16 | 17 | | 名称 | 说明 | 18 | | ---- | ---- | 19 | | vi.c | vi 编辑器源文件 | 20 | 21 | ### 1.2 许可证 ### 22 | 23 | vi 遵循 LGPLv2.1 许可,详见 `LICENSE` 文件。 24 | 25 | ## 2. 获取方式 ## 26 | 27 | RT-Thread online packages ---> 28 | miscellaneous packages ---> 29 | [*] vi: A screen-oriented text editor 30 | 31 | ## 3. 联系方式 ## 32 | 33 | 维护:[Meco Man](https://github.com/mysterywolf) 34 | 35 | 主页: 36 | -------------------------------------------------------------------------------- /SConscript: -------------------------------------------------------------------------------- 1 | from building import * 2 | 3 | src = Glob('editors/*.c') 4 | group = DefineGroup('vi', src, depend = ['PKG_USING_VI']) 5 | Return('group') 6 | -------------------------------------------------------------------------------- /editors/vi.c: -------------------------------------------------------------------------------- 1 | /* vi: set sw=4 ts=4: */ 2 | /* 3 | * tiny vi.c: A small 'vi' clone 4 | * Copyright (C) 2000, 2001 Sterling Huxley 5 | * 6 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. 7 | */ 8 | 9 | //'vi' is a text editor. More specifically, it is the One True 10 | //text editor . It does, however, have a rather steep 11 | //learning curve. If you are not already comfortable with 'vi' 12 | //you may wish to use something else. 13 | 14 | #include 15 | #include 16 | #include "vi_utils.h" 17 | 18 | /* This struct is deliberately not defined. */ 19 | /* See docs/keep_data_small.txt */ 20 | struct globals; 21 | /* '*const' ptr makes gcc optimize code much better. 22 | * Magic prevents ptr_to_globals from going into rodata. 23 | * If you want to assign a value, use SET_PTR_TO_GLOBALS(x) */ 24 | struct globals *ptr_to_globals; 25 | 26 | #if ENABLE_LOCALE_SUPPORT 27 | 28 | #if ENABLE_FEATURE_VI_8BIT 29 | //FIXME: this does not work properly for Unicode anyway 30 | # define Isprint(c) (isprint)(c) 31 | #else 32 | # define Isprint(c) isprint_asciionly(c) 33 | #endif 34 | 35 | #else 36 | 37 | /* 0x9b is Meta-ESC */ 38 | #if ENABLE_FEATURE_VI_8BIT 39 | # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b) 40 | #else 41 | # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f) 42 | #endif 43 | 44 | #endif 45 | 46 | #ifdef RT_USING_POSIX_TERMIOS 47 | #define isbackspace(c) ((c) == term_orig.c_cc[VERASE] || (c) == 8 || (c) == 127) 48 | #else 49 | #define isbackspace(c) ((c) == 8 || (c) == 127) 50 | #endif /* RT_USING_POSIX_TERMIOS */ 51 | 52 | enum { 53 | MAX_TABSTOP = 32, // sanity limit 54 | // User input len. Need not be extra big. 55 | // Lines in file being edited *can* be bigger than this. 56 | MAX_INPUT_LEN = 128, 57 | // Sanity limits. We have only one buffer of this size. 58 | MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN, 59 | MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN, 60 | }; 61 | 62 | // VT102 ESC sequences. 63 | // See "Xterm Control Sequences" 64 | // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 65 | #define ESC "\033" 66 | // Inverse/Normal text 67 | #define ESC_BOLD_TEXT ESC"[7m" 68 | #define ESC_NORM_TEXT ESC"[m" 69 | // Bell 70 | #define ESC_BELL "\007" 71 | // Clear-to-end-of-line 72 | #define ESC_CLEAR2EOL ESC"[K" 73 | // Clear-to-end-of-screen. 74 | // (We use default param here. 75 | // Full sequence is "ESC [ J", 76 | // is 0/1/2 = "erase below/above/all".) 77 | #define ESC_CLEAR2EOS ESC"[J" 78 | // Cursor to given coordinate (1,1: top left) 79 | #define ESC_SET_CURSOR_POS ESC"[%u;%uH" 80 | #define ESC_SET_CURSOR_TOPLEFT ESC"[H" 81 | //UNUSED 82 | //// Cursor up and down 83 | //#define ESC_CURSOR_UP ESC"[A" 84 | //#define ESC_CURSOR_DOWN "\n" 85 | 86 | #if ENABLE_FEATURE_VI_DOT_CMD 87 | static const char modifying_cmds[] ALIGN1 = "aAcCdDiIJoOpPrRs""xX<>~"; 88 | #endif 89 | static const char *_show_usage = "Usage: vi [FILE]\n"; 90 | 91 | enum { 92 | YANKONLY = FALSE, 93 | YANKDEL = TRUE, 94 | FORWARD = 1, // code depends on "1" for array index 95 | BACK = -1, // code depends on "-1" for array index 96 | LIMITED = 0, // char_search() only current line 97 | FULL = 1, // char_search() to the end/beginning of entire text 98 | PARTIAL = 0, // buffer contains partial line 99 | WHOLE = 1, // buffer contains whole lines 100 | MULTI = 2, // buffer may include newlines 101 | 102 | S_BEFORE_WS = 1, // used in skip_thing() for moving "dot" 103 | S_TO_WS = 2, // used in skip_thing() for moving "dot" 104 | S_OVER_WS = 3, // used in skip_thing() for moving "dot" 105 | S_END_PUNCT = 4, // used in skip_thing() for moving "dot" 106 | S_END_ALNUM = 5, // used in skip_thing() for moving "dot" 107 | 108 | C_END = -1, // cursor is at end of line due to '$' command 109 | }; 110 | 111 | 112 | /* vi.c expects chars to be unsigned. */ 113 | /* busybox build system provides that, but it's better */ 114 | /* to audit and fix the source */ 115 | struct globals { 116 | /* many references - keep near the top of globals */ 117 | char *text, *end; // pointers to the user data in memory 118 | char *dot; // where all the action takes place 119 | int text_size; // size of the allocated buffer 120 | 121 | // the rest 122 | #if ENABLE_FEATURE_VI_SETOPTS 123 | smallint vi_setops; // set by setops() 124 | #define VI_AUTOINDENT (1 << 0) 125 | #define VI_EXPANDTAB (1 << 1) 126 | #define VI_ERR_METHOD (1 << 2) 127 | #define VI_IGNORECASE (1 << 3) 128 | #define VI_SHOWMATCH (1 << 4) 129 | #define VI_TABSTOP (1 << 5) 130 | #define autoindent (vi_setops & VI_AUTOINDENT) 131 | #define expandtab (vi_setops & VI_EXPANDTAB ) 132 | #define err_method (vi_setops & VI_ERR_METHOD) // indicate error with beep or flash 133 | #define ignorecase (vi_setops & VI_IGNORECASE) 134 | #define showmatch (vi_setops & VI_SHOWMATCH ) 135 | // order of constants and strings must match 136 | #define OPTS_STR \ 137 | "ai\0""autoindent\0" \ 138 | "et\0""expandtab\0" \ 139 | "fl\0""flash\0" \ 140 | "ic\0""ignorecase\0" \ 141 | "sm\0""showmatch\0" \ 142 | "ts\0""tabstop\0" 143 | #else 144 | #define autoindent (0) 145 | #define expandtab (0) 146 | #define err_method (0) 147 | #define ignorecase (0) 148 | #endif 149 | 150 | #if ENABLE_FEATURE_VI_READONLY 151 | smallint readonly_mode; 152 | #define SET_READONLY_FILE(flags) ((flags) |= 0x01) 153 | #define SET_READONLY_MODE(flags) ((flags) |= 0x02) 154 | #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe) 155 | #else 156 | #define SET_READONLY_FILE(flags) ((void)0) 157 | #define SET_READONLY_MODE(flags) ((void)0) 158 | #define UNSET_READONLY_FILE(flags) ((void)0) 159 | #endif 160 | 161 | smallint editing; // >0 while we are editing a file 162 | // [code audit says "can be 0, 1 or 2 only"] 163 | smallint cmd_mode; // 0=command 1=insert 2=replace 164 | int modified_count; // buffer contents changed if !0 165 | int last_modified_count; // = -1; 166 | int cmdline_filecnt; // how many file names on cmd line 167 | int cmdcnt; // repetition count 168 | char *rstart; // start of text in Replace mode 169 | unsigned rows, columns; // the terminal screen is this size 170 | #if ENABLE_FEATURE_VI_ASK_TERMINAL 171 | int get_rowcol_error; 172 | #endif 173 | int crow, ccol; // cursor is on Crow x Ccol 174 | int offset; // chars scrolled off the screen to the left 175 | int have_status_msg; // is default edit status needed? 176 | // [don't make smallint!] 177 | int last_status_cksum; // hash of current status line 178 | char *current_filename; 179 | #if ENABLE_FEATURE_VI_COLON_EXPAND 180 | char *alt_filename; 181 | #endif 182 | char *screenbegin; // index into text[], of top line on the screen 183 | char *screen; // pointer to the virtual screen buffer 184 | int screensize; // and its size 185 | int tabstop; 186 | int last_search_char; // last char searched for (int because of Unicode) 187 | smallint last_search_cmd; // command used to invoke last char search 188 | #if ENABLE_FEATURE_VI_UNDO_QUEUE 189 | char undo_queue_state; // One of UNDO_INS, UNDO_DEL, UNDO_EMPTY 190 | #endif 191 | 192 | #if ENABLE_FEATURE_VI_DOT_CMD 193 | smallint adding2q; // are we currently adding user input to q 194 | int lmc_len; // length of last_modifying_cmd 195 | char *ioq, *ioq_start; // pointer to string for get_one_char to "read" 196 | int dotcnt; // number of times to repeat '.' command 197 | #endif 198 | #if ENABLE_FEATURE_VI_SEARCH 199 | char *last_search_pattern; // last pattern from a '/' or '?' search 200 | #endif 201 | #if ENABLE_FEATURE_VI_SETOPTS 202 | int char_insert__indentcol; // column of recent autoindent or 0 203 | int newindent; // autoindent value for 'O'/'cc' commands 204 | // or -1 to use indent from previous line 205 | #endif 206 | smallint cmd_error; 207 | 208 | /* former statics */ 209 | #if ENABLE_FEATURE_VI_YANKMARK 210 | char *edit_file__cur_line; 211 | #endif 212 | int refresh__old_offset; 213 | int format_edit_status__tot; 214 | 215 | /* a few references only */ 216 | #if ENABLE_FEATURE_VI_YANKMARK 217 | smalluint YDreg;//,Ureg;// default delete register and orig line for "U" 218 | #define Ureg 27 219 | char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27 220 | char regtype[28]; // buffer type: WHOLE, MULTI or PARTIAL 221 | char *mark[28]; // user marks points somewhere in text[]- a-z and previous context '' 222 | #endif 223 | #ifdef RT_USING_POSIX_TERMIOS 224 | struct termios term_orig; // remember what the cooked mode was 225 | #endif 226 | int cindex; // saved character index for up/down motion 227 | smallint keep_index; // retain saved character index 228 | #if ENABLE_FEATURE_VI_COLON 229 | char *initial_cmds[3]; // currently 2 entries, NULL terminated 230 | #endif 231 | char readbuffer[KEYCODE_BUFFER_SIZE]; 232 | #define STATUS_BUFFER_LEN 200 233 | char status_buffer[STATUS_BUFFER_LEN]; // messages to the user 234 | #if ENABLE_FEATURE_VI_DOT_CMD 235 | char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "." 236 | #endif 237 | char get_input_line__buf[MAX_INPUT_LEN]; /* former static */ 238 | 239 | char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2]; 240 | #if ENABLE_FEATURE_VI_UNDO 241 | // undo_push() operations 242 | #define UNDO_INS 0 243 | #define UNDO_DEL 1 244 | #define UNDO_INS_CHAIN 2 245 | #define UNDO_DEL_CHAIN 3 246 | # if ENABLE_FEATURE_VI_UNDO_QUEUE 247 | #define UNDO_INS_QUEUED 4 248 | #define UNDO_DEL_QUEUED 5 249 | # endif 250 | // Pass-through flags for functions that can be undone 251 | #define NO_UNDO 0 252 | #define ALLOW_UNDO 1 253 | #define ALLOW_UNDO_CHAIN 2 254 | # if ENABLE_FEATURE_VI_UNDO_QUEUE 255 | #define ALLOW_UNDO_QUEUED 3 256 | # else 257 | // If undo queuing disabled, don't invoke the missing queue logic 258 | #define ALLOW_UNDO_QUEUED ALLOW_UNDO 259 | # endif 260 | 261 | struct undo_object { 262 | struct undo_object *prev; // Linking back avoids list traversal (LIFO) 263 | int start; // Offset where the data should be restored/deleted 264 | int length; // total data size 265 | uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped 266 | char undo_text[1]; // text that was deleted (if deletion) 267 | } *undo_stack_tail; 268 | # if ENABLE_FEATURE_VI_UNDO_QUEUE 269 | #define UNDO_USE_SPOS 32 270 | #define UNDO_EMPTY 64 271 | char *undo_queue_spos; // Start position of queued operation 272 | int undo_q; 273 | char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX]; 274 | # endif 275 | #endif /* ENABLE_FEATURE_VI_UNDO */ 276 | 277 | }; 278 | #define G (*ptr_to_globals ) 279 | #define text (G.text ) 280 | #define text_size (G.text_size ) 281 | #define end (G.end ) 282 | #define dot (G.dot ) 283 | #define reg (G.reg ) 284 | #define vi_setops (G.vi_setops ) 285 | #define editing (G.editing ) 286 | #define cmd_mode (G.cmd_mode ) 287 | #define modified_count (G.modified_count ) 288 | #define last_modified_count (G.last_modified_count) 289 | #define cmdline_filecnt (G.cmdline_filecnt ) 290 | #define cmdcnt (G.cmdcnt ) 291 | #define rstart (G.rstart ) 292 | #define rows (G.rows ) 293 | #define columns (G.columns ) 294 | #define crow (G.crow ) 295 | #define ccol (G.ccol ) 296 | #define offset (G.offset ) 297 | #define status_buffer (G.status_buffer ) 298 | #define have_status_msg (G.have_status_msg ) 299 | #define last_status_cksum (G.last_status_cksum ) 300 | #define current_filename (G.current_filename ) 301 | #define alt_filename (G.alt_filename ) 302 | #define screen (G.screen ) 303 | #define screensize (G.screensize ) 304 | #define screenbegin (G.screenbegin ) 305 | #define tabstop (G.tabstop ) 306 | #define last_search_char (G.last_search_char ) 307 | #define last_search_cmd (G.last_search_cmd ) 308 | #if ENABLE_FEATURE_VI_READONLY 309 | #define readonly_mode (G.readonly_mode ) 310 | #else 311 | #define readonly_mode 0 312 | #endif 313 | #define adding2q (G.adding2q ) 314 | #define lmc_len (G.lmc_len ) 315 | #define ioq (G.ioq ) 316 | #define ioq_start (G.ioq_start ) 317 | #define dotcnt (G.dotcnt ) 318 | #define last_search_pattern (G.last_search_pattern) 319 | #define char_insert__indentcol (G.char_insert__indentcol) 320 | #define newindent (G.newindent ) 321 | #define cmd_error (G.cmd_error ) 322 | #define edit_file__cur_line (G.edit_file__cur_line) 323 | #define refresh__old_offset (G.refresh__old_offset) 324 | #define format_edit_status__tot (G.format_edit_status__tot) 325 | #define YDreg (G.YDreg ) 326 | //#define Ureg (G.Ureg ) 327 | #define regtype (G.regtype ) 328 | #define mark (G.mark ) 329 | #define restart (G.restart ) 330 | #ifdef RT_USING_POSIX_TERMIOS 331 | #define term_orig (G.term_orig ) 332 | #endif 333 | #define cindex (G.cindex ) 334 | #define keep_index (G.keep_index ) 335 | #define initial_cmds (G.initial_cmds ) 336 | #define readbuffer (G.readbuffer ) 337 | #define scr_out_buf (G.scr_out_buf ) 338 | #define last_modifying_cmd (G.last_modifying_cmd ) 339 | #define get_input_line__buf (G.get_input_line__buf) 340 | #if ENABLE_FEATURE_VI_UNDO 341 | #define undo_stack_tail (G.undo_stack_tail ) 342 | # if ENABLE_FEATURE_VI_UNDO_QUEUE 343 | #define undo_queue_state (G.undo_queue_state ) 344 | #define undo_q (G.undo_q ) 345 | #define undo_queue (G.undo_queue ) 346 | #define undo_queue_spos (G.undo_queue_spos ) 347 | # endif 348 | #endif 349 | 350 | #define INIT_G() do { \ 351 | SET_PTR_TO_GLOBALS(vi_zalloc(sizeof(G))); \ 352 | last_modified_count --; \ 353 | /* "" but has space for 2 chars: */ \ 354 | IF_FEATURE_VI_SEARCH(last_search_pattern = vi_zalloc(2);) \ 355 | tabstop = 8; \ 356 | IF_FEATURE_VI_SETOPTS(newindent--;) \ 357 | } while (0) 358 | 359 | static void edit_file(char *); // edit one file 360 | static void do_cmd(int); // execute a command 361 | static int next_tabstop(int); 362 | static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot 363 | static char *begin_line(char *); // return pointer to cur line B-o-l 364 | static char *end_line(char *); // return pointer to cur line E-o-l 365 | static char *prev_line(char *); // return pointer to prev line B-o-l 366 | static char *next_line(char *); // return pointer to next line B-o-l 367 | static char *end_screen(void); // get pointer to last char on screen 368 | static int count_lines(char *, char *); // count line from start to stop 369 | static char *find_line(int); // find begining of line #li 370 | static char *move_to_col(char *, int); // move "p" to column l 371 | static void dot_left(void); // move dot left- dont leave line 372 | static void dot_right(void); // move dot right- dont leave line 373 | static void dot_begin(void); // move dot to B-o-l 374 | static void dot_end(void); // move dot to E-o-l 375 | static void dot_next(void); // move dot to next line B-o-l 376 | static void dot_prev(void); // move dot to prev line B-o-l 377 | static void dot_scroll(int, int); // move the screen up or down 378 | static void dot_skip_over_ws(void); // move dot pat WS 379 | static char *bound_dot(char *); // make sure text[0] <= P < "end" 380 | static void new_screen(int, int); // malloc virtual screen memory 381 | #if !ENABLE_FEATURE_VI_UNDO 382 | #define char_insert(a,b,c) char_insert(a,b) 383 | #endif 384 | static char *char_insert(char *, char, int); // insert the char c at 'p' 385 | // might reallocate text[]! use p += stupid_insert(p, ...), 386 | // and be careful to not use pointers into potentially freed text[]! 387 | static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' 388 | static int find_range(char **start, char **stop, int cmd); // return pointers for an object 389 | static int st_test(char *, int, int, char *); // helper for skip_thing() 390 | static char *skip_thing(char *, int, int, int); // skip some object 391 | static char *find_pair(char *, const char); // find matching pair () [] {} 392 | #if !ENABLE_FEATURE_VI_UNDO 393 | #define text_hole_delete(a,b,c) text_hole_delete(a,b) 394 | #endif 395 | static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole 396 | // might reallocate text[]! use p += text_hole_make(p, ...), 397 | // and be careful to not use pointers into potentially freed text[]! 398 | static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole 399 | #if !ENABLE_FEATURE_VI_UNDO 400 | #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d) 401 | #endif 402 | static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete 403 | static void show_help(void); // display some help info 404 | static void rawmode(void); // set "raw" mode on tty 405 | static void cookmode(void); // return to "cooked" mode on tty 406 | // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready) 407 | static int mysleep(int); 408 | static int readit(void); 409 | #if ENABLE_FEATURE_VI_DOT_CMD 410 | static int get_one_char(void); // read 1 char from stdin 411 | #else 412 | # define get_one_char() readit() 413 | #endif 414 | static int get_motion_char(void); // Get type of thing to operate on and adjust count 415 | static int file_insert(const char *, char *, int); // file_insert might reallocate text[]! 416 | static int file_write(char *, char *, char *); 417 | static void screen_erase(void); 418 | static void go_bottom_and_clear_to_eol(void); 419 | static void standout_start(void); // send "start reverse video" sequence 420 | static void standout_end(void); // send "end reverse video" sequence 421 | static void flash(int); // flash the terminal screen 422 | static void show_status_line(void); // put a message on the bottom line 423 | static void status_line(const char *, ...); // print to status buf 424 | static void status_line_bold(const char *, ...); 425 | static void status_line_bold_errno(const char *fn); 426 | static void not_implemented(const char *); // display "Not implemented" message 427 | static int format_edit_status(void); // format file status on status line 428 | static void redraw(int); // force a full screen refresh 429 | static char *format_line(char* /*, int*/); 430 | static void refresh(int); // update the terminal from screen[] 431 | static void indicate_error(void); // use flash or beep to indicate error 432 | static void Hit_Return(void); 433 | #if ENABLE_FEATURE_VI_SEARCH 434 | static char *char_search(char *, const char *, int); // search for pattern starting at p 435 | #endif 436 | #if ENABLE_FEATURE_VI_COLON 437 | static char *get_address(char *p, int *b, int *e, unsigned int *got); // get colon addr, if present 438 | #endif 439 | static void colon(char *); // execute the "colon" mode cmds 440 | #if ENABLE_FEATURE_VI_DOT_CMD 441 | static void start_new_cmd_q(char); // new queue for command 442 | static void end_cmd_q(void); // stop saving input chars 443 | #else 444 | #define end_cmd_q() ((void)0) 445 | #endif 446 | #if ENABLE_FEATURE_VI_SETOPTS 447 | static void showmatching(char *); // show the matching pair () [] {} 448 | #endif 449 | #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) 450 | // might reallocate text[]! use p += string_insert(p, ...), 451 | // and be careful to not use pointers into potentially freed text[]! 452 | # if !ENABLE_FEATURE_VI_UNDO 453 | #define string_insert(a,b,c) string_insert(a,b) 454 | # endif 455 | static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p' 456 | #endif 457 | #if ENABLE_FEATURE_VI_YANKMARK 458 | static char *text_yank(char *, char *, int, int); // save copy of "p" into a register 459 | static char what_reg(void); // what is letter of current YDreg 460 | static void check_context(char); // remember context for '' command 461 | #endif 462 | #if ENABLE_FEATURE_VI_UNDO 463 | static void flush_undo_data(void); 464 | static void undo_push(char *, unsigned, int); // push an operation on the undo stack 465 | static void undo_push_insert(char *, int, int); // convenience function 466 | static void undo_pop(void); // undo the last operation 467 | # if ENABLE_FEATURE_VI_UNDO_QUEUE 468 | static void undo_queue_commit(void); // flush any queued objects to the undo stack 469 | # else 470 | # define undo_queue_commit() ((void)0) 471 | # endif 472 | #else 473 | #define flush_undo_data() ((void)0) 474 | #define undo_queue_commit() ((void)0) 475 | #endif 476 | 477 | static struct optparse options; 478 | 479 | static int vi_main(int argc, char **argv) 480 | { 481 | int c; 482 | char *file_name; 483 | 484 | if(vi_mem_init() == 0) 485 | { 486 | LOG_E("vi initialization failed.\r\n"); 487 | return -1; 488 | } 489 | 490 | INIT_G(); 491 | 492 | #if ENABLE_FEATURE_VI_UNDO 493 | /* undo_stack_tail = NULL; - already is */ 494 | #if ENABLE_FEATURE_VI_UNDO_QUEUE 495 | undo_queue_state = UNDO_EMPTY; 496 | /* undo_q = 0; - already is */ 497 | #endif 498 | #endif 499 | 500 | #ifdef NO_SUCH_APPLET_YET 501 | // if we aren't "vi", we are "view" 502 | if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) { 503 | SET_READONLY_MODE(readonly_mode); 504 | } 505 | #endif 506 | 507 | // 0: all of our options are disabled by default in vim 508 | //vi_setops = 0; 509 | // 1- process EXINIT variable from environment 510 | // 2- if EXINIT is unset process $HOME/.exrc file (not inplemented yet) 511 | // 3- process command line args 512 | #if ENABLE_FEATURE_VI_COLON 513 | { 514 | char *p = getenv("EXINIT"); 515 | if (p && *p) 516 | initial_cmds[0] = vi_strndup(p, MAX_INPUT_LEN); 517 | } 518 | #endif 519 | optparse_init(&options, argc, argv); 520 | while ((c = optparse(&options, 521 | "RHh" IF_FEATURE_VI_COLON("c:"))) != -1) { 522 | switch (c) { 523 | #if ENABLE_FEATURE_VI_READONLY 524 | case 'R': // Read-only flag 525 | SET_READONLY_MODE(readonly_mode); 526 | break; 527 | #endif 528 | #if ENABLE_FEATURE_VI_COLON 529 | case 'c': // cmd line vi command 530 | if (*options.optarg) 531 | initial_cmds[initial_cmds[0] != NULL] = vi_strndup(options.optarg, MAX_INPUT_LEN); 532 | break; 533 | #endif 534 | case 'H': 535 | show_help(); 536 | // fall through 537 | default: 538 | vi_puts(_show_usage); 539 | goto vi_exit; 540 | } 541 | } 542 | 543 | options.argv += options.optind; 544 | cmdline_filecnt = argc - options.optind; 545 | options.optind = 0; 546 | file_name = optparse_arg(&options); 547 | if(file_name == NULL) 548 | { 549 | vi_puts(_show_usage); 550 | goto vi_exit; 551 | } 552 | 553 | // "Save cursor, use alternate screen buffer, clear screen" 554 | vi_puts(ESC"[?1049h"); 555 | // This is the main file handling loop 556 | while (1) 557 | { 558 | edit_file(file_name); // might be NULL on 1st iteration 559 | // NB: optind can be changed by ":next" and ":rewind" commands 560 | options.optind++; 561 | file_name = optparse_arg(&options); 562 | if (options.optind >= cmdline_filecnt) 563 | break; 564 | } 565 | // "Use normal screen buffer, restore cursor" 566 | vi_puts(ESC"[?1049l"); 567 | 568 | vi_exit: 569 | vi_free(text); 570 | vi_free(screen); 571 | vi_free(current_filename); 572 | #if ENABLE_FEATURE_VI_DOT_CMD 573 | vi_free(ioq_start); 574 | #endif 575 | #if ENABLE_FEATURE_VI_SEARCH 576 | vi_free(last_search_pattern); 577 | #endif 578 | vi_free(ptr_to_globals); 579 | 580 | vi_mem_release(); 581 | return 0; 582 | } 583 | MSH_CMD_EXPORT_ALIAS(vi_main, vi, a screen-oriented text editor); 584 | 585 | #if ENABLE_FEATURE_VI_COLON_EXPAND 586 | static void init_filename(char *fn) 587 | { 588 | char *copy = vi_strdup(fn); 589 | 590 | if (current_filename == NULL) { 591 | current_filename = copy; 592 | } else { 593 | vi_free(alt_filename); 594 | alt_filename = copy; 595 | } 596 | } 597 | #else 598 | # define init_filename(f) ((void)(0)) 599 | #endif 600 | 601 | static void update_filename(char *fn) 602 | { 603 | #if ENABLE_FEATURE_VI_COLON_EXPAND 604 | if (fn == NULL) 605 | return; 606 | 607 | if (current_filename == NULL || strcmp(fn, current_filename) != 0) { 608 | vi_free(alt_filename); 609 | alt_filename = current_filename; 610 | current_filename = vi_strdup(fn); 611 | } 612 | #else 613 | if (fn != current_filename) { 614 | vi_free(current_filename); 615 | current_filename = vi_strdup(fn); 616 | } 617 | #endif 618 | } 619 | 620 | /* read text from file or create an empty buf */ 621 | /* will also update current_filename */ 622 | static int init_text_buffer(char *fn) 623 | { 624 | int rc; 625 | 626 | flush_undo_data(); 627 | modified_count = 0; 628 | last_modified_count = -1; 629 | #if ENABLE_FEATURE_VI_YANKMARK 630 | /* init the marks */ 631 | rt_memset(mark, 0, sizeof(mark)); 632 | #endif 633 | 634 | /* allocate/reallocate text buffer */ 635 | vi_free(text); 636 | text_size = 10240; 637 | screenbegin = dot = end = text = vi_zalloc(text_size); 638 | 639 | update_filename(fn); 640 | rc = file_insert(fn, text, 1); 641 | if (rc < 0) { 642 | // file doesnt exist. Start empty buf with dummy line 643 | char_insert(text, '\n', NO_UNDO); 644 | } 645 | return rc; 646 | } 647 | 648 | #if ENABLE_FEATURE_VI_WIN_RESIZE 649 | static int query_screen_dimensions(void) 650 | { 651 | int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows); 652 | if (rows > MAX_SCR_ROWS) 653 | rows = MAX_SCR_ROWS; 654 | if (columns > MAX_SCR_COLS) 655 | columns = MAX_SCR_COLS; 656 | return err; 657 | } 658 | #else 659 | static int query_screen_dimensions(void) { return 0; } 660 | #endif 661 | 662 | static void edit_file(char *fn) 663 | { 664 | #if ENABLE_FEATURE_VI_YANKMARK 665 | #define cur_line edit_file__cur_line 666 | #endif 667 | int c; 668 | 669 | editing = 1; // 0 = exit, 1 = one file, 2 = multiple files 670 | rawmode(); 671 | rows = 24; 672 | columns = 80; 673 | IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions(); 674 | #if ENABLE_FEATURE_VI_ASK_TERMINAL 675 | if (G.get_rowcol_error /* TODO? && no input on stdin */) { 676 | uint64_t k; 677 | vi_puts(ESC"[999;999H" ESC"[6n"); 678 | k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100); 679 | if ((int32_t)k == KEYCODE_CURSOR_POS) { 680 | uint32_t rc = (k >> 32); 681 | columns = (rc & 0x7fff); 682 | if (columns > MAX_SCR_COLS) 683 | columns = MAX_SCR_COLS; 684 | rows = ((rc >> 16) & 0x7fff); 685 | if (rows > MAX_SCR_ROWS) 686 | rows = MAX_SCR_ROWS; 687 | } 688 | } 689 | #endif 690 | new_screen(rows, columns); // get memory for virtual screen 691 | init_text_buffer(fn); 692 | 693 | #if ENABLE_FEATURE_VI_YANKMARK 694 | YDreg = 26; // default Yank/Delete reg 695 | // Ureg = 27; - const // hold orig line for "U" cmd 696 | mark[26] = mark[27] = text; // init "previous context" 697 | #endif 698 | 699 | crow = 0; 700 | ccol = 0; 701 | cmd_mode = 0; // 0=command 1=insert 2='R'eplace 702 | cmdcnt = 0; 703 | offset = 0; // no horizontal offset 704 | c = '\0'; 705 | #if ENABLE_FEATURE_VI_DOT_CMD 706 | vi_free(ioq_start); 707 | ioq_start = NULL; 708 | adding2q = 0; 709 | #endif 710 | 711 | #if ENABLE_FEATURE_VI_COLON 712 | { 713 | char *p, *q; 714 | int n = 0; 715 | 716 | while ((p = initial_cmds[n]) != NULL) { 717 | do { 718 | q = p; 719 | p = strchr(q, '\n'); 720 | if (p) 721 | while (*p == '\n') 722 | *p++ = '\0'; 723 | if (*q) 724 | colon(q); 725 | } while (p); 726 | vi_free(initial_cmds[n]); 727 | initial_cmds[n] = NULL; 728 | n++; 729 | } 730 | } 731 | #endif 732 | redraw(FALSE); // dont force every col re-draw 733 | //------This is the main Vi cmd handling loop ----------------------- 734 | while (editing > 0) { 735 | c = get_one_char(); // get a cmd from user 736 | #if ENABLE_FEATURE_VI_YANKMARK 737 | // save a copy of the current line- for the 'U" command 738 | if (begin_line(dot) != cur_line) { 739 | cur_line = begin_line(dot); 740 | text_yank(begin_line(dot), end_line(dot), Ureg, PARTIAL); 741 | } 742 | #endif 743 | #if ENABLE_FEATURE_VI_DOT_CMD 744 | // If c is a command that changes text[], 745 | // (re)start remembering the input for the "." command. 746 | if (!adding2q 747 | && ioq_start == NULL 748 | && cmd_mode == 0 // command mode 749 | && c > '\0' // exclude NUL and non-ASCII chars 750 | && c < 0x7f // (Unicode and such) 751 | && strchr(modifying_cmds, c) 752 | ) { 753 | start_new_cmd_q(c); 754 | } 755 | #endif 756 | do_cmd(c); // execute the user command 757 | 758 | // poll to see if there is input already waiting. if we are 759 | // not able to display output fast enough to keep up, skip 760 | // the display update until we catch up with input. 761 | if (!readbuffer[0] && mysleep(0) == 0) { 762 | // no input pending - so update output 763 | refresh(FALSE); 764 | show_status_line(); 765 | } 766 | } 767 | //------------------------------------------------------------------- 768 | 769 | go_bottom_and_clear_to_eol(); 770 | cookmode(); 771 | #undef cur_line 772 | } 773 | 774 | //----- The Colon commands ------------------------------------- 775 | #if ENABLE_FEATURE_VI_COLON 776 | // Evaluate colon address expression. Returns a pointer to the 777 | // next character or NULL on error. If 'result' contains a valid 778 | // address 'valid' is TRUE. 779 | static char *get_one_address(char *p, int *result, int *valid) 780 | { 781 | int num, sign, addr, got_addr; 782 | # if ENABLE_FEATURE_VI_YANKMARK || ENABLE_FEATURE_VI_SEARCH 783 | char *q, c; 784 | # endif 785 | IF_FEATURE_VI_SEARCH(int dir;) 786 | 787 | got_addr = FALSE; 788 | addr = count_lines(text, dot); // default to current line 789 | sign = 0; 790 | for (;;) { 791 | if (isblank(*p)) { 792 | if (got_addr) { 793 | addr += sign; 794 | sign = 0; 795 | } 796 | p++; 797 | } else if (!got_addr && *p == '.') { // the current line 798 | p++; 799 | //addr = count_lines(text, dot); 800 | got_addr = TRUE; 801 | } else if (!got_addr && *p == '$') { // the last line in file 802 | p++; 803 | addr = count_lines(text, end - 1); 804 | got_addr = TRUE; 805 | } 806 | # if ENABLE_FEATURE_VI_YANKMARK 807 | else if (!got_addr && *p == '\'') { // is this a mark addr 808 | p++; 809 | c = tolower(*p); 810 | p++; 811 | q = NULL; 812 | if (c >= 'a' && c <= 'z') { 813 | // we have a mark 814 | c = c - 'a'; 815 | q = mark[(unsigned char) c]; 816 | } 817 | if (q == NULL) { // is mark valid 818 | status_line_bold("Mark not set"); 819 | return NULL; 820 | } 821 | addr = count_lines(text, q); 822 | got_addr = TRUE; 823 | } 824 | # endif 825 | # if ENABLE_FEATURE_VI_SEARCH 826 | else if (!got_addr && (*p == '/' || *p == '?')) { // a search pattern 827 | c = *p; 828 | q = strchrnul(p + 1, c); 829 | if (p + 1 != q) { 830 | // save copy of new pattern 831 | vi_free(last_search_pattern); 832 | last_search_pattern = vi_strndup(p, q - p); 833 | } 834 | p = q; 835 | if (*p == c) 836 | p++; 837 | if (c == '/') { 838 | q = next_line(dot); 839 | dir = (FORWARD << 1) | FULL; 840 | } else { 841 | q = begin_line(dot); 842 | dir = ((unsigned)BACK << 1) | FULL; 843 | } 844 | q = char_search(q, last_search_pattern + 1, dir); 845 | if (q == NULL) { 846 | // no match, continue from other end of file 847 | q = char_search(dir > 0 ? text : end - 1, 848 | last_search_pattern + 1, dir); 849 | if (q == NULL) { 850 | status_line_bold("Pattern not found"); 851 | return NULL; 852 | } 853 | } 854 | addr = count_lines(text, q); 855 | got_addr = TRUE; 856 | } 857 | # endif 858 | else if (isdigit(*p)) { 859 | num = 0; 860 | while (isdigit(*p)) 861 | num = num * 10 + *p++ -'0'; 862 | if (!got_addr) { // specific line number 863 | addr = num; 864 | got_addr = TRUE; 865 | } else { // offset from current addr 866 | addr += sign >= 0 ? num : -num; 867 | } 868 | sign = 0; 869 | } else if (*p == '-' || *p == '+') { 870 | if (!got_addr) { // default address is dot 871 | //addr = count_lines(text, dot); 872 | got_addr = TRUE; 873 | } else { 874 | addr += sign; 875 | } 876 | sign = *p++ == '-' ? -1 : 1; 877 | } else { 878 | addr += sign; // consume unused trailing sign 879 | break; 880 | } 881 | } 882 | *result = addr; 883 | *valid = got_addr; 884 | return p; 885 | } 886 | 887 | # define GET_ADDRESS 0 888 | # define GET_SEPARATOR 1 889 | 890 | // Read line addresses for a colon command. The user can enter as 891 | // many as they like but only the last two will be used. 892 | static char *get_address(char *p, int *b, int *e, unsigned int *got) 893 | { 894 | int state = GET_ADDRESS; 895 | int valid; 896 | int addr; 897 | char *save_dot = dot; 898 | 899 | //----- get the address' i.e., 1,3 'a,'b ----- 900 | for (;;) { 901 | if (isblank(*p)) { 902 | p++; 903 | } else if (state == GET_ADDRESS && *p == '%') { // alias for 1,$ 904 | p++; 905 | *b = 1; 906 | *e = count_lines(text, end-1); 907 | *got = 3; 908 | state = GET_SEPARATOR; 909 | } else if (state == GET_ADDRESS) { 910 | valid = FALSE; 911 | p = get_one_address(p, &addr, &valid); 912 | // Quit on error or if the address is invalid and isn't of 913 | // the form ',$' or '1,' (in which case it defaults to dot). 914 | if (p == NULL || !(valid || *p == ',' || *p == ';' || *got & 1)) 915 | break; 916 | *b = *e; 917 | *e = addr; 918 | *got = (*got << 1) | 1; 919 | state = GET_SEPARATOR; 920 | } else if (state == GET_SEPARATOR && (*p == ',' || *p == ';')) { 921 | if (*p == ';') 922 | dot = find_line(*e); 923 | p++; 924 | state = GET_ADDRESS; 925 | } else { 926 | break; 927 | } 928 | } 929 | dot = save_dot; 930 | return p; 931 | } 932 | 933 | # if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS 934 | static void setops(char *args, int flg_no) 935 | { 936 | char *eq; 937 | int index; 938 | 939 | eq = strchr(args, '='); 940 | if (eq) *eq = '\0'; 941 | index = index_in_strings(OPTS_STR, args + flg_no); 942 | if (eq) *eq = '='; 943 | if (index < 0) { 944 | bad: 945 | status_line_bold("bad option: %s", args); 946 | return; 947 | } 948 | 949 | index = 1 << (index >> 1); // convert to VI_bit 950 | 951 | if (index & VI_TABSTOP) { 952 | int t; 953 | if (!eq || flg_no) // no "=NNN" or it is "notabstop"? 954 | goto bad; 955 | t = vi_strtou(eq + 1, NULL, 10); 956 | if (t <= 0 || t > MAX_TABSTOP) 957 | goto bad; 958 | tabstop = t; 959 | return; 960 | } 961 | if (eq) goto bad; // boolean option has "="? 962 | if (flg_no) { 963 | vi_setops &= ~index; 964 | } else { 965 | vi_setops |= index; 966 | } 967 | } 968 | # endif 969 | 970 | # if ENABLE_FEATURE_VI_COLON_EXPAND 971 | static char *expand_args(char *args) 972 | { 973 | char *s, *t; 974 | const char *replace; 975 | 976 | args = vi_strdup(args); 977 | for (s = args; *s; s++) { 978 | if (*s == '%') { 979 | replace = current_filename; 980 | } else if (*s == '#') { 981 | replace = alt_filename; 982 | } else { 983 | if (*s == '\\' && s[1] != '\0') { 984 | for (t = s++; *t; t++) 985 | *t = t[1]; 986 | } 987 | continue; 988 | } 989 | 990 | if (replace == NULL) { 991 | vi_free(args); 992 | status_line_bold("No previous filename"); 993 | return NULL; 994 | } 995 | 996 | *s = '\0'; 997 | t = xasprintf("%s%s%s", args, replace, s+1); 998 | s = t + (s - args) + strlen(replace); 999 | vi_free(args); 1000 | args = t; 1001 | } 1002 | return args; 1003 | } 1004 | # else 1005 | # define expand_args(a) (a) 1006 | # endif 1007 | #endif /* FEATURE_VI_COLON */ 1008 | 1009 | # define strchr_backslash(s, c) strchr(s, c) 1010 | 1011 | // buf must be no longer than MAX_INPUT_LEN! 1012 | static void colon(char *buf) 1013 | { 1014 | #if !ENABLE_FEATURE_VI_COLON 1015 | /* Simple ":cmd" handler with minimal set of commands */ 1016 | char *p = buf; 1017 | int cnt; 1018 | 1019 | if (*p == ':') 1020 | p++; 1021 | cnt = strlen(p); 1022 | if (cnt == 0) 1023 | return; 1024 | if (strncmp(p, "quit", cnt) == 0 1025 | || strcmp(p, "q!") == 0 1026 | ) { 1027 | if (modified_count && p[1] != '!') { 1028 | status_line_bold("No write since last change (:%s! overrides)", p); 1029 | } else { 1030 | editing = 0; 1031 | } 1032 | return; 1033 | } 1034 | if (strncmp(p, "write", cnt) == 0 1035 | || strcmp(p, "wq") == 0 1036 | || strcmp(p, "wn") == 0 1037 | || (p[0] == 'x' && !p[1]) 1038 | ) { 1039 | if (modified_count != 0 || p[0] != 'x') { 1040 | cnt = file_write(current_filename, text, end - 1); 1041 | } 1042 | if (cnt < 0) { 1043 | if (cnt == -1) 1044 | status_line_bold("Write error: %s", strerror(errno)); 1045 | } else { 1046 | modified_count = 0; 1047 | last_modified_count = -1; 1048 | status_line("'%s' %uL, %uC", 1049 | current_filename, 1050 | count_lines(text, end - 1), cnt 1051 | ); 1052 | if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' 1053 | || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N' 1054 | ) { 1055 | editing = 0; 1056 | } 1057 | } 1058 | return; 1059 | } 1060 | if (strncmp(p, "file", cnt) == 0) { 1061 | last_status_cksum = 0; // force status update 1062 | return; 1063 | } 1064 | if (sscanf(p, "%d", &cnt) > 0) { 1065 | dot = find_line(cnt); 1066 | dot_skip_over_ws(); 1067 | return; 1068 | } 1069 | not_implemented(p); 1070 | #else 1071 | 1072 | // check how many addresses we got 1073 | # define GOT_ADDRESS (got & 1) 1074 | # define GOT_RANGE ((got & 3) == 3) 1075 | 1076 | char c, *buf1, *q, *r; 1077 | char *fn, cmd[MAX_INPUT_LEN], *cmdend, *args, *exp = NULL; 1078 | int i, l, li, b, e; 1079 | unsigned int got; 1080 | int useforce; 1081 | 1082 | // :3154 // if (-e line 3154) goto it else stay put 1083 | // :4,33w! foo // write a portion of buffer to file "foo" 1084 | // :w // write all of buffer to current file 1085 | // :q // quit 1086 | // :q! // quit- dont care about modified file 1087 | // :'a,'z!sort -u // filter block through sort 1088 | // :'f // goto mark "f" 1089 | // :'fl // list literal the mark "f" line 1090 | // :.r bar // read file "bar" into buffer before dot 1091 | // :/123/,/abc/d // delete lines from "123" line to "abc" line 1092 | // :/xyz/ // goto the "xyz" line 1093 | // :s/find/replace/ // substitute pattern "find" with "replace" 1094 | // :! // run then return 1095 | // 1096 | 1097 | while (*buf == ':') 1098 | buf++; // move past leading colons 1099 | while (isblank(*buf)) 1100 | buf++; // move past leading blanks 1101 | if (!buf[0] || buf[0] == '"') 1102 | goto ret; // ignore empty lines or those starting with '"' 1103 | 1104 | li = i = 0; 1105 | b = e = -1; 1106 | got = 0; 1107 | li = count_lines(text, end - 1); 1108 | fn = current_filename; 1109 | 1110 | // look for optional address(es) :. :1 :1,9 :'q,'a :% 1111 | buf = get_address(buf, &b, &e, &got); 1112 | if (buf == NULL) { 1113 | goto ret; 1114 | } 1115 | 1116 | // get the COMMAND into cmd[] 1117 | strcpy(cmd, buf); 1118 | buf1 = cmd; 1119 | while (!isspace(*buf1) && *buf1 != '\0') { 1120 | buf1++; 1121 | } 1122 | cmdend = buf1; 1123 | // get any ARGuments 1124 | while (isblank(*buf1)) 1125 | buf1++; 1126 | args = buf1; 1127 | *cmdend = '\0'; 1128 | useforce = FALSE; 1129 | if (cmdend > cmd && cmdend[-1] == '!') { 1130 | useforce = TRUE; 1131 | cmdend[-1] = '\0'; // get rid of ! 1132 | } 1133 | // assume the command will want a range, certain commands 1134 | // (read, substitute) need to adjust these assumptions 1135 | if (!GOT_ADDRESS) { 1136 | q = text; // no addr, use 1,$ for the range 1137 | r = end - 1; 1138 | } else { 1139 | // at least one addr was given, get its details 1140 | if (e < 0 || e > li) { 1141 | status_line_bold("Invalid range"); 1142 | goto ret; 1143 | } 1144 | q = r = find_line(e); 1145 | if (!GOT_RANGE) { 1146 | // if there is only one addr, then it's the line 1147 | // number of the single line the user wants. 1148 | // Reset the end pointer to the end of that line. 1149 | r = end_line(q); 1150 | li = 1; 1151 | } else { 1152 | // we were given two addrs. change the 1153 | // start pointer to the addr given by user. 1154 | if (b < 0 || b > li || b > e) { 1155 | status_line_bold("Invalid range"); 1156 | goto ret; 1157 | } 1158 | q = find_line(b); // what line is #b 1159 | r = end_line(r); 1160 | li = e - b + 1; 1161 | } 1162 | } 1163 | // ------------ now look for the command ------------ 1164 | i = strlen(cmd); 1165 | if (i == 0) { // :123CR goto line #123 1166 | if (e >= 0) { 1167 | dot = find_line(e); // what line is #e 1168 | dot_skip_over_ws(); 1169 | } 1170 | } 1171 | #if ENABLE_FEATURE_ALLOW_EXEC 1172 | else if (cmd[0] == '!') { // run a cmd 1173 | int retcode; 1174 | // :!ls run the 1175 | exp = expand_args(buf + 1); 1176 | if (exp == NULL) 1177 | goto ret; 1178 | go_bottom_and_clear_to_eol(); 1179 | cookmode(); 1180 | retcode = system(exp); // run the cmd 1181 | if (retcode) 1182 | vi_puts("\nshell returned %i\n\n", retcode); 1183 | rawmode(); 1184 | Hit_Return(); // let user see results 1185 | } 1186 | #endif 1187 | else if (cmd[0] == '=' && !cmd[1]) { // where is the address 1188 | if (!GOT_ADDRESS) { // no addr given- use defaults 1189 | e = count_lines(text, dot); 1190 | } 1191 | status_line("%d", e); 1192 | } else if (strncmp(cmd, "delete", i) == 0) { // delete lines 1193 | if (!GOT_ADDRESS) { // no addr given- use defaults 1194 | q = begin_line(dot); // assume .,. for the range 1195 | r = end_line(dot); 1196 | } 1197 | dot = yank_delete(q, r, WHOLE, YANKDEL, ALLOW_UNDO); // save, then delete lines 1198 | dot_skip_over_ws(); 1199 | } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file 1200 | int size; 1201 | 1202 | // don't edit, if the current file has been modified 1203 | if (modified_count && !useforce) { 1204 | status_line_bold("No write since last change (:%s! overrides)", cmd); 1205 | goto ret; 1206 | } 1207 | if (args[0]) { 1208 | // the user supplied a file name 1209 | fn = exp = expand_args(args); 1210 | if (exp == NULL) 1211 | goto ret; 1212 | } else if (current_filename == NULL) { 1213 | // no user file name, no current name- punt 1214 | status_line_bold("No current filename"); 1215 | goto ret; 1216 | } 1217 | 1218 | size = init_text_buffer(fn); 1219 | 1220 | #if ENABLE_FEATURE_VI_YANKMARK 1221 | if (Ureg >= 0 && Ureg < 28) { 1222 | vi_free(reg[Ureg]); // free orig line reg- for 'U' 1223 | reg[Ureg] = NULL; 1224 | } 1225 | /*if (YDreg < 28) - always true*/ { 1226 | vi_free(reg[YDreg]); // free default yank/delete register 1227 | reg[YDreg] = NULL; 1228 | } 1229 | #endif 1230 | // how many lines in text[]? 1231 | li = count_lines(text, end - 1); 1232 | status_line("'%s'%s" 1233 | IF_FEATURE_VI_READONLY("%s") 1234 | " %uL, %uC", 1235 | fn, 1236 | (size < 0 ? " [New file]" : ""), 1237 | IF_FEATURE_VI_READONLY( 1238 | ((readonly_mode) ? " [Readonly]" : ""), 1239 | ) 1240 | li, (int)(end - text) 1241 | ); 1242 | } else if (strncmp(cmd, "file", i) == 0) { // what File is this 1243 | if (e >= 0) { 1244 | status_line_bold("No address allowed on this command"); 1245 | goto ret; 1246 | } 1247 | if (args[0]) { 1248 | // user wants a new filename 1249 | exp = expand_args(args); 1250 | if (exp == NULL) 1251 | goto ret; 1252 | update_filename(exp); 1253 | } else { 1254 | // user wants file status info 1255 | last_status_cksum = 0; // force status update 1256 | } 1257 | } else if (strncmp(cmd, "features", i) == 0) { // what features are available 1258 | // print out values of all features 1259 | go_bottom_and_clear_to_eol(); 1260 | cookmode(); 1261 | show_help(); 1262 | rawmode(); 1263 | Hit_Return(); 1264 | } else if (strncmp(cmd, "list", i) == 0) { // literal print line 1265 | if (!GOT_ADDRESS) { // no addr given- use defaults 1266 | q = begin_line(dot); // assume .,. for the range 1267 | r = end_line(dot); 1268 | } 1269 | go_bottom_and_clear_to_eol(); 1270 | vi_puts("\r"); 1271 | for (; q <= r; q++) { 1272 | int c_is_no_print; 1273 | 1274 | c = *q; 1275 | c_is_no_print = (c & 0x80) && !Isprint(c); 1276 | if (c_is_no_print) { 1277 | c = '.'; 1278 | standout_start(); 1279 | } 1280 | if (c == '\n') { 1281 | vi_puts("$\r"); 1282 | } else if (c < ' ' || c == 127) { 1283 | vi_putchar('^'); 1284 | if (c == 127) 1285 | c = '?'; 1286 | else 1287 | c += '@'; 1288 | } 1289 | vi_putchar(c); 1290 | if (c_is_no_print) 1291 | standout_end(); 1292 | } 1293 | Hit_Return(); 1294 | } else if (strncmp(cmd, "quit", i) == 0 // quit 1295 | || strncmp(cmd, "next", i) == 0 // edit next file 1296 | || strncmp(cmd, "prev", i) == 0 // edit previous file 1297 | ) { 1298 | int n; 1299 | if (useforce) { 1300 | if (*cmd == 'q') { 1301 | // force end of argv list 1302 | options.optind = cmdline_filecnt; 1303 | } 1304 | editing = 0; 1305 | goto ret; 1306 | } 1307 | // don't exit if the file been modified 1308 | if (modified_count) { 1309 | status_line_bold("No write since last change (:%s! overrides)", cmd); 1310 | goto ret; 1311 | } 1312 | // are there other file to edit 1313 | n = cmdline_filecnt - options.optind - 1; 1314 | if (*cmd == 'q' && n > 0) { 1315 | status_line_bold("%u more file(s) to edit", n); 1316 | goto ret; 1317 | } 1318 | if (*cmd == 'n' && n <= 0) { 1319 | status_line_bold("No more files to edit"); 1320 | goto ret; 1321 | } 1322 | if (*cmd == 'p') { 1323 | // are there previous files to edit 1324 | if (options.optind < 1) { 1325 | status_line_bold("No previous files to edit"); 1326 | goto ret; 1327 | } 1328 | options.optind -= 2; 1329 | } 1330 | editing = 0; 1331 | } else if (strncmp(cmd, "read", i) == 0) { // read file into text[] 1332 | int size, num; 1333 | 1334 | if (args[0]) { 1335 | // the user supplied a file name 1336 | fn = exp = expand_args(args); 1337 | if (exp == NULL) 1338 | goto ret; 1339 | init_filename(fn); 1340 | } else if (current_filename == NULL) { 1341 | // no user file name, no current name- punt 1342 | status_line_bold("No current filename"); 1343 | goto ret; 1344 | } 1345 | if (e == 0) { // user said ":0r foo" 1346 | q = text; 1347 | } else { // read after given line or current line if none given 1348 | q = next_line(GOT_ADDRESS ? find_line(e) : dot); 1349 | // read after last line 1350 | if (q == end-1) 1351 | ++q; 1352 | } 1353 | num = count_lines(text, q); 1354 | if (q == end) 1355 | num++; 1356 | { // dance around potentially-reallocated text[] 1357 | uintptr_t ofs = q - text; 1358 | size = file_insert(fn, q, 0); 1359 | q = text + ofs; 1360 | } 1361 | if (size < 0) 1362 | goto ret; // nothing was inserted 1363 | // how many lines in text[]? 1364 | li = count_lines(q, q + size - 1); 1365 | status_line("'%s'" 1366 | IF_FEATURE_VI_READONLY("%s") 1367 | " %uL, %uC", 1368 | fn, 1369 | IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),) 1370 | li, size 1371 | ); 1372 | dot = find_line(num); 1373 | } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args 1374 | if (modified_count && !useforce) { 1375 | status_line_bold("No write since last change (:%s! overrides)", cmd); 1376 | } else { 1377 | // reset the filenames to edit 1378 | options.optind = -1; /* start from 0th file */ 1379 | editing = 0; 1380 | } 1381 | #if ENABLE_FEATURE_VI_SET 1382 | } else if (strncmp(cmd, "set", i) == 0) { // set or clear features 1383 | #if ENABLE_FEATURE_VI_SETOPTS 1384 | char *argp, *argn, oldch; 1385 | #endif 1386 | // only blank is regarded as args delimiter. What about tab '\t'? 1387 | if (!args[0] || strcmp(args, "all") == 0) { 1388 | // print out values of all options 1389 | #if ENABLE_FEATURE_VI_SETOPTS 1390 | status_line_bold( 1391 | "%sautoindent " 1392 | "%sexpandtab " 1393 | "%sflash " 1394 | "%signorecase " 1395 | "%sshowmatch " 1396 | "tabstop=%u", 1397 | autoindent ? "" : "no", 1398 | expandtab ? "" : "no", 1399 | err_method ? "" : "no", 1400 | ignorecase ? "" : "no", 1401 | showmatch ? "" : "no", 1402 | tabstop 1403 | ); 1404 | #endif 1405 | goto ret; 1406 | } 1407 | #if ENABLE_FEATURE_VI_SETOPTS 1408 | argp = args; 1409 | while (*argp) { 1410 | i = 0; 1411 | if (argp[0] == 'n' && argp[1] == 'o') // "noXXX" 1412 | i = 2; 1413 | argn = skip_non_whitespace(argp); 1414 | oldch = *argn; 1415 | *argn = '\0'; 1416 | setops(argp, i); 1417 | *argn = oldch; 1418 | argp = skip_whitespace(argn); 1419 | } 1420 | #endif /* FEATURE_VI_SETOPTS */ 1421 | #endif /* FEATURE_VI_SET */ 1422 | #if ENABLE_FEATURE_VI_SEARCH 1423 | } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern 1424 | char *F, *R, *flags; 1425 | size_t len_F, len_R; 1426 | int gflag = 0; // global replace flag 1427 | int subs = 0; // number of substitutions 1428 | # if ENABLE_FEATURE_VI_VERBOSE_STATUS 1429 | int last_line = 0, lines = 0; 1430 | # endif 1431 | 1432 | // F points to the "find" pattern 1433 | // R points to the "replace" pattern 1434 | // replace the cmd line delimiters "/" with NULs 1435 | c = buf[1]; // what is the delimiter 1436 | F = buf + 2; // start of "find" 1437 | R = strchr_backslash(F, c); // middle delimiter 1438 | if (!R) 1439 | goto colon_s_fail; 1440 | len_F = R - F; 1441 | *R++ = '\0'; // terminate "find" 1442 | flags = strchr_backslash(R, c); 1443 | if (flags) { 1444 | *flags++ = '\0'; // terminate "replace" 1445 | gflag = *flags; 1446 | } 1447 | 1448 | if (len_F) { // save "find" as last search pattern 1449 | vi_free(last_search_pattern); 1450 | last_search_pattern = vi_strdup(F - 1); 1451 | last_search_pattern[0] = '/'; 1452 | } else if (last_search_pattern[1] == '\0') { 1453 | status_line_bold("No previous search"); 1454 | goto ret; 1455 | } else { 1456 | F = last_search_pattern + 1; 1457 | len_F = strlen(F); 1458 | } 1459 | 1460 | if (!GOT_ADDRESS) { // no addr given 1461 | q = begin_line(dot); // start with cur line 1462 | r = end_line(dot); 1463 | b = e = count_lines(text, q); // cur line number 1464 | } else if (!GOT_RANGE) { // one addr given 1465 | b = e; 1466 | } 1467 | 1468 | len_R = strlen(R); 1469 | 1470 | for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0 1471 | char *ls = q; // orig line start 1472 | char *found; 1473 | vc4: 1474 | found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find" 1475 | if (found) { 1476 | uintptr_t bias; 1477 | // we found the "find" pattern - delete it 1478 | // For undo support, the first item should not be chained 1479 | // This needs to be handled differently depending on 1480 | // whether or not regex support is enabled. 1481 | # define TEST_LEN_F 1 // len_F is never zero 1482 | # define TEST_UNDO1 subs 1483 | # define TEST_UNDO2 1 1484 | if (TEST_LEN_F) // match can be empty, no delete needed 1485 | text_hole_delete(found, found + len_F - 1, 1486 | TEST_UNDO1 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO); 1487 | if (len_R != 0) { // insert the "replace" pattern, if required 1488 | bias = string_insert(found, R, 1489 | TEST_UNDO2 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO); 1490 | found += bias; 1491 | ls += bias; 1492 | //q += bias; - recalculated anyway 1493 | } 1494 | if (TEST_LEN_F || len_R != 0) { 1495 | dot = ls; 1496 | subs++; 1497 | # if ENABLE_FEATURE_VI_VERBOSE_STATUS 1498 | if (last_line != i) { 1499 | last_line = i; 1500 | ++lines; 1501 | } 1502 | # endif 1503 | } 1504 | // check for "global" :s/foo/bar/g 1505 | if (gflag == 'g') { 1506 | if ((found + len_R) < end_line(ls)) { 1507 | q = found + len_R; 1508 | goto vc4; // don't let q move past cur line 1509 | } 1510 | } 1511 | } 1512 | q = next_line(ls); 1513 | } 1514 | if (subs == 0) { 1515 | status_line_bold("No match"); 1516 | } else { 1517 | dot_skip_over_ws(); 1518 | # if ENABLE_FEATURE_VI_VERBOSE_STATUS 1519 | if (subs > 1) 1520 | status_line("%d substitutions on %d lines", subs, lines); 1521 | # endif 1522 | } 1523 | #endif /* FEATURE_VI_SEARCH */ 1524 | } else if (strncmp(cmd, "version", i) == 0) { // show software version 1525 | status_line(BB_VER " " BB_BT); 1526 | } else if (strncmp(cmd, "write", i) == 0 // write text to file 1527 | || strcmp(cmd, "wq") == 0 1528 | || strcmp(cmd, "wn") == 0 1529 | || (cmd[0] == 'x' && !cmd[1]) 1530 | ) { 1531 | int size; 1532 | //int forced = FALSE; 1533 | 1534 | // is there a file name to write to? 1535 | if (args[0]) { 1536 | struct stat statbuf; 1537 | 1538 | exp = expand_args(args); 1539 | if (exp == NULL) 1540 | goto ret; 1541 | if (!useforce && (fn == NULL || strcmp(fn, exp) != 0) && 1542 | stat(exp, &statbuf) == 0) { 1543 | status_line_bold("File exists (:w! overrides)"); 1544 | goto ret; 1545 | } 1546 | fn = exp; 1547 | init_filename(fn); 1548 | } 1549 | # if ENABLE_FEATURE_VI_READONLY 1550 | else if (readonly_mode && !useforce && fn) { 1551 | status_line_bold("'%s' is read only", fn); 1552 | goto ret; 1553 | } 1554 | # endif 1555 | //if (useforce) { 1556 | // if "fn" is not write-able, chmod u+w 1557 | // rt_sprintf(syscmd, "chmod u+w %s", fn); 1558 | // system(syscmd); 1559 | // forced = TRUE; 1560 | //} 1561 | if (modified_count != 0 || cmd[0] != 'x') { 1562 | size = r - q + 1; 1563 | l = file_write(fn, q, r); 1564 | } else { 1565 | size = 0; 1566 | l = 0; 1567 | } 1568 | //if (useforce && forced) { 1569 | // chmod u-w 1570 | // rt_sprintf(syscmd, "chmod u-w %s", fn); 1571 | // system(syscmd); 1572 | // forced = FALSE; 1573 | //} 1574 | if (l < 0) { 1575 | if (l == -1) 1576 | status_line_bold_errno(fn); 1577 | } else { 1578 | // how many lines written 1579 | li = count_lines(q, q + l - 1); 1580 | status_line("'%s' %uL, %uC", fn, li, l); 1581 | if (l == size) { 1582 | if (q == text && q + l == end) { 1583 | modified_count = 0; 1584 | last_modified_count = -1; 1585 | } 1586 | if (cmd[1] == 'n') { 1587 | editing = 0; 1588 | } else if (cmd[0] == 'x' || cmd[1] == 'q') { 1589 | // are there other files to edit? 1590 | int n = cmdline_filecnt - options.optind - 1; 1591 | if (n > 0) { 1592 | if (useforce) { 1593 | // force end of argv list 1594 | options.optind = cmdline_filecnt; 1595 | } else { 1596 | status_line_bold("%u more file(s) to edit", n); 1597 | goto ret; 1598 | } 1599 | } 1600 | editing = 0; 1601 | } 1602 | } 1603 | } 1604 | # if ENABLE_FEATURE_VI_YANKMARK 1605 | } else if (strncmp(cmd, "yank", i) == 0) { // yank lines 1606 | if (!GOT_ADDRESS) { // no addr given- use defaults 1607 | q = begin_line(dot); // assume .,. for the range 1608 | r = end_line(dot); 1609 | } 1610 | text_yank(q, r, YDreg, WHOLE); 1611 | li = count_lines(q, r); 1612 | status_line("Yank %d lines (%d chars) into [%c]", 1613 | li, strlen(reg[YDreg]), what_reg()); 1614 | # endif 1615 | } else { 1616 | // cmd unknown 1617 | not_implemented(cmd); 1618 | } 1619 | ret: 1620 | # if ENABLE_FEATURE_VI_COLON_EXPAND 1621 | vi_free(exp); 1622 | # endif 1623 | dot = bound_dot(dot); // make sure "dot" is valid 1624 | return; 1625 | # if ENABLE_FEATURE_VI_SEARCH 1626 | colon_s_fail: 1627 | status_line(":s expression missing delimiters"); 1628 | # endif 1629 | #endif /* FEATURE_VI_COLON */ 1630 | } 1631 | 1632 | static void Hit_Return(void) 1633 | { 1634 | int c; 1635 | 1636 | standout_start(); 1637 | vi_puts("[Hit return to continue]"); 1638 | standout_end(); 1639 | while ((c = get_one_char()) != '\n' && c != '\r') 1640 | continue; 1641 | redraw(TRUE); // force redraw all 1642 | } 1643 | 1644 | static int next_tabstop(int col) 1645 | { 1646 | return col + ((tabstop - 1) - (col % tabstop)); 1647 | } 1648 | 1649 | static int prev_tabstop(int col) 1650 | { 1651 | return col - ((col % tabstop) ? (col % tabstop) : tabstop); 1652 | } 1653 | 1654 | static int next_column(char c, int co) 1655 | { 1656 | if (c == '\t') 1657 | co = next_tabstop(co); 1658 | else if ((unsigned char)c < ' ' || c == 0x7f) 1659 | co++; // display as ^X, use 2 columns 1660 | return co + 1; 1661 | } 1662 | 1663 | static int get_column(char *p) 1664 | { 1665 | const char *r; 1666 | int co = 0; 1667 | 1668 | for (r = begin_line(p); r < p; r++) 1669 | co = next_column(*r, co); 1670 | return co; 1671 | } 1672 | 1673 | //----- Synchronize the cursor to Dot -------------------------- 1674 | static void sync_cursor(char *d, int *row, int *col) 1675 | { 1676 | char *beg_cur; // begin and end of "d" line 1677 | char *tp; 1678 | int cnt, ro, co; 1679 | 1680 | beg_cur = begin_line(d); // first char of cur line 1681 | 1682 | if (beg_cur < screenbegin) { 1683 | // "d" is before top line on screen 1684 | // how many lines do we have to move 1685 | cnt = count_lines(beg_cur, screenbegin); 1686 | sc1: 1687 | screenbegin = beg_cur; 1688 | if (cnt > (rows - 1) / 2) { 1689 | // we moved too many lines. put "dot" in middle of screen 1690 | for (cnt = 0; cnt < (rows - 1) / 2; cnt++) { 1691 | screenbegin = prev_line(screenbegin); 1692 | } 1693 | } 1694 | } else { 1695 | char *end_scr; // begin and end of screen 1696 | end_scr = end_screen(); // last char of screen 1697 | if (beg_cur > end_scr) { 1698 | // "d" is after bottom line on screen 1699 | // how many lines do we have to move 1700 | cnt = count_lines(end_scr, beg_cur); 1701 | if (cnt > (rows - 1) / 2) 1702 | goto sc1; // too many lines 1703 | for (ro = 0; ro < cnt - 1; ro++) { 1704 | // move screen begin the same amount 1705 | screenbegin = next_line(screenbegin); 1706 | // now, move the end of screen 1707 | end_scr = next_line(end_scr); 1708 | end_scr = end_line(end_scr); 1709 | } 1710 | } 1711 | } 1712 | // "d" is on screen- find out which row 1713 | tp = screenbegin; 1714 | for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row 1715 | if (tp == beg_cur) 1716 | break; 1717 | tp = next_line(tp); 1718 | } 1719 | 1720 | // find out what col "d" is on 1721 | co = 0; 1722 | do { // drive "co" to correct column 1723 | if (*tp == '\n') //vda || *tp == '\0') 1724 | break; 1725 | co = next_column(*tp, co) - 1; 1726 | // inserting text before a tab, don't include its position 1727 | if (cmd_mode && tp == d - 1 && *d == '\t') { 1728 | co++; 1729 | break; 1730 | } 1731 | } while (tp++ < d && ++co); 1732 | 1733 | // "co" is the column where "dot" is. 1734 | // The screen has "columns" columns. 1735 | // The currently displayed columns are 0+offset -- columns+ofset 1736 | // |-------------------------------------------------------------| 1737 | // ^ ^ ^ 1738 | // offset | |------- columns ----------------| 1739 | // 1740 | // If "co" is already in this range then we do not have to adjust offset 1741 | // but, we do have to subtract the "offset" bias from "co". 1742 | // If "co" is outside this range then we have to change "offset". 1743 | // If the first char of a line is a tab the cursor will try to stay 1744 | // in column 7, but we have to set offset to 0. 1745 | 1746 | if (co < 0 + offset) { 1747 | offset = co; 1748 | } 1749 | if (co >= columns + offset) { 1750 | offset = co - columns + 1; 1751 | } 1752 | // if the first char of the line is a tab, and "dot" is sitting on it 1753 | // force offset to 0. 1754 | if (d == beg_cur && *d == '\t') { 1755 | offset = 0; 1756 | } 1757 | co -= offset; 1758 | 1759 | *row = ro; 1760 | *col = co; 1761 | } 1762 | 1763 | //----- Text Movement Routines --------------------------------- 1764 | static char *begin_line(char *p) // return pointer to first char cur line 1765 | { 1766 | if (p > text) { 1767 | p = memrchr(text, '\n', p - text); 1768 | if (!p) 1769 | return text; 1770 | return p + 1; 1771 | } 1772 | return p; 1773 | } 1774 | 1775 | static char *end_line(char *p) // return pointer to NL of cur line 1776 | { 1777 | if (p < end - 1) { 1778 | p = memchr(p, '\n', end - p - 1); 1779 | if (!p) 1780 | return end - 1; 1781 | } 1782 | return p; 1783 | } 1784 | 1785 | static char *dollar_line(char *p) // return pointer to just before NL line 1786 | { 1787 | p = end_line(p); 1788 | // Try to stay off of the Newline 1789 | if (*p == '\n' && (p - begin_line(p)) > 0) 1790 | p--; 1791 | return p; 1792 | } 1793 | 1794 | static char *prev_line(char *p) // return pointer first char prev line 1795 | { 1796 | p = begin_line(p); // goto begining of cur line 1797 | if (p > text && p[-1] == '\n') 1798 | p--; // step to prev line 1799 | p = begin_line(p); // goto begining of prev line 1800 | return p; 1801 | } 1802 | 1803 | static char *next_line(char *p) // return pointer first char next line 1804 | { 1805 | p = end_line(p); 1806 | if (p < end - 1 && *p == '\n') 1807 | p++; // step to next line 1808 | return p; 1809 | } 1810 | 1811 | //----- Text Information Routines ------------------------------ 1812 | static char *end_screen(void) 1813 | { 1814 | char *q; 1815 | int cnt; 1816 | 1817 | // find new bottom line 1818 | q = screenbegin; 1819 | for (cnt = 0; cnt < rows - 2; cnt++) 1820 | q = next_line(q); 1821 | q = end_line(q); 1822 | return q; 1823 | } 1824 | 1825 | // count line from start to stop 1826 | static int count_lines(char *start, char *stop) 1827 | { 1828 | char *q; 1829 | int cnt; 1830 | 1831 | if (stop < start) { // start and stop are backwards- reverse them 1832 | q = start; 1833 | start = stop; 1834 | stop = q; 1835 | } 1836 | cnt = 0; 1837 | stop = end_line(stop); 1838 | while (start <= stop && start <= end - 1) { 1839 | start = end_line(start); 1840 | if (*start == '\n') 1841 | cnt++; 1842 | start++; 1843 | } 1844 | return cnt; 1845 | } 1846 | 1847 | static char *find_line(int li) // find begining of line #li 1848 | { 1849 | char *q; 1850 | 1851 | for (q = text; li > 1; li--) { 1852 | q = next_line(q); 1853 | } 1854 | return q; 1855 | } 1856 | 1857 | //----- Dot Movement Routines ---------------------------------- 1858 | static void dot_left(void) 1859 | { 1860 | undo_queue_commit(); 1861 | if (dot > text && dot[-1] != '\n') 1862 | dot--; 1863 | } 1864 | 1865 | static void dot_right(void) 1866 | { 1867 | undo_queue_commit(); 1868 | if (dot < end - 1 && *dot != '\n') 1869 | dot++; 1870 | } 1871 | 1872 | static void dot_begin(void) 1873 | { 1874 | undo_queue_commit(); 1875 | dot = begin_line(dot); // return pointer to first char cur line 1876 | } 1877 | 1878 | static void dot_end(void) 1879 | { 1880 | undo_queue_commit(); 1881 | dot = end_line(dot); // return pointer to last char cur line 1882 | } 1883 | 1884 | static char *move_to_col(char *p, int l) 1885 | { 1886 | int co; 1887 | 1888 | p = begin_line(p); 1889 | co = 0; 1890 | do { 1891 | if (*p == '\n') //vda || *p == '\0') 1892 | break; 1893 | co = next_column(*p, co); 1894 | } while (co <= l && p++ < end); 1895 | return p; 1896 | } 1897 | 1898 | static void dot_next(void) 1899 | { 1900 | undo_queue_commit(); 1901 | dot = next_line(dot); 1902 | } 1903 | 1904 | static void dot_prev(void) 1905 | { 1906 | undo_queue_commit(); 1907 | dot = prev_line(dot); 1908 | } 1909 | 1910 | static void dot_to_char(int cmd) 1911 | { 1912 | char *q = dot; 1913 | int dir = islower(cmd) ? FORWARD : BACK; 1914 | 1915 | if (last_search_char == 0) 1916 | return; 1917 | 1918 | do { 1919 | do { 1920 | q += dir; 1921 | if ((dir == FORWARD ? q > end - 1 : q < text) || *q == '\n') { 1922 | indicate_error(); 1923 | return; 1924 | } 1925 | } while (*q != last_search_char); 1926 | } while (--cmdcnt > 0); 1927 | 1928 | dot = q; 1929 | 1930 | // place cursor before/after char as required 1931 | if (cmd == 't') 1932 | dot_left(); 1933 | else if (cmd == 'T') 1934 | dot_right(); 1935 | } 1936 | 1937 | static void dot_scroll(int cnt, int dir) 1938 | { 1939 | char *q; 1940 | 1941 | undo_queue_commit(); 1942 | for (; cnt > 0; cnt--) { 1943 | if (dir < 0) { 1944 | // scroll Backwards 1945 | // ctrl-Y scroll up one line 1946 | screenbegin = prev_line(screenbegin); 1947 | } else { 1948 | // scroll Forwards 1949 | // ctrl-E scroll down one line 1950 | screenbegin = next_line(screenbegin); 1951 | } 1952 | } 1953 | // make sure "dot" stays on the screen so we dont scroll off 1954 | if (dot < screenbegin) 1955 | dot = screenbegin; 1956 | q = end_screen(); // find new bottom line 1957 | if (dot > q) 1958 | dot = begin_line(q); // is dot is below bottom line? 1959 | dot_skip_over_ws(); 1960 | } 1961 | 1962 | static void dot_skip_over_ws(void) 1963 | { 1964 | // skip WS 1965 | while (isspace((unsigned char)*dot) && *dot != '\n' && dot < end - 1) 1966 | dot++; 1967 | } 1968 | 1969 | static char *bound_dot(char *p) // make sure text[0] <= P < "end" 1970 | { 1971 | if (p >= end && end > text) { 1972 | p = end - 1; 1973 | indicate_error(); 1974 | } 1975 | if (p < text) { 1976 | p = text; 1977 | indicate_error(); 1978 | } 1979 | return p; 1980 | } 1981 | 1982 | //----- Helper Utility Routines -------------------------------- 1983 | 1984 | //---------------------------------------------------------------- 1985 | //----- Char Routines -------------------------------------------- 1986 | /* Chars that are part of a word- 1987 | * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1988 | * Chars that are Not part of a word (stoppers) 1989 | * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~ 1990 | * Chars that are WhiteSpace 1991 | * TAB NEWLINE VT FF RETURN SPACE 1992 | * DO NOT COUNT NEWLINE AS WHITESPACE 1993 | */ 1994 | 1995 | static void new_screen(int ro, int co) 1996 | { 1997 | char *s; 1998 | 1999 | vi_free(screen); 2000 | screensize = ro * co + 8; 2001 | s = screen = vi_malloc(screensize); 2002 | // initialize the new screen. assume this will be a empty file. 2003 | screen_erase(); 2004 | // non-existent text[] lines start with a tilde (~). 2005 | //screen[(1 * co) + 0] = '~'; 2006 | //screen[(2 * co) + 0] = '~'; 2007 | //.. 2008 | //screen[((ro-2) * co) + 0] = '~'; 2009 | ro -= 2; 2010 | while (--ro >= 0) { 2011 | s += co; 2012 | *s = '~'; 2013 | } 2014 | } 2015 | 2016 | #if ENABLE_FEATURE_VI_SEARCH 2017 | # if ENABLE_FEATURE_VI_SETOPTS 2018 | static int mycmp(const char *s1, const char *s2, int len) 2019 | { 2020 | if (ignorecase) { 2021 | return strncasecmp(s1, s2, len); 2022 | } 2023 | return strncmp(s1, s2, len); 2024 | } 2025 | # else 2026 | # define mycmp strncmp 2027 | # endif 2028 | 2029 | static char *char_search(char *p, const char *pat, int dir_and_range) 2030 | { 2031 | char *start, *stop; 2032 | int len; 2033 | int range; 2034 | 2035 | len = strlen(pat); 2036 | range = (dir_and_range & 1); 2037 | if (dir_and_range > 0) { //FORWARD? 2038 | stop = end - 1; // assume range is p..end-1 2039 | if (range == LIMITED) 2040 | stop = next_line(p); // range is to next line 2041 | for (start = p; start < stop; start++) { 2042 | if (mycmp(start, pat, len) == 0) { 2043 | return start; 2044 | } 2045 | } 2046 | } else { //BACK 2047 | stop = text; // assume range is text..p 2048 | if (range == LIMITED) 2049 | stop = prev_line(p); // range is to prev line 2050 | for (start = p - len; start >= stop; start--) { 2051 | if (mycmp(start, pat, len) == 0) { 2052 | return start; 2053 | } 2054 | } 2055 | } 2056 | // pattern not found 2057 | return NULL; 2058 | } 2059 | 2060 | #endif /* FEATURE_VI_SEARCH */ 2061 | 2062 | // find number of characters in indent, p must be at beginning of line 2063 | static size_t indent_len(char *p) 2064 | { 2065 | char *r = p; 2066 | 2067 | while (r < (end - 1) && isblank(*r)) 2068 | r++; 2069 | return r - p; 2070 | } 2071 | 2072 | static char *char_insert(char *p, char c, int undo) // insert the char c at 'p' 2073 | { 2074 | #if ENABLE_FEATURE_VI_SETOPTS 2075 | #define indentcol char_insert__indentcol 2076 | size_t len; 2077 | int col, ntab, nspc; 2078 | #endif 2079 | char *bol = begin_line(p); 2080 | 2081 | if (c == 22) { // Is this an ctrl-V? 2082 | p += stupid_insert(p, '^'); // use ^ to indicate literal next 2083 | refresh(FALSE); // show the ^ 2084 | c = get_one_char(); 2085 | *p = c; 2086 | #if ENABLE_FEATURE_VI_UNDO 2087 | undo_push_insert(p, 1, undo); 2088 | #else 2089 | modified_count++; 2090 | #endif /* ENABLE_FEATURE_VI_UNDO */ 2091 | p++; 2092 | } else if (c == 27) { // Is this an ESC? 2093 | cmd_mode = 0; 2094 | undo_queue_commit(); 2095 | cmdcnt = 0; 2096 | end_cmd_q(); // stop adding to q 2097 | last_status_cksum = 0; // force status update 2098 | if ((dot > text) && (p[-1] != '\n')) { 2099 | p--; 2100 | } 2101 | #if ENABLE_FEATURE_VI_SETOPTS 2102 | if (autoindent) { 2103 | len = indent_len(bol); 2104 | col = get_column(bol + len); 2105 | if (len && col == indentcol && bol[len] == '\n') { 2106 | // remove autoindent from otherwise empty line 2107 | text_hole_delete(bol, bol + len - 1, undo); 2108 | p = bol; 2109 | } 2110 | } 2111 | #endif 2112 | } else if (c == 4) { // ctrl-D reduces indentation 2113 | char *r = bol + indent_len(bol); 2114 | int prev = prev_tabstop(get_column(r)); 2115 | while (r > bol && get_column(r) > prev) { 2116 | if (p > bol) 2117 | p--; 2118 | r--; 2119 | r = text_hole_delete(r, r, ALLOW_UNDO_QUEUED); 2120 | } 2121 | 2122 | #if ENABLE_FEATURE_VI_SETOPTS 2123 | if (autoindent && indentcol && r == end_line(p)) { 2124 | // record changed size of autoindent 2125 | indentcol = get_column(p); 2126 | return p; 2127 | } 2128 | #endif 2129 | #if ENABLE_FEATURE_VI_SETOPTS 2130 | } else if (c == '\t' && expandtab) { // expand tab 2131 | col = get_column(p); 2132 | col = next_tabstop(col) - col + 1; 2133 | while (col--) { 2134 | # if ENABLE_FEATURE_VI_UNDO 2135 | undo_push_insert(p, 1, undo); 2136 | # else 2137 | modified_count++; 2138 | # endif 2139 | p += 1 + stupid_insert(p, ' '); 2140 | } 2141 | #endif 2142 | } else if (isbackspace(c)) { 2143 | if (cmd_mode == 2) { 2144 | // special treatment for backspace in Replace mode 2145 | if (p > rstart) { 2146 | p--; 2147 | #if ENABLE_FEATURE_VI_UNDO 2148 | undo_pop(); 2149 | #endif 2150 | } 2151 | } else if (p > text) { 2152 | p--; 2153 | p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char 2154 | } 2155 | } else { 2156 | // insert a char into text[] 2157 | if (c == 13) 2158 | c = '\n'; // translate \r to \n 2159 | #if ENABLE_FEATURE_VI_UNDO 2160 | # if ENABLE_FEATURE_VI_UNDO_QUEUE 2161 | if (c == '\n') 2162 | undo_queue_commit(); 2163 | # endif 2164 | undo_push_insert(p, 1, undo); 2165 | #else 2166 | modified_count++; 2167 | #endif /* ENABLE_FEATURE_VI_UNDO */ 2168 | p += 1 + stupid_insert(p, c); // insert the char 2169 | #if ENABLE_FEATURE_VI_SETOPTS 2170 | if (showmatch && strchr(")]}", c) != NULL) { 2171 | showmatching(p - 1); 2172 | } 2173 | if (autoindent && c == '\n') { // auto indent the new line 2174 | if (newindent < 0) { 2175 | // use indent of previous line 2176 | bol = prev_line(p); 2177 | len = indent_len(bol); 2178 | col = get_column(bol + len); 2179 | 2180 | if (len && col == indentcol) { 2181 | // previous line was empty except for autoindent 2182 | // move the indent to the current line 2183 | memmove(bol + 1, bol, len); 2184 | *bol = '\n'; 2185 | return p; 2186 | } 2187 | } else { 2188 | // for 'O'/'cc' commands add indent before newly inserted NL 2189 | if (p != end - 1) // but not for 'cc' at EOF 2190 | p--; 2191 | col = newindent; 2192 | } 2193 | 2194 | if (col) { 2195 | // only record indent if in insert/replace mode or for 2196 | // the 'o'/'O'/'cc' commands, which are switched to 2197 | // insert mode early. 2198 | indentcol = cmd_mode != 0 ? col : 0; 2199 | if (expandtab) { 2200 | ntab = 0; 2201 | nspc = col; 2202 | } else { 2203 | ntab = col / tabstop; 2204 | nspc = col % tabstop; 2205 | } 2206 | p += text_hole_make(p, ntab + nspc); 2207 | #if ENABLE_FEATURE_VI_UNDO 2208 | undo_push_insert(p, ntab + nspc, undo); 2209 | #endif 2210 | rt_memset(p, '\t', ntab); 2211 | p += ntab; 2212 | rt_memset(p, ' ', nspc); 2213 | return p + nspc; 2214 | } 2215 | } 2216 | #endif 2217 | } 2218 | #if ENABLE_FEATURE_VI_SETOPTS 2219 | indentcol = 0; 2220 | #undef indentcol 2221 | #endif 2222 | return p; 2223 | } 2224 | 2225 | // might reallocate text[]! use p += stupid_insert(p, ...), 2226 | // and be careful to not use pointers into potentially freed text[]! 2227 | static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p' 2228 | { 2229 | uintptr_t bias; 2230 | bias = text_hole_make(p, 1); 2231 | p += bias; 2232 | *p = c; 2233 | return bias; 2234 | } 2235 | 2236 | static int at_eof(const char *s) 2237 | { 2238 | // does 's' point to end of file, even with no terminating newline? 2239 | return ((s == end - 2 && s[1] == '\n') || s == end - 1); 2240 | } 2241 | 2242 | static int find_range(char **start, char **stop, int cmd) 2243 | { 2244 | char *p, *q, *t; 2245 | int buftype = -1; 2246 | int c; 2247 | 2248 | p = q = dot; 2249 | 2250 | #if ENABLE_FEATURE_VI_YANKMARK 2251 | if (cmd == 'Y') { 2252 | c = 'y'; 2253 | } else 2254 | #endif 2255 | { 2256 | c = get_motion_char(); 2257 | } 2258 | 2259 | #if ENABLE_FEATURE_VI_YANKMARK 2260 | if ((cmd == 'Y' || cmd == c) && strchr("cdy><", c)) { 2261 | #else 2262 | if (cmd == c && strchr("cd><", c)) { 2263 | #endif 2264 | // these cmds operate on whole lines 2265 | buftype = WHOLE; 2266 | if (--cmdcnt > 0) { 2267 | do_cmd('j'); 2268 | if (cmd_error) 2269 | buftype = -1; 2270 | } 2271 | } else if (strchr("^%$0bBeEfFtThnN/?|{}\b\177", c)) { 2272 | // Most operate on char positions within a line. Of those that 2273 | // don't '%' needs no special treatment, search commands are 2274 | // marked as MULTI and "{}" are handled below. 2275 | buftype = strchr("nN/?", c) ? MULTI : PARTIAL; 2276 | do_cmd(c); // execute movement cmd 2277 | if (cmd_error) 2278 | buftype = -1; 2279 | if (p == dot) // no movement is an error 2280 | buftype = -1; 2281 | } else if (strchr("wW", c)) { 2282 | buftype = MULTI; 2283 | do_cmd(c); // execute movement cmd 2284 | // step back one char, but not if we're at end of file, 2285 | // or if we are at EOF and search was for 'w' and we're at 2286 | // the start of a 'W' word. 2287 | if (dot > p && (!at_eof(dot) || (c == 'w' && ispunct(*dot)))) 2288 | dot--; 2289 | t = dot; 2290 | // don't include trailing WS as part of word 2291 | while (dot > p && isspace(*dot)) { 2292 | if (*dot-- == '\n') 2293 | t = dot; 2294 | } 2295 | // for non-change operations WS after NL is not part of word 2296 | if (cmd != 'c' && dot != t && *dot != '\n') 2297 | dot = t; 2298 | } else if (strchr("GHL+-gjk'\r\n", c)) { 2299 | // these operate on whole lines 2300 | buftype = WHOLE; 2301 | do_cmd(c); // execute movement cmd 2302 | if (cmd_error) 2303 | buftype = -1; 2304 | } else if (c == ' ' || c == 'l') { 2305 | // forward motion by character 2306 | int tmpcnt = (cmdcnt ? cmdcnt : 1); 2307 | buftype = PARTIAL; 2308 | do_cmd(c); // execute movement cmd 2309 | // exclude last char unless range isn't what we expected 2310 | // this indicates we've hit EOL 2311 | if (tmpcnt == dot - p) 2312 | dot--; 2313 | } 2314 | 2315 | if (buftype == -1) { 2316 | if (c != 27) 2317 | indicate_error(); 2318 | return buftype; 2319 | } 2320 | 2321 | q = dot; 2322 | if (q < p) { 2323 | t = q; 2324 | q = p; 2325 | p = t; 2326 | } 2327 | 2328 | // movements which don't include end of range 2329 | if (q > p) { 2330 | if (strchr("^0bBFThnN/?|\b\177", c)) { 2331 | q--; 2332 | } else if (strchr("{}", c)) { 2333 | buftype = (p == begin_line(p) && (*q == '\n' || at_eof(q))) ? 2334 | WHOLE : MULTI; 2335 | if (!at_eof(q)) { 2336 | q--; 2337 | if (q > p && p != begin_line(p)) 2338 | q--; 2339 | } 2340 | } 2341 | } 2342 | 2343 | *start = p; 2344 | *stop = q; 2345 | return buftype; 2346 | } 2347 | 2348 | static int st_test(char *p, int type, int dir, char *tested) 2349 | { 2350 | char c, c0, ci; 2351 | int test, inc; 2352 | 2353 | inc = dir; 2354 | c = c0 = p[0]; 2355 | ci = p[inc]; 2356 | test = 0; 2357 | 2358 | if (type == S_BEFORE_WS) { 2359 | c = ci; 2360 | test = (!isspace((unsigned char)c) || c == '\n'); 2361 | } 2362 | if (type == S_TO_WS) { 2363 | c = c0; 2364 | test = (!isspace((unsigned char)c) || c == '\n'); 2365 | } 2366 | if (type == S_OVER_WS) { 2367 | c = c0; 2368 | test = isspace((unsigned char)c); 2369 | } 2370 | if (type == S_END_PUNCT) { 2371 | c = ci; 2372 | test = ispunct((unsigned char)c); 2373 | } 2374 | if (type == S_END_ALNUM) { 2375 | c = ci; 2376 | test = (isalnum((unsigned char)c) || c == '_'); 2377 | } 2378 | *tested = c; 2379 | return test; 2380 | } 2381 | 2382 | static char *skip_thing(char *p, int linecnt, int dir, int type) 2383 | { 2384 | char c; 2385 | 2386 | while (st_test(p, type, dir, &c)) { 2387 | // make sure we limit search to correct number of lines 2388 | if (c == '\n' && --linecnt < 1) 2389 | break; 2390 | if (dir >= 0 && p >= end - 1) 2391 | break; 2392 | if (dir < 0 && p <= text) 2393 | break; 2394 | p += dir; // move to next char 2395 | } 2396 | return p; 2397 | } 2398 | 2399 | // find matching char of pair () [] {} 2400 | // will crash if c is not one of these 2401 | static char *find_pair(char *p, const char c) 2402 | { 2403 | const char *braces = "()[]{}"; 2404 | char match; 2405 | int dir, level; 2406 | 2407 | dir = strchr(braces, c) - braces; 2408 | dir ^= 1; 2409 | match = braces[dir]; 2410 | dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */ 2411 | 2412 | // look for match, count levels of pairs (( )) 2413 | level = 1; 2414 | for (;;) { 2415 | p += dir; 2416 | if (p < text || p >= end) 2417 | return NULL; 2418 | if (*p == c) 2419 | level++; // increase pair levels 2420 | if (*p == match) { 2421 | level--; // reduce pair level 2422 | if (level == 0) 2423 | return p; // found matching pair 2424 | } 2425 | } 2426 | } 2427 | 2428 | #if ENABLE_FEATURE_VI_SETOPTS 2429 | // show the matching char of a pair, () [] {} 2430 | static void showmatching(char *p) 2431 | { 2432 | char *q, *save_dot; 2433 | 2434 | // we found half of a pair 2435 | q = find_pair(p, *p); // get loc of matching char 2436 | if (q == NULL) { 2437 | indicate_error(); // no matching char 2438 | } else { 2439 | // "q" now points to matching pair 2440 | save_dot = dot; // remember where we are 2441 | dot = q; // go to new loc 2442 | refresh(FALSE); // let the user see it 2443 | mysleep(40); // give user some time 2444 | dot = save_dot; // go back to old loc 2445 | refresh(FALSE); 2446 | } 2447 | } 2448 | #endif /* FEATURE_VI_SETOPTS */ 2449 | 2450 | #if ENABLE_FEATURE_VI_UNDO 2451 | static void flush_undo_data(void) 2452 | { 2453 | struct undo_object *undo_entry; 2454 | 2455 | while (undo_stack_tail) { 2456 | undo_entry = undo_stack_tail; 2457 | undo_stack_tail = undo_entry->prev; 2458 | vi_free(undo_entry); 2459 | } 2460 | } 2461 | 2462 | // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com) 2463 | // Add to the undo stack 2464 | static void undo_push(char *src, unsigned length, int u_type) 2465 | { 2466 | struct undo_object *undo_entry; 2467 | # if ENABLE_FEATURE_VI_UNDO_QUEUE 2468 | int use_spos = u_type & UNDO_USE_SPOS; 2469 | # endif 2470 | 2471 | // "u_type" values 2472 | // UNDO_INS: insertion, undo will remove from buffer 2473 | // UNDO_DEL: deleted text, undo will restore to buffer 2474 | // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete 2475 | // The CHAIN operations are for handling multiple operations that the user 2476 | // performs with a single action, i.e. REPLACE mode or find-and-replace commands 2477 | // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue 2478 | // for the INS/DEL operation. 2479 | // UNDO_{INS,DEL} ORed with UNDO_USE_SPOS: commit the undo queue 2480 | 2481 | #if ENABLE_FEATURE_VI_UNDO_QUEUE 2482 | // This undo queuing functionality groups multiple character typing or backspaces 2483 | // into a single large undo object. This greatly reduces calls to malloc() for 2484 | // single-character operations while typing and has the side benefit of letting 2485 | // an undo operation remove chunks of text rather than a single character. 2486 | switch (u_type) { 2487 | case UNDO_EMPTY: // Just in case this ever happens... 2488 | return; 2489 | case UNDO_DEL_QUEUED: 2490 | if (length != 1) 2491 | return; // Only queue single characters 2492 | switch (undo_queue_state) { 2493 | case UNDO_EMPTY: 2494 | undo_queue_state = UNDO_DEL; 2495 | case UNDO_DEL: 2496 | undo_queue_spos = src; 2497 | undo_q++; 2498 | undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src; 2499 | // If queue is full, dump it into an object 2500 | if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX) 2501 | undo_queue_commit(); 2502 | return; 2503 | case UNDO_INS: 2504 | // Switch from storing inserted text to deleted text 2505 | undo_queue_commit(); 2506 | undo_push(src, length, UNDO_DEL_QUEUED); 2507 | return; 2508 | } 2509 | break; 2510 | case UNDO_INS_QUEUED: 2511 | if (length < 1) 2512 | return; 2513 | switch (undo_queue_state) { 2514 | case UNDO_EMPTY: 2515 | undo_queue_state = UNDO_INS; 2516 | undo_queue_spos = src; 2517 | case UNDO_INS: 2518 | while (length--) { 2519 | undo_q++; // Don't need to save any data for insertions 2520 | if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX) 2521 | undo_queue_commit(); 2522 | } 2523 | return; 2524 | case UNDO_DEL: 2525 | // Switch from storing deleted text to inserted text 2526 | undo_queue_commit(); 2527 | undo_push(src, length, UNDO_INS_QUEUED); 2528 | return; 2529 | } 2530 | break; 2531 | } 2532 | #else 2533 | u_type &= ~UNDO_USE_SPOS; 2534 | #endif 2535 | 2536 | // Allocate a new undo object 2537 | if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) { 2538 | // For UNDO_DEL objects, save deleted text 2539 | if ((text + length) == end) 2540 | length--; 2541 | // If this deletion empties text[], strip the newline. When the buffer becomes 2542 | // zero-length, a newline is added back, which requires this to compensate. 2543 | undo_entry = vi_zalloc(offsetof(struct undo_object, undo_text) + length); 2544 | rt_memcpy(undo_entry->undo_text, src, length); 2545 | } else { 2546 | undo_entry = vi_zalloc(sizeof(*undo_entry)); 2547 | } 2548 | undo_entry->length = length; 2549 | #if ENABLE_FEATURE_VI_UNDO_QUEUE 2550 | if (use_spos) { 2551 | undo_entry->start = undo_queue_spos - text; // use start position from queue 2552 | } else { 2553 | undo_entry->start = src - text; // use offset from start of text buffer 2554 | } 2555 | #else 2556 | undo_entry->start = src - text; 2557 | #endif 2558 | undo_entry->u_type = u_type; 2559 | 2560 | // Push it on undo stack 2561 | undo_entry->prev = undo_stack_tail; 2562 | undo_stack_tail = undo_entry; 2563 | modified_count++; 2564 | } 2565 | 2566 | static void undo_push_insert(char *p, int len, int undo) 2567 | { 2568 | switch (undo) { 2569 | case ALLOW_UNDO: 2570 | undo_push(p, len, UNDO_INS); 2571 | break; 2572 | case ALLOW_UNDO_CHAIN: 2573 | undo_push(p, len, UNDO_INS_CHAIN); 2574 | break; 2575 | # if ENABLE_FEATURE_VI_UNDO_QUEUE 2576 | case ALLOW_UNDO_QUEUED: 2577 | undo_push(p, len, UNDO_INS_QUEUED); 2578 | break; 2579 | # endif 2580 | } 2581 | } 2582 | 2583 | // Undo the last operation 2584 | static void undo_pop(void) 2585 | { 2586 | int repeat; 2587 | char *u_start, *u_end; 2588 | struct undo_object *undo_entry; 2589 | 2590 | // Commit pending undo queue before popping (should be unnecessary) 2591 | undo_queue_commit(); 2592 | 2593 | undo_entry = undo_stack_tail; 2594 | // Check for an empty undo stack 2595 | if (!undo_entry) { 2596 | status_line("Already at oldest change"); 2597 | return; 2598 | } 2599 | 2600 | switch (undo_entry->u_type) { 2601 | case UNDO_DEL: 2602 | case UNDO_DEL_CHAIN: 2603 | // make hole and put in text that was deleted; deallocate text 2604 | u_start = text + undo_entry->start; 2605 | text_hole_make(u_start, undo_entry->length); 2606 | rt_memcpy(u_start, undo_entry->undo_text, undo_entry->length); 2607 | # if ENABLE_FEATURE_VI_VERBOSE_STATUS 2608 | status_line("Undo [%d] %s %d chars at position %d", 2609 | modified_count, "restored", 2610 | undo_entry->length, undo_entry->start 2611 | ); 2612 | # endif 2613 | break; 2614 | case UNDO_INS: 2615 | case UNDO_INS_CHAIN: 2616 | // delete what was inserted 2617 | u_start = undo_entry->start + text; 2618 | u_end = u_start - 1 + undo_entry->length; 2619 | text_hole_delete(u_start, u_end, NO_UNDO); 2620 | # if ENABLE_FEATURE_VI_VERBOSE_STATUS 2621 | status_line("Undo [%d] %s %d chars at position %d", 2622 | modified_count, "deleted", 2623 | undo_entry->length, undo_entry->start 2624 | ); 2625 | # endif 2626 | break; 2627 | } 2628 | repeat = 0; 2629 | switch (undo_entry->u_type) { 2630 | // If this is the end of a chain, lower modification count and refresh display 2631 | case UNDO_DEL: 2632 | case UNDO_INS: 2633 | dot = (text + undo_entry->start); 2634 | refresh(FALSE); 2635 | break; 2636 | case UNDO_DEL_CHAIN: 2637 | case UNDO_INS_CHAIN: 2638 | repeat = 1; 2639 | break; 2640 | } 2641 | // Deallocate the undo object we just processed 2642 | undo_stack_tail = undo_entry->prev; 2643 | vi_free(undo_entry); 2644 | modified_count--; 2645 | // For chained operations, continue popping all the way down the chain. 2646 | if (repeat) { 2647 | undo_pop(); // Follow the undo chain if one exists 2648 | } 2649 | } 2650 | 2651 | #if ENABLE_FEATURE_VI_UNDO_QUEUE 2652 | // Flush any queued objects to the undo stack 2653 | static void undo_queue_commit(void) 2654 | { 2655 | // Pushes the queue object onto the undo stack 2656 | if (undo_q > 0) { 2657 | // Deleted character undo events grow from the end 2658 | undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q, 2659 | undo_q, 2660 | (undo_queue_state | UNDO_USE_SPOS) 2661 | ); 2662 | undo_queue_state = UNDO_EMPTY; 2663 | undo_q = 0; 2664 | } 2665 | } 2666 | #endif 2667 | 2668 | #endif /* ENABLE_FEATURE_VI_UNDO */ 2669 | 2670 | // open a hole in text[] 2671 | // might reallocate text[]! use p += text_hole_make(p, ...), 2672 | // and be careful to not use pointers into potentially freed text[]! 2673 | static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole 2674 | { 2675 | uintptr_t bias = 0; 2676 | 2677 | if (size <= 0) 2678 | return bias; 2679 | end += size; // adjust the new END 2680 | if (end >= (text + text_size)) { 2681 | char *new_text; 2682 | text_size += end - (text + text_size) + 10240; 2683 | new_text = vi_realloc(text, text_size); 2684 | bias = (new_text - text); 2685 | screenbegin += bias; 2686 | dot += bias; 2687 | end += bias; 2688 | p += bias; 2689 | #if ENABLE_FEATURE_VI_YANKMARK 2690 | { 2691 | int i; 2692 | for (i = 0; i < ARRAY_SIZE(mark); i++) 2693 | if (mark[i]) 2694 | mark[i] += bias; 2695 | } 2696 | #endif 2697 | text = new_text; 2698 | } 2699 | memmove(p + size, p, end - size - p); 2700 | rt_memset(p, ' ', size); // clear new hole 2701 | return bias; 2702 | } 2703 | 2704 | // close a hole in text[] 2705 | // "undo" value indicates if this operation should be undo-able 2706 | static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive 2707 | { 2708 | char *src, *dest; 2709 | int cnt, hole_size; 2710 | 2711 | // move forwards, from beginning 2712 | // assume p <= q 2713 | src = q + 1; 2714 | dest = p; 2715 | if (q < p) { // they are backward- swap them 2716 | src = p + 1; 2717 | dest = q; 2718 | } 2719 | hole_size = q - p + 1; 2720 | cnt = end - src; 2721 | #if ENABLE_FEATURE_VI_UNDO 2722 | switch (undo) { 2723 | case NO_UNDO: 2724 | break; 2725 | case ALLOW_UNDO: 2726 | undo_push(p, hole_size, UNDO_DEL); 2727 | break; 2728 | case ALLOW_UNDO_CHAIN: 2729 | undo_push(p, hole_size, UNDO_DEL_CHAIN); 2730 | break; 2731 | # if ENABLE_FEATURE_VI_UNDO_QUEUE 2732 | case ALLOW_UNDO_QUEUED: 2733 | undo_push(p, hole_size, UNDO_DEL_QUEUED); 2734 | break; 2735 | # endif 2736 | } 2737 | modified_count--; 2738 | #endif 2739 | if (src < text || src > end) 2740 | goto thd0; 2741 | if (dest < text || dest >= end) 2742 | goto thd0; 2743 | modified_count++; 2744 | if (src >= end) 2745 | goto thd_atend; // just delete the end of the buffer 2746 | memmove(dest, src, cnt); 2747 | thd_atend: 2748 | end = end - hole_size; // adjust the new END 2749 | if (dest >= end) 2750 | dest = end - 1; // make sure dest in below end-1 2751 | if (end <= text) 2752 | dest = end = text; // keep pointers valid 2753 | thd0: 2754 | return dest; 2755 | } 2756 | 2757 | // copy text into register, then delete text. 2758 | // 2759 | static char *yank_delete(char *start, char *stop, int buftype, int yf, int undo) 2760 | { 2761 | char *p; 2762 | 2763 | // make sure start <= stop 2764 | if (start > stop) { 2765 | // they are backwards, reverse them 2766 | p = start; 2767 | start = stop; 2768 | stop = p; 2769 | } 2770 | if (buftype == PARTIAL && *start == '\n') 2771 | return start; 2772 | p = start; 2773 | #if ENABLE_FEATURE_VI_YANKMARK 2774 | text_yank(start, stop, YDreg, buftype); 2775 | #endif 2776 | if (yf == YANKDEL) { 2777 | p = text_hole_delete(start, stop, undo); 2778 | } // delete lines 2779 | return p; 2780 | } 2781 | 2782 | static void show_help(void) 2783 | { 2784 | vi_puts("These features are available:" 2785 | #if ENABLE_FEATURE_VI_SEARCH 2786 | "\n\tPattern searches with / and ?" 2787 | #endif 2788 | #if ENABLE_FEATURE_VI_DOT_CMD 2789 | "\n\tLast command repeat with ." 2790 | #endif 2791 | #if ENABLE_FEATURE_VI_YANKMARK 2792 | "\n\tLine marking with 'x" 2793 | "\n\tNamed buffers with \"x" 2794 | #endif 2795 | #if ENABLE_FEATURE_VI_READONLY 2796 | //not implemented: "\n\tReadonly if vi is called as \"view\"" 2797 | //redundant: usage text says this too: "\n\tReadonly with -R command line arg" 2798 | #endif 2799 | #if ENABLE_FEATURE_VI_SET 2800 | "\n\tSome colon mode commands with :" 2801 | #endif 2802 | #if ENABLE_FEATURE_VI_SETOPTS 2803 | "\n\tSettable options with \":set\"" 2804 | #endif 2805 | #if ENABLE_FEATURE_VI_WIN_RESIZE 2806 | "\n\tAdapt to window re-sizes" 2807 | #endif 2808 | ); 2809 | } 2810 | 2811 | #if ENABLE_FEATURE_VI_DOT_CMD 2812 | static void start_new_cmd_q(char c) 2813 | { 2814 | // get buffer for new cmd 2815 | dotcnt = cmdcnt ? cmdcnt : 1; 2816 | last_modifying_cmd[0] = c; 2817 | lmc_len = 1; 2818 | adding2q = 1; 2819 | } 2820 | 2821 | static void end_cmd_q(void) 2822 | { 2823 | #if ENABLE_FEATURE_VI_YANKMARK 2824 | YDreg = 26; // go back to default Yank/Delete reg 2825 | #endif 2826 | adding2q = 0; 2827 | } 2828 | #endif /* FEATURE_VI_DOT_CMD */ 2829 | 2830 | #if ENABLE_FEATURE_VI_YANKMARK \ 2831 | || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) 2832 | // might reallocate text[]! use p += string_insert(p, ...), 2833 | // and be careful to not use pointers into potentially freed text[]! 2834 | static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p' 2835 | { 2836 | uintptr_t bias; 2837 | int i; 2838 | 2839 | i = strlen(s); 2840 | #if ENABLE_FEATURE_VI_UNDO 2841 | undo_push_insert(p, i, undo); 2842 | #endif 2843 | bias = text_hole_make(p, i); 2844 | p += bias; 2845 | rt_memcpy(p, s, i); 2846 | return bias; 2847 | } 2848 | #endif 2849 | 2850 | //----- Block insert/delete, undo ops -------------------------- 2851 | #if ENABLE_FEATURE_VI_YANKMARK 2852 | // copy text into a register 2853 | static char *text_yank(char *p, char *q, int dest, int buftype) 2854 | { 2855 | char *oldreg = reg[dest]; 2856 | int cnt = q - p; 2857 | if (cnt < 0) { // they are backwards- reverse them 2858 | p = q; 2859 | cnt = -cnt; 2860 | } 2861 | // Don't free register yet. This prevents the memory allocator 2862 | // from reusing the free block so we can detect if it's changed. 2863 | reg[dest] = vi_strndup(p, cnt + 1); 2864 | vi_free(oldreg); 2865 | return p; 2866 | } 2867 | 2868 | static char what_reg(void) 2869 | { 2870 | char c; 2871 | 2872 | c = 'D'; // default to D-reg 2873 | if (YDreg <= 25) 2874 | c = 'a' + (char) YDreg; 2875 | if (YDreg == 26) 2876 | c = 'D'; 2877 | if (YDreg == 27) 2878 | c = 'U'; 2879 | return c; 2880 | } 2881 | 2882 | static void check_context(char cmd) 2883 | { 2884 | // Certain movement commands update the context. 2885 | if (strchr(":%{}'GHLMz/?Nn", cmd) != NULL) { 2886 | mark[27] = mark[26]; // move cur to prev 2887 | mark[26] = dot; // move local to cur 2888 | } 2889 | } 2890 | 2891 | static char *swap_context(char *p) // goto new context for '' command make this the current context 2892 | { 2893 | char *tmp; 2894 | 2895 | // the current context is in mark[26] 2896 | // the previous context is in mark[27] 2897 | // only swap context if other context is valid 2898 | if (text <= mark[27] && mark[27] <= end - 1) { 2899 | tmp = mark[27]; 2900 | mark[27] = mark[26]; 2901 | mark[26] = tmp; 2902 | p = mark[26]; // where we are going- previous context 2903 | } 2904 | return p; 2905 | } 2906 | 2907 | # if ENABLE_FEATURE_VI_VERBOSE_STATUS 2908 | static void yank_status(const char *op, const char *p, int cnt) 2909 | { 2910 | int lines, chars; 2911 | 2912 | lines = chars = 0; 2913 | while (*p) { 2914 | ++chars; 2915 | if (*p++ == '\n') 2916 | ++lines; 2917 | } 2918 | status_line("%s %d lines (%d chars) from [%c]", 2919 | op, lines * cnt, chars * cnt, what_reg()); 2920 | } 2921 | # endif 2922 | #endif /* FEATURE_VI_YANKMARK */ 2923 | 2924 | //----- Set terminal attributes -------------------------------- 2925 | static void rawmode(void) 2926 | { 2927 | #ifdef RT_USING_POSIX_TERMIOS 2928 | // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals 2929 | set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL); 2930 | #endif 2931 | } 2932 | 2933 | static void cookmode(void) 2934 | { 2935 | #ifdef RT_USING_POSIX_TERMIOS 2936 | tcsetattr_stdin_TCSANOW(&term_orig); 2937 | #endif 2938 | } 2939 | 2940 | // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready) 2941 | static int mysleep(int hund) 2942 | { 2943 | struct pollfd pfd[1]; 2944 | 2945 | pfd[0].fd = STDIN_FILENO; 2946 | pfd[0].events = POLLIN; 2947 | return safe_poll(pfd, 1, hund*10) > 0; 2948 | } 2949 | 2950 | //----- IO Routines -------------------------------------------- 2951 | static int readit(void) // read (maybe cursor) key from stdin 2952 | { 2953 | int c; 2954 | 2955 | // Wait for input. TIMEOUT = -1 makes read_key wait even 2956 | // on nonblocking stdin. 2957 | // Note: read_key sets errno to 0 on success. 2958 | again: 2959 | c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1); 2960 | if (c == -1) { // EOF/error 2961 | if (errno == EAGAIN) // paranoia 2962 | goto again; 2963 | go_bottom_and_clear_to_eol(); 2964 | cookmode(); // terminal to "cooked" 2965 | LOG_E("can't read user input"); 2966 | } 2967 | return c; 2968 | } 2969 | 2970 | #if ENABLE_FEATURE_VI_DOT_CMD 2971 | static int get_one_char(void) 2972 | { 2973 | int c; 2974 | 2975 | if (!adding2q) { 2976 | // we are not adding to the q. 2977 | // but, we may be reading from a saved q. 2978 | // (checking "ioq" for NULL is wrong, it's not reset to NULL 2979 | // when done - "ioq_start" is reset instead). 2980 | if (ioq_start != NULL) { 2981 | // there is a queue to get chars from. 2982 | // careful with correct sign expansion! 2983 | c = (unsigned char)*ioq++; 2984 | if (c != '\0') 2985 | return c; 2986 | // the end of the q 2987 | vi_free(ioq_start); 2988 | ioq_start = NULL; 2989 | // read from STDIN: 2990 | } 2991 | return readit(); 2992 | } 2993 | // we are adding STDIN chars to q. 2994 | c = readit(); 2995 | if (lmc_len >= ARRAY_SIZE(last_modifying_cmd) - 2) { 2996 | // last_modifying_cmd[] is too small, can't remember the cmd 2997 | // - drop it 2998 | adding2q = 0; 2999 | lmc_len = 0; 3000 | } else { 3001 | last_modifying_cmd[lmc_len++] = c; 3002 | } 3003 | return c; 3004 | } 3005 | #endif 3006 | 3007 | // Get type of thing to operate on and adjust count 3008 | static int get_motion_char(void) 3009 | { 3010 | int c, cnt; 3011 | 3012 | c = get_one_char(); 3013 | if (isdigit(c)) { 3014 | if (c != '0') { 3015 | // get any non-zero motion count 3016 | for (cnt = 0; isdigit(c); c = get_one_char()) 3017 | cnt = cnt * 10 + (c - '0'); 3018 | cmdcnt = (cmdcnt ? cmdcnt : 1) * cnt; 3019 | } else { 3020 | // ensure standalone '0' works 3021 | cmdcnt = 0; 3022 | } 3023 | } 3024 | 3025 | return c; 3026 | } 3027 | 3028 | // Get input line (uses "status line" area) 3029 | static char *get_input_line(const char *prompt) 3030 | { 3031 | // char [MAX_INPUT_LEN] 3032 | #define buf get_input_line__buf 3033 | 3034 | int c; 3035 | int i; 3036 | 3037 | strcpy(buf, prompt); 3038 | last_status_cksum = 0; // force status update 3039 | go_bottom_and_clear_to_eol(); 3040 | vi_puts(buf); // write out the :, /, or ? prompt 3041 | 3042 | i = strlen(buf); 3043 | while (i < MAX_INPUT_LEN - 1) { 3044 | c = get_one_char(); 3045 | if (c == '\n' || c == '\r' || c == 27) 3046 | break; // this is end of input 3047 | if (isbackspace(c)) { 3048 | // user wants to erase prev char 3049 | buf[--i] = '\0'; 3050 | go_bottom_and_clear_to_eol(); 3051 | if (i <= 0) // user backs up before b-o-l, exit 3052 | break; 3053 | vi_puts(buf); 3054 | } else if (c > 0 && c < 256) { // exclude Unicode 3055 | // (TODO: need to handle Unicode) 3056 | buf[i] = c; 3057 | buf[++i] = '\0'; 3058 | vi_putchar(c); 3059 | } 3060 | } 3061 | refresh(FALSE); 3062 | return buf; 3063 | #undef buf 3064 | } 3065 | 3066 | // might reallocate text[]! 3067 | static int file_insert(const char *fn, char *p, int initial) 3068 | { 3069 | int cnt = -1; 3070 | int fd, size; 3071 | struct stat statbuf; 3072 | 3073 | if (p < text || p > end) { 3074 | status_line_bold("Trying to insert file outside of memory"); 3075 | return cnt; 3076 | } 3077 | 3078 | fd = open(fn, O_RDONLY, 0); 3079 | if (fd < 0) { 3080 | if (!initial) 3081 | status_line_bold_errno(fn); 3082 | return cnt; 3083 | } 3084 | 3085 | /* Validate file */ 3086 | if (fstat(fd, &statbuf) < 0) { 3087 | status_line_bold_errno(fn); 3088 | goto fi; 3089 | } 3090 | if (!S_ISREG(statbuf.st_mode)) { 3091 | status_line_bold("'%s' is not a regular file", fn); 3092 | goto fi; 3093 | } 3094 | size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX); 3095 | p += text_hole_make(p, size); 3096 | cnt = full_read(fd, p, size); 3097 | if (cnt < 0) { 3098 | status_line_bold_errno(fn); 3099 | p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert 3100 | } else if (cnt < size) { 3101 | // There was a partial read, shrink unused space 3102 | p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO); 3103 | status_line_bold("can't read '%s'", fn); 3104 | } 3105 | # if ENABLE_FEATURE_VI_UNDO 3106 | else { 3107 | undo_push_insert(p, size, ALLOW_UNDO); 3108 | } 3109 | # endif 3110 | fi: 3111 | close(fd); 3112 | 3113 | #if ENABLE_FEATURE_VI_READONLY 3114 | if (initial 3115 | && ((access(fn, W_OK) < 0) || 3116 | /* root will always have access() 3117 | * so we check fileperms too */ 3118 | !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) 3119 | ) 3120 | ) { 3121 | SET_READONLY_FILE(readonly_mode); 3122 | } 3123 | #endif 3124 | return cnt; 3125 | } 3126 | 3127 | static int file_write(char *fn, char *first, char *last) 3128 | { 3129 | int fd, cnt, charcnt; 3130 | 3131 | if (fn == 0) { 3132 | status_line_bold("No current filename"); 3133 | return -2; 3134 | } 3135 | /* By popular request we do not open file with O_TRUNC, 3136 | * but instead ftruncate() it _after_ successful write. 3137 | * Might reduce amount of data lost on power fail etc. 3138 | */ 3139 | // ftruncate nosys + O_TRUNC 3140 | fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0666); 3141 | if (fd < 0) 3142 | return -1; 3143 | cnt = last - first + 1; 3144 | charcnt = full_write(fd, first, cnt); 3145 | // ftruncate(fd, charcnt); 3146 | if (charcnt == cnt) { 3147 | // good write 3148 | //modified_count = FALSE; 3149 | } else { 3150 | charcnt = 0; 3151 | } 3152 | close(fd); 3153 | return charcnt; 3154 | } 3155 | 3156 | //----- Terminal Drawing --------------------------------------- 3157 | // The terminal is made up of 'rows' line of 'columns' columns. 3158 | // classically this would be 24 x 80. 3159 | // screen coordinates 3160 | // 0,0 ... 0,79 3161 | // 1,0 ... 1,79 3162 | // . ... . 3163 | // . ... . 3164 | // 22,0 ... 22,79 3165 | // 23,0 ... 23,79 <- status line 3166 | 3167 | //----- Move the cursor to row x col (count from 0, not 1) ------- 3168 | static void place_cursor(int row, int col) 3169 | { 3170 | char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2]; 3171 | 3172 | if (row < 0) row = 0; 3173 | if (row >= rows) row = rows - 1; 3174 | if (col < 0) col = 0; 3175 | if (col >= columns) col = columns - 1; 3176 | 3177 | rt_sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1); 3178 | vi_puts(cm1); 3179 | } 3180 | 3181 | //----- Erase from cursor to end of line ----------------------- 3182 | static void clear_to_eol(void) 3183 | { 3184 | vi_puts(ESC_CLEAR2EOL); 3185 | } 3186 | 3187 | static void go_bottom_and_clear_to_eol(void) 3188 | { 3189 | place_cursor(rows - 1, 0); 3190 | clear_to_eol(); 3191 | } 3192 | 3193 | //----- Start standout mode ------------------------------------ 3194 | static void standout_start(void) 3195 | { 3196 | vi_puts(ESC_BOLD_TEXT); 3197 | } 3198 | 3199 | //----- End standout mode -------------------------------------- 3200 | static void standout_end(void) 3201 | { 3202 | vi_puts(ESC_NORM_TEXT); 3203 | } 3204 | 3205 | //----- Flash the screen -------------------------------------- 3206 | static void flash(int h) 3207 | { 3208 | standout_start(); 3209 | redraw(TRUE); 3210 | mysleep(h); 3211 | standout_end(); 3212 | redraw(TRUE); 3213 | } 3214 | 3215 | static void indicate_error(void) 3216 | { 3217 | cmd_error = TRUE; 3218 | if (!err_method) { 3219 | vi_puts(ESC_BELL); 3220 | } else { 3221 | flash(10); 3222 | } 3223 | } 3224 | 3225 | //----- Screen[] Routines -------------------------------------- 3226 | //----- Erase the Screen[] memory ------------------------------ 3227 | static void screen_erase(void) 3228 | { 3229 | rt_memset(screen, ' ', screensize); // clear new screen 3230 | } 3231 | 3232 | static int bufsum(char *buf, int count) 3233 | { 3234 | int sum = 0; 3235 | char *e = buf + count; 3236 | 3237 | while (buf < e) 3238 | sum += (unsigned char) *buf++; 3239 | return sum; 3240 | } 3241 | 3242 | //----- Draw the status line at bottom of the screen ------------- 3243 | static void show_status_line(void) 3244 | { 3245 | int cnt = 0, cksum = 0; 3246 | 3247 | // either we already have an error or status message, or we 3248 | // create one. 3249 | if (!have_status_msg) { 3250 | cnt = format_edit_status(); 3251 | cksum = bufsum(status_buffer, cnt); 3252 | } 3253 | if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) { 3254 | last_status_cksum = cksum; // remember if we have seen this line 3255 | go_bottom_and_clear_to_eol(); 3256 | vi_puts(status_buffer); 3257 | if (have_status_msg) { 3258 | if (((int)strlen(status_buffer) - (have_status_msg - 1)) > 3259 | (columns - 1) ) { 3260 | have_status_msg = 0; 3261 | Hit_Return(); 3262 | } 3263 | have_status_msg = 0; 3264 | } 3265 | place_cursor(crow, ccol); // put cursor back in correct place 3266 | } 3267 | } 3268 | 3269 | //----- format the status buffer, the bottom line of screen ------ 3270 | // format status buffer, with STANDOUT mode 3271 | static void status_line_bold(const char *format, ...) 3272 | { 3273 | va_list args; 3274 | 3275 | va_start(args, format); 3276 | strcpy(status_buffer, ESC_BOLD_TEXT); 3277 | rt_vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1), 3278 | STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT), 3279 | format, args 3280 | ); 3281 | strcat(status_buffer, ESC_NORM_TEXT); 3282 | va_end(args); 3283 | 3284 | have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1); 3285 | } 3286 | 3287 | static void status_line_bold_errno(const char *fn) 3288 | { 3289 | status_line_bold("'%s' %s", fn, strerror(errno)); 3290 | } 3291 | 3292 | //----- format the status buffer, the bottom line of screen ------ 3293 | static void status_line(const char *format, ...) 3294 | { 3295 | va_list args; 3296 | 3297 | va_start(args, format); 3298 | rt_vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args); 3299 | va_end(args); 3300 | 3301 | have_status_msg = 1; 3302 | } 3303 | 3304 | // copy s to buf, convert unprintable 3305 | static void print_literal(char *buf, const char *s) 3306 | { 3307 | char *d; 3308 | unsigned char c; 3309 | 3310 | if (!s[0]) 3311 | s = "(NULL)"; 3312 | 3313 | d = buf; 3314 | for (; *s; s++) { 3315 | c = *s; 3316 | if ((c & 0x80) && !Isprint(c)) 3317 | c = '?'; 3318 | if (c < ' ' || c == 0x7f) { 3319 | *d++ = '^'; 3320 | c |= '@'; /* 0x40 */ 3321 | if (c == 0x7f) 3322 | c = '?'; 3323 | } 3324 | *d++ = c; 3325 | *d = '\0'; 3326 | if (d - buf > MAX_INPUT_LEN - 10) // paranoia 3327 | break; 3328 | } 3329 | } 3330 | 3331 | static void not_implemented(const char *s) 3332 | { 3333 | char buf[MAX_INPUT_LEN]; 3334 | 3335 | print_literal(buf, s); 3336 | status_line_bold("'%s' is not implemented", buf); 3337 | } 3338 | 3339 | // show file status on status line 3340 | static int format_edit_status(void) 3341 | { 3342 | static const char cmd_mode_indicator[] ALIGN1 = "-IR-"; 3343 | 3344 | #define tot format_edit_status__tot 3345 | 3346 | int cur, percent, ret, trunc_at; 3347 | 3348 | // modified_count is now a counter rather than a flag. this 3349 | // helps reduce the amount of line counting we need to do. 3350 | // (this will cause a mis-reporting of modified status 3351 | // once every MAXINT editing operations.) 3352 | 3353 | // it would be nice to do a similar optimization here -- if 3354 | // we haven't done a motion that could have changed which line 3355 | // we're on, then we shouldn't have to do this count_lines() 3356 | cur = count_lines(text, dot); 3357 | 3358 | // count_lines() is expensive. 3359 | // Call it only if something was changed since last time 3360 | // we were here: 3361 | if (modified_count != last_modified_count) { 3362 | tot = cur + count_lines(dot, end - 1) - 1; 3363 | last_modified_count = modified_count; 3364 | } 3365 | 3366 | // current line percent 3367 | // ------------- ~~ ---------- 3368 | // total lines 100 3369 | if (tot > 0) { 3370 | percent = (100 * cur) / tot; 3371 | } else { 3372 | cur = tot = 0; 3373 | percent = 100; 3374 | } 3375 | 3376 | trunc_at = columns < STATUS_BUFFER_LEN-1 ? 3377 | columns : STATUS_BUFFER_LEN-1; 3378 | 3379 | ret = rt_snprintf(status_buffer, trunc_at+1, 3380 | #if ENABLE_FEATURE_VI_READONLY 3381 | "%c %s%s%s %d/%d %d%%", 3382 | #else 3383 | "%c %s%s %d/%d %d%%", 3384 | #endif 3385 | cmd_mode_indicator[cmd_mode & 3], 3386 | (current_filename != NULL ? current_filename : "No file"), 3387 | #if ENABLE_FEATURE_VI_READONLY 3388 | (readonly_mode ? " [Readonly]" : ""), 3389 | #endif 3390 | (modified_count ? " [Modified]" : ""), 3391 | cur, tot, percent); 3392 | 3393 | if (ret >= 0 && ret < trunc_at) 3394 | return ret; /* it all fit */ 3395 | 3396 | return trunc_at; /* had to truncate */ 3397 | #undef tot 3398 | } 3399 | 3400 | //----- Force refresh of all Lines ----------------------------- 3401 | static void redraw(int full_screen) 3402 | { 3403 | // cursor to top,left; clear to the end of screen 3404 | vi_puts(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS); 3405 | screen_erase(); // erase the internal screen buffer 3406 | last_status_cksum = 0; // force status update 3407 | refresh(full_screen); // this will redraw the entire display 3408 | show_status_line(); 3409 | } 3410 | 3411 | //----- Format a text[] line into a buffer --------------------- 3412 | static char *format_line(char *src /*, int li*/) 3413 | { 3414 | unsigned char c; 3415 | int co; 3416 | int ofs = offset; 3417 | char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2] 3418 | 3419 | c = '~'; // char in col 0 in non-existent lines is '~' 3420 | co = 0; 3421 | while (co < columns + tabstop) { 3422 | // have we gone past the end? 3423 | if (src < end) { 3424 | c = *src++; 3425 | if (c == '\n') 3426 | break; 3427 | if ((c & 0x80) && !Isprint(c)) { 3428 | c = '.'; 3429 | } 3430 | if (c < ' ' || c == 0x7f) { 3431 | if (c == '\t') { 3432 | c = ' '; 3433 | // co % 8 != 7 3434 | while ((co % tabstop) != (tabstop - 1)) { 3435 | dest[co++] = c; 3436 | } 3437 | } else { 3438 | dest[co++] = '^'; 3439 | if (c == 0x7f) 3440 | c = '?'; 3441 | else 3442 | c += '@'; // Ctrl-X -> 'X' 3443 | } 3444 | } 3445 | } 3446 | dest[co++] = c; 3447 | // discard scrolled-off-to-the-left portion, 3448 | // in tabstop-sized pieces 3449 | if (ofs >= tabstop && co >= tabstop) { 3450 | memmove(dest, dest + tabstop, co); 3451 | co -= tabstop; 3452 | ofs -= tabstop; 3453 | } 3454 | if (src >= end) 3455 | break; 3456 | } 3457 | // check "short line, gigantic offset" case 3458 | if (co < ofs) 3459 | ofs = co; 3460 | // discard last scrolled off part 3461 | co -= ofs; 3462 | dest += ofs; 3463 | // fill the rest with spaces 3464 | if (co < columns) 3465 | rt_memset(&dest[co], ' ', columns - co); 3466 | return dest; 3467 | } 3468 | 3469 | //----- Refresh the changed screen lines ----------------------- 3470 | // Copy the source line from text[] into the buffer and note 3471 | // if the current screenline is different from the new buffer. 3472 | // If they differ then that line needs redrawing on the terminal. 3473 | // 3474 | static void refresh(int full_screen) 3475 | { 3476 | #define old_offset refresh__old_offset 3477 | 3478 | int li, changed; 3479 | char *tp, *sp; // pointer into text[] and screen[] 3480 | 3481 | if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) { 3482 | unsigned c = columns, r = rows; 3483 | query_screen_dimensions(); 3484 | if (c != columns || r != rows) { 3485 | full_screen = TRUE; 3486 | /* update screen memory since SIGWINCH won't have done it */ 3487 | new_screen(rows, columns); 3488 | } 3489 | } 3490 | sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot") 3491 | tp = screenbegin; // index into text[] of top line 3492 | 3493 | // compare text[] to screen[] and mark screen[] lines that need updating 3494 | for (li = 0; li < rows - 1; li++) { 3495 | int cs, ce; // column start & end 3496 | char *out_buf; 3497 | // format current text line 3498 | out_buf = format_line(tp /*, li*/); 3499 | 3500 | // skip to the end of the current text[] line 3501 | if (tp < end) { 3502 | char *t = memchr(tp, '\n', end - tp); 3503 | if (!t) t = end - 1; 3504 | tp = t + 1; 3505 | } 3506 | 3507 | // see if there are any changes between vitual screen and out_buf 3508 | changed = FALSE; // assume no change 3509 | cs = 0; 3510 | ce = columns - 1; 3511 | sp = &screen[li * columns]; // start of screen line 3512 | if (full_screen) { 3513 | // force re-draw of every single column from 0 - columns-1 3514 | goto re0; 3515 | } 3516 | // compare newly formatted buffer with virtual screen 3517 | // look forward for first difference between buf and screen 3518 | for (; cs <= ce; cs++) { 3519 | if (out_buf[cs] != sp[cs]) { 3520 | changed = TRUE; // mark for redraw 3521 | break; 3522 | } 3523 | } 3524 | 3525 | // look backward for last difference between out_buf and screen 3526 | for (; ce >= cs; ce--) { 3527 | if (out_buf[ce] != sp[ce]) { 3528 | changed = TRUE; // mark for redraw 3529 | break; 3530 | } 3531 | } 3532 | // now, cs is index of first diff, and ce is index of last diff 3533 | 3534 | // if horz offset has changed, force a redraw 3535 | if (offset != old_offset) { 3536 | re0: 3537 | changed = TRUE; 3538 | } 3539 | 3540 | // make a sanity check of columns indexes 3541 | if (cs < 0) cs = 0; 3542 | if (ce > columns - 1) ce = columns - 1; 3543 | if (cs > ce) { cs = 0; ce = columns - 1; } 3544 | // is there a change between vitual screen and out_buf 3545 | if (changed) { 3546 | // copy changed part of buffer to virtual screen 3547 | rt_memcpy(sp+cs, out_buf+cs, ce-cs+1); 3548 | place_cursor(li, cs); 3549 | // write line out to terminal 3550 | vi_write(&sp[cs], ce - cs + 1); /* fwrite(&sp[cs], ce - cs + 1, 1, stdout); */ 3551 | } 3552 | } 3553 | 3554 | place_cursor(crow, ccol); 3555 | 3556 | if (!keep_index) 3557 | cindex = ccol + offset; 3558 | 3559 | old_offset = offset; 3560 | #undef old_offset 3561 | } 3562 | 3563 | //--------------------------------------------------------------------- 3564 | //----- the Ascii Chart ----------------------------------------------- 3565 | // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel 3566 | // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si 3567 | // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb 3568 | // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us 3569 | // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 ' 3570 | // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f / 3571 | // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7 3572 | // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ? 3573 | // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G 3574 | // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O 3575 | // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W 3576 | // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _ 3577 | // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g 3578 | // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o 3579 | // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w 3580 | // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del 3581 | //--------------------------------------------------------------------- 3582 | 3583 | //----- Execute a Vi Command ----------------------------------- 3584 | static void do_cmd(int c) 3585 | { 3586 | char *p, *q, *save_dot; 3587 | char buf[12]; 3588 | int dir; 3589 | int cnt, i, j; 3590 | int c1; 3591 | #if ENABLE_FEATURE_VI_YANKMARK 3592 | char *orig_dot = dot; 3593 | #endif 3594 | #if ENABLE_FEATURE_VI_UNDO 3595 | int allow_undo = ALLOW_UNDO; 3596 | int undo_del = UNDO_DEL; 3597 | #endif 3598 | 3599 | // c1 = c; // quiet the compiler 3600 | // cnt = yf = 0; // quiet the compiler 3601 | // p = q = save_dot = buf; // quiet the compiler 3602 | rt_memset(buf, '\0', sizeof(buf)); 3603 | keep_index = FALSE; 3604 | cmd_error = FALSE; 3605 | 3606 | show_status_line(); 3607 | 3608 | // if this is a cursor key, skip these checks 3609 | switch (c) { 3610 | case KEYCODE_UP: 3611 | case KEYCODE_DOWN: 3612 | case KEYCODE_LEFT: 3613 | case KEYCODE_RIGHT: 3614 | case KEYCODE_HOME: 3615 | case KEYCODE_END: 3616 | case KEYCODE_PAGEUP: 3617 | case KEYCODE_PAGEDOWN: 3618 | case KEYCODE_DELETE: 3619 | goto key_cmd_mode; 3620 | } 3621 | 3622 | if (cmd_mode == 2) { 3623 | // flip-flop Insert/Replace mode 3624 | if (c == KEYCODE_INSERT) 3625 | goto dc_i; 3626 | // we are 'R'eplacing the current *dot with new char 3627 | if (*dot == '\n') { 3628 | // don't Replace past E-o-l 3629 | cmd_mode = 1; // convert to insert 3630 | undo_queue_commit(); 3631 | } else { 3632 | if (1 <= c || Isprint(c)) { 3633 | if (c != 27 && !isbackspace(c)) 3634 | dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO); 3635 | dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); 3636 | } 3637 | goto dc1; 3638 | } 3639 | } 3640 | if (cmd_mode == 1) { 3641 | // hitting "Insert" twice means "R" replace mode 3642 | if (c == KEYCODE_INSERT) goto dc5; 3643 | // insert the char c at "dot" 3644 | if (1 <= c || Isprint(c)) { 3645 | dot = char_insert(dot, c, ALLOW_UNDO_QUEUED); 3646 | } 3647 | goto dc1; 3648 | } 3649 | 3650 | key_cmd_mode: 3651 | switch (c) { 3652 | //case 0x01: // soh 3653 | //case 0x09: // ht 3654 | //case 0x0b: // vt 3655 | //case 0x0e: // so 3656 | //case 0x0f: // si 3657 | //case 0x10: // dle 3658 | //case 0x11: // dc1 3659 | //case 0x13: // dc3 3660 | //case 0x16: // syn 3661 | //case 0x17: // etb 3662 | //case 0x18: // can 3663 | //case 0x1c: // fs 3664 | //case 0x1d: // gs 3665 | //case 0x1e: // rs 3666 | //case 0x1f: // us 3667 | //case '!': // !- 3668 | //case '#': // #- 3669 | //case '&': // &- 3670 | //case '(': // (- 3671 | //case ')': // )- 3672 | //case '*': // *- 3673 | //case '=': // =- 3674 | //case '@': // @- 3675 | //case 'K': // K- 3676 | //case 'Q': // Q- 3677 | //case 'S': // S- 3678 | //case 'V': // V- 3679 | //case '[': // [- 3680 | //case '\\': // \- 3681 | //case ']': // ]- 3682 | //case '_': // _- 3683 | //case '`': // `- 3684 | //case 'v': // v- 3685 | default: // unrecognized command 3686 | buf[0] = c; 3687 | buf[1] = '\0'; 3688 | not_implemented(buf); 3689 | end_cmd_q(); // stop adding to q 3690 | case 0x00: // nul- ignore 3691 | break; 3692 | case 2: // ctrl-B scroll up full screen 3693 | case KEYCODE_PAGEUP: // Cursor Key Page Up 3694 | dot_scroll(rows - 2, -1); 3695 | break; 3696 | case 4: // ctrl-D scroll down half screen 3697 | dot_scroll((rows - 2) / 2, 1); 3698 | break; 3699 | case 5: // ctrl-E scroll down one line 3700 | dot_scroll(1, 1); 3701 | break; 3702 | case 6: // ctrl-F scroll down full screen 3703 | case KEYCODE_PAGEDOWN: // Cursor Key Page Down 3704 | dot_scroll(rows - 2, 1); 3705 | break; 3706 | case 7: // ctrl-G show current status 3707 | last_status_cksum = 0; // force status update 3708 | break; 3709 | case 'h': // h- move left 3710 | case KEYCODE_LEFT: // cursor key Left 3711 | case 8: // ctrl-H- move left (This may be ERASE char) 3712 | case 0x7f: // DEL- move left (This may be ERASE char) 3713 | do { 3714 | dot_left(); 3715 | } while (--cmdcnt > 0); 3716 | break; 3717 | case 10: // Newline ^J 3718 | case 'j': // j- goto next line, same col 3719 | case KEYCODE_DOWN: // cursor key Down 3720 | case 13: // Carriage Return ^M 3721 | case '+': // +- goto next line 3722 | q = dot; 3723 | do { 3724 | p = next_line(q); 3725 | if (p == end_line(q)) { 3726 | indicate_error(); 3727 | goto dc1; 3728 | } 3729 | q = p; 3730 | } while (--cmdcnt > 0); 3731 | dot = q; 3732 | if (c == 13 || c == '+') { 3733 | dot_skip_over_ws(); 3734 | } else { 3735 | // try to stay in saved column 3736 | dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex); 3737 | keep_index = TRUE; 3738 | } 3739 | break; 3740 | case 12: // ctrl-L force redraw whole screen 3741 | case 18: // ctrl-R force redraw 3742 | redraw(TRUE); // this will redraw the entire display 3743 | break; 3744 | case 21: // ctrl-U scroll up half screen 3745 | dot_scroll((rows - 2) / 2, -1); 3746 | break; 3747 | case 25: // ctrl-Y scroll up one line 3748 | dot_scroll(1, -1); 3749 | break; 3750 | case 27: // esc 3751 | if (cmd_mode == 0) 3752 | indicate_error(); 3753 | cmd_mode = 0; // stop inserting 3754 | undo_queue_commit(); 3755 | end_cmd_q(); 3756 | last_status_cksum = 0; // force status update 3757 | break; 3758 | case ' ': // move right 3759 | case 'l': // move right 3760 | case KEYCODE_RIGHT: // Cursor Key Right 3761 | do { 3762 | dot_right(); 3763 | } while (--cmdcnt > 0); 3764 | break; 3765 | #if ENABLE_FEATURE_VI_YANKMARK 3766 | case '"': // "- name a register to use for Delete/Yank 3767 | c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower() 3768 | if ((unsigned)c1 <= 25) { // a-z? 3769 | YDreg = c1; 3770 | } else { 3771 | indicate_error(); 3772 | } 3773 | break; 3774 | case '\'': // '- goto a specific mark 3775 | c1 = (get_one_char() | 0x20); 3776 | if ((unsigned)(c1 - 'a') <= 25) { // a-z? 3777 | c1 = (c1 - 'a'); 3778 | // get the b-o-l 3779 | q = mark[c1]; 3780 | if (text <= q && q < end) { 3781 | dot = q; 3782 | dot_begin(); // go to B-o-l 3783 | dot_skip_over_ws(); 3784 | } else { 3785 | indicate_error(); 3786 | } 3787 | } else if (c1 == '\'') { // goto previous context 3788 | dot = swap_context(dot); // swap current and previous context 3789 | dot_begin(); // go to B-o-l 3790 | dot_skip_over_ws(); 3791 | #if ENABLE_FEATURE_VI_YANKMARK 3792 | orig_dot = dot; // this doesn't update stored contexts 3793 | #endif 3794 | } else { 3795 | indicate_error(); 3796 | } 3797 | break; 3798 | case 'm': // m- Mark a line 3799 | // this is really stupid. If there are any inserts or deletes 3800 | // between text[0] and dot then this mark will not point to the 3801 | // correct location! It could be off by many lines! 3802 | // Well..., at least its quick and dirty. 3803 | c1 = (get_one_char() | 0x20) - 'a'; 3804 | if ((unsigned)c1 <= 25) { // a-z? 3805 | // remember the line 3806 | mark[c1] = dot; 3807 | } else { 3808 | indicate_error(); 3809 | } 3810 | break; 3811 | case 'P': // P- Put register before 3812 | case 'p': // p- put register after 3813 | p = reg[YDreg]; 3814 | if (p == NULL) { 3815 | status_line_bold("Nothing in register %c", what_reg()); 3816 | break; 3817 | } 3818 | cnt = 0; 3819 | i = cmdcnt ? cmdcnt : 1; 3820 | // are we putting whole lines or strings 3821 | if (regtype[YDreg] == WHOLE) { 3822 | if (c == 'P') { 3823 | dot_begin(); // putting lines- Put above 3824 | } 3825 | else /* if ( c == 'p') */ { 3826 | // are we putting after very last line? 3827 | if (end_line(dot) == (end - 1)) { 3828 | dot = end; // force dot to end of text[] 3829 | } else { 3830 | dot_next(); // next line, then put before 3831 | } 3832 | } 3833 | } else { 3834 | if (c == 'p') 3835 | dot_right(); // move to right, can move to NL 3836 | // how far to move cursor if register doesn't have a NL 3837 | if (strchr(p, '\n') == NULL) 3838 | cnt = i * strlen(p) - 1; 3839 | } 3840 | do { 3841 | // dot is adjusted if text[] is reallocated so we don't have to 3842 | string_insert(dot, p, allow_undo); // insert the string 3843 | # if ENABLE_FEATURE_VI_UNDO 3844 | allow_undo = ALLOW_UNDO_CHAIN; 3845 | # endif 3846 | } while (--cmdcnt > 0); 3847 | dot += cnt; 3848 | dot_skip_over_ws(); 3849 | # if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS 3850 | yank_status("Put", p, i); 3851 | # endif 3852 | end_cmd_q(); // stop adding to q 3853 | break; 3854 | case 'U': // U- Undo; replace current line with original version 3855 | if (reg[Ureg] != NULL) { 3856 | p = begin_line(dot); 3857 | q = end_line(dot); 3858 | p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line 3859 | p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line 3860 | dot = p; 3861 | dot_skip_over_ws(); 3862 | # if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS 3863 | yank_status("Undo", reg[Ureg], 1); 3864 | # endif 3865 | } 3866 | break; 3867 | #endif /* FEATURE_VI_YANKMARK */ 3868 | #if ENABLE_FEATURE_VI_UNDO 3869 | case 'u': // u- undo last operation 3870 | undo_pop(); 3871 | break; 3872 | #endif 3873 | case '$': // $- goto end of line 3874 | case KEYCODE_END: // Cursor Key End 3875 | for (;;) { 3876 | dot = end_line(dot); 3877 | if (--cmdcnt <= 0) 3878 | break; 3879 | dot_next(); 3880 | } 3881 | cindex = C_END; 3882 | keep_index = TRUE; 3883 | break; 3884 | case '%': // %- find matching char of pair () [] {} 3885 | for (q = dot; q < end && *q != '\n'; q++) { 3886 | if (strchr("()[]{}", *q) != NULL) { 3887 | // we found half of a pair 3888 | p = find_pair(q, *q); 3889 | if (p == NULL) { 3890 | indicate_error(); 3891 | } else { 3892 | dot = p; 3893 | } 3894 | break; 3895 | } 3896 | } 3897 | if (*q == '\n') 3898 | indicate_error(); 3899 | break; 3900 | case 'f': // f- forward to a user specified char 3901 | case 'F': // F- backward to a user specified char 3902 | case 't': // t- move to char prior to next x 3903 | case 'T': // T- move to char after previous x 3904 | last_search_char = get_one_char(); // get the search char 3905 | last_search_cmd = c; 3906 | // fall through 3907 | case ';': // ;- look at rest of line for last search char 3908 | case ',': // ,- repeat latest search in opposite direction 3909 | dot_to_char(c != ',' ? last_search_cmd : last_search_cmd ^ 0x20); 3910 | break; 3911 | #if ENABLE_FEATURE_VI_DOT_CMD 3912 | case '.': // .- repeat the last modifying command 3913 | // Stuff the last_modifying_cmd back into stdin 3914 | // and let it be re-executed. 3915 | if (lmc_len != 0) { 3916 | if (cmdcnt) // update saved count if current count is non-zero 3917 | dotcnt = cmdcnt; 3918 | last_modifying_cmd[lmc_len] = '\0'; 3919 | ioq = ioq_start = xasprintf("%u%s", dotcnt, last_modifying_cmd); 3920 | } 3921 | break; 3922 | #endif 3923 | #if ENABLE_FEATURE_VI_SEARCH 3924 | case 'N': // N- backward search for last pattern 3925 | dir = last_search_pattern[0] == '/' ? BACK : FORWARD; 3926 | goto dc4; // now search for pattern 3927 | case '?': // ?- backward search for a pattern 3928 | case '/': // /- forward search for a pattern 3929 | buf[0] = c; 3930 | buf[1] = '\0'; 3931 | q = get_input_line(buf); // get input line- use "status line" 3932 | if (!q[0]) // user changed mind and erased the "/"- do nothing 3933 | break; 3934 | if (!q[1]) { // if no pat re-use old pat 3935 | if (last_search_pattern[0]) 3936 | last_search_pattern[0] = c; 3937 | } else { // strlen(q) > 1: new pat- save it and find 3938 | vi_free(last_search_pattern); 3939 | last_search_pattern = vi_strdup(q); 3940 | } 3941 | // fall through 3942 | case 'n': // n- repeat search for last pattern 3943 | // search rest of text[] starting at next char 3944 | // if search fails "dot" is unchanged 3945 | dir = last_search_pattern[0] == '/' ? FORWARD : BACK; 3946 | dc4: 3947 | if (last_search_pattern[1] == '\0') { 3948 | status_line_bold("No previous search"); 3949 | break; 3950 | } 3951 | do { 3952 | q = char_search(dot + dir, last_search_pattern + 1, 3953 | (dir << 1) | FULL); 3954 | if (q != NULL) { 3955 | dot = q; // good search, update "dot" 3956 | } else { 3957 | // no pattern found between "dot" and top/bottom of file 3958 | // continue from other end of file 3959 | const char *msg; 3960 | q = char_search(dir == FORWARD ? text : end - 1, 3961 | last_search_pattern + 1, (dir << 1) | FULL); 3962 | if (q != NULL) { // found something 3963 | dot = q; // found new pattern- goto it 3964 | msg = "search hit %s, continuing at %s"; 3965 | } else { // pattern is nowhere in file 3966 | cmdcnt = 0; // force exit from loop 3967 | msg = "Pattern not found"; 3968 | } 3969 | if (dir == FORWARD) 3970 | status_line_bold(msg, "BOTTOM", "TOP"); 3971 | else 3972 | status_line_bold(msg, "TOP", "BOTTOM"); 3973 | } 3974 | } while (--cmdcnt > 0); 3975 | break; 3976 | case '{': // {- move backward paragraph 3977 | case '}': // }- move forward paragraph 3978 | dir = c == '}' ? FORWARD : BACK; 3979 | do { 3980 | int skip = TRUE; // initially skip consecutive empty lines 3981 | while (dir == FORWARD ? dot < end - 1 : dot > text) { 3982 | if (*dot == '\n' && dot[dir] == '\n') { 3983 | if (!skip) { 3984 | if (dir == FORWARD) 3985 | ++dot; // move to next blank line 3986 | goto dc2; 3987 | } 3988 | } 3989 | else { 3990 | skip = FALSE; 3991 | } 3992 | dot += dir; 3993 | } 3994 | goto dc6; // end of file 3995 | dc2: 3996 | continue; 3997 | } while (--cmdcnt > 0); 3998 | break; 3999 | #endif /* FEATURE_VI_SEARCH */ 4000 | case '0': // 0- goto beginning of line 4001 | case '1': // 1- 4002 | case '2': // 2- 4003 | case '3': // 3- 4004 | case '4': // 4- 4005 | case '5': // 5- 4006 | case '6': // 6- 4007 | case '7': // 7- 4008 | case '8': // 8- 4009 | case '9': // 9- 4010 | if (c == '0' && cmdcnt < 1) { 4011 | dot_begin(); // this was a standalone zero 4012 | } else { 4013 | cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number 4014 | } 4015 | break; 4016 | case ':': // :- the colon mode commands 4017 | p = get_input_line(":"); // get input line- use "status line" 4018 | colon(p); // execute the command 4019 | break; 4020 | case '<': // <- Left shift something 4021 | case '>': // >- Right shift something 4022 | cnt = count_lines(text, dot); // remember what line we are on 4023 | if (find_range(&p, &q, c) == -1) 4024 | goto dc6; 4025 | i = count_lines(p, q); // # of lines we are shifting 4026 | for (p = begin_line(p); i > 0; i--, p = next_line(p)) { 4027 | if (c == '<') { 4028 | // shift left- remove tab or tabstop spaces 4029 | if (*p == '\t') { 4030 | // shrink buffer 1 char 4031 | text_hole_delete(p, p, allow_undo); 4032 | } else if (*p == ' ') { 4033 | // we should be calculating columns, not just SPACE 4034 | for (j = 0; *p == ' ' && j < tabstop; j++) { 4035 | text_hole_delete(p, p, allow_undo); 4036 | #if ENABLE_FEATURE_VI_UNDO 4037 | allow_undo = ALLOW_UNDO_CHAIN; 4038 | #endif 4039 | } 4040 | } 4041 | } else if (/* c == '>' && */ p != end_line(p)) { 4042 | // shift right -- add tab or tabstop spaces on non-empty lines 4043 | char_insert(p, '\t', allow_undo); 4044 | } 4045 | #if ENABLE_FEATURE_VI_UNDO 4046 | allow_undo = ALLOW_UNDO_CHAIN; 4047 | #endif 4048 | } 4049 | dot = find_line(cnt); // what line were we on 4050 | dot_skip_over_ws(); 4051 | end_cmd_q(); // stop adding to q 4052 | break; 4053 | case 'A': // A- append at e-o-l 4054 | dot_end(); // go to e-o-l 4055 | //**** fall through to ... 'a' 4056 | case 'a': // a- append after current char 4057 | if (*dot != '\n') 4058 | dot++; 4059 | goto dc_i; 4060 | case 'B': // B- back a blank-delimited Word 4061 | case 'E': // E- end of a blank-delimited word 4062 | case 'W': // W- forward a blank-delimited word 4063 | dir = FORWARD; 4064 | if (c == 'B') 4065 | dir = BACK; 4066 | do { 4067 | if (c == 'W' || isspace(dot[dir])) { 4068 | dot = skip_thing(dot, 1, dir, S_TO_WS); 4069 | dot = skip_thing(dot, 2, dir, S_OVER_WS); 4070 | } 4071 | if (c != 'W') 4072 | dot = skip_thing(dot, 1, dir, S_BEFORE_WS); 4073 | } while (--cmdcnt > 0); 4074 | break; 4075 | case 'C': // C- Change to e-o-l 4076 | case 'D': // D- delete to e-o-l 4077 | save_dot = dot; 4078 | dot = dollar_line(dot); // move to before NL 4079 | // copy text into a register and delete 4080 | dot = yank_delete(save_dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO); // delete to e-o-l 4081 | if (c == 'C') 4082 | goto dc_i; // start inserting 4083 | #if ENABLE_FEATURE_VI_DOT_CMD 4084 | if (c == 'D') 4085 | end_cmd_q(); // stop adding to q 4086 | #endif 4087 | break; 4088 | case 'g': // 'gg' goto a line number (vim) (default: very first line) 4089 | c1 = get_one_char(); 4090 | if (c1 != 'g') { 4091 | buf[0] = 'g'; 4092 | // c1 < 0 if the key was special. Try "g" 4093 | // TODO: if Unicode? 4094 | buf[1] = (c1 >= 0 ? c1 : '*'); 4095 | buf[2] = '\0'; 4096 | not_implemented(buf); 4097 | cmd_error = TRUE; 4098 | break; 4099 | } 4100 | if (cmdcnt == 0) 4101 | cmdcnt = 1; 4102 | // fall through 4103 | case 'G': // G- goto to a line number (default= E-O-F) 4104 | dot = end - 1; // assume E-O-F 4105 | if (cmdcnt > 0) { 4106 | dot = find_line(cmdcnt); // what line is #cmdcnt 4107 | } 4108 | dot_begin(); 4109 | dot_skip_over_ws(); 4110 | break; 4111 | case 'H': // H- goto top line on screen 4112 | dot = screenbegin; 4113 | if (cmdcnt > (rows - 1)) { 4114 | cmdcnt = (rows - 1); 4115 | } 4116 | while (--cmdcnt > 0) { 4117 | dot_next(); 4118 | } 4119 | dot_begin(); 4120 | dot_skip_over_ws(); 4121 | break; 4122 | case 'I': // I- insert before first non-blank 4123 | dot_begin(); // 0 4124 | dot_skip_over_ws(); 4125 | //**** fall through to ... 'i' 4126 | case 'i': // i- insert before current char 4127 | case KEYCODE_INSERT: // Cursor Key Insert 4128 | dc_i: 4129 | #if ENABLE_FEATURE_VI_SETOPTS 4130 | newindent = -1; 4131 | #endif 4132 | cmd_mode = 1; // start inserting 4133 | undo_queue_commit(); // commit queue when cmd_mode changes 4134 | break; 4135 | case 'J': // J- join current and next lines together 4136 | do { 4137 | dot_end(); // move to NL 4138 | if (dot < end - 1) { // make sure not last char in text[] 4139 | #if ENABLE_FEATURE_VI_UNDO 4140 | undo_push(dot, 1, UNDO_DEL); 4141 | *dot++ = ' '; // replace NL with space 4142 | undo_push((dot - 1), 1, UNDO_INS_CHAIN); 4143 | #else 4144 | *dot++ = ' '; 4145 | modified_count++; 4146 | #endif 4147 | while (isblank(*dot)) { // delete leading WS 4148 | text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN); 4149 | } 4150 | } 4151 | } while (--cmdcnt > 0); 4152 | end_cmd_q(); // stop adding to q 4153 | break; 4154 | case 'L': // L- goto bottom line on screen 4155 | dot = end_screen(); 4156 | if (cmdcnt > (rows - 1)) { 4157 | cmdcnt = (rows - 1); 4158 | } 4159 | while (--cmdcnt > 0) { 4160 | dot_prev(); 4161 | } 4162 | dot_begin(); 4163 | dot_skip_over_ws(); 4164 | break; 4165 | case 'M': // M- goto middle line on screen 4166 | dot = screenbegin; 4167 | for (cnt = 0; cnt < (rows-1) / 2; cnt++) 4168 | dot = next_line(dot); 4169 | dot_skip_over_ws(); 4170 | break; 4171 | case 'O': // O- open an empty line above 4172 | dot_begin(); 4173 | #if ENABLE_FEATURE_VI_SETOPTS 4174 | // special case: use indent of current line 4175 | newindent = get_column(dot + indent_len(dot)); 4176 | #endif 4177 | goto dc3; 4178 | case 'o': // o- open an empty line below 4179 | dot_end(); 4180 | dc3: 4181 | #if ENABLE_FEATURE_VI_SETOPTS 4182 | cmd_mode = 1; // switch to insert mode early 4183 | #endif 4184 | dot = char_insert(dot, '\n', ALLOW_UNDO); 4185 | if (c == 'O' && !autoindent) { 4186 | // done in char_insert() for 'O'+autoindent 4187 | dot_prev(); 4188 | } 4189 | goto dc_i; 4190 | case 'R': // R- continuous Replace char 4191 | dc5: 4192 | cmd_mode = 2; 4193 | undo_queue_commit(); 4194 | rstart = dot; 4195 | break; 4196 | case KEYCODE_DELETE: 4197 | if (dot < end - 1) 4198 | dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO); 4199 | break; 4200 | case 'X': // X- delete char before dot 4201 | case 'x': // x- delete the current char 4202 | case 's': // s- substitute the current char 4203 | dir = 0; 4204 | if (c == 'X') 4205 | dir = -1; 4206 | do { 4207 | if (dot[dir] != '\n') { 4208 | if (c == 'X') 4209 | dot--; // delete prev char 4210 | dot = yank_delete(dot, dot, PARTIAL, YANKDEL, allow_undo); // delete char 4211 | #if ENABLE_FEATURE_VI_UNDO 4212 | allow_undo = ALLOW_UNDO_CHAIN; 4213 | #endif 4214 | } 4215 | } while (--cmdcnt > 0); 4216 | end_cmd_q(); // stop adding to q 4217 | if (c == 's') 4218 | goto dc_i; // start inserting 4219 | break; 4220 | case 'Z': // Z- if modified, {write}; exit 4221 | c1 = get_one_char(); 4222 | // ZQ means to exit without saving 4223 | if (c1 == 'Q') { 4224 | editing = 0; 4225 | options.optind = cmdline_filecnt; 4226 | break; 4227 | } 4228 | // ZZ means to save file (if necessary), then exit 4229 | if (c1 != 'Z') { 4230 | indicate_error(); 4231 | break; 4232 | } 4233 | if (modified_count) { 4234 | if (ENABLE_FEATURE_VI_READONLY && readonly_mode && current_filename) { 4235 | status_line_bold("'%s' is read only", current_filename); 4236 | break; 4237 | } 4238 | cnt = file_write(current_filename, text, end - 1); 4239 | if (cnt < 0) { 4240 | if (cnt == -1) 4241 | status_line_bold("Write error: %s", strerror(errno)); 4242 | } else if (cnt == (end - 1 - text + 1)) { 4243 | editing = 0; 4244 | } 4245 | } else { 4246 | editing = 0; 4247 | } 4248 | // are there other files to edit? 4249 | j = cmdline_filecnt - options.optind - 1; 4250 | if (editing == 0 && j > 0) { 4251 | editing = 1; 4252 | modified_count = 0; 4253 | last_modified_count = -1; 4254 | status_line_bold("%u more file(s) to edit", j); 4255 | } 4256 | break; 4257 | case '^': // ^- move to first non-blank on line 4258 | dot_begin(); 4259 | dot_skip_over_ws(); 4260 | break; 4261 | case 'b': // b- back a word 4262 | case 'e': // e- end of word 4263 | dir = FORWARD; 4264 | if (c == 'b') 4265 | dir = BACK; 4266 | do { 4267 | if ((dot + dir) < text || (dot + dir) > end - 1) 4268 | break; 4269 | dot += dir; 4270 | if (isspace(*dot)) { 4271 | dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS); 4272 | } 4273 | if (isalnum(*dot) || *dot == '_') { 4274 | dot = skip_thing(dot, 1, dir, S_END_ALNUM); 4275 | } else if (ispunct(*dot)) { 4276 | dot = skip_thing(dot, 1, dir, S_END_PUNCT); 4277 | } 4278 | } while (--cmdcnt > 0); 4279 | break; 4280 | case 'c': // c- change something 4281 | case 'd': // d- delete something 4282 | #if ENABLE_FEATURE_VI_YANKMARK 4283 | case 'y': // y- yank something 4284 | case 'Y': // Y- Yank a line 4285 | #endif 4286 | { 4287 | int yf = YANKDEL; // assume either "c" or "d" 4288 | int buftype; 4289 | #if ENABLE_FEATURE_VI_YANKMARK 4290 | # if ENABLE_FEATURE_VI_VERBOSE_STATUS 4291 | char *savereg = reg[YDreg]; 4292 | # endif 4293 | if (c == 'y' || c == 'Y') 4294 | yf = YANKONLY; 4295 | #endif 4296 | // determine range, and whether it spans lines 4297 | buftype = find_range(&p, &q, c); 4298 | if (buftype == -1) // invalid range 4299 | goto dc6; 4300 | if (buftype == WHOLE) { 4301 | save_dot = p; // final cursor position is start of range 4302 | p = begin_line(p); 4303 | #if ENABLE_FEATURE_VI_SETOPTS 4304 | if (c == 'c') // special case: use indent of current line 4305 | newindent = get_column(p + indent_len(p)); 4306 | #endif 4307 | q = end_line(q); 4308 | } 4309 | dot = yank_delete(p, q, buftype, yf, ALLOW_UNDO); // delete word 4310 | if (buftype == WHOLE) { 4311 | if (c == 'c') { 4312 | #if ENABLE_FEATURE_VI_SETOPTS 4313 | cmd_mode = 1; // switch to insert mode early 4314 | #endif 4315 | dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN); 4316 | // on the last line of file don't move to prev line, 4317 | // handled in char_insert() if autoindent is enabled 4318 | if (dot != (end-1) && !autoindent) { 4319 | dot_prev(); 4320 | } 4321 | } else if (c == 'd') { 4322 | dot_begin(); 4323 | dot_skip_over_ws(); 4324 | } else { 4325 | dot = save_dot; 4326 | } 4327 | } 4328 | // if CHANGING, not deleting, start inserting after the delete 4329 | if (c == 'c') { 4330 | goto dc_i; // start inserting 4331 | } 4332 | #if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS 4333 | // only update status if a yank has actually happened 4334 | if (reg[YDreg] != savereg) 4335 | yank_status(c == 'd' ? "Delete" : "Yank", reg[YDreg], 1); 4336 | #endif 4337 | dc6: 4338 | end_cmd_q(); // stop adding to q 4339 | break; 4340 | } 4341 | case 'k': // k- goto prev line, same col 4342 | case KEYCODE_UP: // cursor key Up 4343 | case '-': // -- goto prev line 4344 | q = dot; 4345 | do { 4346 | p = prev_line(q); 4347 | if (p == begin_line(q)) { 4348 | indicate_error(); 4349 | goto dc1; 4350 | } 4351 | q = p; 4352 | } while (--cmdcnt > 0); 4353 | dot = q; 4354 | if (c == '-') { 4355 | dot_skip_over_ws(); 4356 | } else { 4357 | // try to stay in saved column 4358 | dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex); 4359 | keep_index = TRUE; 4360 | } 4361 | break; 4362 | case 'r': // r- replace the current char with user input 4363 | c1 = get_one_char(); // get the replacement char 4364 | if (c1 != 27) { 4365 | if (end_line(dot) - dot < (cmdcnt ? cmdcnt : 1)) { 4366 | indicate_error(); 4367 | goto dc6; 4368 | } 4369 | do { 4370 | dot = text_hole_delete(dot, dot, allow_undo); 4371 | #if ENABLE_FEATURE_VI_UNDO 4372 | allow_undo = ALLOW_UNDO_CHAIN; 4373 | #endif 4374 | dot = char_insert(dot, c1, allow_undo); 4375 | } while (--cmdcnt > 0); 4376 | dot_left(); 4377 | } 4378 | end_cmd_q(); // stop adding to q 4379 | break; 4380 | case 'w': // w- forward a word 4381 | do { 4382 | if (isalnum(*dot) || *dot == '_') { // we are on ALNUM 4383 | dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM); 4384 | } else if (ispunct(*dot)) { // we are on PUNCT 4385 | dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT); 4386 | } 4387 | if (dot < end - 1) 4388 | dot++; // move over word 4389 | if (isspace(*dot)) { 4390 | dot = skip_thing(dot, 2, FORWARD, S_OVER_WS); 4391 | } 4392 | } while (--cmdcnt > 0); 4393 | break; 4394 | case 'z': // z- 4395 | c1 = get_one_char(); // get the replacement char 4396 | cnt = 0; 4397 | if (c1 == '.') 4398 | cnt = (rows - 2) / 2; // put dot at center 4399 | if (c1 == '-') 4400 | cnt = rows - 2; // put dot at bottom 4401 | screenbegin = begin_line(dot); // start dot at top 4402 | dot_scroll(cnt, -1); 4403 | break; 4404 | case '|': // |- move to column "cmdcnt" 4405 | dot = move_to_col(dot, cmdcnt - 1); // try to move to column 4406 | break; 4407 | case '~': // ~- flip the case of letters a-z -> A-Z 4408 | do { 4409 | #if ENABLE_FEATURE_VI_UNDO 4410 | if (isalpha(*dot)) { 4411 | undo_push(dot, 1, undo_del); 4412 | *dot = islower(*dot) ? toupper(*dot) : tolower(*dot); 4413 | undo_push(dot, 1, UNDO_INS_CHAIN); 4414 | undo_del = UNDO_DEL_CHAIN; 4415 | } 4416 | #else 4417 | if (islower(*dot)) { 4418 | *dot = toupper(*dot); 4419 | modified_count++; 4420 | } else if (isupper(*dot)) { 4421 | *dot = tolower(*dot); 4422 | modified_count++; 4423 | } 4424 | #endif 4425 | dot_right(); 4426 | } while (--cmdcnt > 0); 4427 | end_cmd_q(); // stop adding to q 4428 | break; 4429 | //----- The Cursor and Function Keys ----------------------------- 4430 | case KEYCODE_HOME: // Cursor Key Home 4431 | dot_begin(); 4432 | break; 4433 | // The Fn keys could point to do_macro which could translate them 4434 | #if 0 4435 | case KEYCODE_FUN1: // Function Key F1 4436 | case KEYCODE_FUN2: // Function Key F2 4437 | case KEYCODE_FUN3: // Function Key F3 4438 | case KEYCODE_FUN4: // Function Key F4 4439 | case KEYCODE_FUN5: // Function Key F5 4440 | case KEYCODE_FUN6: // Function Key F6 4441 | case KEYCODE_FUN7: // Function Key F7 4442 | case KEYCODE_FUN8: // Function Key F8 4443 | case KEYCODE_FUN9: // Function Key F9 4444 | case KEYCODE_FUN10: // Function Key F10 4445 | case KEYCODE_FUN11: // Function Key F11 4446 | case KEYCODE_FUN12: // Function Key F12 4447 | break; 4448 | #endif 4449 | } 4450 | 4451 | dc1: 4452 | // if text[] just became empty, add back an empty line 4453 | if (end == text) { 4454 | char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line 4455 | dot = text; 4456 | } 4457 | // it is OK for dot to exactly equal to end, otherwise check dot validity 4458 | if (dot != end) { 4459 | dot = bound_dot(dot); // make sure "dot" is valid 4460 | } 4461 | #if ENABLE_FEATURE_VI_YANKMARK 4462 | if (dot != orig_dot) 4463 | check_context(c); // update the current context 4464 | #endif 4465 | 4466 | if (!isdigit(c)) 4467 | cmdcnt = 0; // cmd was not a number, reset cmdcnt 4468 | cnt = dot - begin_line(dot); 4469 | // Try to stay off of the Newline 4470 | if (*dot == '\n' && cnt > 0 && cmd_mode == 0) 4471 | dot--; 4472 | } 4473 | -------------------------------------------------------------------------------- /editors/vi_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. 3 | */ 4 | 5 | #include "vi_utils.h" 6 | #include 7 | 8 | #ifndef VI_SANDBOX_SIZE_KB 9 | #define VI_SANDBOX_SIZE_KB 20 /* KB */ 10 | #endif 11 | 12 | static const char *vi_outof_momory_warning = "vi sandbox runs out of memory, please enlarge VI_SANDBOX_SIZE_KB"; 13 | 14 | int index_in_strings(const char *strings, const char *key) 15 | { 16 | int j, idx = 0; 17 | 18 | while (*strings) { 19 | /* Do we see "key\0" at current position in strings? */ 20 | for (j = 0; *strings == key[j]; ++j) { 21 | if (*strings++ == '\0') { 22 | //bb_error_msg("found:'%s' i:%u", key, idx); 23 | return idx; /* yes */ 24 | } 25 | } 26 | /* No. Move to the start of the next string. */ 27 | while (*strings++ != '\0') 28 | continue; 29 | idx++; 30 | } 31 | return -1; 32 | } 33 | 34 | #ifdef VI_ENABLE_COLON 35 | /* Find out if the last character of a string matches the one given */ 36 | char* last_char_is(const char *s, int c) 37 | { 38 | if (!s[0]) 39 | return NULL; 40 | while (s[1]) 41 | s++; 42 | return (*s == (char)c) ? (char *) s : NULL; 43 | } 44 | #endif 45 | 46 | #ifdef VI_ENABLE_SETOPTS 47 | char* skip_whitespace(const char *s) 48 | { 49 | /* In POSIX/C locale (the only locale we care about: do we REALLY want 50 | * to allow Unicode whitespace in, say, .conf files? nuts!) 51 | * isspace is only these chars: "\t\n\v\f\r" and space. 52 | * "\t\n\v\f\r" happen to have ASCII codes 9,10,11,12,13. 53 | * Use that. 54 | */ 55 | while (*s == ' ' || (unsigned char)(*s - 9) <= (13 - 9)) 56 | s++; 57 | 58 | return (char *) s; 59 | } 60 | 61 | char* skip_non_whitespace(const char *s) 62 | { 63 | while (*s != '\0' && *s != ' ' && (unsigned char)(*s - 9) > (13 - 9)) 64 | s++; 65 | 66 | return (char *) s; 67 | } 68 | #endif 69 | 70 | ssize_t safe_read(int fd, void *buf, size_t count) 71 | { 72 | ssize_t n; 73 | 74 | for (;;) { 75 | n = read(fd, buf, count); 76 | if (n >= 0 || errno != EINTR) 77 | break; 78 | /* Some callers set errno=0, are upset when they see EINTR. 79 | * Returning EINTR is wrong since we retry read(), 80 | * the "error" was transient. 81 | */ 82 | errno = 0; 83 | /* repeat the read() */ 84 | } 85 | 86 | return n; 87 | } 88 | 89 | /* 90 | * Read all of the supplied buffer from a file. 91 | * This does multiple reads as necessary. 92 | * Returns the amount read, or -1 on an error. 93 | * A short read is returned on an end of file. 94 | */ 95 | ssize_t full_read(int fd, void *buf, size_t len) 96 | { 97 | ssize_t cc; 98 | ssize_t total; 99 | 100 | total = 0; 101 | 102 | while (len) { 103 | cc = safe_read(fd, buf, len); 104 | 105 | if (cc < 0) { 106 | if (total) { 107 | /* we already have some! */ 108 | /* user can do another read to know the error code */ 109 | return total; 110 | } 111 | return cc; /* read() returns -1 on failure. */ 112 | } 113 | if (cc == 0) 114 | break; 115 | buf = ((char *)buf) + cc; 116 | total += cc; 117 | len -= cc; 118 | } 119 | 120 | return total; 121 | } 122 | 123 | ssize_t safe_write(int fd, const void *buf, size_t count) 124 | { 125 | ssize_t n; 126 | 127 | for (;;) { 128 | n = write(fd, buf, count); 129 | if (n >= 0 || errno != EINTR) 130 | break; 131 | /* Some callers set errno=0, are upset when they see EINTR. 132 | * Returning EINTR is wrong since we retry write(), 133 | * the "error" was transient. 134 | */ 135 | errno = 0; 136 | /* repeat the write() */ 137 | } 138 | 139 | return n; 140 | } 141 | 142 | /* 143 | * Write all of the supplied buffer out to a file. 144 | * This does multiple writes as necessary. 145 | * Returns the amount written, or -1 if error was seen 146 | * on the very first write. 147 | */ 148 | ssize_t full_write(int fd, const void *buf, size_t len) 149 | { 150 | ssize_t cc; 151 | ssize_t total; 152 | 153 | total = 0; 154 | 155 | while (len) { 156 | cc = safe_write(fd, buf, len); 157 | 158 | if (cc < 0) { 159 | if (total) { 160 | /* we already wrote some! */ 161 | /* user can do another write to know the error code */ 162 | return total; 163 | } 164 | return cc; /* write() returns -1 on failure. */ 165 | } 166 | 167 | total += cc; 168 | buf = ((const char *)buf) + cc; 169 | len -= cc; 170 | } 171 | 172 | return total; 173 | } 174 | 175 | /* Wrapper which restarts poll on EINTR or ENOMEM. 176 | * On other errors does perror("poll") and returns. 177 | * Warning! May take longer than timeout_ms to return! */ 178 | int safe_poll(struct pollfd *ufds, nfds_t nfds, int timeout) 179 | { 180 | while (1) { 181 | int n = poll(ufds, nfds, timeout); 182 | if (n >= 0) 183 | return n; 184 | /* Make sure we inch towards completion */ 185 | if (timeout > 0) 186 | timeout--; 187 | /* E.g. strace causes poll to return this */ 188 | if (errno == EINTR) 189 | continue; 190 | /* Kernel is very low on memory. Retry. */ 191 | /* I doubt many callers would handle this correctly! */ 192 | if (errno == ENOMEM) 193 | continue; 194 | LOG_E("safe_poll"); 195 | return n; 196 | } 197 | } 198 | 199 | static mem_sandbox_t vi_sandbox = RT_NULL; 200 | 201 | unsigned char vi_mem_init(void) 202 | { 203 | vi_sandbox = mem_sandbox_create(VI_SANDBOX_SIZE_KB * 1024); 204 | if(vi_sandbox == RT_NULL) 205 | { 206 | LOG_E("vi sandbox create error"); 207 | return 0; 208 | } 209 | else 210 | { 211 | return 1; 212 | } 213 | } 214 | 215 | void vi_mem_release(void) 216 | { 217 | mem_sandbox_delete(vi_sandbox); 218 | } 219 | 220 | void *vi_malloc(rt_size_t size) 221 | { 222 | void * p; 223 | p = mem_sandbox_malloc(vi_sandbox, size); 224 | if(p == RT_NULL) 225 | { 226 | LOG_E(vi_outof_momory_warning); 227 | RT_ASSERT(p != RT_NULL); 228 | return RT_NULL; 229 | } 230 | return p; 231 | } 232 | 233 | void *vi_realloc(void *rmem, rt_size_t newsize) 234 | { 235 | void *p; 236 | p = mem_sandbox_realloc(vi_sandbox, rmem, newsize); 237 | if(p == RT_NULL && newsize != 0) 238 | { 239 | LOG_E(vi_outof_momory_warning); 240 | RT_ASSERT(p != RT_NULL); 241 | return RT_NULL; 242 | } 243 | return p; 244 | } 245 | 246 | void vi_free(void *ptr) 247 | { 248 | mem_sandbox_free(vi_sandbox, ptr); 249 | } 250 | 251 | void* vi_zalloc(size_t size) 252 | { 253 | void *ptr = vi_malloc(size); 254 | rt_memset(ptr, 0, size); 255 | return ptr; 256 | } 257 | 258 | char *vi_strdup(const char *s) 259 | { 260 | void *p; 261 | p = mem_sandbox_strdup(vi_sandbox, s); 262 | if(p == RT_NULL) 263 | { 264 | LOG_E(vi_outof_momory_warning); 265 | RT_ASSERT(p != RT_NULL); 266 | return RT_NULL; 267 | } 268 | return p; 269 | } 270 | 271 | char *vi_strndup(const char *s, size_t n) 272 | { 273 | void *p; 274 | p = mem_sandbox_strndup(vi_sandbox, s, n); 275 | if(p == RT_NULL) 276 | { 277 | LOG_E(vi_outof_momory_warning); 278 | RT_ASSERT(p != RT_NULL); 279 | return RT_NULL; 280 | } 281 | return p; 282 | } 283 | 284 | int vi_putchar(int c) 285 | { 286 | rt_kprintf("%c", c); 287 | return (int)c; 288 | } 289 | 290 | void vi_puts(const char *s) 291 | { 292 | rt_kprintf(s); 293 | } 294 | 295 | void vi_write(const void *buffer, uint32_t size) 296 | { 297 | rt_device_write(rt_console_get_device(), 0, buffer, size); 298 | } 299 | 300 | int64_t read_key(int fd, char *buffer, int timeout) 301 | { 302 | struct pollfd pfd; 303 | const char *seq; 304 | int n; 305 | 306 | /* Known escape sequences for cursor and function keys. 307 | * See "Xterm Control Sequences" 308 | * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 309 | */ 310 | static const char esccmds[] ALIGN1 = { 311 | 'O','A' |0x80, (char) KEYCODE_UP , 312 | 'O','B' |0x80, (char) KEYCODE_DOWN , 313 | 'O','C' |0x80, (char) KEYCODE_RIGHT , 314 | 'O','D' |0x80, (char) KEYCODE_LEFT , 315 | 'O','H' |0x80, (char) KEYCODE_HOME , 316 | 'O','F' |0x80, (char) KEYCODE_END , 317 | #if 0 318 | 'O','P' |0x80, (char) KEYCODE_FUN1 , 319 | /* [ESC] ESC O [2] P - [Alt-][Shift-]F1 */ 320 | /* ESC [ O 1 ; 2 P - Shift-F1 */ 321 | /* ESC [ O 1 ; 3 P - Alt-F1 */ 322 | /* ESC [ O 1 ; 4 P - Alt-Shift-F1 */ 323 | /* ESC [ O 1 ; 5 P - Ctrl-F1 */ 324 | /* ESC [ O 1 ; 6 P - Ctrl-Shift-F1 */ 325 | 'O','Q' |0x80, (char) KEYCODE_FUN2 , 326 | 'O','R' |0x80, (char) KEYCODE_FUN3 , 327 | 'O','S' |0x80, (char) KEYCODE_FUN4 , 328 | #endif 329 | '[','A' |0x80, (char) KEYCODE_UP , 330 | '[','B' |0x80, (char) KEYCODE_DOWN , 331 | '[','C' |0x80, (char) KEYCODE_RIGHT , 332 | '[','D' |0x80, (char) KEYCODE_LEFT , 333 | /* ESC [ 1 ; 2 x, where x = A/B/C/D: Shift- */ 334 | /* ESC [ 1 ; 3 x, where x = A/B/C/D: Alt- - implemented below */ 335 | /* ESC [ 1 ; 4 x, where x = A/B/C/D: Alt-Shift- */ 336 | /* ESC [ 1 ; 5 x, where x = A/B/C/D: Ctrl- - implemented below */ 337 | /* ESC [ 1 ; 6 x, where x = A/B/C/D: Ctrl-Shift- */ 338 | /* ESC [ 1 ; 7 x, where x = A/B/C/D: Ctrl-Alt- */ 339 | /* ESC [ 1 ; 8 x, where x = A/B/C/D: Ctrl-Alt-Shift- */ 340 | '[','H' |0x80, (char) KEYCODE_HOME , /* xterm */ 341 | '[','F' |0x80, (char) KEYCODE_END , /* xterm */ 342 | /* [ESC] ESC [ [2] H - [Alt-][Shift-]Home (End similarly?) */ 343 | /* '[','Z' |0x80, (char) KEYCODE_SHIFT_TAB, */ 344 | '[','1','~' |0x80, (char) KEYCODE_HOME , /* vt100? linux vt? or what? */ 345 | '[','2','~' |0x80, (char) KEYCODE_INSERT , 346 | /* ESC [ 2 ; 3 ~ - Alt-Insert */ 347 | '[','3','~' |0x80, (char) KEYCODE_DELETE , 348 | /* [ESC] ESC [ 3 [;2] ~ - [Alt-][Shift-]Delete */ 349 | /* ESC [ 3 ; 3 ~ - Alt-Delete */ 350 | /* ESC [ 3 ; 5 ~ - Ctrl-Delete */ 351 | '[','4','~' |0x80, (char) KEYCODE_END , /* vt100? linux vt? or what? */ 352 | '[','5','~' |0x80, (char) KEYCODE_PAGEUP , 353 | /* ESC [ 5 ; 3 ~ - Alt-PgUp */ 354 | /* ESC [ 5 ; 5 ~ - Ctrl-PgUp */ 355 | /* ESC [ 5 ; 7 ~ - Ctrl-Alt-PgUp */ 356 | '[','6','~' |0x80, (char) KEYCODE_PAGEDOWN, 357 | '[','7','~' |0x80, (char) KEYCODE_HOME , /* vt100? linux vt? or what? */ 358 | '[','8','~' |0x80, (char) KEYCODE_END , /* vt100? linux vt? or what? */ 359 | #if 0 360 | '[','1','1','~'|0x80, (char) KEYCODE_FUN1 , /* old xterm, deprecated by ESC O P */ 361 | '[','1','2','~'|0x80, (char) KEYCODE_FUN2 , /* old xterm... */ 362 | '[','1','3','~'|0x80, (char) KEYCODE_FUN3 , /* old xterm... */ 363 | '[','1','4','~'|0x80, (char) KEYCODE_FUN4 , /* old xterm... */ 364 | '[','1','5','~'|0x80, (char) KEYCODE_FUN5 , 365 | /* [ESC] ESC [ 1 5 [;2] ~ - [Alt-][Shift-]F5 */ 366 | '[','1','7','~'|0x80, (char) KEYCODE_FUN6 , 367 | '[','1','8','~'|0x80, (char) KEYCODE_FUN7 , 368 | '[','1','9','~'|0x80, (char) KEYCODE_FUN8 , 369 | '[','2','0','~'|0x80, (char) KEYCODE_FUN9 , 370 | '[','2','1','~'|0x80, (char) KEYCODE_FUN10 , 371 | '[','2','3','~'|0x80, (char) KEYCODE_FUN11 , 372 | '[','2','4','~'|0x80, (char) KEYCODE_FUN12 , 373 | /* ESC [ 2 4 ; 2 ~ - Shift-F12 */ 374 | /* ESC [ 2 4 ; 3 ~ - Alt-F12 */ 375 | /* ESC [ 2 4 ; 4 ~ - Alt-Shift-F12 */ 376 | /* ESC [ 2 4 ; 5 ~ - Ctrl-F12 */ 377 | /* ESC [ 2 4 ; 6 ~ - Ctrl-Shift-F12 */ 378 | #endif 379 | /* '[','1',';','5','A' |0x80, (char) KEYCODE_CTRL_UP , - unused */ 380 | /* '[','1',';','5','B' |0x80, (char) KEYCODE_CTRL_DOWN , - unused */ 381 | '[','1',';','5','C' |0x80, (char) KEYCODE_CTRL_RIGHT, 382 | '[','1',';','5','D' |0x80, (char) KEYCODE_CTRL_LEFT , 383 | /* '[','1',';','3','A' |0x80, (char) KEYCODE_ALT_UP , - unused */ 384 | /* '[','1',';','3','B' |0x80, (char) KEYCODE_ALT_DOWN , - unused */ 385 | '[','1',';','3','C' |0x80, (char) KEYCODE_ALT_RIGHT, 386 | '[','1',';','3','D' |0x80, (char) KEYCODE_ALT_LEFT , 387 | /* '[','3',';','3','~' |0x80, (char) KEYCODE_ALT_DELETE, - unused */ 388 | 0 389 | }; 390 | pfd.fd = fd; 391 | pfd.events = POLLIN; 392 | buffer++; /* saved chars counter is in buffer[-1] now */ 393 | 394 | start_over: 395 | errno = 0; 396 | n = (unsigned char)buffer[-1]; 397 | if (n == 0) { 398 | /* If no data, wait for input. 399 | * If requested, wait TIMEOUT ms. TIMEOUT = -1 is useful 400 | * if fd can be in non-blocking mode. 401 | */ 402 | if (timeout >= -1) { 403 | if (safe_poll(&pfd, 1, timeout) == 0) { 404 | /* Timed out */ 405 | errno = EAGAIN; 406 | return -1; 407 | } 408 | } 409 | /* It is tempting to read more than one byte here, 410 | * but it breaks pasting. Example: at shell prompt, 411 | * user presses "c","a","t" and then pastes "\nline\n". 412 | * When we were reading 3 bytes here, we were eating 413 | * "li" too, and cat was getting wrong input. 414 | */ 415 | n = safe_read(fd, buffer, 1); 416 | if (n <= 0) 417 | return -1; 418 | } 419 | 420 | { 421 | unsigned char c = buffer[0]; 422 | n--; 423 | if (n) 424 | memmove(buffer, buffer + 1, n); 425 | /* Only ESC starts ESC sequences */ 426 | if (c != 27) { 427 | buffer[-1] = n; 428 | return c; 429 | } 430 | } 431 | 432 | /* Loop through known ESC sequences */ 433 | seq = esccmds; 434 | while (*seq != '\0') { 435 | /* n - position in sequence we did not read yet */ 436 | int i = 0; /* position in sequence to compare */ 437 | 438 | /* Loop through chars in this sequence */ 439 | while (1) { 440 | /* So far escape sequence matched up to [i-1] */ 441 | if (n <= i) { 442 | int read_num; 443 | /* Need more chars, read another one if it wouldn't block. 444 | * Note that escape sequences come in as a unit, 445 | * so if we block for long it's not really an escape sequence. 446 | * Timeout is needed to reconnect escape sequences 447 | * split up by transmission over a serial console. */ 448 | if (safe_poll(&pfd, 1, 50) == 0) { 449 | /* No more data! 450 | * Array is sorted from shortest to longest, 451 | * we can't match anything later in array - 452 | * anything later is longer than this seq. 453 | * Break out of both loops. */ 454 | goto got_all; 455 | } 456 | errno = 0; 457 | read_num = safe_read(fd, buffer + n, 1); 458 | if (read_num <= 0) { 459 | /* If EAGAIN, then fd is O_NONBLOCK and poll lied: 460 | * in fact, there is no data. */ 461 | if (errno != EAGAIN) { 462 | /* otherwise: it's EOF/error */ 463 | buffer[-1] = 0; 464 | return -1; 465 | } 466 | goto got_all; 467 | } 468 | n++; 469 | } 470 | if (buffer[i] != (seq[i] & 0x7f)) { 471 | /* This seq doesn't match, go to next */ 472 | seq += i; 473 | /* Forward to last char */ 474 | while (!(*seq & 0x80)) 475 | seq++; 476 | /* Skip it and the keycode which follows */ 477 | seq += 2; 478 | break; 479 | } 480 | if (seq[i] & 0x80) { 481 | /* Entire seq matched */ 482 | n = 0; 483 | /* n -= i; memmove(...); 484 | * would be more correct, 485 | * but we never read ahead that much, 486 | * and n == i here. */ 487 | buffer[-1] = 0; 488 | return (signed char)seq[i+1]; 489 | } 490 | i++; 491 | } 492 | } 493 | /* We did not find matching sequence. 494 | * We possibly read and stored more input in buffer[] by now. 495 | * n = bytes read. Try to read more until we time out. 496 | */ 497 | while (n < KEYCODE_BUFFER_SIZE-1) { /* 1 for count byte at buffer[-1] */ 498 | int read_num; 499 | if (safe_poll(&pfd, 1, 50) == 0) { 500 | /* No more data! */ 501 | break; 502 | } 503 | errno = 0; 504 | read_num = safe_read(fd, buffer + n, 1); 505 | if (read_num <= 0) { 506 | /* If EAGAIN, then fd is O_NONBLOCK and poll lied: 507 | * in fact, there is no data. */ 508 | if (errno != EAGAIN) { 509 | /* otherwise: it's EOF/error */ 510 | buffer[-1] = 0; 511 | return -1; 512 | } 513 | break; 514 | } 515 | n++; 516 | /* Try to decipher "ESC [ NNN ; NNN R" sequence */ 517 | if ((ENABLE_FEATURE_VI_ASK_TERMINAL) 518 | && n >= 5 519 | && buffer[0] == '[' 520 | && buffer[n-1] == 'R' 521 | && isdigit((unsigned char)buffer[1]) 522 | ) { 523 | char *end; 524 | unsigned long row, col; 525 | 526 | row = strtoul(buffer + 1, &end, 10); 527 | if (*end != ';' || !isdigit((unsigned char)end[1])) 528 | continue; 529 | col = strtoul(end + 1, &end, 10); 530 | if (*end != 'R') 531 | continue; 532 | if (row < 1 || col < 1 || (row | col) > 0x7fff) 533 | continue; 534 | 535 | buffer[-1] = 0; 536 | /* Pack into "1 " 32-bit sequence */ 537 | row |= ((unsigned)(-1) << 15); 538 | col |= (row << 16); 539 | /* Return it in high-order word */ 540 | return ((int64_t) col << 32) | (uint32_t)KEYCODE_CURSOR_POS; 541 | } 542 | } 543 | got_all: 544 | 545 | if (n <= 1) { 546 | /* Alt-x is usually returned as ESC x. 547 | * Report ESC, x is remembered for the next call. 548 | */ 549 | buffer[-1] = n; 550 | return 27; 551 | } 552 | 553 | /* We were doing "buffer[-1] = n; return c;" here, but this results 554 | * in unknown key sequences being interpreted as ESC + garbage. 555 | * This was not useful. Pretend there was no key pressed, 556 | * go and wait for a new keypress: 557 | */ 558 | buffer[-1] = 0; 559 | goto start_over; 560 | } 561 | 562 | static int vi_vasprintf(char **string_ptr, const char *format, va_list p) 563 | { 564 | int r; 565 | va_list p2; 566 | char buf[128]; 567 | 568 | va_copy(p2, p); 569 | r = rt_vsnprintf(buf, 128, format, p); 570 | va_end(p); 571 | 572 | /* Note: can't use xstrdup/xmalloc, they call vasprintf (us) on failure! */ 573 | 574 | if (r < 128) { 575 | va_end(p2); 576 | *string_ptr = vi_strdup(buf); 577 | return (*string_ptr ? r : -1); 578 | } 579 | 580 | *string_ptr = vi_malloc(r+1); 581 | r = (*string_ptr ? rt_vsnprintf(*string_ptr, r+1, format, p2) : -1); 582 | va_end(p2); 583 | 584 | return r; 585 | } 586 | 587 | // Die with an error message if we can't malloc() enough space and do an 588 | // rt_sprintf() into that space. 589 | char* xasprintf(const char *format, ...) 590 | { 591 | va_list p; 592 | int r; 593 | char *string_ptr; 594 | 595 | va_start(p, format); 596 | r = vi_vasprintf(&string_ptr, format, p); 597 | va_end(p); 598 | if (r < 0) 599 | LOG_E("die_memory_exhausted"); 600 | return string_ptr; 601 | } 602 | 603 | 604 | #ifdef RT_USING_POSIX_TERMIOS 605 | static int wh_helper(int value, int def_val, const char *env_name, int *err) 606 | { 607 | /* Envvars override even if "value" from ioctl is valid (>0). 608 | * Rationale: it's impossible to guess what user wants. 609 | * For example: "man CMD | ...": should "man" format output 610 | * to stdout's width? stdin's width? /dev/tty's width? 80 chars? 611 | * We _cant_ know it. If "..." saves text for e.g. email, 612 | * then it's probably 80 chars. 613 | * If "..." is, say, "grep -v DISCARD | $PAGER", then user 614 | * would prefer his tty's width to be used! 615 | * 616 | * Since we don't know, at least allow user to do this: 617 | * "COLUMNS=80 man CMD | ..." 618 | */ 619 | char *s = getenv(env_name); 620 | if (s) { 621 | value = atoi(s); 622 | /* If LINES/COLUMNS are set, pretend that there is 623 | * no error getting w/h, this prevents some ugly 624 | * cursor tricks by our callers */ 625 | *err = 0; 626 | } 627 | 628 | if (value <= 1 || value >= 30000) 629 | value = def_val; 630 | return value; 631 | } 632 | 633 | int get_terminal_width_height(int fd, unsigned *width, unsigned *height) 634 | { 635 | struct winsize win; 636 | int err; 637 | 638 | if (fd == -1) { 639 | fd = STDOUT_FILENO; 640 | } 641 | 642 | win.ws_row = 0; 643 | win.ws_col = 0; 644 | /* I've seen ioctl returning 0, but row/col is (still?) 0. 645 | * We treat that as an error too. */ 646 | err = ioctl(fd, TIOCGWINSZ, &win) != 0 || win.ws_row == 0; 647 | if (height) 648 | *height = wh_helper(win.ws_row, 24, "LINES", &err); 649 | if (width) 650 | *width = wh_helper(win.ws_col, 80, "COLUMNS", &err); 651 | 652 | return err; 653 | } 654 | 655 | int tcsetattr_stdin_TCSANOW(const struct termios *tp) 656 | { 657 | return tcsetattr(STDIN_FILENO, TCSANOW, tp); 658 | } 659 | 660 | static int get_termios_and_make_raw(int fd, struct termios *newterm, struct termios *oldterm, int flags) 661 | { 662 | //TODO: slattach, shell read might be adapted to use this too: grep for "tcsetattr", "[VTIME] = 0" 663 | int r; 664 | 665 | rt_memset(oldterm, 0, sizeof(*oldterm)); /* paranoia */ 666 | r = tcgetattr(fd, oldterm); 667 | *newterm = *oldterm; 668 | 669 | /* Turn off buffered input (ICANON) 670 | * Turn off echoing (ECHO) 671 | * and separate echoing of newline (ECHONL, normally off anyway) 672 | */ 673 | newterm->c_lflag &= ~(ICANON | ECHO | ECHONL); 674 | if (flags & TERMIOS_CLEAR_ISIG) { 675 | /* dont recognize INT/QUIT/SUSP chars */ 676 | newterm->c_lflag &= ~ISIG; 677 | } 678 | /* reads will block only if < 1 char is available */ 679 | newterm->c_cc[VMIN] = 1; 680 | /* no timeout (reads block forever) */ 681 | newterm->c_cc[VTIME] = 0; 682 | /* IXON, IXOFF, and IXANY: 683 | * IXOFF=1: sw flow control is enabled on input queue: 684 | * tty transmits a STOP char when input queue is close to full 685 | * and transmits a START char when input queue is nearly empty. 686 | * IXON=1: sw flow control is enabled on output queue: 687 | * tty will stop sending if STOP char is received, 688 | * and resume sending if START is received, or if any char 689 | * is received and IXANY=1. 690 | */ 691 | if (flags & TERMIOS_RAW_CRNL_INPUT) { 692 | /* IXON=0: XON/XOFF chars are treated as normal chars (why we do this?) */ 693 | /* dont convert CR to NL on input */ 694 | newterm->c_iflag &= ~(IXON | ICRNL); 695 | } 696 | if (flags & TERMIOS_RAW_CRNL_OUTPUT) { 697 | /* dont convert NL to CR+NL on output */ 698 | newterm->c_oflag &= ~(ONLCR); 699 | /* Maybe clear more c_oflag bits? Usually, only OPOST and ONLCR are set. 700 | * OPOST Enable output processing (reqd for OLCUC and *NL* bits to work) 701 | * OLCUC Map lowercase characters to uppercase on output. 702 | * OCRNL Map CR to NL on output. 703 | * ONOCR Don't output CR at column 0. 704 | * ONLRET Don't output CR. 705 | */ 706 | } 707 | if (flags & TERMIOS_RAW_INPUT) { 708 | #ifndef IMAXBEL 709 | # define IMAXBEL 0 710 | #endif 711 | #ifndef IUCLC 712 | # define IUCLC 0 713 | #endif 714 | #ifndef IXANY 715 | # define IXANY 0 716 | #endif 717 | /* IXOFF=0: disable sending XON/XOFF if input buf is full 718 | * IXON=0: input XON/XOFF chars are not special 719 | * BRKINT=0: dont send SIGINT on break 720 | * IMAXBEL=0: dont echo BEL on input line too long 721 | * INLCR,ICRNL,IUCLC: dont convert anything on input 722 | */ 723 | newterm->c_iflag &= ~(IXOFF|IXON|IXANY|BRKINT|INLCR|ICRNL|IUCLC|IMAXBEL); 724 | } 725 | return r; 726 | } 727 | 728 | int set_termios_to_raw(int fd, struct termios *oldterm, int flags) 729 | { 730 | struct termios newterm; 731 | 732 | get_termios_and_make_raw(fd, &newterm, oldterm, flags); 733 | return tcsetattr(fd, TCSANOW, &newterm); 734 | } 735 | 736 | #endif /* RT_USING_POSIX_TERMIOS */ 737 | -------------------------------------------------------------------------------- /editors/vi_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. 3 | */ 4 | 5 | #ifndef __VI_UTILS_H__ 6 | #define __VI_UTILS_H__ 7 | 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 | 22 | #define DBG_TAG "vi" 23 | #define DBG_LVL DBG_INFO 24 | #include 25 | 26 | #define BB_VER "latest: 2021-08-29" 27 | #define BB_BT "Busybox vi for RT-Thread" 28 | 29 | //config:config FEATURE_VI_MAX_LEN 30 | //config: int "Maximum screen width in vi" 31 | //config: range 256 16384 32 | //config: default 4096 33 | //config: depends on VI 34 | //config: help 35 | //config: Contrary to what you may think, this is not eating much. 36 | //config: Make it smaller than 4k only if you are very limited on memory. 37 | #ifdef VI_MAX_LEN 38 | #define CONFIG_FEATURE_VI_MAX_LEN VI_MAX_LEN 39 | #else 40 | #define CONFIG_FEATURE_VI_MAX_LEN 4096 41 | #endif 42 | 43 | //config:config FEATURE_VI_ASK_TERMINAL 44 | //config: bool "Use 'tell me cursor position' ESC sequence to measure window" 45 | //config: default y 46 | //config: depends on VI 47 | //config: help 48 | //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set, 49 | //config: this option makes vi perform a last-ditch effort to find it: 50 | //config: position cursor to 999,999 and ask terminal to report real 51 | //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin. 52 | //config: 53 | //config: This is not clean but helps a lot on serial lines and such. 54 | #ifdef VI_ENABLE_VI_ASK_TERMINAL 55 | #define ENABLE_FEATURE_VI_ASK_TERMINAL 1 56 | #define IF_FEATURE_VI_ASK_TERMINAL(...) __VA_ARGS__ 57 | #else 58 | #define ENABLE_FEATURE_VI_ASK_TERMINAL 0 59 | #define IF_FEATURE_VI_ASK_TERMINAL(...) 60 | #endif 61 | 62 | //config:config FEATURE_VI_COLON 63 | //config: bool "Enable \":\" colon commands (no \"ex\" mode)" 64 | //config: default y 65 | //config: depends on VI 66 | //config: help 67 | //config: Enable a limited set of colon commands for vi. This does not 68 | //config: provide an "ex" mode. 69 | #ifdef VI_ENABLE_COLON 70 | #define ENABLE_FEATURE_VI_COLON 1 71 | #define IF_FEATURE_VI_COLON(...) __VA_ARGS__ 72 | #else 73 | #define ENABLE_FEATURE_VI_COLON 0 74 | #define IF_FEATURE_VI_COLON(...) 75 | #endif 76 | 77 | //config:config FEATURE_VI_COLON_EXPAND 78 | //config: bool "Expand \"%\" and \"#\" in colon commands" 79 | //config: default y 80 | //config: depends on FEATURE_VI_COLON 81 | //config: help 82 | //config: Expand the special characters \"%\" (current filename) 83 | //config: and \"#\" (alternate filename) in colon commands. 84 | #ifdef VI_ENABLE_COLON_EXPAND 85 | #define ENABLE_FEATURE_VI_COLON_EXPAND 1 86 | #else 87 | #define ENABLE_FEATURE_VI_COLON_EXPAND 0 88 | #endif 89 | 90 | //config:config FEATURE_VI_SEARCH 91 | //config: bool "Enable search and replace cmds" 92 | //config: default y 93 | //config: depends on VI 94 | //config: help 95 | //config: Select this if you wish to be able to do search and replace in 96 | //config: busybox vi. 97 | #ifdef VI_ENABLE_SEARCH 98 | #define ENABLE_FEATURE_VI_SEARCH 1 99 | #define IF_FEATURE_VI_SEARCH(...) __VA_ARGS__ 100 | #else 101 | #define ENABLE_FEATURE_VI_SEARCH 0 102 | #define IF_FEATURE_VI_SEARCH(...) 103 | #endif 104 | 105 | //config:config FEATURE_VI_READONLY 106 | //config: bool "Enable -R option and \"view\" mode" 107 | //config: default y 108 | //config: depends on VI 109 | //config: help 110 | //config: Enable the read-only command line option, which allows the user to 111 | //config: open a file in read-only mode. 112 | #ifdef VI_ENABLE_READONLY 113 | #define ENABLE_FEATURE_VI_READONLY 1 114 | #define IF_FEATURE_VI_READONLY(...) __VA_ARGS__ 115 | #else 116 | #define ENABLE_FEATURE_VI_READONLY 0 117 | #define IF_FEATURE_VI_READONLY(...) 118 | #endif 119 | 120 | //config:config FEATURE_VI_SET 121 | //config: bool "Support for :set" 122 | //config: default y 123 | //config: depends on VI 124 | //config: help 125 | //config: Support for ":set". 126 | #ifdef VI_ENABLE_SET 127 | #define ENABLE_FEATURE_VI_SET 1 128 | #define IF_FEATURE_VI_SET(...) __VA_ARGS__ 129 | #else 130 | #define ENABLE_FEATURE_VI_SET 0 131 | #define IF_FEATURE_VI_SET(...) 132 | #endif 133 | 134 | //config:config FEATURE_VI_SETOPTS 135 | //config: bool "Enable set-able options, ai ic showmatch" 136 | //config: default y 137 | //config: depends on VI 138 | //config: help 139 | //config: Enable the editor to set some (ai, ic, showmatch) options. 140 | #ifdef VI_ENABLE_SETOPTS 141 | #define ENABLE_FEATURE_VI_SETOPTS 1 142 | #define IF_FEATURE_VI_SETOPTS(...) __VA_ARGS__ 143 | #else 144 | #define ENABLE_FEATURE_VI_SETOPTS 0 145 | #define IF_FEATURE_VI_SETOPTS(...) 146 | #endif 147 | 148 | //config: 149 | //config:config FEATURE_VI_WIN_RESIZE 150 | //config: bool "Handle window resize" 151 | //config: default y 152 | //config: depends on VI 153 | //config: help 154 | //config: Make busybox vi behave nicely with terminals that get resized. 155 | #ifdef VI_ENABLE_WIN_RESIZE 156 | #define ENABLE_FEATURE_VI_WIN_RESIZE 1 157 | #define IF_FEATURE_VI_WIN_RESIZE(...) __VA_ARGS__ 158 | #else 159 | #define ENABLE_FEATURE_VI_WIN_RESIZE 0 160 | #define IF_FEATURE_VI_WIN_RESIZE(...) 161 | #endif 162 | 163 | //config:config FEATURE_VI_YANKMARK 164 | //config: bool "Enable yank/put commands and mark cmds" 165 | //config: default y 166 | //config: depends on VI 167 | //config: help 168 | //config: This will enable you to use yank and put, as well as mark in 169 | //config: busybox vi. 170 | //config: 171 | #ifdef VI_ENABLE_YANKMARK 172 | #define ENABLE_FEATURE_VI_YANKMARK 1 173 | #define IF_FEATURE_VI_YANKMARK(...) __VA_ARGS__ 174 | #define ARRAY_SIZE(x) ((unsigned)(sizeof(x) / sizeof((x)[0]))) 175 | #else 176 | #define ENABLE_FEATURE_VI_YANKMARK 0 177 | #define IF_FEATURE_VI_YANKMARK(...) 178 | #endif 179 | 180 | //config:config FEATURE_VI_DOT_CMD 181 | //config: bool "Remember previous cmd and \".\" cmd" 182 | //config: default y 183 | //config: depends on VI 184 | //config: help 185 | //config: Make busybox vi remember the last command and be able to repeat it. 186 | #ifdef VI_ENABLE_DOT_CMD 187 | #define ENABLE_FEATURE_VI_DOT_CMD 1 188 | #define IF_FEATURE_VI_DOT_CMD(...) __VA_ARGS__ 189 | #else 190 | #define ENABLE_FEATURE_VI_DOT_CMD 0 191 | #define IF_FEATURE_VI_DOT_CMD(...) 192 | #endif 193 | 194 | //config:config FEATURE_VI_UNDO 195 | //config: bool "Support undo command 'u'" 196 | //config: default y 197 | //config: depends on VI 198 | //config: help 199 | //config: Support the 'u' command to undo insertion, deletion, and replacement 200 | //config: of text. 201 | #ifdef VI_ENABLE_UNDO 202 | #define ENABLE_FEATURE_VI_UNDO 1 203 | #define IF_FEATURE_VI_UNDO(...) __VA_ARGS__ 204 | #else 205 | #define ENABLE_FEATURE_VI_UNDO 0 206 | #define IF_FEATURE_VI_UNDO(...) 207 | #endif 208 | 209 | //config:config FEATURE_VI_UNDO_QUEUE 210 | //config: bool "Enable undo operation queuing" 211 | //config: default y 212 | //config: depends on FEATURE_VI_UNDO 213 | //config: help 214 | //config: The vi undo functions can use an intermediate queue to greatly lower 215 | //config: malloc() calls and overhead. When the maximum size of this queue is 216 | //config: reached, the contents of the queue are committed to the undo stack. 217 | //config: This increases the size of the undo code and allows some undo 218 | //config: operations (especially un-typing/backspacing) to be far more useful. 219 | //config:config FEATURE_VI_UNDO_QUEUE_MAX 220 | //config: int "Maximum undo character queue size" 221 | //config: default 256 222 | //config: range 32 65536 223 | //config: depends on FEATURE_VI_UNDO_QUEUE 224 | //config: help 225 | //config: This option sets the number of bytes used at runtime for the queue. 226 | //config: Smaller values will create more undo objects and reduce the amount 227 | //config: of typed or backspaced characters that are grouped into one undo 228 | //config: operation; larger values increase the potential size of each undo 229 | //config: and will generally malloc() larger objects and less frequently. 230 | //config: Unless you want more (or less) frequent "undo points" while typing, 231 | //config: you should probably leave this unchanged. 232 | #ifdef VI_ENABLE_UNDO_QUEUE 233 | #define ENABLE_FEATURE_VI_UNDO_QUEUE 1 234 | #define IF_FEATURE_VI_UNDO_QUEUE(...) __VA_ARGS__ 235 | #define CONFIG_FEATURE_VI_UNDO_QUEUE_MAX VI_UNDO_QUEUE_MAX 236 | #else 237 | #define ENABLE_FEATURE_VI_UNDO_QUEUE 0 238 | #define IF_FEATURE_VI_UNDO_QUEUE(...) 239 | #endif 240 | 241 | //config:config FEATURE_VI_VERBOSE_STATUS 242 | //config: bool "Enable verbose status reporting" 243 | //config: default y 244 | //config: depends on VI 245 | //config: help 246 | //config: Enable more verbose reporting of the results of yank, change, 247 | //config: delete, undo and substitution commands. 248 | #ifdef VI_ENABLE_VERBOSE_STATUS 249 | #define ENABLE_FEATURE_VI_VERBOSE_STATUS 1 250 | #else 251 | #define ENABLE_FEATURE_VI_VERBOSE_STATUS 0 252 | #endif 253 | 254 | //config:config FEATURE_VI_8BIT 255 | //config: bool "Allow vi to display 8-bit chars (otherwise shows dots)" 256 | //config: default n 257 | //config: depends on VI 258 | //config: help 259 | //config: If your terminal can display characters with high bit set, 260 | //config: you may want to enable this. Note: vi is not Unicode-capable. 261 | //config: If your terminal combines several 8-bit bytes into one character 262 | //config: (as in Unicode mode), this will not work properly. 263 | #ifdef VI_ENABLE_8BIT 264 | #define ENABLE_FEATURE_VI_8BIT 1 265 | #define IF_FEATURE_VI_8BIT(...) __VA_ARGS__ 266 | #else 267 | #define ENABLE_FEATURE_VI_8BIT 0 268 | #define IF_FEATURE_VI_8BIT(...) 269 | #endif 270 | 271 | /*----------------------------------------------------------------*/ 272 | 273 | #define SET_PTR_TO_GLOBALS(x) do { \ 274 | (*(struct globals**)&ptr_to_globals) = (void*)(x); \ 275 | barrier(); \ 276 | } while (0) 277 | 278 | /* "Keycodes" that report an escape sequence. 279 | * We use something which fits into signed char, 280 | * yet doesn't represent any valid Unicode character. 281 | * Also, -1 is reserved for error indication and we don't use it. */ 282 | enum { 283 | KEYCODE_UP = -2, 284 | KEYCODE_DOWN = -3, 285 | KEYCODE_RIGHT = -4, 286 | KEYCODE_LEFT = -5, 287 | KEYCODE_HOME = -6, 288 | KEYCODE_END = -7, 289 | KEYCODE_INSERT = -8, 290 | KEYCODE_DELETE = -9, 291 | KEYCODE_PAGEUP = -10, 292 | KEYCODE_PAGEDOWN = -11, 293 | // -12 is reserved for Alt/Ctrl/Shift-TAB 294 | #if 0 295 | KEYCODE_FUN1 = -13, 296 | KEYCODE_FUN2 = -14, 297 | KEYCODE_FUN3 = -15, 298 | KEYCODE_FUN4 = -16, 299 | KEYCODE_FUN5 = -17, 300 | KEYCODE_FUN6 = -18, 301 | KEYCODE_FUN7 = -19, 302 | KEYCODE_FUN8 = -20, 303 | KEYCODE_FUN9 = -21, 304 | KEYCODE_FUN10 = -22, 305 | KEYCODE_FUN11 = -23, 306 | KEYCODE_FUN12 = -24, 307 | #endif 308 | /* Be sure that last defined value is small enough 309 | * to not interfere with Alt/Ctrl/Shift bits. 310 | * So far we do not exceed -31 (0xfff..fffe1), 311 | * which gives us three upper bits in LSB to play with. 312 | */ 313 | //KEYCODE_SHIFT_TAB = (-12) & ~0x80, 314 | //KEYCODE_SHIFT_... = KEYCODE_... & ~0x80, 315 | //KEYCODE_CTRL_UP = KEYCODE_UP & ~0x40, 316 | //KEYCODE_CTRL_DOWN = KEYCODE_DOWN & ~0x40, 317 | KEYCODE_CTRL_RIGHT = KEYCODE_RIGHT & ~0x40, 318 | KEYCODE_CTRL_LEFT = KEYCODE_LEFT & ~0x40, 319 | //KEYCODE_ALT_UP = KEYCODE_UP & ~0x20, 320 | //KEYCODE_ALT_DOWN = KEYCODE_DOWN & ~0x20, 321 | KEYCODE_ALT_RIGHT = KEYCODE_RIGHT & ~0x20, 322 | KEYCODE_ALT_LEFT = KEYCODE_LEFT & ~0x20, 323 | 324 | KEYCODE_CURSOR_POS = -0x100, /* 0xfff..fff00 */ 325 | /* How long is the longest ESC sequence we know? 326 | * We want it big enough to be able to contain 327 | * cursor position sequence "ESC [ 9999 ; 9999 R" 328 | */ 329 | KEYCODE_BUFFER_SIZE = 16 330 | }; 331 | 332 | typedef enum {FALSE = 0, TRUE = !FALSE} bool; 333 | typedef int smallint; 334 | typedef unsigned smalluint; 335 | 336 | #ifndef F_OK 337 | #define F_OK 0 /* Tests whether the file exists. */ 338 | #define R_OK 4 /* Tests whether the file can be accessed for reading. */ 339 | #define W_OK 2 /* Tests whether the file can be accessed for writing. */ 340 | #define X_OK 1 /* Tests whether the file can be accessed for execution. */ 341 | #endif 342 | 343 | #if !defined(__GNUC__) 344 | #define ALIGN1 345 | #define barrier() 346 | #else 347 | #define ALIGN1 __attribute__((aligned(1))) 348 | /* At least gcc 3.4.6 on mipsel system needs optimization barrier */ 349 | #define barrier() __asm__ __volatile__("":::"memory") 350 | #endif 351 | 352 | #define vi_strtou strtoul 353 | unsigned char vi_mem_init(void); 354 | void vi_mem_release(void); 355 | void *vi_malloc(rt_size_t size); 356 | void *vi_realloc(void *rmem, rt_size_t newsize); 357 | void vi_free(void *ptr); 358 | void* vi_zalloc(size_t size); 359 | char *vi_strdup(const char *s); 360 | char *vi_strndup(const char *s, size_t n); 361 | int vi_putchar(int c); 362 | void vi_puts(const char *s); 363 | void vi_write(const void *buffer, uint32_t size); 364 | 365 | int64_t read_key(int fd, char *buffer, int timeout); 366 | char* xasprintf(const char *format, ...); 367 | 368 | #ifdef VI_ENABLE_COLON 369 | char* last_char_is(const char *s, int c); 370 | #endif 371 | 372 | #ifdef VI_ENABLE_SETOPTS 373 | char* skip_whitespace(const char *s); 374 | char* skip_non_whitespace(const char *s); 375 | #endif 376 | 377 | int index_in_strings(const char *strings, const char *key); 378 | ssize_t safe_read(int fd, void *buf, size_t count); 379 | int safe_poll(struct pollfd *ufds, nfds_t nfds, int timeout); 380 | ssize_t full_write(int fd, const void *buf, size_t len); 381 | ssize_t full_read(int fd, void *buf, size_t len); 382 | 383 | #ifdef RT_USING_POSIX_TERMIOS 384 | #include 385 | #define TERMIOS_CLEAR_ISIG (1 << 0) 386 | #define TERMIOS_RAW_CRNL_INPUT (1 << 1) 387 | #define TERMIOS_RAW_CRNL_OUTPUT (1 << 2) 388 | #define TERMIOS_RAW_CRNL (TERMIOS_RAW_CRNL_INPUT|TERMIOS_RAW_CRNL_OUTPUT) 389 | #define TERMIOS_RAW_INPUT (1 << 3) 390 | int tcsetattr_stdin_TCSANOW(const struct termios *tp); 391 | int get_terminal_width_height(int fd, unsigned *width, unsigned *height); 392 | int set_termios_to_raw(int fd, struct termios *oldterm, int flags); 393 | #endif 394 | 395 | //config: TODO for RT-Thread 396 | //config:config FEATURE_VI_USE_SIGNALS 397 | //config: bool "Catch signals" 398 | //config: default y 399 | //config: depends on VI 400 | //config: help 401 | //config: Selecting this option will make busybox vi signal aware. This will 402 | //config: make busybox vi support SIGWINCH to deal with Window Changes, catch 403 | //config: Ctrl-Z and Ctrl-C and alarms. 404 | 405 | #endif 406 | -------------------------------------------------------------------------------- /vi-doc.md: -------------------------------------------------------------------------------- 1 | # Vi Editor Cheat Sheet 2 | 3 | ## 光标移动命令 4 | ### 字符 5 | | 按键 | 解释 | 6 | |---|---| 7 | | **h,j,k,l** | 左,右,上,下 | 8 | ### 文本 9 | | 按键 | 解释 | 10 | |---|---| 11 | | **w,W,b,B** | 向前,向后一个词 | 12 | | **e,E** | 词尾 | 13 | | **(,)** | 下一个,前一个句子的开头 | 14 | | **{,}** | 下一个,前一个段落的开头 | 15 | | **[[, ]]** | 下一个,前一个小节的开头 | 16 | ### 行 17 | | 按键 | 解释 | 18 | |---|---| 19 | | **0,$** | 所在行的最前,最后位置 | 20 | | **^** | 所在行的第一个非空字符位置 | 21 | | **+,-** | 下一,前一行的第一个字符位置 | 22 | | **H** | 屏幕显示区内最顶端一行 | 23 | | **M** | 屏幕显示区内中间一行 | 24 | | **L** | 屏幕显示区内最底端一行 | 25 | ### 滚动 26 | | 按键 | 解释 | 27 | |---|---| 28 | | **[Ctrl]F, [Ctrl]B** | 向前,向后滚动一个屏幕显示范围 | 29 | | **[Ctrl]D, [Ctrl]U** | 向下,向上滚动半个屏幕显示范围 | 30 | | **[Ctrl]E, [Ctrl]Y** | 在窗口下方,上方再多显示一行 | 31 | | **z[Enter]** | 滚动屏幕,直到光标在屏幕最上方 | 32 | | **z.** | 滚动屏幕,直到光标在屏幕中间 | 33 | | **z-** | 滚动屏幕,直到光标在屏幕最下方 | 34 | ### 搜索 35 | | 按键 | 解释 | 36 | |---|---| 37 | | **/** *pattern* | 向前搜索字符*pattern* | 38 | | **?** *pattern* | 向后搜索字符*pattern* | 39 | | **n,N** | 在相同,不同的搜索方向上搜索上一次搜索的字符 | 40 | | **f** *x* | 在所在行向前搜索字符*x* | 41 | | **F** *x* | 在所在行向后搜索字符*x* | 42 | | **;** | 重复执行上一个当前行搜索 | 43 | | **,** | 重复执行上一个当前行搜索,但以相反的方向 | 44 | ### 行号 45 | | 按键 | 解释 | 46 | |---|---| 47 | | **[Ctrl]G** | 显示当前行号 | 48 | | **:set nu** | 显示所有行号 | 49 | | **:set nonu** | 取消显示所有行号 | 50 | | *n* **G** | 移动到行号是*n*的一行 | 51 | | **G** | 移动到当前文件的最后一行 | 52 | ### 标记位置 53 | | 按键 | 解释 | 54 | |---|---| 55 | | **m** *x* | 标记当前位置为 *x* | 56 | | **`** *x* | 移动光标至标记 *x* | 57 | | **``** | 返回上一个标记 | 58 | | **'** *x* | 移动到包含标记*x*的一行的开头 | 59 | | **''** | 返回包含上一个标记的一行的开头 | 60 | 61 | ## 编辑命令 62 | ### 输入 63 | | 按键 | 解释 | 64 | |---|---| 65 | | **i,a** | 在光标前,后输入内容 | 66 | | **I,A** | 在光标行首,尾输入内容 | 67 | | **o,O** | 在光标的下,上开启新行,输入内容 | 68 | ### 替换 69 | | 按键 | 解释 | 70 | |---|---| 71 | | **r** | 替换光标所在字符一次 | 72 | | **~** | 更改大小写 | 73 | | **c** *m* | 在移动范围为*m*的区域内更改文本,移动范围请查阅光标移动命令。(如:cw 为更改下一个词) | 74 | | **cc** | 更改当前行 | 75 | | **C** | 更改至行尾的内容 | 76 | | **R** | 一直替换光标所在字符,直到按下[ESC]结束 | 77 | | **s** | 删除当前字符,并继续输入 | 78 | | **S** | 删除当前行,并继续输入 | 79 | ### 删除,移动 80 | | 按键 | 解释 | 81 | |---|---| 82 | | **x** | 删除当前字符 | 83 | | **X** | 删除光标左边的字符 | 84 | | **d** *m* | 在移动范围为*m*的区域内删除文本,移动范围请查阅光标移动命令。(如:dw 为删除下一个词) | 85 | | **dd** | 删除当前行 | 86 | | **D** | 删除至行尾的内容 | 87 | | **p,P** | 将删除的内容插入在光标前,后位置 | 88 | ### Yank(拷贝) 89 | | 按键 | 解释 | 90 | |---|---| 91 | | **y** *m* | 在移动范围为*m*的区域内拷贝文本,移动范围请查阅光标移动命令。(如:yw 为拷贝下一个词) | 92 | | **yy,Y** | 拷贝当前行 | 93 | | **p,P** | 将拷贝的内容插入在光标前,后位置 | 94 | ### 其他命令 95 | | 按键 | 解释 | 96 | |---|---| 97 | | **.** | 重复上一个编辑操作 | 98 | | **u** | 撤销上一个编辑操作 | 99 | | **U** | 撤销当前行的编辑操作 | 100 | | **J** | 将光标所在行与下一行结合成同一行 | 101 | 102 | ## 保存和退出 103 | | 按键 | 解释 | 104 | |---|---| 105 | | **ZZ** | 保存并退出 | 106 | | **:x** | 保存并退出 | 107 | | **:wq** | 保存("write" 写入)并退出 | 108 | | **:w** | 保存文件 | 109 | | **:w!** | 保存文件(强制) | 110 | | **:30,60w** *newfile* | 将从第30行到第60行的内容保存为新文件,叫做*newfile* | 111 | | **:30,60w>>** *file* | 将从第30行到第60行的内容保存添加至文件*file* | 112 | | **:q** | 退出 | 113 | | **:q!** | 退出,放弃所有更改 | 114 | | **:e!** | 放弃自上次保存后的所有修改 | 115 | --------------------------------------------------------------------------------