graph: uncomment is_boundary propagation
[tig.git] / tig.h
blobcd9404f2c4debd2e4e05c931fee2e036f1d628ac
1 /* Copyright (c) 2006-2013 Jonas Fonseca <fonseca@diku.dk>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #ifndef TIG_H
15 #define TIG_H
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
21 #include "compat/compat.h"
23 #ifndef TIG_VERSION
24 #define TIG_VERSION "unknown-version"
25 #endif
27 #ifndef DEBUG
28 #define NDEBUG
29 #endif
31 /* necessary on Snow Leopard to use WINDOW struct */
32 #ifdef NCURSES_OPAQUE
33 #undef NCURSES_OPAQUE
34 #endif
35 #define NCURSES_OPAQUE 0
38 #include <assert.h>
39 #include <errno.h>
40 #include <ctype.h>
41 #include <signal.h>
42 #include <stdarg.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #include <sys/stat.h>
49 #include <sys/select.h>
50 #include <unistd.h>
51 #include <sys/time.h>
52 #include <time.h>
53 #include <fcntl.h>
55 #include <regex.h>
57 #include <locale.h>
58 #include <langinfo.h>
59 #include <iconv.h>
61 /* ncurses(3): Must be defined to have extended wide-character functions. */
62 #define _XOPEN_SOURCE_EXTENDED
64 #if defined HAVE_NCURSESW_CURSES_H
65 # include <ncursesw/curses.h>
66 #elif defined HAVE_NCURSESW_H
67 # include <ncursesw.h>
68 #elif defined HAVE_NCURSES_CURSES_H
69 # include <ncurses/curses.h>
70 #elif defined HAVE_NCURSES_H
71 # include <ncurses.h>
72 #elif defined HAVE_CURSES_H
73 # include <curses.h>
74 #else
75 #ifdef WARN_MISSING_CURSES_CONFIGURATION
76 # warning SysV or X/Open-compatible Curses installation is required.
77 # warning Will assume Curses is found in default include and library path.
78 # warning To fix any build issues please use autotools to configure Curses.
79 # warning See INSTALL.adoc file for instructions.
80 #endif
81 # include <curses.h>
82 #endif
84 #if __GNUC__ >= 3
85 #define TIG_NORETURN __attribute__((__noreturn__))
86 #define PRINTF_LIKE(fmt, args) __attribute__((format (printf, fmt, args)))
87 #else
88 #define TIG_NORETURN
89 #define PRINTF_LIKE(fmt, args)
90 #endif
92 #define ABS(x) ((x) >= 0 ? (x) : -(x))
93 #define MIN(x, y) ((x) < (y) ? (x) : (y))
94 #define MAX(x, y) ((x) > (y) ? (x) : (y))
96 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
97 #define STRING_SIZE(x) (sizeof(x) - 1)
99 #define SIZEOF_STR 1024 /* Default string size. */
100 #define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
101 #define SIZEOF_REV 41 /* Holds a SHA-1 and an ending NUL. */
102 #define SIZEOF_ARG 32 /* Default argument array size. */
104 /* This color name can be used to refer to the default term colors. */
105 #define COLOR_DEFAULT (-1)
107 #define ICONV_NONE ((iconv_t) -1)
108 #ifndef ICONV_CONST
109 #define ICONV_CONST /* nothing */
110 #endif
111 #define ICONV_TRANSLIT "//TRANSLIT//IGNORE"
113 /* The format and size of the date column in the main view. */
114 #define DATE_FORMAT "%Y-%m-%d %H:%M"
115 #define DATE_WIDTH STRING_SIZE("2006-04-29 14:21")
116 #define DATE_SHORT_WIDTH STRING_SIZE("2006-04-29")
118 #define ID_WIDTH 7
119 #define AUTHOR_WIDTH 18
120 #define FILENAME_WIDTH 18
122 #define MIN_VIEW_HEIGHT 4
123 #define MIN_VIEW_WIDTH 4
125 #define NULL_ID "0000000000000000000000000000000000000000"
127 #define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
129 /* Some ASCII-shorthands fitted into the ncurses namespace. */
130 #define KEY_CTL(x) ((x) & 0x1f) /* KEY_CTL(A) == ^A == \1 */
131 #define KEY_TAB '\t'
132 #define KEY_RETURN '\r'
133 #define KEY_ESC 27
136 * Allocation helpers ... Entering macro hell to never be seen again.
139 #define DEFINE_ALLOCATOR(name, type, chunk_size) \
140 static type * \
141 name(type **mem, size_t size, size_t increase) \
143 size_t num_chunks = (size + chunk_size - 1) / chunk_size; \
144 size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
145 type *tmp = *mem; \
147 if (mem == NULL || num_chunks != num_chunks_new) { \
148 size_t newsize = num_chunks_new * chunk_size * sizeof(type); \
150 tmp = realloc(tmp, newsize); \
151 if (tmp) { \
152 *mem = tmp; \
153 if (num_chunks_new > num_chunks) { \
154 size_t offset = num_chunks * chunk_size; \
155 size_t oldsize = offset * sizeof(type); \
157 memset(tmp + offset, 0, newsize - oldsize); \
162 return tmp; \
165 static inline int
166 count_digits(unsigned long i)
168 int digits;
170 for (digits = 0; i; digits++)
171 i /= 10;
172 return digits;
176 * Strings.
179 #define prefixcmp(str1, str2) \
180 strncmp(str1, str2, STRING_SIZE(str2))
182 static inline bool
183 string_isnumber(const char *str)
185 int pos;
187 for (pos = 0; str[pos]; pos++) {
188 if (!isdigit(str[pos]))
189 return FALSE;
192 return pos > 0;
195 static inline bool
196 iscommit(char *str)
198 int pos;
200 for (pos = 0; str[pos]; pos++) {
201 if (!isxdigit(str[pos]))
202 return FALSE;
205 return 7 <= pos && pos < SIZEOF_REV;
208 static inline int
209 ascii_toupper(int c)
211 if (c >= 'a' && c <= 'z')
212 c &= ~0x20;
213 return c;
216 static inline int
217 ascii_tolower(int c)
219 if (c >= 'A' && c <= 'Z')
220 c |= 0x20;
221 return c;
224 static inline int
225 suffixcmp(const char *str, int slen, const char *suffix)
227 size_t len = slen >= 0 ? slen : strlen(str);
228 size_t suffixlen = strlen(suffix);
230 return suffixlen < len ? strcmp(str + len - suffixlen, suffix) : -1;
233 static inline void
234 string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
236 if (srclen > dstlen - 1)
237 srclen = dstlen - 1;
239 strncpy(dst, src, srclen);
240 dst[srclen] = 0;
243 /* Shorthands for safely copying into a fixed buffer. */
245 #define FORMAT_BUFFER(buf, bufsize, fmt, retval, allow_truncate) \
246 do { \
247 va_list args; \
248 va_start(args, fmt); \
249 retval = vsnprintf(buf, bufsize, fmt, args); \
250 va_end(args); \
251 if (retval >= (bufsize) && allow_truncate) { \
252 (buf)[(bufsize) - 1] = 0; \
253 (buf)[(bufsize) - 2] = '.'; \
254 (buf)[(bufsize) - 3] = '.'; \
255 (buf)[(bufsize) - 4] = '.'; \
256 retval = (bufsize) - 1; \
257 } else if (retval < 0 || retval >= (bufsize)) { \
258 retval = -1; \
260 } while (0)
262 #define string_copy(dst, src) \
263 string_ncopy_do(dst, sizeof(dst), src, sizeof(src))
265 #define string_ncopy(dst, src, srclen) \
266 string_ncopy_do(dst, sizeof(dst), src, srclen)
268 static inline void
269 string_copy_rev(char *dst, const char *src)
271 size_t srclen;
273 if (!*src)
274 return;
276 for (srclen = 0; srclen < SIZEOF_REV; srclen++)
277 if (isspace(src[srclen]))
278 break;
280 string_ncopy_do(dst, SIZEOF_REV, src, srclen);
283 static inline void
284 string_copy_rev_from_commit_line(char *dst, const char *src)
286 string_copy_rev(dst, src + STRING_SIZE("commit "));
289 #define string_rev_is_null(rev) !strncmp(rev, NULL_ID, STRING_SIZE(NULL_ID))
291 #define string_add(dst, from, src) \
292 string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
294 static inline size_t
295 string_expanded_length(const char *src, size_t srclen, size_t tabsize, size_t max_size)
297 size_t size, pos;
299 for (size = pos = 0; pos < srclen && size < max_size; pos++) {
300 if (src[pos] == '\t') {
301 size_t expanded = tabsize - (size % tabsize);
303 size += expanded;
304 } else {
305 size++;
309 return pos;
312 static inline size_t
313 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
315 size_t size, pos;
317 for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
318 if (src[pos] == '\t') {
319 size_t expanded = tabsize - (size % tabsize);
321 if (expanded + size >= dstlen - 1)
322 expanded = dstlen - size - 1;
323 memcpy(dst + size, " ", expanded);
324 size += expanded;
325 } else {
326 dst[size++] = src[pos];
330 dst[size] = 0;
331 return pos;
334 static inline char *
335 chomp_string(char *name)
337 int namelen;
339 while (isspace(*name))
340 name++;
342 namelen = strlen(name) - 1;
343 while (namelen > 0 && isspace(name[namelen]))
344 name[namelen--] = 0;
346 return name;
349 static inline bool PRINTF_LIKE(4, 5)
350 string_nformat(char *buf, size_t bufsize, size_t *bufpos, const char *fmt, ...)
352 size_t pos = bufpos ? *bufpos : 0;
353 int retval;
355 FORMAT_BUFFER(buf + pos, bufsize - pos, fmt, retval, FALSE);
356 if (bufpos && retval > 0)
357 *bufpos = pos + retval;
359 return pos >= bufsize ? FALSE : TRUE;
362 #define string_format(buf, fmt, args...) \
363 string_nformat(buf, sizeof(buf), NULL, fmt, args)
365 #define string_format_size(buf, size, fmt, args...) \
366 string_nformat(buf, size, NULL, fmt, args)
368 #define string_format_from(buf, from, fmt, args...) \
369 string_nformat(buf, sizeof(buf), from, fmt, args)
371 static inline int
372 strcmp_null(const char *s1, const char *s2)
374 if (!s1 || !s2) {
375 return (!!s1) - (!!s2);
378 return strcmp(s1, s2);
382 * Enumerations
385 struct enum_map_entry {
386 const char *name;
387 int namelen;
388 int value;
391 struct enum_map {
392 const struct enum_map_entry *entries;
393 const int size;
396 #define ENUM_MAP_ENTRY(name, value) { name, STRING_SIZE(name), value }
398 #define ENUM_SYM_MACRO(prefix, name) prefix##_##name
399 #define ENUM_MAP_MACRO(prefix, name) ENUM_MAP_ENTRY(#name, ENUM_SYM_MACRO(prefix, name))
401 #define DEFINE_ENUM(name, info) \
402 enum name { info(ENUM_SYM_MACRO) }; \
403 static const struct enum_map_entry name##_map_entries[] = { info(ENUM_MAP_MACRO) }; \
404 static const struct enum_map name##_map[] = { { name##_map_entries, ARRAY_SIZE(name##_map_entries) } }
406 static inline int
407 string_enum_compare(const char *str1, const char *str2, int len)
409 size_t i;
411 #define string_enum_sep(x) ((x) == '-' || (x) == '_' || (x) == '.')
413 /* Diff-Header == DIFF_HEADER */
414 for (i = 0; i < len; i++) {
415 if (ascii_toupper(str1[i]) == ascii_toupper(str2[i]))
416 continue;
418 if (string_enum_sep(str1[i]) &&
419 string_enum_sep(str2[i]))
420 continue;
422 return str1[i] - str2[i];
425 return 0;
428 #define enum_equals(entry, str, len) \
429 ((entry).namelen == (len) && !string_enum_compare((entry).name, str, len))
431 static inline char *
432 enum_map_name(const char *name, size_t namelen)
434 static char buf[SIZEOF_STR];
435 int bufpos;
437 for (bufpos = 0; bufpos <= namelen; bufpos++) {
438 buf[bufpos] = ascii_tolower(name[bufpos]);
439 if (buf[bufpos] == '_')
440 buf[bufpos] = '-';
443 buf[bufpos] = 0;
444 return buf;
447 #define enum_name(entry) enum_map_name((entry).name, (entry).namelen)
449 static inline bool
450 map_enum_do(const struct enum_map_entry *map, size_t map_size, int *value, const char *name)
452 size_t namelen = strlen(name);
453 int i;
455 for (i = 0; i < map_size; i++)
456 if (enum_equals(map[i], name, namelen)) {
457 *value = map[i].value;
458 return TRUE;
461 return FALSE;
464 #define map_enum(attr, map, name) \
465 map_enum_do(map, ARRAY_SIZE(map), attr, name)
468 * Unicode / UTF-8 handling
470 * NOTE: Much of the following code for dealing with Unicode is derived from
471 * ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
472 * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
475 static inline int
476 unicode_width(unsigned long c, int tab_size)
478 if (c >= 0x1100 &&
479 (c <= 0x115f /* Hangul Jamo */
480 || c == 0x2329
481 || c == 0x232a
482 || (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f)
483 /* CJK ... Yi */
484 || (c >= 0xac00 && c <= 0xd7a3) /* Hangul Syllables */
485 || (c >= 0xf900 && c <= 0xfaff) /* CJK Compatibility Ideographs */
486 || (c >= 0xfe30 && c <= 0xfe6f) /* CJK Compatibility Forms */
487 || (c >= 0xff00 && c <= 0xff60) /* Fullwidth Forms */
488 || (c >= 0xffe0 && c <= 0xffe6)
489 || (c >= 0x20000 && c <= 0x2fffd)
490 || (c >= 0x30000 && c <= 0x3fffd)))
491 return 2;
493 if ((c >= 0x0300 && c <= 0x036f) /* combining diacretical marks */
494 || (c >= 0x1dc0 && c <= 0x1dff) /* combining diacretical marks supplement */
495 || (c >= 0x20d0 && c <= 0x20ff) /* combining diacretical marks for symbols */
496 || (c >= 0xfe20 && c <= 0xfe2f)) /* combining half marks */
497 return 0;
499 if (c == '\t')
500 return tab_size;
502 return 1;
505 /* Number of bytes used for encoding a UTF-8 character indexed by first byte.
506 * Illegal bytes are set one. */
507 static const unsigned char utf8_bytes[256] = {
508 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
509 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
510 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
511 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
512 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
513 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
514 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
515 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
518 static inline unsigned char
519 utf8_char_length(const char *string, const char *end)
521 int c = *(unsigned char *) string;
523 return utf8_bytes[c];
526 /* Decode UTF-8 multi-byte representation into a Unicode character. */
527 static inline unsigned long
528 utf8_to_unicode(const char *string, size_t length)
530 unsigned long unicode;
532 switch (length) {
533 case 1:
534 unicode = string[0];
535 break;
536 case 2:
537 unicode = (string[0] & 0x1f) << 6;
538 unicode += (string[1] & 0x3f);
539 break;
540 case 3:
541 unicode = (string[0] & 0x0f) << 12;
542 unicode += ((string[1] & 0x3f) << 6);
543 unicode += (string[2] & 0x3f);
544 break;
545 case 4:
546 unicode = (string[0] & 0x0f) << 18;
547 unicode += ((string[1] & 0x3f) << 12);
548 unicode += ((string[2] & 0x3f) << 6);
549 unicode += (string[3] & 0x3f);
550 break;
551 case 5:
552 unicode = (string[0] & 0x0f) << 24;
553 unicode += ((string[1] & 0x3f) << 18);
554 unicode += ((string[2] & 0x3f) << 12);
555 unicode += ((string[3] & 0x3f) << 6);
556 unicode += (string[4] & 0x3f);
557 break;
558 case 6:
559 unicode = (string[0] & 0x01) << 30;
560 unicode += ((string[1] & 0x3f) << 24);
561 unicode += ((string[2] & 0x3f) << 18);
562 unicode += ((string[3] & 0x3f) << 12);
563 unicode += ((string[4] & 0x3f) << 6);
564 unicode += (string[5] & 0x3f);
565 break;
566 default:
567 return 0;
570 /* Invalid characters could return the special 0xfffd value but NUL
571 * should be just as good. */
572 return unicode > 0xffff ? 0 : unicode;
575 /* Calculates how much of string can be shown within the given maximum width
576 * and sets trimmed parameter to non-zero value if all of string could not be
577 * shown. If the reserve flag is TRUE, it will reserve at least one
578 * trailing character, which can be useful when drawing a delimiter.
580 * Returns the number of bytes to output from string to satisfy max_width. */
581 static inline size_t
582 utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve, int tab_size)
584 const char *string = *start;
585 const char *end = strchr(string, '\0');
586 unsigned char last_bytes = 0;
587 size_t last_ucwidth = 0;
589 *width = 0;
590 *trimmed = 0;
592 while (string < end) {
593 unsigned char bytes = utf8_char_length(string, end);
594 size_t ucwidth;
595 unsigned long unicode;
597 if (string + bytes > end)
598 break;
600 /* Change representation to figure out whether
601 * it is a single- or double-width character. */
603 unicode = utf8_to_unicode(string, bytes);
604 /* FIXME: Graceful handling of invalid Unicode character. */
605 if (!unicode)
606 break;
608 ucwidth = unicode_width(unicode, tab_size);
609 if (skip > 0) {
610 skip -= ucwidth <= skip ? ucwidth : skip;
611 *start += bytes;
613 *width += ucwidth;
614 if (*width > max_width) {
615 *trimmed = 1;
616 *width -= ucwidth;
617 if (reserve && *width == max_width) {
618 string -= last_bytes;
619 *width -= last_ucwidth;
621 break;
624 string += bytes;
625 if (ucwidth) {
626 last_bytes = bytes;
627 last_ucwidth = ucwidth;
628 } else {
629 last_bytes += bytes;
633 return string - *start;
636 #endif
638 /* vim: set ts=8 sw=8 noexpandtab: */