2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Define basic key/mouse interactions that conform to
6 * CUA. All special modes should build on this.
14 #define PANE_DATA_VOID
17 /* num2 is used to track if successive commands are related.
18 * Only low 16 bits identify command, other bits are free.
22 N2_undo_insert, /* adjacent commands form a single undo set */
23 N2_undo_delete, /* adjacent commands form a single undo set */
24 N2_undo_change, /* adjacent commands form a single undo set */
25 N2_recentre, /* repeated recentre goes to different places */
26 N2_yank, /* repeated yank-pop takes different result */
27 N2_match, /* repeated ` after Cx` repeats the search */
28 N2_undo, /* Last command was 'undo' too */
29 N2_close_others,/* Last command was close-other, just a 1 can repeat */
30 N2_runmacro, /* Last command was CX-e, just an 'e' can repeat */
31 N2_shift, /* Last command was CX-< or CX-> */
32 N2_growx, /* Last command was CX-{ or CX-} */
33 N2_uniquote, /* Last command was :C-q inserting a unicode from name */
35 static inline int N2(const struct cmd_info *ci safe)
37 return ci->num2 & 0xffff;
40 static inline int N2a(const struct cmd_info *ci safe)
42 return ci->num2 >> 16;
46 /* selection:active encodes 4 different states for the selection
47 * 0 - inactive. The other-end might exist but it is passive, not displayed
48 * and not used unless explicitly asked for (Cx Cx)
49 * 1 - active. Selection is active and will remain active if cursor
50 * moves or text is changed. Is used in various ways.
51 * 2 - transient. Is active, but will be canceled on movement or change.
52 * Will be used for copy/cut, but little else
53 * 3 - replacable Transient plus text entry will delete selected content.
56 static void set_selection(struct pane *p safe, struct mark *pt,
57 struct mark *mk, int type)
62 active = attr_find_int(mk->attrs, "selection:active");
65 attr_set_int(&mk->attrs, "selection:active", type);
67 pt = call_ret(mark, "doc:point", p);
71 attr_set_int(&pt->attrs, "selection:active", 1);
72 if (!mark_same(pt, mk))
73 call("view:changed", p, 0, pt, NULL, 0, mk);
76 DEF_CMD(basic_selection_set)
78 set_selection(ci->focus, ci->mark2, ci->mark, ci->num);
82 static bool clear_selection(struct pane *p safe, struct mark *pt,
83 struct mark *mk, int type)
88 active = attr_find_int(mk->attrs, "selection:active");
91 if (type && active < type)
93 attr_set_int(&mk->attrs, "selection:active", 0);
95 pt = call_ret(mark, "doc:point", p);
98 attr_set_int(&pt->attrs, "selection:active", 0);
99 if (!mark_same(pt, mk))
100 call("view:changed", p, 0, pt, NULL, 0, mk);
104 DEF_CMD(basic_selection_clear)
106 if (clear_selection(ci->focus, ci->mark2, ci->mark, ci->num))
112 static void update_sel(struct pane *p safe,
113 struct mark *pt safe, struct mark *m2 safe,
116 struct mark *mfirst, *mlast;
119 call("Move-to", p, 1, m2);
120 mk = call_ret(mark2, "doc:point", p);
124 type = attr_find(m2->attrs, "selection-type");
126 attr_set_str(&m2->attrs, "selection-type", type);
128 if (type && strcmp(type, "char") != 0) {
130 if (pt->seq < mk->seq) {
137 if (strcmp(type, "word") == 0) {
138 wint_t wch = doc_prior(p, mfirst);
139 /* never move back over spaces */
140 if (wch != WEOF && !iswspace(wch))
141 call("doc:word", p, -1, mfirst);
142 wch = doc_following(p, mlast);
143 /* For forward over a single space is OK */
144 if (wch != WEOF && iswspace(wch))
147 call("doc:word", p, 1, mlast);
149 call("doc:EOL", p, -1, mfirst);
150 /* Include trailing newline */
151 call("doc:EOL", p, 1, mlast, NULL, 1);
155 /* Don't set selection until range is non-empty, else we
156 * might clear some other selection too early.
158 if (!mark_same(pt, mk)) {
159 /* Must call 'claim' first as it might be claiming from us */
160 call("selection:claim", p);
161 set_selection(p, pt, mk, 2);
167 /* The second mark (managed by core-doc) is used to record the
168 * selected starting point. When double- or triple- click
169 * asks for word or line selection, the actually start, which
170 * is stored in the first mark, may be different.
171 * That selected starting point will record the current unit
172 * siez in the emacs:selection-type attribute.
174 struct mark *pt = call_ret(mark, "doc:point", ci->focus);
175 struct mark *mk = call_ret(mark2, "doc:point", ci->focus);
176 struct mark *m2 = call_ret(mark2, "doc:point", ci->focus, 2);
177 struct mark *m = mark_new(ci->focus);
181 /* Not in document, not my problem */
185 /* NOTE must find new location before view changes. */
186 call("Move-CursorXY", ci->focus, 0, m, "prepare",
187 0, NULL, NULL, ci->x, ci->y);
189 clear_selection(ci->focus, pt, mk, 0);
190 call("Move-to", ci->focus, 0, m);
191 pane_take_focus(ci->focus);
193 if (m2 && strcmp(ci->key, "M:DPress-1") == 0) {
194 type = attr_find(m2->attrs, "emacs:selection-type");
197 else if (strcmp(type, "char") == 0)
199 else if (strcmp(type, "word") == 0)
205 /* Record start of selection */
206 call("Move-to", ci->focus, 2, m);
207 m2 = call_ret(mark2, "doc:point", ci->focus, 2);
209 attr_set_str(&m2->attrs, "emacs:selection-type", type);
212 /* Record co-ordinate of start so we can tell if the mouse moved. */
213 attr_set_int(&m2->attrs, "emacs:track-selection",
214 1 + ci->x * 10000 + ci->y);
215 update_sel(ci->focus, pt, m2, type);
222 DEF_CMD(basic_release)
224 struct mark *p = call_ret(mark, "doc:point", ci->focus);
225 struct mark *mk = call_ret(mark2, "doc:point", ci->focus);
226 struct mark *m2 = call_ret(mark2, "doc:point", ci->focus, 2);
227 struct mark *m = mark_new(ci->focus);
232 if (!p || !m2 || !m) {
233 /* Not in a document or no selection start - not my problem */
238 prev_pos = attr_find_int(m2->attrs, "emacs:track-selection");
239 type = attr_find(m2->attrs, "emacs:selection-type");
240 moved = prev_pos != (1 + ci->x * 10000 + ci->y);
241 attr_set_int(&m2->attrs, "emacs:track-selection", 0);
243 call("Move-CursorXY", ci->focus,
244 0, m, "activate", 0, NULL, NULL, ci->x, ci->y);
245 /* That action might have closed a pane. Better check... */
246 if (ci->focus->damaged & DAMAGED_CLOSED) {
249 /* Moved the mouse, so new location is point */
250 call("Move-to", ci->focus, 0, m);
251 update_sel(ci->focus, p, m2, NULL);
252 } else if (type && strcmp(type, "char") != 0) {
253 /* Otherwise use the old location. Point might not
254 * be there exactly if it was moved to end of word/line
256 call("Move-to", ci->focus, 0, m2);
257 update_sel(ci->focus, p, m2, NULL);
259 clear_selection(ci->focus, p, mk, 0);
266 DEF_CMD(basic_menu_open)
268 /* If there is a menu action here, activate it. */
269 /* Don't move the cursor though */
270 struct mark *m = mark_new(ci->focus);
273 ret = call("Move-CursorXY", ci->focus, 0, m, "menu",
274 0, NULL, NULL, ci->x, ci->y);
279 DEF_CMD(basic_menu_select)
281 /* If a menu was opened it should have claimed the mouse focus
282 * so ci->focus is now the menu. We want to activate the entry
285 struct mark *m = mark_new(ci->focus);
288 ret = call("Move-CursorXY", ci->focus, 0, m, "activate",
289 0, NULL, NULL, ci->x, ci->y);
294 DEF_CMD(basic_motion)
296 struct mark *p = call_ret(mark, "doc:point", ci->focus);
297 struct mark *m2 = call_ret(mark2, "doc:point", ci->focus, 2);
302 if (attr_find_int(m2->attrs, "emacs:track-selection") <= 0)
305 call("Move-CursorXY", ci->focus,
306 0, NULL, NULL, 0, NULL, NULL, ci->x, ci->y);
308 update_sel(ci->focus, p, m2, NULL);
312 DEF_CMD(basic_paste_direct)
314 /* This command is an explicit paste command and the content
315 * is available via "Paste:get".
316 * It might come via the mouse (with x,y) or via a keystroke.
319 if (ci->key[0] == 'M') {
320 call("Move-CursorXY", ci->focus,
321 0, NULL, NULL, 0, NULL, NULL, ci->x, ci->y);
322 pane_take_focus(ci->focus);
325 s = call_ret(str, "Paste:get", ci->focus);
327 struct mark *pt = call_ret(mark, "doc:point", ci->focus);
329 call("Move-to", ci->focus, 1);
330 mk = call_ret(mark2, "doc:point", ci->focus);
331 call("Replace", ci->focus, 0, mk, s, 0, pt);
332 set_selection(ci->focus, pt, mk, 2);
341 struct call_return cr;
343 char *selection = "bg:white-80,vis-nl,menu-at-mouse,action-menu:emacs:selection-menu"; // grey
348 cr = call_ret(all, "doc:point", ci->focus);
349 if (cr.ret <= 0 || !cr.m || !cr.m2 || !ci->mark)
351 active = attr_find_int(cr.m2->attrs, "selection:active");
354 if (active >= 3) /* replacable */
355 selection = "bg:red+80,vis-nl"; // pink
356 if (mark_same(cr.m, cr.m2))
358 if (strcmp(ci->str, "render:interactive-mark") == 0) {
359 if (ci->mark == cr.m2 && cr.m2->seq < cr.m->seq)
360 return comm_call(ci->comm2, "attr:callback", ci->focus, 0,
361 ci->mark, selection, 210);
362 if (ci->mark == cr.m2)
363 return comm_call(ci->comm2, "attr:callback", ci->focus, -1,
364 ci->mark, selection, 210);
366 if (strcmp(ci->str, "render:interactive-point") == 0) {
367 if (cr.m == ci->mark && cr.m->seq < cr.m2->seq)
368 return comm_call(ci->comm2, "attr:cb", ci->focus, 0,
369 ci->mark, selection, 210);
370 if (cr.m == ci->mark)
371 return comm_call(ci->comm2, "attr:callback", ci->focus, -1,
372 ci->mark, selection, 210);
374 if (strcmp(ci->str, "start-of-line") == 0) {
375 if ((cr.m->seq < ci->mark->seq && ci->mark->seq < cr.m2->seq &&
376 !mark_same(ci->mark, cr.m2)) ||
377 (cr.m2->seq < ci->mark->seq && ci->mark->seq < cr.m->seq &&
378 !mark_same(ci->mark, cr.m)))
379 return comm_call(ci->comm2, "attr:cb", ci->focus, 0,
380 ci->mark, selection, 210);
385 DEF_CMD(basic_selection_menu)
389 p = call_ret(pane, "attach-menu", ci->focus, 0, NULL, "V", 0, NULL,
390 "emacs:selection-menu-action", ci->x, ci->y+1);
393 call("global-multicall-selection-menu:add-", p);
394 call("menu-add", p, 0, NULL, "de-select", 0, NULL, ":ESC");
398 DEF_CMD(basic_selection_menu_action)
400 struct pane *home = ci->home;
401 const char *c = ci->str;
406 /* command for focus */
407 call(c+1, ci->focus, 0, ci->mark);
411 call("Keystroke-sequence", home, 0, NULL, c);
417 /* On abort, forget mark */
418 struct mark *m = call_ret(mark2, "doc:point", ci->focus);
420 clear_selection(ci->focus, NULL, m, 0);
424 DEF_CMD(basic_sel_claimed)
426 /* Should possibly just change the color of our selection */
427 struct mark *mk = call_ret(mark2, "doc:point", ci->focus);
429 clear_selection(ci->focus, NULL, mk, 0);
433 DEF_CMD(basic_sel_commit)
435 struct mark *mk = call_ret(mark2, "doc:point", ci->focus);
436 struct mark *p = call_ret(mark, "doc:point", ci->focus);
438 if (p && mk && !mark_same(p, mk)) {
441 str = call_ret(strsave, "doc:get-str", ci->focus,
445 call("copy:save", ci->focus, 0, NULL, str);
451 DEF_CMD(basic_insert)
455 struct mark *mk = call_ret(mark2, "doc:point", ci->focus);
457 bool first = N2(ci) != N2_undo_insert;
462 if (clear_selection(ci->focus, NULL, mk, 3)) {
463 call("Replace", ci->focus, 1, mk, NULL, !first);
466 clear_selection(ci->focus, NULL, mk, 2);
468 str = ksuffix(ci, "K-");
469 /* Resubmit as doc:char-$str. By default this will be inserted
470 * but panes like lib-viewer might have other plans.
471 * lib-viewer could catch the original "K-", but sometimes
472 * the major mode might not want that.
474 strcat(strcpy(dc, "doc:char-"), str);
475 ret = call(dc, ci->focus, ci->num, ci->mark, NULL, !first);
476 call("Mode:set-num2", ci->focus, N2_undo_insert);
478 return ret < 0 ? ret : 1;
484 } other_inserts[] = {
491 DEF_CMD(basic_insert_other)
495 struct mark *m = NULL;
496 struct mark *mk = call_ret(mark2, "doc:point", ci->focus);
497 bool first = N2(ci) != N2_undo_insert;
503 for (i = 0; other_inserts[i].key; i++)
504 if (strcmp(safe_cast other_inserts[i].key, ci->key) == 0)
506 ins = other_inserts[i].insert;
510 if (clear_selection(ci->focus, NULL, mk, 3)) {
511 call("Replace", ci->focus, 1, mk, NULL, !first);
514 clear_selection(ci->focus, NULL, mk, 2);
518 m = mark_dup(ci->mark);
519 /* Move m before ci->mark, so it doesn't move when we insert */
523 ret = call("Replace", ci->focus, 1, m, ins, !first, ci->mark);
525 mark_to_mark(ci->mark, m);
528 /* A newline starts a new undo */
529 call("Mode:set-num2", ci->focus, (*ins == '\n') ? 0 : N2_undo_insert);
530 return ret < 0 ? ret : 1;
533 DEF_CMD(basic_interactive_insert)
535 /* If some pane want to insert text just like it was typed,
536 * it calls this, and we set up for proper undo
539 bool first = N2(ci) != N2_undo_insert;
544 if (clear_selection(ci->focus, NULL, ci->mark, 3)) {
545 call("Replace", ci->focus, 1, ci->mark, NULL, !first);
548 clear_selection(ci->focus, NULL, ci->mark, 2);
549 ret = call("Replace", ci->focus, 1, ci->mark, ci->str,
551 call("Mode:set-num2", ci->focus,
552 strchr(ci->str, '\n') ? 0 : N2_undo_insert);
553 return ret < 0 ? ret : 1;
556 DEF_CMD(basic_interactive_delete)
558 /* If some pane want to delete text just like backspace was typed,
559 * it calls this, and we set up for proper undo
565 ret = call("Replace", ci->focus, 1, ci->mark, "",
566 N2(ci) == N2_undo_insert, ci->mark2);
567 call("Mode:set-num2", ci->focus,
568 strchr(ci->str, '\n') ? 0 : N2_undo_delete);
569 return ret < 0 ? ret : 1;
574 call("Tile:close", ci->focus);
578 DEF_CMD(basic_refresh)
580 call("Window:refresh", ci->focus);
584 static struct map *basic_map;
585 DEF_LOOKUP_CMD(mode_basic, basic_map);
587 DEF_PFX_CMD(help_cmd, ":Help");
589 static void basic_init(void)
595 /* Some Function keys that CUA defines */
596 key_add(m, "K:F1", &help_cmd.c);
597 key_add(m, "K:F4", &basic_close);
598 key_add(m, "K:F5", &basic_refresh);
600 key_add_range(m, "K- ", "K-~", &basic_insert);
601 key_add_range(m, "K-\200", "K-\377\377\377\377", &basic_insert);
602 key_add(m, "K:Tab", &basic_insert_other);
603 //key_add(m, "K:LF", &basic_insert_other);
604 key_add(m, "K:Enter", &basic_insert_other);
605 key_add(m, "Interactive:insert", &basic_interactive_insert);
606 key_add(m, "Interactive:delete", &basic_interactive_delete);
608 key_add(m, "M:Press-1", &basic_press);
609 key_add(m, "M:Release-1", &basic_release);
610 key_add(m, "M:Press-3", &basic_menu_open);
611 key_add(m, "M:Release-3", &basic_menu_select);
612 key_add(m, "M:DPress-1", &basic_press);
613 key_add(m, "M:Motion", &basic_motion);
614 key_add(m, "K:Paste", &basic_paste_direct);
615 key_add(m, "M:Paste", &basic_paste_direct);
617 key_add(m, "Notify:selection:claimed", &basic_sel_claimed);
618 key_add(m, "Notify:selection:commit", &basic_sel_commit);
620 key_add(m, "map-attr", &basic_attrs);
621 key_add(m, "emacs:selection-menu", &basic_selection_menu);
622 key_add(m, "emacs:selection-menu-action", &basic_selection_menu_action);
624 key_add(m, "selection:set", &basic_selection_set);
625 key_add(m, "selection:clear", &basic_selection_clear);
627 key_add(m, "Abort", &basic_abort);
628 key_add(m, "Cancel", &basic_abort);
633 DEF_CMD(attach_mode_basic)
635 struct pane *p = pane_register(ci->focus, 0, &mode_basic.c);
639 comm_call(ci->comm2, "cb", p);
643 void edlib_init(struct pane *ed safe)
646 call_comm("global-set-command", ed, &attach_mode_basic,
647 0, NULL, "attach-mode-basic");