1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #include <string.h> |
5 | #include <assert.h> |
6 | #include <stdbool.h> |
7 | #include <stdlib.h> |
8 | |
9 | #include <uv.h> |
10 | |
11 | #include "nvim/ascii.h" |
12 | #include "nvim/lib/kvec.h" |
13 | #include "nvim/log.h" |
14 | #include "nvim/event/loop.h" |
15 | #include "nvim/event/libuv_process.h" |
16 | #include "nvim/event/rstream.h" |
17 | #include "nvim/os/shell.h" |
18 | #include "nvim/os/signal.h" |
19 | #include "nvim/types.h" |
20 | #include "nvim/main.h" |
21 | #include "nvim/vim.h" |
22 | #include "nvim/message.h" |
23 | #include "nvim/memory.h" |
24 | #include "nvim/ui.h" |
25 | #include "nvim/screen.h" |
26 | #include "nvim/memline.h" |
27 | #include "nvim/option_defs.h" |
28 | #include "nvim/charset.h" |
29 | #include "nvim/strings.h" |
30 | |
31 | #define DYNAMIC_BUFFER_INIT { NULL, 0, 0 } |
32 | #define NS_1_SECOND 1000000000U // 1 second, in nanoseconds |
33 | #define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data. |
34 | |
35 | typedef struct { |
36 | char *data; |
37 | size_t cap, len; |
38 | } DynamicBuffer; |
39 | |
40 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
41 | # include "os/shell.c.generated.h" |
42 | #endif |
43 | |
44 | /// Builds the argument vector for running the user-configured 'shell' (p_sh) |
45 | /// with an optional command prefixed by 'shellcmdflag' (p_shcf). E.g.: |
46 | /// |
47 | /// ["shell", "-extra_args", "-shellcmdflag", "command with spaces"] |
48 | /// |
49 | /// @param cmd Command string, or NULL to run an interactive shell. |
50 | /// @param extra_args Extra arguments to the shell, or NULL. |
51 | /// @return Newly allocated argument vector. Must be freed with shell_free_argv. |
52 | char **shell_build_argv(const char *cmd, const char *) |
53 | FUNC_ATTR_NONNULL_RET |
54 | { |
55 | size_t argc = tokenize(p_sh, NULL) + (cmd ? tokenize(p_shcf, NULL) : 0); |
56 | char **rv = xmalloc((argc + 4) * sizeof(*rv)); |
57 | |
58 | // Split 'shell' |
59 | size_t i = tokenize(p_sh, rv); |
60 | |
61 | if (extra_args) { |
62 | rv[i++] = xstrdup(extra_args); // Push a copy of `extra_args` |
63 | } |
64 | |
65 | if (cmd) { |
66 | i += tokenize(p_shcf, rv + i); // Split 'shellcmdflag' |
67 | rv[i++] = shell_xescape_xquote(cmd); // Copy (and escape) `cmd`. |
68 | } |
69 | |
70 | rv[i] = NULL; |
71 | |
72 | assert(rv[0]); |
73 | |
74 | return rv; |
75 | } |
76 | |
77 | /// Releases the memory allocated by `shell_build_argv`. |
78 | /// |
79 | /// @param argv The argument vector. |
80 | void shell_free_argv(char **argv) |
81 | { |
82 | char **p = argv; |
83 | if (p == NULL) { |
84 | // Nothing was allocated, return |
85 | return; |
86 | } |
87 | while (*p != NULL) { |
88 | // Free each argument |
89 | xfree(*p); |
90 | p++; |
91 | } |
92 | xfree(argv); |
93 | } |
94 | |
95 | /// Joins shell arguments from `argv` into a new string. |
96 | /// If the result is too long it is truncated with ellipsis ("..."). |
97 | /// |
98 | /// @returns[allocated] `argv` joined to a string. |
99 | char *shell_argv_to_str(char **const argv) |
100 | FUNC_ATTR_NONNULL_ALL |
101 | { |
102 | size_t n = 0; |
103 | char **p = argv; |
104 | char *rv = xcalloc(256, sizeof(*rv)); |
105 | const size_t maxsize = (256 * sizeof(*rv)); |
106 | if (*p == NULL) { |
107 | return rv; |
108 | } |
109 | while (*p != NULL) { |
110 | xstrlcat(rv, "'" , maxsize); |
111 | xstrlcat(rv, *p, maxsize); |
112 | n = xstrlcat(rv, "' " , maxsize); |
113 | if (n >= maxsize) { |
114 | break; |
115 | } |
116 | p++; |
117 | } |
118 | if (n < maxsize) { |
119 | rv[n - 1] = '\0'; |
120 | } else { |
121 | // Command too long, show ellipsis: "/bin/bash 'foo' 'bar'..." |
122 | rv[maxsize - 4] = '.'; |
123 | rv[maxsize - 3] = '.'; |
124 | rv[maxsize - 2] = '.'; |
125 | rv[maxsize - 1] = '\0'; |
126 | } |
127 | return rv; |
128 | } |
129 | |
130 | /// Calls the user-configured 'shell' (p_sh) for running a command or wildcard |
131 | /// expansion. |
132 | /// |
133 | /// @param cmd The command to execute, or NULL to run an interactive shell. |
134 | /// @param opts Options that control how the shell will work. |
135 | /// @param extra_args Extra arguments to the shell, or NULL. |
136 | /// |
137 | /// @return shell command exit code |
138 | int os_call_shell(char_u *cmd, ShellOpts opts, char_u *) |
139 | { |
140 | DynamicBuffer input = DYNAMIC_BUFFER_INIT; |
141 | char *output = NULL, **output_ptr = NULL; |
142 | int current_state = State; |
143 | bool forward_output = true; |
144 | |
145 | // While the child is running, ignore terminating signals |
146 | signal_reject_deadly(); |
147 | |
148 | if (opts & (kShellOptHideMess | kShellOptExpand)) { |
149 | forward_output = false; |
150 | } else { |
151 | State = EXTERNCMD; |
152 | |
153 | if (opts & kShellOptWrite) { |
154 | read_input(&input); |
155 | } |
156 | |
157 | if (opts & kShellOptRead) { |
158 | output_ptr = &output; |
159 | forward_output = false; |
160 | } else if (opts & kShellOptDoOut) { |
161 | // Caller has already redirected output |
162 | forward_output = false; |
163 | } |
164 | } |
165 | |
166 | size_t nread; |
167 | int exitcode = do_os_system(shell_build_argv((char *)cmd, (char *)extra_args), |
168 | input.data, input.len, output_ptr, &nread, |
169 | emsg_silent, forward_output); |
170 | xfree(input.data); |
171 | |
172 | if (output) { |
173 | (void)write_output(output, nread, true); |
174 | xfree(output); |
175 | } |
176 | |
177 | if (!emsg_silent && exitcode != 0 && !(opts & kShellOptSilent)) { |
178 | MSG_PUTS(_("\nshell returned " )); |
179 | msg_outnum(exitcode); |
180 | msg_putchar('\n'); |
181 | } |
182 | |
183 | State = current_state; |
184 | signal_accept_deadly(); |
185 | |
186 | return exitcode; |
187 | } |
188 | |
189 | /// os_system - synchronously execute a command in the shell |
190 | /// |
191 | /// example: |
192 | /// char *output = NULL; |
193 | /// size_t nread = 0; |
194 | /// char *argv[] = {"ls", "-la", NULL}; |
195 | /// int exitcode = os_sytem(argv, NULL, 0, &output, &nread); |
196 | /// |
197 | /// @param argv The commandline arguments to be passed to the shell. `argv` |
198 | /// will be consumed. |
199 | /// @param input The input to the shell (NULL for no input), passed to the |
200 | /// stdin of the resulting process. |
201 | /// @param len The length of the input buffer (not used if `input` == NULL) |
202 | /// @param[out] output Pointer to a location where the output will be |
203 | /// allocated and stored. Will point to NULL if the shell |
204 | /// command did not output anything. If NULL is passed, |
205 | /// the shell output will be ignored. |
206 | /// @param[out] nread the number of bytes in the returned buffer (if the |
207 | /// returned buffer is not NULL) |
208 | /// @return the return code of the process, -1 if the process couldn't be |
209 | /// started properly |
210 | int os_system(char **argv, |
211 | const char *input, |
212 | size_t len, |
213 | char **output, |
214 | size_t *nread) FUNC_ATTR_NONNULL_ARG(1) |
215 | { |
216 | return do_os_system(argv, input, len, output, nread, true, false); |
217 | } |
218 | |
219 | static int do_os_system(char **argv, |
220 | const char *input, |
221 | size_t len, |
222 | char **output, |
223 | size_t *nread, |
224 | bool silent, |
225 | bool forward_output) |
226 | { |
227 | out_data_decide_throttle(0); // Initialize throttle decider. |
228 | out_data_ring(NULL, 0); // Initialize output ring-buffer. |
229 | bool has_input = (input != NULL && input[0] != '\0'); |
230 | |
231 | // the output buffer |
232 | DynamicBuffer buf = DYNAMIC_BUFFER_INIT; |
233 | stream_read_cb data_cb = system_data_cb; |
234 | if (nread) { |
235 | *nread = 0; |
236 | } |
237 | |
238 | if (forward_output) { |
239 | data_cb = out_data_cb; |
240 | } else if (!output) { |
241 | data_cb = NULL; |
242 | } |
243 | |
244 | // Copy the program name in case we need to report an error. |
245 | char prog[MAXPATHL]; |
246 | xstrlcpy(prog, argv[0], MAXPATHL); |
247 | |
248 | LibuvProcess uvproc = libuv_process_init(&main_loop, &buf); |
249 | Process *proc = &uvproc.process; |
250 | MultiQueue *events = multiqueue_new_child(main_loop.events); |
251 | proc->events = events; |
252 | proc->argv = argv; |
253 | int status = process_spawn(proc, has_input, true, true); |
254 | if (status) { |
255 | loop_poll_events(&main_loop, 0); |
256 | // Failed, probably 'shell' is not executable. |
257 | if (!silent) { |
258 | MSG_PUTS(_("\nshell failed to start: " )); |
259 | msg_outtrans((char_u *)os_strerror(status)); |
260 | MSG_PUTS(": " ); |
261 | msg_outtrans((char_u *)prog); |
262 | msg_putchar('\n'); |
263 | } |
264 | multiqueue_free(events); |
265 | return -1; |
266 | } |
267 | |
268 | // Note: unlike process events, stream events are not queued, as we want to |
269 | // deal with stream events as fast a possible. It prevents closing the |
270 | // streams while there's still data in the OS buffer (due to the process |
271 | // exiting before all data is read). |
272 | if (has_input) { |
273 | wstream_init(&proc->in, 0); |
274 | } |
275 | rstream_init(&proc->out, 0); |
276 | rstream_start(&proc->out, data_cb, &buf); |
277 | rstream_init(&proc->err, 0); |
278 | rstream_start(&proc->err, data_cb, &buf); |
279 | |
280 | // write the input, if any |
281 | if (has_input) { |
282 | WBuffer *input_buffer = wstream_new_buffer((char *)input, len, 1, NULL); |
283 | |
284 | if (!wstream_write(&proc->in, input_buffer)) { |
285 | // couldn't write, stop the process and tell the user about it |
286 | process_stop(proc); |
287 | return -1; |
288 | } |
289 | // close the input stream after everything is written |
290 | wstream_set_write_cb(&proc->in, shell_write_cb, NULL); |
291 | } |
292 | |
293 | // Invoke busy_start here so LOOP_PROCESS_EVENTS_UNTIL will not change the |
294 | // busy state. |
295 | ui_busy_start(); |
296 | ui_flush(); |
297 | if (forward_output) { |
298 | msg_sb_eol(); |
299 | msg_start(); |
300 | msg_no_more = true; |
301 | lines_left = -1; |
302 | } |
303 | int exitcode = process_wait(proc, -1, NULL); |
304 | if (!got_int && out_data_decide_throttle(0)) { |
305 | // Last chunk of output was skipped; display it now. |
306 | out_data_ring(NULL, SIZE_MAX); |
307 | } |
308 | if (forward_output) { |
309 | // caller should decide if wait_return is invoked |
310 | no_wait_return++; |
311 | msg_end(); |
312 | no_wait_return--; |
313 | msg_no_more = false; |
314 | } |
315 | |
316 | ui_busy_stop(); |
317 | |
318 | // prepare the out parameters if requested |
319 | if (output) { |
320 | if (buf.len == 0) { |
321 | // no data received from the process, return NULL |
322 | *output = NULL; |
323 | xfree(buf.data); |
324 | } else { |
325 | // NUL-terminate to make the output directly usable as a C string |
326 | buf.data[buf.len] = NUL; |
327 | *output = buf.data; |
328 | } |
329 | |
330 | if (nread) { |
331 | *nread = buf.len; |
332 | } |
333 | } |
334 | |
335 | assert(multiqueue_empty(events)); |
336 | multiqueue_free(events); |
337 | |
338 | return exitcode; |
339 | } |
340 | |
341 | /// - ensures at least `desired` bytes in buffer |
342 | /// |
343 | /// TODO(aktau): fold with kvec/garray |
344 | static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired) |
345 | { |
346 | if (buf->cap >= desired) { |
347 | assert(buf->data); |
348 | return; |
349 | } |
350 | |
351 | buf->cap = desired; |
352 | kv_roundup32(buf->cap); |
353 | buf->data = xrealloc(buf->data, buf->cap); |
354 | } |
355 | |
356 | static void system_data_cb(Stream *stream, RBuffer *buf, size_t count, |
357 | void *data, bool eof) |
358 | { |
359 | DynamicBuffer *dbuf = data; |
360 | |
361 | size_t nread = buf->size; |
362 | dynamic_buffer_ensure(dbuf, dbuf->len + nread + 1); |
363 | rbuffer_read(buf, dbuf->data + dbuf->len, nread); |
364 | dbuf->len += nread; |
365 | } |
366 | |
367 | /// Tracks output received for the current executing shell command, and displays |
368 | /// a pulsing "..." when output should be skipped. Tracking depends on the |
369 | /// synchronous/blocking nature of ":!". |
370 | /// |
371 | /// Purpose: |
372 | /// 1. CTRL-C is more responsive. #1234 #5396 |
373 | /// 2. Improves performance of :! (UI, esp. TUI, is the bottleneck). |
374 | /// 3. Avoids OOM during long-running, spammy :!. |
375 | /// |
376 | /// Vim does not need this hack because: |
377 | /// 1. :! in terminal-Vim runs in cooked mode, so CTRL-C is caught by the |
378 | /// terminal and raises SIGINT out-of-band. |
379 | /// 2. :! in terminal-Vim uses a tty (Nvim uses pipes), so commands |
380 | /// (e.g. `git grep`) may page themselves. |
381 | /// |
382 | /// @param size Length of data, used with internal state to decide whether |
383 | /// output should be skipped. size=0 resets the internal state and |
384 | /// returns the previous decision. |
385 | /// |
386 | /// @returns true if output should be skipped and pulse was displayed. |
387 | /// Returns the previous decision if size=0. |
388 | static bool out_data_decide_throttle(size_t size) |
389 | { |
390 | static uint64_t started = 0; // Start time of the current throttle. |
391 | static size_t received = 0; // Bytes observed since last throttle. |
392 | static size_t visit = 0; // "Pulse" count of the current throttle. |
393 | static char pulse_msg[] = { ' ', ' ', ' ', '\0' }; |
394 | |
395 | if (!size) { |
396 | bool previous_decision = (visit > 0); |
397 | started = received = visit = 0; |
398 | return previous_decision; |
399 | } |
400 | |
401 | received += size; |
402 | if (received < OUT_DATA_THRESHOLD |
403 | // Display at least the first chunk of output even if it is big. |
404 | || (!started && received < size + 1000)) { |
405 | return false; |
406 | } else if (!visit) { |
407 | started = os_hrtime(); |
408 | } else { |
409 | uint64_t since = os_hrtime() - started; |
410 | if (since < (visit * 0.1L * NS_1_SECOND)) { |
411 | return true; |
412 | } |
413 | if (since > (3 * NS_1_SECOND)) { |
414 | received = visit = 0; |
415 | return false; |
416 | } |
417 | } |
418 | |
419 | visit++; |
420 | // Pulse "..." at the bottom of the screen. |
421 | size_t tick = visit % 4; |
422 | pulse_msg[0] = (tick > 0) ? '.' : ' '; |
423 | pulse_msg[1] = (tick > 1) ? '.' : ' '; |
424 | pulse_msg[2] = (tick > 2) ? '.' : ' '; |
425 | if (visit == 1) { |
426 | msg_putchar('\n'); |
427 | } |
428 | msg_putchar('\r'); // put cursor at start of line |
429 | msg_puts(pulse_msg); |
430 | ui_flush(); |
431 | return true; |
432 | } |
433 | |
434 | /// Saves output in a quasi-ringbuffer. Used to ensure the last ~page of |
435 | /// output for a shell-command is always displayed. |
436 | /// |
437 | /// Init mode: Resets the internal state. |
438 | /// output = NULL |
439 | /// size = 0 |
440 | /// Print mode: Displays the current saved data. |
441 | /// output = NULL |
442 | /// size = SIZE_MAX |
443 | /// |
444 | /// @param output Data to save, or NULL to invoke a special mode. |
445 | /// @param size Length of `output`. |
446 | static void out_data_ring(char *output, size_t size) |
447 | { |
448 | #define MAX_CHUNK_SIZE (OUT_DATA_THRESHOLD / 2) |
449 | static char last_skipped[MAX_CHUNK_SIZE]; // Saved output. |
450 | static size_t last_skipped_len = 0; |
451 | |
452 | assert(output != NULL || (size == 0 || size == SIZE_MAX)); |
453 | |
454 | if (output == NULL && size == 0) { // Init mode |
455 | last_skipped_len = 0; |
456 | return; |
457 | } |
458 | |
459 | if (output == NULL && size == SIZE_MAX) { // Print mode |
460 | out_data_append_to_screen(last_skipped, &last_skipped_len, true); |
461 | return; |
462 | } |
463 | |
464 | // This is basically a ring-buffer... |
465 | if (size >= MAX_CHUNK_SIZE) { // Save mode |
466 | size_t start = size - MAX_CHUNK_SIZE; |
467 | memcpy(last_skipped, output + start, MAX_CHUNK_SIZE); |
468 | last_skipped_len = MAX_CHUNK_SIZE; |
469 | } else if (size > 0) { |
470 | // Length of the old data that can be kept. |
471 | size_t keep_len = MIN(last_skipped_len, MAX_CHUNK_SIZE - size); |
472 | size_t keep_start = last_skipped_len - keep_len; |
473 | // Shift the kept part of the old data to the start. |
474 | if (keep_start) { |
475 | memmove(last_skipped, last_skipped + keep_start, keep_len); |
476 | } |
477 | // Copy the entire new data to the remaining space. |
478 | memcpy(last_skipped + keep_len, output, size); |
479 | last_skipped_len = keep_len + size; |
480 | } |
481 | } |
482 | |
483 | /// Continue to append data to last screen line. |
484 | /// |
485 | /// @param output Data to append to screen lines. |
486 | /// @param remaining Size of data. |
487 | /// @param new_line If true, next data output will be on a new line. |
488 | static void out_data_append_to_screen(char *output, size_t *count, bool eof) |
489 | FUNC_ATTR_NONNULL_ALL |
490 | { |
491 | char *p = output, *end = output + *count; |
492 | while (p < end) { |
493 | if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) { |
494 | msg_putchar_attr((uint8_t)(*p), 0); |
495 | p++; |
496 | } else { |
497 | // Note: this is not 100% precise: |
498 | // 1. we don't check if received continuation bytes are already invalid |
499 | // and we thus do some buffering that could be avoided |
500 | // 2. we don't compose chars over buffer boundaries, even if we see an |
501 | // incomplete UTF-8 sequence that could be composing with the last |
502 | // complete sequence. |
503 | // This will be corrected when we switch to vterm based implementation |
504 | int i = *p ? utfc_ptr2len_len((char_u *)p, (int)(end-p)) : 1; |
505 | if (!eof && i == 1 && utf8len_tab_zero[*(uint8_t *)p] > (end-p)) { |
506 | *count = (size_t)(p - output); |
507 | goto end; |
508 | } |
509 | |
510 | (void)msg_outtrans_len_attr((char_u *)p, i, 0); |
511 | p += i; |
512 | } |
513 | } |
514 | |
515 | end: |
516 | ui_flush(); |
517 | } |
518 | |
519 | static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, |
520 | bool eof) |
521 | { |
522 | size_t cnt; |
523 | char *ptr = rbuffer_read_ptr(buf, &cnt); |
524 | |
525 | if (ptr != NULL && cnt > 0 |
526 | && out_data_decide_throttle(cnt)) { // Skip output above a threshold. |
527 | // Save the skipped output. If it is the final chunk, we display it later. |
528 | out_data_ring(ptr, cnt); |
529 | } else if (ptr != NULL) { |
530 | out_data_append_to_screen(ptr, &cnt, eof); |
531 | } |
532 | |
533 | if (cnt) { |
534 | rbuffer_consumed(buf, cnt); |
535 | } |
536 | |
537 | // Move remaining data to start of buffer, so the buffer can never |
538 | // wrap around. |
539 | rbuffer_reset(buf); |
540 | } |
541 | |
542 | /// Parses a command string into a sequence of words, taking quotes into |
543 | /// consideration. |
544 | /// |
545 | /// @param str The command string to be parsed |
546 | /// @param argv The vector that will be filled with copies of the parsed |
547 | /// words. It can be NULL if the caller only needs to count words. |
548 | /// @return The number of words parsed. |
549 | static size_t tokenize(const char_u *const str, char **const argv) |
550 | FUNC_ATTR_NONNULL_ARG(1) |
551 | { |
552 | size_t argc = 0; |
553 | const char *p = (const char *) str; |
554 | |
555 | while (*p != NUL) { |
556 | const size_t len = word_length((const char_u *) p); |
557 | |
558 | if (argv != NULL) { |
559 | // Fill the slot |
560 | argv[argc] = vim_strnsave_unquoted(p, len); |
561 | } |
562 | |
563 | argc++; |
564 | p = (const char *) skipwhite((char_u *) (p + len)); |
565 | } |
566 | |
567 | return argc; |
568 | } |
569 | |
570 | /// Calculates the length of a shell word. |
571 | /// |
572 | /// @param str A pointer to the first character of the word |
573 | /// @return The offset from `str` at which the word ends. |
574 | static size_t word_length(const char_u *str) |
575 | { |
576 | const char_u *p = str; |
577 | bool inquote = false; |
578 | size_t length = 0; |
579 | |
580 | // Move `p` to the end of shell word by advancing the pointer while it's |
581 | // inside a quote or it's a non-whitespace character |
582 | while (*p && (inquote || (*p != ' ' && *p != TAB))) { |
583 | if (*p == '"') { |
584 | // Found a quote character, switch the `inquote` flag |
585 | inquote = !inquote; |
586 | } else if (*p == '\\' && inquote) { |
587 | p++; |
588 | length++; |
589 | } |
590 | |
591 | p++; |
592 | length++; |
593 | } |
594 | |
595 | return length; |
596 | } |
597 | |
598 | /// To remain compatible with the old implementation (which forked a process |
599 | /// for writing) the entire text is copied to a temporary buffer before the |
600 | /// event loop starts. If we don't (by writing in chunks returned by `ml_get`) |
601 | /// the buffer being modified might get modified by reading from the process |
602 | /// before we finish writing. |
603 | static void read_input(DynamicBuffer *buf) |
604 | { |
605 | size_t written = 0, l = 0, len = 0; |
606 | linenr_T lnum = curbuf->b_op_start.lnum; |
607 | char_u *lp = ml_get(lnum); |
608 | |
609 | for (;;) { |
610 | l = strlen((char *)lp + written); |
611 | if (l == 0) { |
612 | len = 0; |
613 | } else if (lp[written] == NL) { |
614 | // NL -> NUL translation |
615 | len = 1; |
616 | dynamic_buffer_ensure(buf, buf->len + len); |
617 | buf->data[buf->len++] = NUL; |
618 | } else { |
619 | char_u *s = vim_strchr(lp + written, NL); |
620 | len = s == NULL ? l : (size_t)(s - (lp + written)); |
621 | dynamic_buffer_ensure(buf, buf->len + len); |
622 | memcpy(buf->data + buf->len, lp + written, len); |
623 | buf->len += len; |
624 | } |
625 | |
626 | if (len == l) { |
627 | // Finished a line, add a NL, unless this line should not have one. |
628 | if (lnum != curbuf->b_op_end.lnum |
629 | || (!curbuf->b_p_bin && curbuf->b_p_fixeol) |
630 | || (lnum != curbuf->b_no_eol_lnum |
631 | && (lnum != curbuf->b_ml.ml_line_count || curbuf->b_p_eol))) { |
632 | dynamic_buffer_ensure(buf, buf->len + 1); |
633 | buf->data[buf->len++] = NL; |
634 | } |
635 | ++lnum; |
636 | if (lnum > curbuf->b_op_end.lnum) { |
637 | break; |
638 | } |
639 | lp = ml_get(lnum); |
640 | written = 0; |
641 | } else if (len > 0) { |
642 | written += len; |
643 | } |
644 | } |
645 | } |
646 | |
647 | static size_t write_output(char *output, size_t remaining, bool eof) |
648 | { |
649 | if (!output) { |
650 | return 0; |
651 | } |
652 | |
653 | char *start = output; |
654 | size_t off = 0; |
655 | while (off < remaining) { |
656 | if (output[off] == NL) { |
657 | // Insert the line |
658 | output[off] = NUL; |
659 | ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, |
660 | false); |
661 | size_t skip = off + 1; |
662 | output += skip; |
663 | remaining -= skip; |
664 | off = 0; |
665 | continue; |
666 | } |
667 | |
668 | if (output[off] == NUL) { |
669 | // Translate NUL to NL |
670 | output[off] = NL; |
671 | } |
672 | off++; |
673 | } |
674 | |
675 | if (eof) { |
676 | if (remaining) { |
677 | // append unfinished line |
678 | ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); |
679 | // remember that the NL was missing |
680 | curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; |
681 | output += remaining; |
682 | } else { |
683 | curbuf->b_no_eol_lnum = 0; |
684 | } |
685 | } |
686 | |
687 | ui_flush(); |
688 | |
689 | return (size_t)(output - start); |
690 | } |
691 | |
692 | static void shell_write_cb(Stream *stream, void *data, int status) |
693 | { |
694 | if (status) { |
695 | // Can happen if system() tries to send input to a shell command that was |
696 | // backgrounded (:call system("cat - &", "foo")). #3529 #5241 |
697 | msg_schedule_emsgf(_("E5677: Error writing input to shell-command: %s" ), |
698 | uv_err_name(status)); |
699 | } |
700 | stream_close(stream, NULL, NULL); |
701 | } |
702 | |
703 | /// Applies 'shellxescape' (p_sxe) and 'shellxquote' (p_sxq) to a command. |
704 | /// |
705 | /// @param cmd Command string |
706 | /// @return Escaped/quoted command string (allocated). |
707 | static char *shell_xescape_xquote(const char *cmd) |
708 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT |
709 | { |
710 | if (*p_sxq == NUL) { |
711 | return xstrdup(cmd); |
712 | } |
713 | |
714 | const char *ecmd = cmd; |
715 | if (*p_sxe != NUL && STRCMP(p_sxq, "(" ) == 0) { |
716 | ecmd = (char *)vim_strsave_escaped_ext((char_u *)cmd, p_sxe, '^', false); |
717 | } |
718 | size_t ncmd_size = strlen(ecmd) + STRLEN(p_sxq) * 2 + 1; |
719 | char *ncmd = xmalloc(ncmd_size); |
720 | |
721 | // When 'shellxquote' is ( append ). |
722 | // When 'shellxquote' is "( append )". |
723 | if (STRCMP(p_sxq, "(" ) == 0) { |
724 | vim_snprintf(ncmd, ncmd_size, "(%s)" , ecmd); |
725 | } else if (STRCMP(p_sxq, "\"(" ) == 0) { |
726 | vim_snprintf(ncmd, ncmd_size, "\"(%s)\"" , ecmd); |
727 | } else { |
728 | vim_snprintf(ncmd, ncmd_size, "%s%s%s" , p_sxq, ecmd, p_sxq); |
729 | } |
730 | |
731 | if (ecmd != cmd) { |
732 | xfree((void *)ecmd); |
733 | } |
734 | |
735 | return ncmd; |
736 | } |
737 | |
738 | |