2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * A buffer can be viewed in a pane.
6 * The pane is (typically) a tile in a display.
7 * As well as content from the buffer, a 'view' provides
8 * a scroll bar and a status line.
9 * These serve to visually separate different views from each other.
20 #define PANE_DATA_TYPE struct view_data
27 int border_width, border_height;
31 struct mark *viewpoint;
34 #include "core-pane.h"
36 /* 0 to 4 borders are possible */
42 BORDER_STATUS = 16, // override
45 static struct map *view_map safe;
46 DEF_LOOKUP_CMD(view_handle, view_map);
47 static struct pane *do_view_attach(struct pane *par safe, int border);
48 static int calc_border(struct pane *p safe);
50 static const char default_status[] =
51 "{!CountLinesAsync}M:{doc-modified?,*,-}{doc-readonly?,%%, } D:{doc-file-changed?,CHANGED:,}{doc-name%-15} L{^line}/{lines} {display-context}{render-default}/{view-default} {doc-status}";
52 static const char default_title[] =
55 static char *format_status(const char *status safe,
56 struct pane *focus safe,
73 buf_append(&b, *status);
78 close = strchr(status, '}');
81 f = strnsave(focus, status, close-status);
86 if (*f == '!' && pm) {
87 /* This is a command to call */
88 call(f+1, focus, 0, pm);
95 /* Some extras here for future expansion */
96 l = strcspn(f, ":+?#!@$%^&*=<>");
100 attr = attr_find(*mark_attr(pm), f);
102 attr = pane_attr_get(focus, f);
106 case '%': /* make spaces visible */
107 if (attr[0] <= ' ' ||
108 (attr[0] && attr[strlen(attr)-1] <= ' '))
109 attr = strconcat(focus, "\"", attr, "\"");
112 /* Format in a field */
115 buf_concat(&b, attr);
116 width += strlen(attr);
122 width -= strlen(attr);
127 buf_concat(&b, attr);
131 /* flag - no, 0, empty, false are second option */
136 if (strcasecmp(attr, "no") == 0 ||
137 strcasecmp(attr, "false") == 0 ||
138 strcmp(attr, "0") == 0 ||
146 while (*f && *f != sep) {
152 buf_concat(&b, attr);
155 return buf_final(&b);
158 static void one_char(struct pane *p safe, const char *s, char *attr, int x, int y)
160 call("Draw:text", p, -1, NULL, s, 0, NULL, attr, x, y);
163 DEF_CMD(view_refresh)
165 struct pane *p = ci->home;
166 struct view_data *vd = p->data;
174 if (vd->line_height <= 0)
177 call("Draw:clear", p, 0, NULL, "bg:white");
178 pm = call_ret(mark, "doc:point", ci->focus);
179 status = pane_attr_get(ci->focus, "status-line");
181 status = default_status;
182 status = format_status(status, ci->focus, pm);
183 title = pane_attr_get(ci->focus, "pane-title");
185 title = default_title;
186 title = format_status(title, ci->focus, pm);
190 if (vd->border & BORDER_LEFT) {
191 /* Left border is (currently) always a scroll bar */
192 for (i = 0; i < p->h; i += vd->line_height)
193 one_char(p, "┃", "inverse", 0, i + vd->ascent);
195 if (p->h > 4 * vd->line_height) {
201 call("CountLinesAsync", ci->focus, 0, vd->viewpoint);
202 vpln = attr_find_int(*mark_attr(vd->viewpoint),
205 call("CountLinesAsync", ci->focus, 0, pm);
206 vpln = attr_find_int(*mark_attr(pm), "line");
209 l = pane_attr_get_int(ci->focus, "lines", 1);
212 mid = vd->line_height + (p->h - 4 * vd->line_height) * vpln / l;
213 one_char(p, "^", NULL, 0, mid-vd->line_height + vd->ascent);
214 one_char(p, "#", "inverse", 0, mid + vd->ascent);
215 one_char(p, "v", NULL, 0, mid+vd->line_height + vd->ascent);
216 one_char(p, "+", "inverse", 0, p->h
217 - vd->line_height + vd->ascent);
218 vd->scroll_bar_y = mid;
221 if (vd->border & BORDER_RIGHT) {
222 for (i = 0; i < p->h; i += vd->line_height)
223 one_char(p, "┃", "inverse", p->w-vd->border_width,
226 if (vd->border & BORDER_TOP) {
228 for (i = 0; i < p->w; i += vd->border_width)
229 one_char(p, "━", "inverse", i, vd->ascent);
230 label = (p->w - strlen(title?:"") * vd->border_width) / 2;
231 if (label < vd->border_width)
233 one_char(p, title, "inverse",
236 if (vd->border & BORDER_BOT) {
237 for (i = 0; i < p->w; i+= vd->border_width)
238 one_char(p, "═", "inverse", i,
239 p->h-vd->border_height+vd->ascent);
241 if (!(vd->border & BORDER_TOP) ||
242 (vd->border & BORDER_STATUS)) {
243 one_char(p, status, "inverse",
245 p->h-vd->border_height + vd->ascent);
248 if (!(~vd->border & (BORDER_LEFT|BORDER_BOT)))
250 one_char(p, "┗", "inverse", 0, p->h-vd->border_height+vd->ascent);
251 if (!(~vd->border & (BORDER_RIGHT|BORDER_TOP)))
252 one_char(p, "╳", "inverse", p->w-vd->border_width, vd->ascent);
253 if (!(~vd->border & (BORDER_LEFT|BORDER_TOP)))
254 one_char(p, "┏", "inverse", 0, vd->ascent);
255 if (!(~vd->border & (BORDER_RIGHT|BORDER_BOT)))
256 one_char(p, "┛", "inverse", p->w-vd->border_width, p->h-vd->border_height+vd->ascent);
261 DEF_CMD_CLOSED(view_close)
263 struct view_data *vd = ci->home->data;
265 mark_free(vd->viewpoint);
266 vd->viewpoint = NULL;
272 struct view_data *vd = ci->home->data;
273 struct pane *parent = ci->focus;
276 p2 = do_view_attach(parent, vd->old_border);
278 pane_clone_children(ci->home, p2);
282 DEF_CMD(view_child_notify)
284 struct pane *p = ci->home;
285 struct view_data *vd = p->data;
289 if (vd->child && ci->num > 0)
290 pane_close(vd->child);
292 vd->child = ci->focus;
293 else if (vd->child == ci->focus)
295 p->focus = vd->child;
299 DEF_CMD(view_refresh_size)
301 struct pane *p = ci->home;
302 struct view_data *vd = p->data;
309 vd->border = calc_border(ci->focus);
310 b = vd->border < 0 ? 0 : vd->border;
311 if (vd->line_height < 0) {
312 /* FIXME should use scale */
313 struct call_return cr = call_ret(all, "Draw:text-size", ci->home,
320 vd->line_height = cr.y;
321 vd->border_height = cr.y;
322 vd->border_width = cr.x;
324 attr_set_int(&p->attrs, "border-width", cr.x);
325 attr_set_int(&p->attrs, "border-height", cr.y);
327 if (h < vd->border_height * 3 &&
328 (b & (BORDER_TOP|BORDER_BOT)) ==
329 (BORDER_TOP|BORDER_BOT)) {
333 if (w < vd->border_width * 3 &&
334 (b & (BORDER_LEFT|BORDER_RIGHT)) ==
335 (BORDER_LEFT|BORDER_RIGHT)) {
342 if (b & BORDER_LEFT) {
343 x += vd->border_width; w -= vd->border_width;
345 if (b & BORDER_RIGHT) {
346 w -= vd->border_width;
348 if (b & BORDER_TOP) {
349 y += vd->border_height; h -= vd->border_height;
351 if (b & BORDER_BOT) {
352 h -= vd->border_height;
358 pane_damaged(p, DAMAGED_REFRESH);
360 pane_resize(vd->child, x, y, w, h);
365 DEF_CMD(view_status_changed)
367 if (strcmp(ci->key, "mark:moving") == 0) {
368 struct mark *pt = call_ret(mark, "doc:point", ci->home);
372 pane_damaged(ci->home, DAMAGED_VIEW);
373 pane_damaged(ci->home, DAMAGED_REFRESH);
374 if (strcmp(ci->key, "view:changed") == 0)
379 DEF_CMD(view_reposition)
381 struct view_data *vd = ci->home->data;
386 if (!vd->viewpoint || !mark_same(vd->viewpoint, ci->mark)) {
387 pane_damaged(ci->home, DAMAGED_REFRESH);
389 mark_free(vd->viewpoint);
391 vd->viewpoint = mark_dup(ci->mark);
393 vd->viewpoint = NULL;
398 static struct pane *do_view_attach(struct pane *par safe, int border)
400 struct view_data *vd;
403 p = pane_register(par, 0, &view_handle.c);
408 vd->old_border = border;
409 vd->line_height = -1;
410 vd->border_width = vd->border_height = -1;
411 /* Capture status-changed notification so we can update 'changed' flag in
413 call("doc:request:doc:status-changed", p);
414 call("doc:request:doc:replaced", p);
415 call("doc:request:mark:moving", p);
416 /* And update display-context */
417 call("Window:request:display-context", p);
421 static int calc_border(struct pane *p safe)
424 char *borderstr = pane_attr_get(p, "borders");
427 if (strchr(borderstr, 'T')) borders |= BORDER_TOP;
428 if (strchr(borderstr, 'B')) borders |= BORDER_BOT;
429 if (strchr(borderstr, 'L')) borders |= BORDER_LEFT;
430 if (strchr(borderstr, 'R')) borders |= BORDER_RIGHT;
431 if (strchr(borderstr, 's')) borders |= BORDER_STATUS;
437 int borders = calc_border(ci->focus);
438 struct pane *p = do_view_attach(ci->focus, borders);
442 return comm_call(ci->comm2, "callback:attach", p);
447 struct pane *p = ci->home;
448 struct view_data *vd = p->data;
449 struct pane *c = vd->child;
450 int mid = vd->scroll_bar_y;
451 int lh = vd->line_height;
456 cih = pane_mapxy(ci->focus, ci->home, ci->x, ci->y, False);
459 /* Event was in the child */
463 /* Ignore if not in scroll-bar, which it to left of child */
464 if (cih.y < c->y || // above child
465 cih.y >= c->y + c->h || // below child
466 cih.x >= c->x) // Not to right of child
470 /* scroll bar too small to be useful */
473 scale = 100; /* 10% for small movements */
476 if (cih.y < mid - lh) {
480 } else if (cih.y <= mid) {
483 } else if (cih.y <= mid + lh) {
486 /* big scroll down */
489 call("Move-View", pane_focus(ci->focus), num * scale);
493 DEF_CMD(view_release)
495 /* Make sure release doesn't go to parent if not in child */
497 if (ci->focus != ci->home)
498 /* Event was in the child */
505 if (strcmp(ci->key, "M:Press-4") == 0)
506 call("Move-View", pane_focus(ci->focus), -200);
508 call("Move-View", pane_focus(ci->focus), 200);
512 DEF_CMD(view_refresh_view)
514 struct pane *p = ci->home;
515 struct view_data *vd = p->data;
518 border = calc_border(ci->focus);
519 if (vd->border >= 0 && border != vd->border) {
521 pane_damaged(p, DAMAGED_SIZE);
528 struct view_data *vd = ci->home->data;
531 mark_clip(vd->viewpoint, ci->mark, ci->mark2, !!ci->num);
537 struct pane *p = ci->home;
538 struct view_data *vd = p->data;
543 vd->border = vd->old_border;
545 pane_damaged(p, DAMAGED_SIZE);
546 return Efallthrough; /* allow other handlers to change borders */
549 void edlib_init(struct pane *ed safe)
551 view_map = key_alloc();
553 key_add(view_map, "M:Click-1", &view_click);
554 key_add(view_map, "M:Press-1", &view_click);
555 key_add(view_map, "M:Release-1", &view_release);
556 key_add(view_map, "M:DPress-1", &view_click);
557 key_add(view_map, "M:TPress-1", &view_click);
558 key_add(view_map, "M:Press-4", &view_scroll);
559 key_add(view_map, "M:Press-5", &view_scroll);
560 key_add(view_map, "Tile:border", &view_border);
561 key_add(view_map, "Refresh:view", &view_refresh_view);
562 key_add(view_map, "Close", &view_close);
563 key_add(view_map, "Clone", &view_clone);
564 key_add(view_map, "Child-Notify", &view_child_notify);
565 key_add(view_map, "Refresh:size", &view_refresh_size);
566 key_add(view_map, "Refresh", &view_refresh);
567 key_add(view_map, "doc:status-changed", &view_status_changed);
568 key_add(view_map, "doc:replaced", &view_status_changed);
569 key_add(view_map, "mark:moving", &view_status_changed);
570 key_add(view_map, "view:changed", &view_status_changed);
571 key_add(view_map, "display-context", &view_status_changed);
572 key_add(view_map, "render:reposition", &view_reposition);
573 key_add(view_map, "Notify:clip", &view_clip);
575 call_comm("global-set-command", ed, &view_attach, 0, NULL, "attach-view");