1 /*
2 *
3 * CLEX File Manager
4 *
5 * Copyright (C) 2001-2018 Vlado Potisk <vlado_potisk@clex.sk>
6 *
7 * CLEX is free software without warranty of any kind; see the
8 * GNU General Public License as set out in the "COPYING" document
9 * which accompanies the CLEX File Manager package.
10 *
11 * CLEX can be downloaded from http://www.clex.sk
12 *
13 */
14
15 #include "clexheaders.h"
16
17 #include <stdarg.h> /* log.h */
18 #include <string.h> /* strcmp() */
19 #include <wctype.h> /* iswlower() */
20
21 #include "edit.h"
22
23 #include "cfg.h" /* cfg_str() */
24 #include "control.h" /* get_current_mode() */
25 #include "filter.h" /* cx_filter() */
26 #include "filepanel.h" /* cx_files_enter() */
27 #include "inout.h" /* win_edit() */
28 #include "history.h" /* hist_reset_index() */
29 #include "log.h" /* msgout() */
30 #include "mbwstring.h" /* wc_cols() */
31 #include "util.h" /* jshash() */
32
33 static FLAG cmd_auto = 0; /* $! control sequence */
34
35 /* adjust 'offset' so the cursor is visible */
36 /* returns 1 if offset has changed, otherwise 0 */
37 #define OFFSET_STEP 16
38 int
edit_adjust(void)39 edit_adjust(void)
40 {
41 int old_offset, offset, screen, curs, pwidth, cols, o2c;
42 wchar_t *str;
43
44 if (textline == 0)
45 return 0;
46
47 offset = textline->offset;
48 curs = textline->curs;
49 pwidth = textline->promptwidth;
50 str = USTR(textline->line);
51
52 if (curs > 0 && curs < textline->size) {
53 /* composed UTF-8 characters: place the cursor on the base character */
54 while (curs > 0 && utf_iscomposing(str[curs]))
55 curs--;
56 textline->curs = curs;
57 }
58
59 /*
60 * screen = number of display columns available,
61 * substract columns possibly left unused because of double-width chars
62 * not fitting at the right border, reserved position for the '>' mark,
63 * and the bottom right corner position that could cause a scroll
64 */
65 screen = (disp_data.scrcols - 1) * disp_data.cmdlines - 2;
66
67 if (offset <= curs && wc_cols(str,offset,curs) < screen - (offset ? 1 : pwidth))
68 /* no need for a change */
69 return 0;
70
71 old_offset = offset;
72 if (offset && wc_cols(str,0,curs) < screen - pwidth) {
73 /* 1. zero offset is preferred whenever possible */
74 textline->offset = 0;
75 return 1;
76 }
77 /* o2c = desired width in columns from the offset to the cursor */
78 if (wc_cols(str,curs,-1) < screen - 1)
79 /* 2. if EOL is visible, minimize the unused space after the EOL */
80 o2c = screen - 1;
81 else if (curs < offset)
82 /* 3. if the cursor appears to move left, put it on the first line */
83 o2c = disp_data.scrcols - 1;
84 else
85 /* 4. if the cursor appears to move right, put it on the last line */
86 o2c = (disp_data.cmdlines - 1) * disp_data.scrcols + OFFSET_STEP;
87
88 /* estimate the offset, then fine adjust it in few iteration rounds */
89 offset = curs <= o2c ? 0 : (curs - o2c) / OFFSET_STEP * OFFSET_STEP;
90 cols = wc_cols(str,offset,curs);
91 while (offset > OFFSET_STEP && cols < o2c) {
92 cols += wc_cols(str,offset - OFFSET_STEP,offset);
93 offset -= OFFSET_STEP;
94 }
95 while (offset == 0 || cols >= o2c) {
96 cols -= wc_cols(str,offset,offset + OFFSET_STEP);
97 offset += OFFSET_STEP;
98 }
99 return old_offset != (textline->offset = offset);
100 }
101
102 /* make changes to 'textline' visible on the screen */
103 void
edit_update(void)104 edit_update(void)
105 {
106 edit_adjust();
107 win_edit();
108 }
109
110 /*
111 * if you have only moved the cursor, use this optimized
112 * version of edit_update() instead
113 */
114 void
edit_update_cursor(void)115 edit_update_cursor(void)
116 {
117 if (edit_adjust())
118 win_edit();
119 }
120
121 /*
122 * edit_islong() can be called after win_edit() and it returns 0
123 * if the entire 'textline' is displayed from the beginning to the end,
124 * or 1 if it does not fit due to excessive length
125 */
126 int
edit_islong(void)127 edit_islong(void)
128 {
129 return textline->offset || sum_linechars() < textline->size;
130 }
131
132 int
edit_isauto(void)133 edit_isauto(void)
134 {
135 return cmd_auto;
136 }
137
138 void
cx_edit_begin(void)139 cx_edit_begin(void)
140 {
141 textline->curs = 0;
142 edit_update_cursor();
143 }
144
145 void
cx_edit_end(void)146 cx_edit_end(void)
147 {
148 textline->curs = textline->size;
149 edit_update_cursor();
150 }
151
152 /*
153 * "_nu_" means "no update" version, the caller is responsible
154 * for calling the update function edit_update().
155 *
156 * The point is to invoke several _nu_ functions and then make the 'update' just once.
157 *
158 * Note: The edit_update() consists of edit_adjust() followed by * win_edit().
159 * If the 'offset' is fine (e.g. after edit_nu_kill), the edit_adjust() may be skipped.
160 */
161 static void
edit_nu_left(void)162 edit_nu_left(void)
163 {
164 FLAG move = 1;
165
166 /* composed UTF-8 characters: move the cursor also over combining chars */
167 while (move && textline->curs > 0)
168 move = utf_iscomposing(USTR(textline->line)[--textline->curs]);
169 }
170
171 void
cx_edit_left(void)172 cx_edit_left(void)
173 {
174 edit_nu_left();
175 edit_update_cursor();
176 }
177
178 static void
edit_nu_right(void)179 edit_nu_right(void)
180 {
181 FLAG move = 1;
182
183 /* composed UTF-8 characters: move the cursor also over combining chars */
184 while (move && textline->curs < textline->size)
185 move = utf_iscomposing(USTR(textline->line)[++textline->curs]);
186 }
187
188 void
cx_edit_right(void)189 cx_edit_right(void)
190 {
191 edit_nu_right();
192 edit_update_cursor();
193 }
194
195 void
cx_edit_up(void)196 cx_edit_up(void)
197 {
198 int width, curs;
199 wchar_t *line;
200
201 line = USTR(textline->line);
202 width = disp_data.scrcols;
203 curs = textline->curs;
204 while (curs > 0 && width > 0) {
205 curs--;
206 width -= WCW(line[curs]);
207 }
208 textline->curs = curs;
209 edit_update_cursor();
210 }
211
212 void
cx_edit_down(void)213 cx_edit_down(void)
214 {
215 int width, curs;
216 wchar_t *line;
217
218 line = USTR(textline->line);
219 width = disp_data.scrcols;
220 curs = textline->curs;
221 while (curs < textline->size && width > 0) {
222 curs++;
223 width -= WCW(line[curs]);
224 }
225 textline->curs = curs;
226 edit_update_cursor();
227 }
228
229 static wchar_t
wordsep(void)230 wordsep(void)
231 {
232 if (textline == &line_cmd)
233 return L' ';
234 if (textline == &line_dir)
235 return L'/';
236 return get_current_mode() == MODE_BM_EDIT2 ? L'/' : L' ';
237 }
238
239 /* move one word left */
240 void
cx_edit_w_left(void)241 cx_edit_w_left(void)
242 {
243 const wchar_t *line;
244 wchar_t wsep;
245 int curs;
246
247 wsep = wordsep();
248 if ((curs = textline->curs) > 0) {
249 line = USTR(textline->line);
250 while (curs > 0 && line[curs - 1] == wsep)
251 curs--;
252 while (curs > 0 && line[curs - 1] != wsep)
253 curs--;
254 textline->curs = curs;
255 edit_update_cursor();
256 }
257 }
258
259 /* move one word right */
260 void
cx_edit_w_right(void)261 cx_edit_w_right(void)
262 {
263 const wchar_t *line;
264 wchar_t wsep;
265 int curs;
266
267 wsep = wordsep();
268 if ((curs = textline->curs) < textline->size) {
269 line = USTR(textline->line);
270 while (curs < textline->size && line[curs] != wsep)
271 curs++;
272 while (curs < textline->size && line[curs] == wsep)
273 curs++;
274 textline->curs = curs;
275 edit_update_cursor();
276 }
277 }
278
279 void
cx_edit_mouse(void)280 cx_edit_mouse(void)
281 {
282 int mode;
283
284 if (!MI_AREA(LINE))
285 return;
286 if (!MI_CLICK && !MI_WHEEL)
287 return;
288
289 if (textline->size) {
290 if (MI_CLICK) {
291 if (minp.cursor < 0)
292 return;
293 textline->curs = minp.cursor;
294 }
295 else {
296 mode = get_current_mode();
297 if (mode == MODE_SELECT || mode == MODE_DESELECT || mode == MODE_CFG_EDIT_NUM)
298 MI_B(4) ? cx_edit_left() : cx_edit_right();
299 else
300 MI_B(4) ? cx_edit_w_left() : cx_edit_w_right();
301 }
302 }
303 if (panel->filtering == 1)
304 cx_filter();
305 }
306
307 void
edit_nu_kill(void)308 edit_nu_kill(void)
309 {
310 if (textline == &line_cmd)
311 hist_reset_index();
312
313 textline->curs = textline->size = textline->offset = 0;
314 /*
315 * usw_copy() is called to possibly shrink the allocated memory block,
316 * other delete functions don't do that
317 */
318 usw_copy(&textline->line,L"");
319 disp_data.noenter = 0;
320 }
321
322 void
cx_edit_kill(void)323 cx_edit_kill(void)
324 {
325 edit_nu_kill();
326 win_edit();
327 }
328
329 /* delete 'cnt' chars at cursor position */
330 static void
delete_chars(int cnt)331 delete_chars(int cnt)
332 {
333 int i;
334 wchar_t *line;
335
336 line = USTR(textline->line);
337 textline->size -= cnt;
338 for (i = textline->curs; i <= textline->size; i++)
339 line[i] = line[i + cnt];
340 }
341
342 void
cx_edit_backsp(void)343 cx_edit_backsp(void)
344 {
345 int pos;
346 FLAG del = 1;
347
348 if (textline->curs == 0)
349 return;
350
351 pos = textline->curs;
352 /* composed UTF-8 characters: delete also the combining chars */
353 while (del && textline->curs > 0)
354 del = utf_iscomposing(USTR(textline->line)[--textline->curs]);
355 delete_chars(pos - textline->curs);
356 edit_update();
357 }
358
359 void
cx_edit_delchar(void)360 cx_edit_delchar(void)
361 {
362 int pos;
363 FLAG del = 1;
364
365 if (textline->curs == textline->size)
366 return;
367
368 pos = textline->curs;
369 /* composed UTF-8 characters: delete also the combining chars */
370 while (del && textline->curs < textline->size)
371 del = utf_iscomposing(USTR(textline->line)[++pos]);
372 delete_chars(pos - textline->curs);
373 edit_update();
374 }
375
376 /* delete until the end of line */
377 void
cx_edit_delend(void)378 cx_edit_delend(void)
379 {
380 USTR(textline->line)[textline->size = textline->curs] = L'\0';
381 edit_update();
382 }
383
384 /* delete word */
385 void
cx_edit_w_del(void)386 cx_edit_w_del(void)
387 {
388 int eow;
389 wchar_t *line;
390
391 eow = textline->curs;
392 line = USTR(textline->line);
393 if (line[eow] == L' ' || line[eow] == L'\0')
394 return;
395
396 while (textline->curs > 0 && line[textline->curs - 1] != L' ')
397 textline->curs--;
398 while (eow < textline->size && line[eow] != L' ')
399 eow++;
400 while (line[eow] == L' ')
401 eow++;
402 delete_chars(eow - textline->curs);
403 edit_update();
404 }
405
406 /* make room for 'cnt' chars at cursor position */
407 static wchar_t *
insert_space(int cnt)408 insert_space(int cnt)
409 {
410 int i;
411 wchar_t *line, *ins;
412
413 usw_resize(&textline->line,textline->size + cnt + 1);
414 line = USTR(textline->line);
415 ins = line + textline->curs; /* insert new character(s) here */
416 textline->size += cnt;
417 textline->curs += cnt;
418 for (i = textline->size; i >= textline->curs; i--)
419 line[i] = line[i - cnt];
420
421 return ins;
422 }
423
424 void
edit_nu_insertchar(wchar_t ch)425 edit_nu_insertchar(wchar_t ch)
426 {
427 *insert_space(1) = ch;
428 }
429
430 void
edit_insertchar(wchar_t ch)431 edit_insertchar(wchar_t ch)
432 {
433 edit_nu_insertchar(ch);
434 edit_update();
435 }
436
437 void
edit_nu_putstr(const wchar_t * str)438 edit_nu_putstr(const wchar_t *str)
439 {
440 usw_copy(&textline->line,str);
441 textline->curs = textline->size = wcslen(str);
442 }
443
444 void
edit_putstr(const wchar_t * str)445 edit_putstr(const wchar_t *str)
446 {
447 edit_nu_putstr(str);
448 edit_update();
449 }
450
451 /*
452 * returns number of quoting characters required:
453 * 2 = single quotes 'X' (newline)
454 * 1 = backslash quoting \X (shell metacharacters)
455 * 0 = no quoting (regular characters)
456 * in other words: it returns non-zero if 'ch' is a special character
457 */
458 int
edit_isspecial(wchar_t ch)459 edit_isspecial(wchar_t ch)
460 {
461 if (ch == WCH_CTRL('J') || ch == WCH_CTRL('M'))
462 return 2; /* 2 extra characters: X -> 'X' */
463 /* built-in metacharacters (let's play it safe) */
464 if (wcschr(L"\t ()<>[]{}#$&\\|?*;\'\"`~",ch))
465 return 1; /* 1 extra character: X -> \X */
466 /* C-shell */
467 if (user_data.shelltype == SHELL_CSH && (ch == L'!' || ch == L':'))
468 return 1;
469 /* additional special characters to be quoted */
470 if (*cfg_str(CFG_QUOTE) && wcschr(cfg_str(CFG_QUOTE),ch))
471 return 1;
472
473 return 0;
474 }
475
476 /* return value: like edit_isspecial() */
477 static int
how_to_quote(wchar_t ch,int qlevel)478 how_to_quote(wchar_t ch, int qlevel)
479 {
480 if (qlevel == QUOT_NORMAL) {
481 /* full quoting */
482 if (ch == L'=' || ch == L':')
483 /* not special, but quoted to help the name completion */
484 return 1;
485 return edit_isspecial(ch);
486 }
487 if (qlevel == QUOT_IN_QUOTES) {
488 /* inside double quotes */
489 if (ch == L'\"' || ch == L'\\' || ch == L'$' || ch == L'`')
490 return 1;
491 }
492 return 0;
493 }
494
495 void
edit_nu_insertstr(const wchar_t * str,int qlevel)496 edit_nu_insertstr(const wchar_t *str, int qlevel)
497 {
498 int len, mch;
499 wchar_t ch, *ins;
500
501 for (len = mch = 0; (ch = str[len]) != L'\0'; len++)
502 if (qlevel != QUOT_NONE)
503 mch += how_to_quote(ch,qlevel);
504 if (len == 0)
505 return;
506
507 ins = insert_space(len + mch);
508 while ((ch = *str++) != L'\0') {
509 if (qlevel != QUOT_NONE) {
510 mch = how_to_quote(ch,qlevel);
511 if (mch == 2) {
512 *ins++ = L'\'';
513 *ins++ = ch;
514 ch = L'\'';
515 }
516 else if (mch == 1)
517 *ins++ = L'\\';
518 }
519 *ins++ = ch;
520 }
521
522 /*
523 * if there is no quoting this is equivalent of:
524 * len = wcslen(str); wcsncpy(insert_space(len),src,len);
525 */
526 }
527
528 void
edit_insertstr(const wchar_t * str,int qlevel)529 edit_insertstr(const wchar_t *str, int qlevel)
530 {
531 edit_nu_insertstr(str,qlevel);
532 edit_update();
533 }
534
535 /*
536 * insert string, expand $x variables:
537 * $$ -> literal $
538 * $1 -> current directory name (primary panel's directory)
539 * $2 -> secondary directory name (secondary panel's directory)
540 * $F -> current file name
541 * $S -> names of all selected file(s)
542 * $f -> $S - if the <ESC> key was pressed and
543 * at least one file has been selected
544 * $F - otherwise
545 * everything else is copied literally
546 */
547 void
edit_macro(const wchar_t * macro)548 edit_macro(const wchar_t *macro)
549 {
550 wchar_t ch, *ins;
551 const wchar_t *src;
552 int i, cnt, curs, sel;
553 FLAG prefix, noenter = 0, automatic = 0, warn_dotdir = 0;
554 FILE_ENTRY *pfe;
555
556 /*
557 * implementation note: avoid char by char inserts whenever
558 * possible because of the overhead
559 */
560
561 if (textline->curs == 0 || wcschr(L" :=",USTR(textline->line)[textline->curs - 1]))
562 while (*macro == L' ')
563 macro++;
564
565 curs = -1;
566 for (src = macro, prefix = 0; (ch = *macro++) != L'\0'; ) {
567 if (TCLR(prefix)) {
568 /* insert everything seen before the '$' prefix */
569 cnt = macro - src - 2 /* two chars in "$x" */;
570 if (cnt > 0)
571 wcsncpy(insert_space(cnt),src,cnt);
572 src = macro;
573
574 /* now handle $x */
575 if (ch == L'f' && panel->cnt > 0) {
576 ch = ppanel_file->selected && kinp.prev_esc ? L'S' : L'F';
577 if (ch == L'F' && ppanel_file->selected
578 && !NOPT(NOTIF_SELECTED))
579 msgout(MSG_i | MSG_NOTIFY,"press <ESC> before <Fn> if you "
580 "want to work with selected files");
581 }
582 switch (ch) {
583 case L'$':
584 edit_nu_insertchar(L'$');
585 break;
586 case L'1':
587 edit_nu_insertstr(USTR(ppanel_file->dirw),QUOT_NORMAL);
588 break;
589 case L'2':
590 edit_nu_insertstr(USTR(ppanel_file->other->dirw),QUOT_NORMAL);
591 break;
592 case L'c':
593 curs = textline->curs;
594 break;
595 case L'S':
596 if (panel->cnt > 0) {
597 for (i = cnt = 0, sel = ppanel_file->selected; cnt < sel; i++) {
598 pfe = ppanel_file->files[i];
599 if (pfe->select) {
600 if (pfe->dotdir) {
601 sel--;
602 warn_dotdir = 1;
603 continue;
604 }
605 if (cnt++)
606 edit_nu_insertchar(L' ');
607 edit_nu_insertstr(SDSTR(pfe->filew),QUOT_NORMAL);
608 }
609 }
610 }
611 break;
612 case L'f':
613 break;
614 case L'F':
615 if (panel->cnt > 0) {
616 pfe = ppanel_file->files[ppanel_file->pd->curs];
617 edit_nu_insertstr(SDSTR(pfe->filew),QUOT_NORMAL);
618 }
619 break;
620 case L':':
621 curs = -1;
622 edit_nu_kill();
623 break;
624 case L'!':
625 automatic = 1;
626 break;
627 case L'~':
628 noenter = 1;
629 break;
630 default:
631 ins = insert_space(2);
632 ins[0] = L'$';
633 ins[1] = ch;
634 }
635 }
636 else if (ch == L'$')
637 prefix = 1;
638 }
639
640 /* insert the rest */
641 edit_insertstr(src,QUOT_NONE);
642
643 if (curs >= 0)
644 textline->curs = curs;
645
646 if (noenter) {
647 disp_data.noenter = 1;
648 disp_data.noenter_hash = jshash(USTR(textline->line));
649 }
650
651 if (warn_dotdir && !NOPT(NOTIF_DOTDIR))
652 msgout(MSG_i | MSG_NOTIFY,"directory names . and .. not inserted");
653
654 if (panel->filtering == 1)
655 cx_filter();
656
657 if (automatic && textline->size) {
658 cmd_auto = 1;
659 cx_files_enter();
660 cmd_auto = 0;
661 }
662 }
663
cx_edit_cmd_f2(void)664 void cx_edit_cmd_f2(void) { edit_macro(L"$f "); }
cx_edit_cmd_f3(void)665 void cx_edit_cmd_f3(void) { edit_macro(cfg_str(CFG_CMD_F3)); }
cx_edit_cmd_f4(void)666 void cx_edit_cmd_f4(void) { edit_macro(cfg_str(CFG_CMD_F4)); }
cx_edit_cmd_f5(void)667 void cx_edit_cmd_f5(void) { edit_macro(cfg_str(CFG_CMD_F5)); }
cx_edit_cmd_f6(void)668 void cx_edit_cmd_f6(void) { edit_macro(cfg_str(CFG_CMD_F6)); }
cx_edit_cmd_f7(void)669 void cx_edit_cmd_f7(void) { edit_macro(cfg_str(CFG_CMD_F7)); }
cx_edit_cmd_f8(void)670 void cx_edit_cmd_f8(void) { edit_macro(cfg_str(CFG_CMD_F8)); }
cx_edit_cmd_f9(void)671 void cx_edit_cmd_f9(void) { edit_macro(cfg_str(CFG_CMD_F9)); }
cx_edit_cmd_f10(void)672 void cx_edit_cmd_f10(void) { edit_macro(cfg_str(CFG_CMD_F10)); }
cx_edit_cmd_f11(void)673 void cx_edit_cmd_f11(void) { edit_macro(cfg_str(CFG_CMD_F11)); }
cx_edit_cmd_f12(void)674 void cx_edit_cmd_f12(void) { edit_macro(cfg_str(CFG_CMD_F12)); }
675
676 static void
paste_exit()677 paste_exit()
678 {
679 /* when called from the complete/insert panel: exit the panel */
680 if (get_current_mode() == MODE_PASTE)
681 next_mode = MODE_SPECIAL_RETURN;
682 }
683
684 void
cx_edit_paste_path(void)685 cx_edit_paste_path(void)
686 {
687 if (ppanel_file->pd->cnt)
688 edit_macro(strcmp(USTR(ppanel_file->dir),"/") ? L" $1/$F " : L" /$F ");
689 paste_exit();
690 }
691
692 void
cx_edit_paste_link(void)693 cx_edit_paste_link(void)
694 {
695 FILE_ENTRY *pfe;
696
697 if (ppanel_file->pd->cnt) {
698 pfe = ppanel_file->files[ppanel_file->pd->curs];
699 if (!pfe->symlink)
700 msgout(MSG_i,"not a symbolic link");
701 else {
702 edit_nu_insertstr(USTR(pfe->linkw),QUOT_NORMAL);
703 edit_insertchar(' ');
704 }
705 }
706 paste_exit();
707 }
708
709 void
cx_edit_paste_currentfile(void)710 cx_edit_paste_currentfile(void)
711 {
712 if (ppanel_file->pd->cnt)
713 edit_macro(L"$F ");
714 paste_exit();
715 }
716
717 void
cx_edit_paste_filenames(void)718 cx_edit_paste_filenames(void)
719 {
720 if (ppanel_file->pd->cnt) {
721 if (ppanel_file->selected)
722 edit_macro(L" $S ");
723 else
724 msgout(MSG_i,"no selected files");
725 }
726 paste_exit();
727 }
728
729 void
cx_edit_paste_dir1(void)730 cx_edit_paste_dir1(void)
731 {
732 edit_macro(L" $1");
733 paste_exit();
734 }
735
736 void
cx_edit_paste_dir2(void)737 cx_edit_paste_dir2(void)
738 {
739 edit_macro(L" $2");
740 paste_exit();
741 }
742
743 void
cx_edit_flipcase(void)744 cx_edit_flipcase(void)
745 {
746 wchar_t ch;
747
748 ch = USTR(textline->line)[textline->curs];
749 if (ch == L'\0')
750 return;
751 if (iswlower(ch))
752 ch = towupper(ch);
753 else if (iswupper(ch))
754 ch = towlower(ch);
755 else {
756 cx_edit_right();
757 return;
758 }
759 USTR(textline->line)[textline->curs] = ch;
760 edit_nu_right();
761 edit_update();
762 }
763
764 void
edit_setprompt(TEXTLINE * pline,const wchar_t * prompt)765 edit_setprompt(TEXTLINE *pline, const wchar_t *prompt)
766 {
767 wchar_t *p;
768 int width;
769
770 for (p = usw_copy(&pline->prompt,prompt), width = 0; *p != L'\0'; p++) {
771 width += WCW(*p);
772 if (width > MAX_PROMPT_WIDTH) {
773 p -= 2;
774 width = width - wc_cols(p,0,3) + 2;
775 wcscpy(p,L"> ");
776 msgout(MSG_NOTICE,"Long prompt string truncated: \"%ls\"",USTR(pline->prompt));
777 break;
778 }
779 }
780 pline->promptwidth = width;
781 }
782