blob: bbd6ea369cb992bdf0eb41e1c63219aae7a4cefd [file]
/* 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 <http://www.gnu.org/licenses/>. */
#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