├── 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 |
--------------------------------------------------------------------------------