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