/src/tmux/window-switch.c
Line | Count | Source |
1 | | /* $OpenBSD: window-switch.c,v 1.7 2026/10/16 17:28:19 nicm Exp $ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com> |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 | | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 | | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include <sys/types.h> |
20 | | |
21 | | #include <ctype.h> |
22 | | #include <stdlib.h> |
23 | | #include <string.h> |
24 | | |
25 | | #include "tmux.h" |
26 | | |
27 | | static struct screen *window_switch_init(struct window_mode_entry *, |
28 | | struct cmdq_item *, struct cmd_find_state *, |
29 | | struct args *); |
30 | | static void window_switch_free(struct window_mode_entry *); |
31 | | static void window_switch_resize(struct window_mode_entry *, u_int, |
32 | | u_int); |
33 | | static void window_switch_key(struct window_mode_entry *, |
34 | | struct client *, struct session *, |
35 | | struct winlink *, key_code, struct mouse_event *); |
36 | | static enum prompt_result window_switch_prompt_callback(void *, const char *, |
37 | | enum prompt_key_result); |
38 | | |
39 | 0 | #define WINDOW_SWITCH_DEFAULT_COMMAND "switch-client -Zt '%%'" |
40 | | |
41 | | #define WINDOW_SWITCH_DEFAULT_FORMAT \ |
42 | 0 | "#{?window_format," \ |
43 | 0 | "#{window_name} " \ |
44 | 0 | "#[dim]#{session_name}:#{window_index}#{window_flags}#[default] " \ |
45 | 0 | "#[dim]#{pane_current_command}#[default] " \ |
46 | 0 | "#[dim]#{?#{!=:#{pane_title},#{host_short}},#{pane_title},}#[default]" \ |
47 | 0 | "," \ |
48 | 0 | "#{session_name} " \ |
49 | 0 | "#[dim]#{session_windows} windows#[default] " \ |
50 | 0 | "#{?session_attached,attached,#[dim]detached#[default]} " \ |
51 | 0 | "#[dim]#{window_name}#[default]" \ |
52 | 0 | "}" |
53 | | |
54 | | const struct window_mode window_switch_mode = { |
55 | | .name = "switch-mode", |
56 | | .default_format = WINDOW_SWITCH_DEFAULT_FORMAT, |
57 | | |
58 | | .init = window_switch_init, |
59 | | .free = window_switch_free, |
60 | | .resize = window_switch_resize, |
61 | | .key = window_switch_key, |
62 | | }; |
63 | | |
64 | | enum window_switch_type { |
65 | | WINDOW_SWITCH_TYPE_SESSION, |
66 | | WINDOW_SWITCH_TYPE_WINDOW, |
67 | | WINDOW_SWITCH_TYPE_INPUT |
68 | | }; |
69 | | |
70 | | struct window_switch_itemdata { |
71 | | enum window_switch_type type; |
72 | | int session; |
73 | | int winlink; |
74 | | |
75 | | uint64_t tag; |
76 | | char *text; |
77 | | const char *value; |
78 | | bitstr_t *match; |
79 | | |
80 | | u_int score; |
81 | | u_int order; |
82 | | }; |
83 | | |
84 | | struct window_switch_modedata { |
85 | | struct window_pane *wp; |
86 | | struct screen screen; |
87 | | int zoomed; |
88 | | |
89 | | char *format; |
90 | | char *command; |
91 | | |
92 | | char **lines; |
93 | | u_int lines_size; |
94 | | |
95 | | enum window_switch_type type; |
96 | | enum sort_order order; |
97 | | int reversed; |
98 | | char *filter; |
99 | | struct prompt *prompt; |
100 | | u_int prompt_cx; |
101 | | |
102 | | struct window_switch_itemdata **item_list; |
103 | | u_int item_size; |
104 | | |
105 | | struct window_switch_itemdata **matches; |
106 | | u_int matches_size; |
107 | | |
108 | | u_int current; |
109 | | u_int offset; |
110 | | }; |
111 | | |
112 | | static void |
113 | | window_switch_free_item(struct window_switch_itemdata *item) |
114 | 0 | { |
115 | 0 | free(item->match); |
116 | 0 | free(item->text); |
117 | 0 | free(item); |
118 | 0 | } |
119 | | |
120 | | static struct window_switch_itemdata * |
121 | | window_switch_add_item(struct window_switch_modedata *data) |
122 | 0 | { |
123 | 0 | struct window_switch_itemdata *item; |
124 | |
|
125 | 0 | data->item_list = xreallocarray(data->item_list, data->item_size + 1, |
126 | 0 | sizeof *data->item_list); |
127 | 0 | item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); |
128 | 0 | return (item); |
129 | 0 | } |
130 | | |
131 | | static void |
132 | | window_switch_add_session(struct window_switch_modedata *data, |
133 | | struct session *s, u_int *order) |
134 | 0 | { |
135 | 0 | struct window_switch_itemdata *item; |
136 | 0 | struct format_tree *ft; |
137 | |
|
138 | 0 | ft = format_create(NULL, NULL, FORMAT_NONE, 0); |
139 | 0 | format_defaults(ft, NULL, s, NULL, NULL); |
140 | |
|
141 | 0 | item = window_switch_add_item(data); |
142 | 0 | item->type = WINDOW_SWITCH_TYPE_SESSION; |
143 | 0 | item->session = s->id; |
144 | 0 | item->winlink = -1; |
145 | 0 | item->tag = (uint64_t)s; |
146 | 0 | item->order = (*order)++; |
147 | 0 | item->text = format_expand(ft, data->format); |
148 | |
|
149 | 0 | format_free(ft); |
150 | 0 | } |
151 | | |
152 | | static void |
153 | | window_switch_add_window(struct window_switch_modedata *data, |
154 | | struct winlink *wl, u_int *order) |
155 | 0 | { |
156 | 0 | struct window_switch_itemdata *item; |
157 | 0 | struct format_tree *ft; |
158 | |
|
159 | 0 | ft = format_create(NULL, NULL, FORMAT_NONE, 0); |
160 | 0 | format_defaults(ft, NULL, wl->session, wl, NULL); |
161 | |
|
162 | 0 | item = window_switch_add_item(data); |
163 | 0 | item->type = WINDOW_SWITCH_TYPE_WINDOW; |
164 | 0 | item->session = wl->session->id; |
165 | 0 | item->winlink = wl->idx; |
166 | 0 | item->tag = (uint64_t)wl; |
167 | 0 | item->order = (*order)++; |
168 | 0 | item->text = format_expand(ft, data->format); |
169 | |
|
170 | 0 | format_free(ft); |
171 | 0 | } |
172 | | |
173 | | static void |
174 | | window_switch_add_input(struct window_switch_modedata *data, u_int idx, |
175 | | struct format_tree *ft) |
176 | 0 | { |
177 | 0 | struct window_switch_itemdata *item; |
178 | |
|
179 | 0 | item = window_switch_add_item(data); |
180 | 0 | item->type = WINDOW_SWITCH_TYPE_INPUT; |
181 | 0 | item->value = data->lines[idx]; |
182 | 0 | item->order = data->reversed ? data->lines_size - idx - 1 : idx; |
183 | 0 | format_add(ft, "switch_item", "%s", item->value); |
184 | 0 | item->text = format_expand(ft, data->format); |
185 | 0 | } |
186 | | |
187 | | static int |
188 | | window_switch_compare(const void *a0, const void *b0) |
189 | 0 | { |
190 | 0 | struct window_switch_itemdata *const *a = a0; |
191 | 0 | struct window_switch_itemdata *const *b = b0; |
192 | |
|
193 | 0 | if ((*a)->score > (*b)->score) |
194 | 0 | return (-1); |
195 | 0 | if ((*a)->score < (*b)->score) |
196 | 0 | return (1); |
197 | 0 | if ((*a)->order < (*b)->order) |
198 | 0 | return (-1); |
199 | 0 | if ((*a)->order > (*b)->order) |
200 | 0 | return (1); |
201 | 0 | return (0); |
202 | 0 | } |
203 | | |
204 | | static void |
205 | | window_switch_build(struct window_switch_modedata *data) |
206 | 0 | { |
207 | 0 | struct window_switch_itemdata *item, **m = NULL; |
208 | 0 | const char *f = data->filter; |
209 | 0 | u_int ns, nw, i, n = 0, order = 0; |
210 | 0 | u_int sx = screen_size_x(&data->screen); |
211 | 0 | struct session **sl; |
212 | 0 | struct winlink **wl; |
213 | 0 | struct sort_criteria sort_crit; |
214 | 0 | struct cmd_find_state fs; |
215 | 0 | struct format_tree *ft; |
216 | |
|
217 | 0 | sort_crit.order = data->order; |
218 | 0 | sort_crit.reversed = data->reversed; |
219 | 0 | sort_crit.order_seq = NULL; |
220 | |
|
221 | 0 | for (i = 0; i < data->item_size; i++) |
222 | 0 | window_switch_free_item(data->item_list[i]); |
223 | 0 | free(data->item_list); |
224 | 0 | data->item_list = NULL; |
225 | 0 | data->item_size = 0; |
226 | |
|
227 | 0 | switch (data->type) { |
228 | 0 | case WINDOW_SWITCH_TYPE_SESSION: |
229 | 0 | sl = sort_get_sessions(&ns, &sort_crit); |
230 | 0 | for (i = 0; i < ns; i++) |
231 | 0 | window_switch_add_session(data, sl[i], &order); |
232 | 0 | break; |
233 | 0 | case WINDOW_SWITCH_TYPE_WINDOW: |
234 | 0 | wl = sort_get_winlinks(&nw, &sort_crit); |
235 | 0 | for (i = 0; i < nw; i++) |
236 | 0 | window_switch_add_window(data, wl[i], &order); |
237 | 0 | break; |
238 | 0 | case WINDOW_SWITCH_TYPE_INPUT: |
239 | 0 | cmd_find_from_pane(&fs, data->wp, 0); |
240 | 0 | ft = format_create(NULL, NULL, FORMAT_NONE, 0); |
241 | 0 | format_defaults(ft, NULL, fs.s, fs.wl, fs.wp); |
242 | 0 | for (i = 0; i < data->lines_size; i++) |
243 | 0 | window_switch_add_input(data, i, ft); |
244 | 0 | format_free(ft); |
245 | 0 | break; |
246 | 0 | } |
247 | | |
248 | 0 | for (i = 0; i < data->item_size; i++) { |
249 | 0 | item = data->item_list[i]; |
250 | 0 | if (*f == '\0') { |
251 | 0 | m = xreallocarray(m, n + 1, sizeof *m); |
252 | 0 | m[n++] = item; |
253 | 0 | continue; |
254 | 0 | } |
255 | | |
256 | 0 | item->match = fuzzy_match(f, item->text, sx, &item->score); |
257 | 0 | if (item->match == NULL) |
258 | 0 | continue; |
259 | 0 | m = xreallocarray(m, n + 1, sizeof *m); |
260 | 0 | m[n++] = item; |
261 | 0 | } |
262 | 0 | if (n > 1) |
263 | 0 | qsort(m, n, sizeof *m, window_switch_compare); |
264 | |
|
265 | 0 | free(data->matches); |
266 | 0 | data->matches = m; |
267 | 0 | data->matches_size = n; |
268 | 0 | } |
269 | | |
270 | | static u_int |
271 | | window_switch_visible(struct window_switch_modedata *data) |
272 | 0 | { |
273 | 0 | u_int sy = screen_size_y(&data->screen); |
274 | |
|
275 | 0 | if (sy <= 1) |
276 | 0 | return (0); |
277 | 0 | return (sy - 1); |
278 | 0 | } |
279 | | |
280 | | static void |
281 | | window_switch_set_current(struct window_switch_modedata *data, u_int current) |
282 | 0 | { |
283 | 0 | u_int visible = window_switch_visible(data); |
284 | |
|
285 | 0 | if (data->matches_size == 0) { |
286 | 0 | data->current = 0; |
287 | 0 | data->offset = 0; |
288 | 0 | return; |
289 | 0 | } |
290 | | |
291 | 0 | if (current > data->matches_size - 1) |
292 | 0 | current = data->matches_size - 1; |
293 | 0 | data->current = current; |
294 | |
|
295 | 0 | if (data->current < data->offset) |
296 | 0 | data->offset = data->current; |
297 | 0 | else if (visible != 0 && data->current >= data->offset + visible) |
298 | 0 | data->offset = data->current - visible + 1; |
299 | 0 | } |
300 | | |
301 | | static void |
302 | | window_switch_draw_screen(struct window_mode_entry *wme) |
303 | 0 | { |
304 | 0 | struct window_pane *wp = wme->wp; |
305 | 0 | struct window_switch_modedata *data = wme->data; |
306 | 0 | struct options *oo = wp->options; |
307 | 0 | struct screen_write_ctx ctx; |
308 | 0 | struct screen *s = &data->screen; |
309 | 0 | u_int sx = screen_size_x(s), i, j; |
310 | 0 | u_int sy = screen_size_y(s), visible, idx; |
311 | 0 | struct window_switch_itemdata *item; |
312 | 0 | struct grid_cell mgc, sgc, gc; |
313 | 0 | const struct grid_cell *dgc = &grid_default_cell; |
314 | 0 | struct prompt_draw_data pdd; |
315 | 0 | screen_write_start(&ctx, s); |
316 | 0 | screen_write_clearscreen(&ctx, 8); |
317 | |
|
318 | 0 | if (sy <= 1) { |
319 | 0 | screen_write_stop(&ctx); |
320 | 0 | return; |
321 | 0 | } |
322 | | |
323 | 0 | style_apply(&mgc, oo, "switch-mode-match-style", NULL); |
324 | 0 | style_apply(&sgc, oo, "mode-style", NULL); |
325 | |
|
326 | 0 | visible = window_switch_visible(data); |
327 | 0 | for (i = 0; i < visible; i++) { |
328 | 0 | idx = data->offset + i; |
329 | 0 | if (idx >= data->matches_size) |
330 | 0 | break; |
331 | 0 | item = data->matches[idx]; |
332 | |
|
333 | 0 | screen_write_cursormove(&ctx, 0, i, 0); |
334 | 0 | if (idx != data->current) |
335 | 0 | format_draw(&ctx, dgc, sx, item->text, NULL, 0); |
336 | 0 | else { |
337 | 0 | screen_write_clearendofline(&ctx, sgc.bg); |
338 | 0 | format_draw(&ctx, &sgc, sx, item->text, NULL, 0); |
339 | 0 | } |
340 | |
|
341 | 0 | if (item->match == NULL) |
342 | 0 | continue; |
343 | 0 | for (j = 0; j < sx; j++) { |
344 | 0 | if (!bit_test(item->match, j)) |
345 | 0 | continue; |
346 | 0 | grid_get_cell(s->grid, j, i, &gc); |
347 | 0 | gc.attr = mgc.attr; |
348 | 0 | gc.fg = mgc.fg; |
349 | 0 | gc.bg = mgc.bg; |
350 | 0 | screen_write_cursormove(&ctx, j, i, 0); |
351 | 0 | screen_write_cell(&ctx, &gc); |
352 | 0 | } |
353 | 0 | } |
354 | |
|
355 | 0 | if (data->prompt != NULL) { |
356 | 0 | pdd.ctx = &ctx; |
357 | 0 | pdd.cursor_x = &data->prompt_cx; |
358 | 0 | pdd.area_x = 0; |
359 | 0 | pdd.area_width = sx; |
360 | 0 | pdd.prompt_line = sy - 1; |
361 | 0 | s->mode |= MODE_CURSOR; |
362 | 0 | prompt_draw(data->prompt, &pdd); |
363 | 0 | screen_write_cursormove(&ctx, data->prompt_cx, sy - 1, 0); |
364 | 0 | } |
365 | 0 | screen_write_stop(&ctx); |
366 | 0 | } |
367 | | |
368 | | static struct screen * |
369 | | window_switch_init(struct window_mode_entry *wme, |
370 | | __unused struct cmdq_item *item, struct cmd_find_state *fs, |
371 | | struct args *args) |
372 | 0 | { |
373 | 0 | struct window_pane *wp = wme->wp; |
374 | 0 | struct window_switch_modedata *data; |
375 | 0 | struct screen *s; |
376 | 0 | struct prompt_create_data pd; |
377 | |
|
378 | 0 | wme->data = data = xcalloc(1, sizeof *data); |
379 | 0 | data->wp = wp; |
380 | |
|
381 | 0 | data->order = SORT_NAME; |
382 | 0 | data->reversed = 0; |
383 | 0 | if (args != NULL) { |
384 | 0 | if (args_has(args, 'O')) { |
385 | 0 | data->order = sort_order_from_string(args_get(args, 'O')); |
386 | 0 | if (data->order == SORT_END) |
387 | 0 | data->order = SORT_NAME; |
388 | 0 | } |
389 | 0 | if (args_has(args, 'r')) |
390 | 0 | data->reversed = 1; |
391 | 0 | } |
392 | |
|
393 | 0 | if (args_has(args, 'w')) |
394 | 0 | data->type = WINDOW_SWITCH_TYPE_WINDOW; |
395 | 0 | else |
396 | 0 | data->type = WINDOW_SWITCH_TYPE_SESSION; |
397 | 0 | if (args_has(args, 'f')) |
398 | 0 | data->type = WINDOW_SWITCH_TYPE_INPUT; |
399 | |
|
400 | 0 | data->filter = xstrdup(""); |
401 | 0 | if (args == NULL || !args_has(args, 'F')) { |
402 | 0 | if (data->type == WINDOW_SWITCH_TYPE_INPUT) |
403 | 0 | data->format = xstrdup("#{switch_item}"); |
404 | 0 | else |
405 | 0 | data->format = xstrdup(WINDOW_SWITCH_DEFAULT_FORMAT); |
406 | 0 | } else |
407 | 0 | data->format = xstrdup(args_get(args, 'F')); |
408 | 0 | if (args == NULL || args_count(args) == 0) |
409 | 0 | data->command = xstrdup(WINDOW_SWITCH_DEFAULT_COMMAND); |
410 | 0 | else |
411 | 0 | data->command = xstrdup(args_string(args, 0)); |
412 | |
|
413 | 0 | memset(&pd, 0, sizeof pd); |
414 | 0 | prompt_set_options(&pd, fs->s); |
415 | 0 | pd.fs = fs; |
416 | 0 | pd.prompt = "(search) "; |
417 | 0 | pd.input = ""; |
418 | 0 | pd.type = PROMPT_TYPE_SEARCH; |
419 | 0 | pd.flags = PROMPT_INCREMENTAL|PROMPT_NOFORMAT|PROMPT_ISMODE| |
420 | 0 | PROMPT_EDITARROWS; |
421 | 0 | pd.inputcb = window_switch_prompt_callback; |
422 | 0 | pd.data = data; |
423 | 0 | data->prompt = prompt_create(&pd); |
424 | 0 | prompt_update(data->prompt, "(search) ", data->filter); |
425 | |
|
426 | 0 | s = &data->screen; |
427 | 0 | screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); |
428 | |
|
429 | 0 | if (!args_has(args, 'Z')) |
430 | 0 | data->zoomed = -1; |
431 | 0 | else { |
432 | 0 | data->zoomed = (wp->window->flags & WINDOW_ZOOMED); |
433 | 0 | if (!data->zoomed && window_zoom(wp) == 0) |
434 | 0 | server_redraw_window(wp->window); |
435 | 0 | } |
436 | |
|
437 | 0 | window_switch_build(data); |
438 | 0 | prompt_incremental_start(data->prompt); |
439 | 0 | window_switch_draw_screen(wme); |
440 | |
|
441 | 0 | return (s); |
442 | 0 | } |
443 | | |
444 | | void |
445 | | window_switch_set_input(struct window_mode_entry *wme, struct evbuffer *buffer) |
446 | 0 | { |
447 | 0 | struct window_switch_modedata *data = wme->data; |
448 | 0 | char *line; |
449 | 0 | size_t size; |
450 | |
|
451 | 0 | for (;;) { |
452 | 0 | line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); |
453 | 0 | if (line == NULL) { |
454 | 0 | size = EVBUFFER_LENGTH(buffer); |
455 | 0 | if (size == 0) |
456 | 0 | break; |
457 | 0 | line = xmemdup(EVBUFFER_DATA(buffer), size); |
458 | 0 | evbuffer_drain(buffer, size); |
459 | 0 | } |
460 | 0 | if (*line == '\0') { |
461 | 0 | free(line); |
462 | 0 | continue; |
463 | 0 | } |
464 | 0 | data->lines = xreallocarray(data->lines, data->lines_size + 1, |
465 | 0 | sizeof *data->lines); |
466 | 0 | data->lines[data->lines_size++] = line; |
467 | 0 | } |
468 | 0 | window_switch_build(data); |
469 | 0 | window_switch_set_current(data, 0); |
470 | 0 | window_switch_draw_screen(wme); |
471 | 0 | wme->wp->flags |= PANE_REDRAW; |
472 | 0 | } |
473 | | |
474 | | static void |
475 | | window_switch_free(struct window_mode_entry *wme) |
476 | 0 | { |
477 | 0 | struct window_switch_modedata *data = wme->data; |
478 | 0 | u_int i; |
479 | |
|
480 | 0 | if (data->zoomed == 0) |
481 | 0 | server_unzoom_window(wme->wp->window); |
482 | |
|
483 | 0 | for (i = 0; i < data->item_size; i++) |
484 | 0 | window_switch_free_item(data->item_list[i]); |
485 | 0 | free(data->item_list); |
486 | 0 | for (i = 0; i < data->lines_size; i++) |
487 | 0 | free(data->lines[i]); |
488 | 0 | free(data->lines); |
489 | |
|
490 | 0 | free(data->matches); |
491 | 0 | free(data->filter); |
492 | 0 | prompt_free(data->prompt); |
493 | 0 | free(data->format); |
494 | 0 | free(data->command); |
495 | 0 | screen_free(&data->screen); |
496 | |
|
497 | 0 | free(data); |
498 | 0 | } |
499 | | |
500 | | static void |
501 | | window_switch_resize(struct window_mode_entry *wme, u_int sx, u_int sy) |
502 | 0 | { |
503 | 0 | struct window_switch_modedata *data = wme->data; |
504 | 0 | struct screen *s = &data->screen; |
505 | |
|
506 | 0 | screen_resize(s, sx, sy, 0); |
507 | 0 | window_switch_build(data); |
508 | 0 | window_switch_set_current(data, data->current); |
509 | 0 | window_switch_draw_screen(wme); |
510 | 0 | } |
511 | | |
512 | | static int |
513 | | window_switch_run_command(struct window_switch_modedata *data, struct client *c) |
514 | 0 | { |
515 | 0 | struct window_switch_itemdata *item; |
516 | 0 | struct cmd_find_state fs; |
517 | 0 | struct session *s; |
518 | 0 | struct winlink *wl; |
519 | 0 | char *target = NULL; |
520 | 0 | struct cmdq_state *state; |
521 | 0 | char *command, *error; |
522 | 0 | enum cmd_parse_status status; |
523 | |
|
524 | 0 | if (data->matches_size == 0) |
525 | 0 | return (0); |
526 | 0 | item = data->matches[data->current]; |
527 | |
|
528 | 0 | cmd_find_clear_state(&fs, 0); |
529 | 0 | switch (item->type) { |
530 | 0 | case WINDOW_SWITCH_TYPE_SESSION: |
531 | 0 | s = session_find_by_id(item->session); |
532 | 0 | if (s != NULL) { |
533 | 0 | xasprintf(&target, "$%u:", s->id); |
534 | 0 | cmd_find_from_session(&fs, s, 0); |
535 | 0 | } |
536 | 0 | break; |
537 | 0 | case WINDOW_SWITCH_TYPE_WINDOW: |
538 | 0 | s = session_find_by_id(item->session); |
539 | 0 | if (s != NULL) { |
540 | 0 | wl = winlink_find_by_index(&s->windows, item->winlink); |
541 | 0 | if (s != NULL && wl != NULL) { |
542 | 0 | xasprintf(&target, "$%u:%u.", s->id, wl->idx); |
543 | 0 | cmd_find_from_winlink(&fs, wl, 0); |
544 | 0 | } |
545 | 0 | } |
546 | 0 | break; |
547 | 0 | case WINDOW_SWITCH_TYPE_INPUT: |
548 | 0 | target = xstrdup(item->value); |
549 | 0 | cmd_find_from_pane(&fs, data->wp, 0); |
550 | 0 | break; |
551 | 0 | } |
552 | 0 | if (target == NULL) |
553 | 0 | return (0); |
554 | | |
555 | 0 | command = cmd_template_replace(data->command, target, 1); |
556 | 0 | if (command != NULL && *command != '\0') { |
557 | 0 | state = cmdq_new_state(&fs, NULL, 0); |
558 | 0 | if (item->type == WINDOW_SWITCH_TYPE_INPUT) |
559 | 0 | cmdq_add_format(state, "switch_item", "%s", item->value); |
560 | 0 | status = cmd_parse_and_append(command, NULL, c, state, &error); |
561 | 0 | if (status == CMD_PARSE_ERROR) { |
562 | 0 | if (c != NULL) { |
563 | 0 | *error = toupper((u_char)*error); |
564 | 0 | status_message_set(c, -1, 1, 0, 0, "%s", error); |
565 | 0 | } |
566 | 0 | free(error); |
567 | 0 | } |
568 | 0 | cmdq_free_state(state); |
569 | 0 | } |
570 | 0 | free(command); |
571 | 0 | free(target); |
572 | 0 | return (1); |
573 | 0 | } |
574 | | |
575 | | static enum prompt_result |
576 | | window_switch_prompt_callback(void *arg, const char *s, |
577 | | enum prompt_key_result key) |
578 | 0 | { |
579 | 0 | struct window_switch_modedata *data = arg; |
580 | |
|
581 | 0 | if (key != PROMPT_KEY_HANDLED) |
582 | 0 | return (PROMPT_CONTINUE); |
583 | | |
584 | 0 | if (s == NULL) |
585 | 0 | s = ""; |
586 | 0 | else if (*s != '\0') |
587 | 0 | s++; |
588 | |
|
589 | 0 | free(data->filter); |
590 | 0 | data->filter = xstrdup(s); |
591 | 0 | window_switch_build(data); |
592 | 0 | data->current = 0; |
593 | 0 | data->offset = 0; |
594 | |
|
595 | 0 | return (PROMPT_CONTINUE); |
596 | 0 | } |
597 | | |
598 | | static void |
599 | | window_switch_key(struct window_mode_entry *wme, struct client *c, |
600 | | __unused struct session *s, __unused struct winlink *wl, key_code key, |
601 | | struct mouse_event *m) |
602 | 0 | { |
603 | 0 | struct window_pane *wp = wme->wp; |
604 | 0 | struct window_switch_modedata *data = wme->data; |
605 | 0 | u_int visible, current = data->current; |
606 | 0 | u_int x, y, size = data->matches_size; |
607 | 0 | enum prompt_key_result result; |
608 | 0 | int redraw = 0; |
609 | |
|
610 | 0 | if (KEYC_IS_MOUSE(key)) { |
611 | 0 | if (m == NULL || cmd_mouse_at(wp, m, &x, &y, 0) != 0) |
612 | 0 | return; |
613 | 0 | if (data->prompt != NULL && screen_size_y(&data->screen) != 0 && |
614 | 0 | y == screen_size_y(&data->screen) - 1 && |
615 | 0 | MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_1 && |
616 | 0 | !MOUSE_DRAG(m->b) && |
617 | 0 | !MOUSE_RELEASE(m->b)) { |
618 | 0 | result = prompt_mouse(data->prompt, x, 0, |
619 | 0 | screen_size_x(&data->screen), &redraw); |
620 | 0 | if (redraw || result == PROMPT_KEY_HANDLED) { |
621 | 0 | window_switch_draw_screen(wme); |
622 | 0 | wp->flags |= PANE_REDRAW; |
623 | 0 | } |
624 | 0 | return; |
625 | 0 | } |
626 | 0 | switch (key) { |
627 | 0 | case KEYC_WHEELUP_PANE: |
628 | 0 | if (size != 0 && current != 0) |
629 | 0 | window_switch_set_current(data, current - 1); |
630 | 0 | goto moved; |
631 | 0 | case KEYC_WHEELDOWN_PANE: |
632 | 0 | if (size != 0 && current != size - 1) |
633 | 0 | window_switch_set_current(data, current + 1); |
634 | 0 | goto moved; |
635 | 0 | case KEYC_MOUSEDOWN1_PANE: |
636 | 0 | case KEYC_DOUBLECLICK1_PANE: |
637 | 0 | if (y >= window_switch_visible(data) || |
638 | 0 | data->offset + y >= size) |
639 | 0 | return; |
640 | 0 | window_switch_set_current(data, data->offset + y); |
641 | 0 | if (key == KEYC_DOUBLECLICK1_PANE) { |
642 | 0 | if (window_switch_run_command(data, c)) |
643 | 0 | window_pane_reset_mode(wp); |
644 | 0 | return; |
645 | 0 | } |
646 | 0 | goto moved; |
647 | 0 | } |
648 | 0 | return; |
649 | 0 | } |
650 | | |
651 | 0 | switch (key) { |
652 | 0 | case 'p'|KEYC_CTRL: |
653 | 0 | case 'k'|KEYC_CTRL: |
654 | 0 | key = KEYC_UP; |
655 | 0 | break; |
656 | 0 | case 'n'|KEYC_CTRL: |
657 | 0 | case 'j'|KEYC_CTRL: |
658 | 0 | key = KEYC_DOWN; |
659 | 0 | break; |
660 | 0 | } |
661 | | |
662 | 0 | switch (key) { |
663 | 0 | case '\r': |
664 | 0 | if (window_switch_run_command(data, c)) |
665 | 0 | window_pane_reset_mode(wp); |
666 | 0 | return; |
667 | 0 | case '\033': /* Escape */ |
668 | 0 | case '['|KEYC_CTRL: |
669 | 0 | case 'c'|KEYC_CTRL: |
670 | 0 | case 'g'|KEYC_CTRL: |
671 | 0 | window_pane_reset_mode(wp); |
672 | 0 | return; |
673 | 0 | } |
674 | | |
675 | 0 | if (data->prompt != NULL) { |
676 | 0 | result = prompt_key(data->prompt, key, &redraw); |
677 | 0 | if (redraw) { |
678 | 0 | window_switch_draw_screen(wme); |
679 | 0 | wp->flags |= PANE_REDRAW; |
680 | 0 | } |
681 | 0 | if (result == PROMPT_KEY_HANDLED || |
682 | 0 | result == PROMPT_KEY_NOT_HANDLED) |
683 | 0 | return; |
684 | 0 | current = data->current; |
685 | 0 | size = data->matches_size; |
686 | 0 | } |
687 | | |
688 | 0 | switch (key) { |
689 | 0 | case KEYC_UP: |
690 | 0 | if (size == 0) |
691 | 0 | goto moved; |
692 | 0 | if (current == 0) |
693 | 0 | window_switch_set_current(data, size - 1); |
694 | 0 | else |
695 | 0 | window_switch_set_current(data, current - 1); |
696 | 0 | goto moved; |
697 | 0 | case KEYC_DOWN: |
698 | 0 | if (size == 0) |
699 | 0 | goto moved; |
700 | 0 | if (current == size - 1) |
701 | 0 | window_switch_set_current(data, 0); |
702 | 0 | else |
703 | 0 | window_switch_set_current(data, current + 1); |
704 | 0 | goto moved; |
705 | 0 | case KEYC_PPAGE: |
706 | 0 | visible = window_switch_visible(data); |
707 | 0 | if (current >= visible) |
708 | 0 | window_switch_set_current(data, current - visible); |
709 | 0 | else |
710 | 0 | window_switch_set_current(data, 0); |
711 | 0 | goto moved; |
712 | 0 | case KEYC_NPAGE: |
713 | 0 | visible = window_switch_visible(data); |
714 | 0 | window_switch_set_current(data, current + visible); |
715 | 0 | goto moved; |
716 | 0 | case KEYC_HOME: |
717 | 0 | window_switch_set_current(data, 0); |
718 | 0 | goto moved; |
719 | 0 | case KEYC_END: |
720 | 0 | if (size > 0) |
721 | 0 | window_switch_set_current(data, size - 1); |
722 | 0 | goto moved; |
723 | 0 | } |
724 | | |
725 | 0 | moved: |
726 | 0 | window_switch_draw_screen(wme); |
727 | 0 | wp->flags |= PANE_REDRAW; |
728 | 0 | } |