/* util.c -- various utility functions Copyright 1993-2023 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "info.h" #include "session.h" #include "util.h" #include "tag.h" /* wrapper for asprintf */ static int xvasprintf (char **ptr, const char *template, va_list ap) { int ret; ret = vasprintf (ptr, template, ap); if (ret < 0) abort (); /* out of memory */ return ret; } int xasprintf (char **ptr, const char *template, ...) { va_list v; va_start (v, template); return xvasprintf (ptr, template, v); } /* Return the file buffer which belongs to WINDOW's node. */ FILE_BUFFER * file_buffer_of_window (WINDOW *window) { /* If this window has no node, then it has no file buffer. */ if (!window->node) return NULL; if (window->node->fullpath) return info_find_file (window->node->fullpath); return NULL; } /* Return "(FILENAME)NODENAME" for NODE, or just "NODENAME" if NODE's filename is not set. Return value should not be freed. */ char * node_printed_rep (NODE *node) { static char *rep; if (node->fullpath) { char *filename = filename_non_directory (node->fullpath); rep = xrealloc (rep, 1 + strlen (filename) + 1 + strlen (node->nodename) + 1); sprintf (rep, "(%s)%s", filename, node->nodename); return rep; } else return node->nodename; } /* Return a pointer to the part of PATHNAME that simply defines the file. */ char * filename_non_directory (char *pathname) { register char *filename = pathname + strlen (pathname); if (HAVE_DRIVE (pathname)) pathname += 2; while (filename > pathname && !IS_SLASH (filename[-1])) filename--; return filename; } /* Return non-zero if NODE is one especially created by Info. */ int internal_info_node_p (NODE *node) { return (node != NULL) && (node->flags & N_IsInternal); } /* Make NODE appear to be one especially created by Info. */ void name_internal_node (NODE *node, char *name) { if (!node) return; node->fullpath = ""; node->subfile = 0; node->nodename = name; node->flags |= N_IsInternal; } /* Return the window displaying NAME, the name of an internally created Info window. */ WINDOW * get_internal_info_window (char *name) { WINDOW *win; for (win = windows; win; win = win->next) if (internal_info_node_p (win->node) && (strcmp (win->node->nodename, name) == 0)) break; return win; } /* If ITER points to an ANSI escape sequence, process it, set PLEN to its length in bytes, and return 1. Otherwise, return 0. */ int ansi_escape (mbi_iterator_t iter, size_t *plen) { if (raw_escapes_p && *mbi_cur_ptr (iter) == '\033' && mbi_avail (iter)) { mbi_advance (iter); if (*mbi_cur_ptr (iter) == '[' && mbi_avail (iter)) { ITER_SETBYTES (iter, 1); mbi_advance (iter); if (isdigit (*mbi_cur_ptr (iter)) && mbi_avail (iter)) { ITER_SETBYTES (iter, 1); mbi_advance (iter); if (*mbi_cur_ptr (iter) == 'm') { *plen = 4; return 1; } else if (isdigit (*mbi_cur_ptr (iter)) && mbi_avail (iter)) { ITER_SETBYTES (iter, 1); mbi_advance (iter); if (*mbi_cur_ptr (iter) == 'm') { *plen = 5; return 1; } } } } } return 0; } static struct text_buffer printed_rep = { 0 }; /* Return pointer to string that is the printed representation of character (or other logical unit) at ITER if it were printed at screen column PL_CHARS. Use ITER_SETBYTES (util.h) on ITER if we need to advance past a unit that the multibyte iteractor doesn't know about (like an ANSI escape sequence). If ITER points at an end-of-line character, set *DELIM to this character. *PCHARS gets the number of screen columns taken up by outputting the return value, and *PBYTES the number of bytes in returned string. Return value is not null-terminated. Return value must not be freed by caller. */ char * printed_representation (mbi_iterator_t *iter, int *delim, size_t pl_chars, size_t *pchars, size_t *pbytes) { struct text_buffer *rep = &printed_rep; char *cur_ptr = (char *) mbi_cur_ptr (*iter); size_t cur_len = mb_len (mbi_cur (*iter)); text_buffer_reset (&printed_rep); if (mb_isprint (mbi_cur (*iter))) { /* cur.wc gives a wchar_t object. See mbiter.h in the gnulib/lib directory. */ *pchars = wcwidth ((*iter).cur.wc); *pbytes = cur_len; return cur_ptr; } else if (cur_len == 1) { if (*cur_ptr == '\n' || *cur_ptr == '\r') { /* If this is a CRLF line ending, ignore this character. */ if (*cur_ptr == '\r' && cur_ptr[1] == '\n') { *pchars = 0; *pbytes = 0; return cur_ptr; } *pchars = 1; *pbytes = cur_len; *delim = *cur_ptr; text_buffer_add_char (rep, ' '); return cur_ptr; } else if (ansi_escape (*iter, &cur_len)) { *pchars = 0; *pbytes = cur_len; ITER_SETBYTES (*iter, cur_len); return cur_ptr; } else if (*cur_ptr == '\t') { int i = 0; *pchars = ((pl_chars + 8) & 0xf8) - pl_chars; *pbytes = *pchars; /* We must output spaces instead of the tab because a tab may not clear characters already on the screen. */ for (i = 0; i < *pbytes; i++) text_buffer_add_char (rep, ' '); return text_buffer_base (rep); } } /* Show CTRL-x as "^X". */ if (iscntrl (*cur_ptr) && *(unsigned char *)cur_ptr < 127) { *pchars = 2; *pbytes = 2; text_buffer_add_char (rep, '^'); text_buffer_add_char (rep, *cur_ptr | 0x40); return text_buffer_base (rep); } else if (*cur_ptr == DEL) { *pchars = 0; *pbytes = 0; return text_buffer_base (rep); } else { /* Original byte was not recognized as anything. Display its octal value. This could happen in the C locale for bytes above 128, or for bytes 128-159 in an ISO-8859-1 locale. Don't output the bytes as they are, because they could have special meaning to the terminal. */ *pchars = 4; *pbytes = 4; text_buffer_printf (rep, "\\%o", *(unsigned char *)cur_ptr); return text_buffer_base (rep); } } /* Flexible Text Buffer */ void text_buffer_init (struct text_buffer *buf) { memset (buf, 0, sizeof *buf); } void text_buffer_free (struct text_buffer *buf) { free (buf->base); } size_t text_buffer_vprintf (struct text_buffer *buf, const char *format, va_list ap) { ssize_t n; va_list ap_copy; if (!buf->base) { if (buf->size == 0) buf->size = MIN_TEXT_BUF_ALLOC; /* Initial allocation */ buf->base = xmalloc (buf->size); } for (;;) { va_copy (ap_copy, ap); n = vsnprintf (buf->base + buf->off, buf->size - buf->off, format, ap_copy); va_end (ap_copy); if (n < 0 || buf->off + n >= buf->size || !memchr (buf->base + buf->off, '\0', buf->size - buf->off + 1)) { size_t newlen = buf->size * 2; if (newlen < buf->size) xalloc_die (); buf->size = newlen; buf->base = xrealloc (buf->base, buf->size); } else { buf->off += n; break; } } return n; } /* Make sure there are LEN free bytes at end of BUF. */ void text_buffer_alloc (struct text_buffer *buf, size_t len) { if (buf->off + len > buf->size) { buf->size = buf->off + len; if (buf->size < MIN_TEXT_BUF_ALLOC) buf->size = MIN_TEXT_BUF_ALLOC; buf->base = xrealloc (buf->base, buf->size); } } /* Return number of bytes that can be written to text buffer without reallocating the text buffer. */ size_t text_buffer_space_left (struct text_buffer *buf) { /* buf->size is the offset of the first byte after the allocated space. buf->off is the offset of the first byte to be written to. */ return buf->size - buf->off; } #if HAVE_ICONV /* Run iconv using text buffer as output buffer. */ size_t text_buffer_iconv (struct text_buffer *buf, iconv_t iconv_state, ICONV_CONST char **inbuf, size_t *inbytesleft) { size_t out_bytes_left; char *outptr; size_t iconv_ret; size_t extra_alloc = 1; while (1) { outptr = text_buffer_base (buf) + text_buffer_off (buf); out_bytes_left = text_buffer_space_left (buf); iconv_ret = iconv (iconv_state, inbuf, inbytesleft, &outptr, &out_bytes_left); text_buffer_off (buf) = outptr - text_buffer_base (buf); if (iconv_ret != (size_t) -1) break; /* success */ /* If we ran out of space, allocate more and try again. */ if (errno == E2BIG) text_buffer_alloc (buf, (extra_alloc *= 4)); else break; /* let calling code deal with it */ } return iconv_ret; } #endif /* HAVE_ICONV */ size_t text_buffer_add_string (struct text_buffer *buf, const char *str, size_t len) { text_buffer_alloc (buf, len); memcpy (buf->base + buf->off, str, len); buf->off += len; return len; } size_t text_buffer_fill (struct text_buffer *buf, int c, size_t len) { char *p; int i; text_buffer_alloc (buf, len); for (i = 0, p = buf->base + buf->off; i < len; i++) *p++ = c; buf->off += len; return len; } void text_buffer_add_char (struct text_buffer *buf, int c) { char ch = c; text_buffer_add_string (buf, &ch, 1); } size_t text_buffer_printf (struct text_buffer *buf, const char *format, ...) { va_list ap; size_t n; va_start (ap, format); n = text_buffer_vprintf (buf, format, ap); va_end (ap); return n; } #if defined(__MSDOS__) || defined(__MINGW32__) /* Cannot use FILENAME_CMP here, since that does not consider forward- and back-slash characters equal. */ int fncmp (const char *fn1, const char *fn2) { const char *s1 = fn1, *s2 = fn2; while (tolower (*s1) == tolower (*s2) || (IS_SLASH (*s1) && IS_SLASH (*s2))) { if (*s1 == 0) return 0; s1++; s2++; } return tolower (*s1) - tolower (*s2); } #endif