2 * Copyright Neil Brown ©2020-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
7 * Provide log() and related functions to collect trace data.
8 * Store it in a buffer accessible as a document, and optionally
9 * write to a file or stderr.
19 #define PRIVATE_DOC_REF
24 #define DOC_DATA_TYPE struct log
25 #define DOC_NEXT(p,m,r,b) log_next(p,r,b)
26 #define DOC_PREV(p,m,r,b) log_prev(p,r,b)
45 #include "core-pane.h"
47 static struct pane *log_pane;
49 #define LBSIZE (8192 - sizeof(struct logbuf))
51 static struct logbuf *safe get_new_buf(struct log *d safe)
53 struct logbuf *b = malloc(sizeof(*b) + LBSIZE);
55 list_add_tail(&b->h, &d->log);
60 static struct logbuf *safe get_buf(struct log *d safe)
62 if (!list_empty(&d->log)) {
64 b = list_last_entry(&d->log, struct logbuf, h);
68 return get_new_buf(d);
71 void LOG(char *fmt, ...)
84 ld = log_pane->doc_data;
85 if (ld->refresh_active) {
86 /* Mustn't log anything if doc is being viewed */
87 if (pane_notify("doc:notify-viewers", log_pane))
93 gettimeofday(&now, NULL);
96 if (edlib_testing(log_pane))
99 n = snprintf(b->text + b->end, LBSIZE - b->end - 1,
101 now.tv_sec % 10000, now.tv_usec / 1000);
102 if (n < LBSIZE - b->end - 1)
103 n += vsnprintf(b->text + b->end + n, LBSIZE - b->end - 1 - n,
107 if (b->end != 0 && n >= LBSIZE - b->end - 1) {
108 /* Didn't fit, allocate new buf */
111 if (edlib_testing(log_pane))
114 n = snprintf(b->text, LBSIZE - 1, "%ld.%03ld:",
118 n += vsnprintf(b->text + n, LBSIZE - 1 - n, fmt, ap);
121 if (n >= LBSIZE - 1) {
122 /* Too long for buffer - truncate */
125 b->text[b->end + n++] = '\n';
126 b->text[b->end + n] = '\0';
129 fwrite(b->text + b->end, 1, n, ld->log_file);
130 fflush(ld->log_file);
133 pane_notify("doc:replaced", log_pane, 1);
139 struct log *l = ci->home->doc_data;
145 len = strlen(ci->str);
149 if (b->end != 0 && len >= LBSIZE - b->end - 1) {
150 /* Doesn't fit, allocate new buf */
155 strncpy(b->text + b->end, ci->str, len);
157 b->text[b->end + len++] = '\n';
158 b->text[b->end + len] = '\0';
161 pane_notify("doc:replaced", ci->home, 1);
167 struct log *log = ci->home->doc_data;
168 struct mark *from = ci->mark, *to = ci->mark2;
170 struct logbuf *b, *first, *last;
173 int bytes = strcmp(ci->key, "doc:content-bytes") == 0;
191 list_for_each_entry_from(b, &log->log, h) {
200 list_for_each_entry_from(b, &log->log, h) {
202 const char *s = b->text + head;
203 int ln = b->end - head;
209 while ((m2 = mark_next(m)) &&
210 m2->ref.b == m->ref.b)
223 wc = get_utf8(&s, s+ln);
227 while ((m2 = mark_next(m)) &&
228 m2->ref.b == m->ref.b &&
229 m2->ref.o <= s - b->text)
231 m->ref.o = s - b->text;
234 rv = comm_call(ci->comm2, "consume", ci->focus,
235 wc, m, s, ln, NULL, NULL, size, 0);
237 if (rv <= 0 || rv > ln + 1) {
256 struct log *log = ci->home->doc_data;
257 struct mark *m = ci->mark;
261 mark_to_end(ci->home, m, ci->num != 1);
264 m->ref.b = list_first_entry_or_null(&log->log, struct logbuf, h);
270 static inline wint_t log_next(struct pane *p safe, struct doc_ref *r safe, bool bytes)
272 struct log *log = p->doc_data;
278 const char *s = &r->b->text[r->o];
283 ret = get_utf8(&s, r->b->text + r->b->end);
284 r->o = s - r->b->text;
285 if (r->o >= r->b->end) {
286 if (r->b == list_last_entry(&log->log,
290 r->b = list_next_entry(r->b, h);
297 static inline wint_t log_prev(struct pane *p safe, struct doc_ref *r safe, bool bytes)
299 struct log *log = p->doc_data;
302 if (list_empty(&log->log))
305 r->b = list_last_entry(&log->log, struct logbuf, h);
307 } else if (r->o == 0) {
308 if (r->b != list_first_entry(&log->log,
310 r->b = list_prev_entry(r->b, h);
318 r->o = utf8_round_len(r->b->text, r->o - 1);
319 s = r->b->text + r->o;
323 return get_utf8(&s, r->b->text + r->b->end);
328 return do_char_byte(ci);
331 DEF_CMD(log_val_marks)
333 /* mark1 and mark2 must be correctly ordered */
334 struct log *log = ci->home->doc_data;
338 if (!ci->mark || !ci->mark2)
341 if (ci->mark->ref.b == ci->mark2->ref.b) {
342 if (ci->mark->ref.o < ci->mark2->ref.o)
344 LOG("log_val_marks: same buf, bad offset: %d, %d",
345 ci->mark->ref.o, ci->mark2->ref.o);
348 if (ci->mark->ref.b == NULL) {
349 LOG("log_val_marks: mark.b is NULL");
353 list_for_each_entry(b, &log->log, h) {
354 if (ci->mark->ref.b == b)
356 if (ci->mark2->ref.b == b) {
359 LOG("log_val_marks: mark2.b found before mark1");
363 if (ci->mark2->ref.b == NULL) {
366 LOG("log_val_marks: mark2.b (NULL) found before mark1");
370 LOG("log_val_marks: Neither mark found in buf list");
372 LOG("log_val_marks: mark2 not found in buf list");
378 /* Not allowed to destroy this document
379 * So handle command here, so we don't get
380 * to the default handler
389 /* Not sure what I want here yet */
390 attr_set_str(&log_pane->attrs, "render-default", "text");
391 attr_set_str(&log_pane->attrs, "doc-type", "text");
392 attr_set_str(&log_pane->attrs, "view-default", "viewer");
393 call("doc:set-name", log_pane, 0, NULL, "*Debug Log*");
394 call("global-multicall-doc:appeared-", log_pane);
398 DEF_CMD_CLOSED(log_close)
400 struct log *l = ci->home->doc_data;
402 while (!list_empty(&l->log)) {
403 struct logbuf *b = list_first_entry(&l->log, struct logbuf, h);
407 if (l->log_file && l->log_file != stderr)
412 DEF_CMD(log_refresh_active)
414 struct log *l = ci->home->doc_data;
416 l->refresh_active = ci->num;
420 static struct map *log_map;
421 DEF_LOOKUP_CMD(log_handle, log_map);
431 p = doc_register(ci->focus, &log_handle.c);
435 INIT_LIST_HEAD(&l->log);
436 attr_set_str(&p->attrs, "render-default", "text");
437 attr_set_str(&p->attrs, "doc-type", "text");
438 attr_set_str(&p->attrs, "render-default", "text");
439 call("doc:set-name", p, 0, NULL, ci->str);
440 call("global-multicall-doc:appeared-", p);
441 comm_call(ci->comm2, "cb", p);
445 static void log_init(struct pane *ed safe)
450 log_pane = doc_register(ed, &log_handle.c);
453 ld = log_pane->doc_data;
454 if (edlib_testing(ed))
455 /* line-count is SYNC when testing, and log can
456 * get big - so disable
458 attr_set_str(&log_pane->attrs, "linecount-disable", "yes");
460 INIT_LIST_HEAD(&ld->log);
462 call("editor:request:Refresh-active", log_pane);
464 fname = getenv("EDLIB_LOG");
465 if (!fname || !*fname)
467 if (strcmp(fname, "stderr") == 0) {
468 ld->log_file = stderr;
472 ld->log_file = fopen(fname, "a");
474 LOG("log: Cannot open \"%s\" for logging\n", fname);
477 void log_setup(struct pane *ed safe)
479 log_map = key_alloc();
481 key_add_chain(log_map, doc_default_cmd);
482 key_add(log_map, "doc:content", &log_content);
483 key_add(log_map, "doc:content-bytes", &log_content);
484 key_add(log_map, "doc:set-ref", &log_set_ref);
485 key_add(log_map, "doc:char", &log_char);
486 key_add(log_map, "doc:destroy", &log_destroy);
487 key_add(log_map, "doc:log:append", &log_append);
488 key_add(log_map, "Close", &log_close);
489 key_add(log_map, "Refresh-active", &log_refresh_active);
490 if(0)key_add(log_map, "debug:validate-marks", &log_val_marks);
493 call_comm("global-set-command", ed, &log_view, 0, NULL,
494 "interactive-cmd-view-log");
495 call_comm("global-set-command", ed, &log_new, 0, NULL,
497 LOG("log: testing 1 %d 3 Α Β Ψ α β γ", 2);