Fix cursor position after zero width space, GitHub issue 3469.
[tmux-openbsd.git] / window-client.c
blob8d501b0d2f213b47ffc34ce0cf0e705ca3cb8e28
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
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.
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.
19 #include <sys/types.h>
20 #include <sys/time.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
26 #include "tmux.h"
28 static struct screen *window_client_init(struct window_mode_entry *,
29 struct cmd_find_state *, struct args *);
30 static void window_client_free(struct window_mode_entry *);
31 static void window_client_resize(struct window_mode_entry *, u_int,
32 u_int);
33 static void window_client_update(struct window_mode_entry *);
34 static void window_client_key(struct window_mode_entry *,
35 struct client *, struct session *,
36 struct winlink *, key_code, struct mouse_event *);
38 #define WINDOW_CLIENT_DEFAULT_COMMAND "detach-client -t '%%'"
40 #define WINDOW_CLIENT_DEFAULT_FORMAT \
41 "#{t/p:client_activity}: session #{session_name}"
43 #define WINDOW_CLIENT_DEFAULT_KEY_FORMAT \
44 "#{?#{e|<:#{line},10}," \
45 "#{line}" \
46 "," \
47 "#{?#{e|<:#{line},36}," \
48 "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
49 "," \
50 "" \
51 "}" \
52 "}"
54 static const struct menu_item window_client_menu_items[] = {
55 { "Detach", 'd', NULL },
56 { "Detach Tagged", 'D', NULL },
57 { "", KEYC_NONE, NULL },
58 { "Tag", 't', NULL },
59 { "Tag All", '\024', NULL },
60 { "Tag None", 'T', NULL },
61 { "", KEYC_NONE, NULL },
62 { "Cancel", 'q', NULL },
64 { NULL, KEYC_NONE, NULL }
67 const struct window_mode window_client_mode = {
68 .name = "client-mode",
69 .default_format = WINDOW_CLIENT_DEFAULT_FORMAT,
71 .init = window_client_init,
72 .free = window_client_free,
73 .resize = window_client_resize,
74 .update = window_client_update,
75 .key = window_client_key,
78 enum window_client_sort_type {
79 WINDOW_CLIENT_BY_NAME,
80 WINDOW_CLIENT_BY_SIZE,
81 WINDOW_CLIENT_BY_CREATION_TIME,
82 WINDOW_CLIENT_BY_ACTIVITY_TIME,
84 static const char *window_client_sort_list[] = {
85 "name",
86 "size",
87 "creation",
88 "activity"
90 static struct mode_tree_sort_criteria *window_client_sort;
92 struct window_client_itemdata {
93 struct client *c;
96 struct window_client_modedata {
97 struct window_pane *wp;
99 struct mode_tree_data *data;
100 char *format;
101 char *key_format;
102 char *command;
104 struct window_client_itemdata **item_list;
105 u_int item_size;
108 static struct window_client_itemdata *
109 window_client_add_item(struct window_client_modedata *data)
111 struct window_client_itemdata *item;
113 data->item_list = xreallocarray(data->item_list, data->item_size + 1,
114 sizeof *data->item_list);
115 item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
116 return (item);
119 static void
120 window_client_free_item(struct window_client_itemdata *item)
122 server_client_unref(item->c);
123 free(item);
126 static int
127 window_client_cmp(const void *a0, const void *b0)
129 const struct window_client_itemdata *const *a = a0;
130 const struct window_client_itemdata *const *b = b0;
131 const struct window_client_itemdata *itema = *a;
132 const struct window_client_itemdata *itemb = *b;
133 struct client *ca = itema->c;
134 struct client *cb = itemb->c;
135 int result = 0;
137 switch (window_client_sort->field) {
138 case WINDOW_CLIENT_BY_SIZE:
139 result = ca->tty.sx - cb->tty.sx;
140 if (result == 0)
141 result = ca->tty.sy - cb->tty.sy;
142 break;
143 case WINDOW_CLIENT_BY_CREATION_TIME:
144 if (timercmp(&ca->creation_time, &cb->creation_time, >))
145 result = -1;
146 else if (timercmp(&ca->creation_time, &cb->creation_time, <))
147 result = 1;
148 break;
149 case WINDOW_CLIENT_BY_ACTIVITY_TIME:
150 if (timercmp(&ca->activity_time, &cb->activity_time, >))
151 result = -1;
152 else if (timercmp(&ca->activity_time, &cb->activity_time, <))
153 result = 1;
154 break;
157 /* Use WINDOW_CLIENT_BY_NAME as default order and tie breaker. */
158 if (result == 0)
159 result = strcmp(ca->name, cb->name);
161 if (window_client_sort->reversed)
162 result = -result;
163 return (result);
166 static void
167 window_client_build(void *modedata, struct mode_tree_sort_criteria *sort_crit,
168 __unused uint64_t *tag, const char *filter)
170 struct window_client_modedata *data = modedata;
171 struct window_client_itemdata *item;
172 u_int i;
173 struct client *c;
174 char *text, *cp;
176 for (i = 0; i < data->item_size; i++)
177 window_client_free_item(data->item_list[i]);
178 free(data->item_list);
179 data->item_list = NULL;
180 data->item_size = 0;
182 TAILQ_FOREACH(c, &clients, entry) {
183 if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
184 continue;
186 item = window_client_add_item(data);
187 item->c = c;
189 c->references++;
192 window_client_sort = sort_crit;
193 qsort(data->item_list, data->item_size, sizeof *data->item_list,
194 window_client_cmp);
196 for (i = 0; i < data->item_size; i++) {
197 item = data->item_list[i];
198 c = item->c;
200 if (filter != NULL) {
201 cp = format_single(NULL, filter, c, NULL, NULL, NULL);
202 if (!format_true(cp)) {
203 free(cp);
204 continue;
206 free(cp);
209 text = format_single(NULL, data->format, c, NULL, NULL, NULL);
210 mode_tree_add(data->data, NULL, item, (uint64_t)c, c->name,
211 text, -1);
212 free(text);
216 static void
217 window_client_draw(__unused void *modedata, void *itemdata,
218 struct screen_write_ctx *ctx, u_int sx, u_int sy)
220 struct window_client_itemdata *item = itemdata;
221 struct client *c = item->c;
222 struct screen *s = ctx->s;
223 struct window_pane *wp;
224 u_int cx = s->cx, cy = s->cy, lines, at;
226 if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
227 return;
228 wp = c->session->curw->window->active;
230 lines = status_line_size(c);
231 if (lines >= sy)
232 lines = 0;
233 if (status_at_line(c) == 0)
234 at = lines;
235 else
236 at = 0;
238 screen_write_cursormove(ctx, cx, cy + at, 0);
239 screen_write_preview(ctx, &wp->base, sx, sy - 2 - lines);
241 if (at != 0)
242 screen_write_cursormove(ctx, cx, cy + 2, 0);
243 else
244 screen_write_cursormove(ctx, cx, cy + sy - 1 - lines, 0);
245 screen_write_hline(ctx, sx, 0, 0);
247 if (at != 0)
248 screen_write_cursormove(ctx, cx, cy, 0);
249 else
250 screen_write_cursormove(ctx, cx, cy + sy - lines, 0);
251 screen_write_fast_copy(ctx, &c->status.screen, 0, 0, sx, lines);
254 static void
255 window_client_menu(void *modedata, struct client *c, key_code key)
257 struct window_client_modedata *data = modedata;
258 struct window_pane *wp = data->wp;
259 struct window_mode_entry *wme;
261 wme = TAILQ_FIRST(&wp->modes);
262 if (wme == NULL || wme->data != modedata)
263 return;
264 window_client_key(wme, c, NULL, NULL, key, NULL);
267 static key_code
268 window_client_get_key(void *modedata, void *itemdata, u_int line)
270 struct window_client_modedata *data = modedata;
271 struct window_client_itemdata *item = itemdata;
272 struct format_tree *ft;
273 char *expanded;
274 key_code key;
276 ft = format_create(NULL, NULL, FORMAT_NONE, 0);
277 format_defaults(ft, item->c, NULL, 0, NULL);
278 format_add(ft, "line", "%u", line);
280 expanded = format_expand(ft, data->key_format);
281 key = key_string_lookup_string(expanded);
282 free(expanded);
283 format_free(ft);
284 return (key);
287 static struct screen *
288 window_client_init(struct window_mode_entry *wme,
289 __unused struct cmd_find_state *fs, struct args *args)
291 struct window_pane *wp = wme->wp;
292 struct window_client_modedata *data;
293 struct screen *s;
295 wme->data = data = xcalloc(1, sizeof *data);
296 data->wp = wp;
298 if (args == NULL || !args_has(args, 'F'))
299 data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT);
300 else
301 data->format = xstrdup(args_get(args, 'F'));
302 if (args == NULL || !args_has(args, 'K'))
303 data->key_format = xstrdup(WINDOW_CLIENT_DEFAULT_KEY_FORMAT);
304 else
305 data->key_format = xstrdup(args_get(args, 'K'));
306 if (args == NULL || args_count(args) == 0)
307 data->command = xstrdup(WINDOW_CLIENT_DEFAULT_COMMAND);
308 else
309 data->command = xstrdup(args_string(args, 0));
311 data->data = mode_tree_start(wp, args, window_client_build,
312 window_client_draw, NULL, window_client_menu, NULL,
313 window_client_get_key, data, window_client_menu_items,
314 window_client_sort_list, nitems(window_client_sort_list), &s);
315 mode_tree_zoom(data->data, args);
317 mode_tree_build(data->data);
318 mode_tree_draw(data->data);
320 return (s);
323 static void
324 window_client_free(struct window_mode_entry *wme)
326 struct window_client_modedata *data = wme->data;
327 u_int i;
329 if (data == NULL)
330 return;
332 mode_tree_free(data->data);
334 for (i = 0; i < data->item_size; i++)
335 window_client_free_item(data->item_list[i]);
336 free(data->item_list);
338 free(data->format);
339 free(data->key_format);
340 free(data->command);
342 free(data);
345 static void
346 window_client_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
348 struct window_client_modedata *data = wme->data;
350 mode_tree_resize(data->data, sx, sy);
353 static void
354 window_client_update(struct window_mode_entry *wme)
356 struct window_client_modedata *data = wme->data;
358 mode_tree_build(data->data);
359 mode_tree_draw(data->data);
360 data->wp->flags |= PANE_REDRAW;
363 static void
364 window_client_do_detach(void *modedata, void *itemdata,
365 __unused struct client *c, key_code key)
367 struct window_client_modedata *data = modedata;
368 struct window_client_itemdata *item = itemdata;
370 if (item == mode_tree_get_current(data->data))
371 mode_tree_down(data->data, 0);
372 if (key == 'd' || key == 'D')
373 server_client_detach(item->c, MSG_DETACH);
374 else if (key == 'x' || key == 'X')
375 server_client_detach(item->c, MSG_DETACHKILL);
376 else if (key == 'z' || key == 'Z')
377 server_client_suspend(item->c);
380 static void
381 window_client_key(struct window_mode_entry *wme, struct client *c,
382 __unused struct session *s, __unused struct winlink *wl, key_code key,
383 struct mouse_event *m)
385 struct window_pane *wp = wme->wp;
386 struct window_client_modedata *data = wme->data;
387 struct mode_tree_data *mtd = data->data;
388 struct window_client_itemdata *item;
389 int finished;
391 finished = mode_tree_key(mtd, c, &key, m, NULL, NULL);
392 switch (key) {
393 case 'd':
394 case 'x':
395 case 'z':
396 item = mode_tree_get_current(mtd);
397 window_client_do_detach(data, item, c, key);
398 mode_tree_build(mtd);
399 break;
400 case 'D':
401 case 'X':
402 case 'Z':
403 mode_tree_each_tagged(mtd, window_client_do_detach, c, key, 0);
404 mode_tree_build(mtd);
405 break;
406 case '\r':
407 item = mode_tree_get_current(mtd);
408 mode_tree_run_command(c, NULL, data->command, item->c->ttyname);
409 finished = 1;
410 break;
412 if (finished || server_client_how_many() == 0)
413 window_pane_reset_mode(wp);
414 else {
415 mode_tree_draw(mtd);
416 wp->flags |= PANE_REDRAW;