1 /* util.c -- various utility functions
2
3 Copyright 1993-2023 Free Software Foundation, Inc.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
17
18 #include "info.h"
19 #include "session.h"
20 #include "util.h"
21 #include "tag.h"
22
23 /* wrapper for asprintf */
24 static int
25 xvasprintf (char **ptr, const char *template, va_list ap)
26 {
27 int ret;
28 ret = vasprintf (ptr, template, ap);
29 if (ret < 0)
30 abort (); /* out of memory */
31 return ret;
32 }
33
34 int
35 xasprintf (char **ptr, const char *template, ...)
36 {
37 va_list v;
38 va_start (v, template);
39 return xvasprintf (ptr, template, v);
40 }
41
42 /* Return the file buffer which belongs to WINDOW's node. */
43 FILE_BUFFER *
44 file_buffer_of_window (WINDOW *window)
45 {
46 /* If this window has no node, then it has no file buffer. */
47 if (!window->node)
48 return NULL;
49
50 if (window->node->fullpath)
51 return info_find_file (window->node->fullpath);
52
53 return NULL;
54 }
55
56 /* Return "(FILENAME)NODENAME" for NODE, or just "NODENAME" if NODE's
57 filename is not set. Return value should not be freed. */
58 char *
59 node_printed_rep (NODE *node)
60 {
61 static char *rep;
62
63 if (node->fullpath)
64 {
65 char *filename = filename_non_directory (node->fullpath);
66 rep = xrealloc (rep, 1 + strlen (filename) + 1 + strlen (node->nodename) + 1);
67 sprintf (rep, "(%s)%s", filename, node->nodename);
68 return rep;
69 }
70 else
71 return node->nodename;
72 }
73
74
75 /* Return a pointer to the part of PATHNAME that simply defines the file. */
76 char *
77 filename_non_directory (char *pathname)
78 {
79 register char *filename = pathname + strlen (pathname);
80
81 if (HAVE_DRIVE (pathname))
82 pathname += 2;
83
84 while (filename > pathname && !IS_SLASH (filename[-1]))
85 filename--;
86
87 return filename;
88 }
89
90 /* Return non-zero if NODE is one especially created by Info. */
91 int
92 internal_info_node_p (NODE *node)
93 {
94 return (node != NULL) && (node->flags & N_IsInternal);
95 }
96
97 /* Make NODE appear to be one especially created by Info. */
98 void
99 name_internal_node (NODE *node, char *name)
100 {
101 if (!node)
102 return;
103
104 node->fullpath = "";
105 node->subfile = 0;
106 node->nodename = name;
107 node->flags |= N_IsInternal;
108 }
109
110 /* Return the window displaying NAME, the name of an internally created
111 Info window. */
112 WINDOW *
113 get_internal_info_window (char *name)
114 {
115 WINDOW *win;
116
117 for (win = windows; win; win = win->next)
118 if (internal_info_node_p (win->node) &&
119 (strcmp (win->node->nodename, name) == 0))
120 break;
121
122 return win;
123 }
124
125
126 /* If ITER points to an ANSI escape sequence, process it, set PLEN to its
127 length in bytes, and return 1.
128 Otherwise, return 0.
129 */
130 int
131 ansi_escape (mbi_iterator_t iter, size_t *plen)
132 {
133 if (raw_escapes_p && *mbi_cur_ptr (iter) == '\033' && mbi_avail (iter))
134 {
135 mbi_advance (iter);
136 if (*mbi_cur_ptr (iter) == '[' && mbi_avail (iter))
137 {
138 ITER_SETBYTES (iter, 1);
139 mbi_advance (iter);
140 if (isdigit (*mbi_cur_ptr (iter)) && mbi_avail (iter))
141 {
142 ITER_SETBYTES (iter, 1);
143 mbi_advance (iter);
144 if (*mbi_cur_ptr (iter) == 'm')
145 {
146 *plen = 4;
147 return 1;
148 }
149 else if (isdigit (*mbi_cur_ptr (iter)) && mbi_avail (iter))
150 {
151 ITER_SETBYTES (iter, 1);
152 mbi_advance (iter);
153 if (*mbi_cur_ptr (iter) == 'm')
154 {
155 *plen = 5;
156 return 1;
157 }
158 }
159 }
160 }
161 }
162
163 return 0;
164 }
165
166 static struct text_buffer printed_rep = { 0 };
167
168 /* Return pointer to string that is the printed representation of character
169 (or other logical unit) at ITER if it were printed at screen column
170 PL_CHARS. Use ITER_SETBYTES (util.h) on ITER if we need to advance
171 past a unit that the multibyte iteractor doesn't know about (like an ANSI
172 escape sequence). If ITER points at an end-of-line character, set *DELIM to
173 this character. *PCHARS gets the number of screen columns taken up by
174 outputting the return value, and *PBYTES the number of bytes in returned
175 string. Return value is not null-terminated. Return value must not be
176 freed by caller. */
177 char *
178 printed_representation (mbi_iterator_t *iter, int *delim, size_t pl_chars,
179 size_t *pchars, size_t *pbytes)
180 {
181 struct text_buffer *rep = &printed_rep;
182
183 char *cur_ptr = (char *) mbi_cur_ptr (*iter);
184 size_t cur_len = mb_len (mbi_cur (*iter));
185
186 text_buffer_reset (&printed_rep);
187
188 if (mb_isprint (mbi_cur (*iter)))
189 {
190 /* cur.wc gives a wchar_t object. See mbiter.h in the
191 gnulib/lib directory. */
192 *pchars = wcwidth ((*iter).cur.wc);
193 *pbytes = cur_len;
194 return cur_ptr;
195 }
196 else if (cur_len == 1)
197 {
198 if (*cur_ptr == '\n' || *cur_ptr == '\r')
199 {
200 /* If this is a CRLF line ending, ignore this character. */
201 if (*cur_ptr == '\r' && cur_ptr[1] == '\n')
202 {
203 *pchars = 0;
204 *pbytes = 0;
205 return cur_ptr;
206 }
207
208 *pchars = 1;
209 *pbytes = cur_len;
210 *delim = *cur_ptr;
211 text_buffer_add_char (rep, ' ');
212 return cur_ptr;
213 }
214 else if (ansi_escape (*iter, &cur_len))
215 {
216 *pchars = 0;
217 *pbytes = cur_len;
218 ITER_SETBYTES (*iter, cur_len);
219
220 return cur_ptr;
221 }
222 else if (*cur_ptr == '\t')
223 {
224 int i = 0;
225
226 *pchars = ((pl_chars + 8) & 0xf8) - pl_chars;
227 *pbytes = *pchars;
228
229 /* We must output spaces instead of the tab because a tab may
230 not clear characters already on the screen. */
231 for (i = 0; i < *pbytes; i++)
232 text_buffer_add_char (rep, ' ');
233 return text_buffer_base (rep);
234 }
235 }
236
237 /* Show CTRL-x as "^X". */
238 if (iscntrl (*cur_ptr) && *(unsigned char *)cur_ptr < 127)
239 {
240 *pchars = 2;
241 *pbytes = 2;
242 text_buffer_add_char (rep, '^');
243 text_buffer_add_char (rep, *cur_ptr | 0x40);
244 return text_buffer_base (rep);
245 }
246 else if (*cur_ptr == DEL)
247 {
248 *pchars = 0;
249 *pbytes = 0;
250 return text_buffer_base (rep);
251 }
252 else
253 {
254 /* Original byte was not recognized as anything. Display its octal
255 value. This could happen in the C locale for bytes above 128,
256 or for bytes 128-159 in an ISO-8859-1 locale. Don't output the bytes
257 as they are, because they could have special meaning to the
258 terminal. */
259 *pchars = 4;
260 *pbytes = 4;
261 text_buffer_printf (rep, "\\%o", *(unsigned char *)cur_ptr);
262 return text_buffer_base (rep);
263 }
264 }
265
266 /* Flexible Text Buffer */
267
268 void
269 text_buffer_init (struct text_buffer *buf)
270 {
271 memset (buf, 0, sizeof *buf);
272 }
273
274 void
275 text_buffer_free (struct text_buffer *buf)
276 {
277 free (buf->base);
278 }
279
280 size_t
281 text_buffer_vprintf (struct text_buffer *buf, const char *format, va_list ap)
282 {
283 ssize_t n;
284 va_list ap_copy;
285
286 if (!buf->base)
287 {
288 if (buf->size == 0)
289 buf->size = MIN_TEXT_BUF_ALLOC; /* Initial allocation */
290
291 buf->base = xmalloc (buf->size);
292 }
293
294 for (;;)
295 {
296 va_copy (ap_copy, ap);
297 n = vsnprintf (buf->base + buf->off, buf->size - buf->off,
298 format, ap_copy);
299 va_end (ap_copy);
300 if (n < 0 || buf->off + n >= buf->size ||
301 !memchr (buf->base + buf->off, '\0', buf->size - buf->off + 1))
302 {
303 size_t newlen = buf->size * 2;
304 if (newlen < buf->size)
305 xalloc_die ();
306 buf->size = newlen;
307 buf->base = xrealloc (buf->base, buf->size);
308 }
309 else
310 {
311 buf->off += n;
312 break;
313 }
314 }
315 return n;
316 }
317
318 /* Make sure there are LEN free bytes at end of BUF. */
319 void
320 text_buffer_alloc (struct text_buffer *buf, size_t len)
321 {
322 if (buf->off + len > buf->size)
323 {
324 buf->size = buf->off + len;
325 if (buf->size < MIN_TEXT_BUF_ALLOC)
326 buf->size = MIN_TEXT_BUF_ALLOC;
327 buf->base = xrealloc (buf->base, buf->size);
328 }
329 }
330
331 /* Return number of bytes that can be written to text buffer without
332 reallocating the text buffer. */
333 size_t
334 text_buffer_space_left (struct text_buffer *buf)
335 {
336 /* buf->size is the offset of the first byte after the allocated space.
337 buf->off is the offset of the first byte to be written to. */
338 return buf->size - buf->off;
339 }
340
341 #if HAVE_ICONV
342
343 /* Run iconv using text buffer as output buffer. */
344 size_t
345 text_buffer_iconv (struct text_buffer *buf, iconv_t iconv_state,
346 ICONV_CONST char **inbuf, size_t *inbytesleft)
347 {
348 size_t out_bytes_left;
349 char *outptr;
350 size_t iconv_ret;
351 size_t extra_alloc = 1;
352
353 while (1)
354 {
355 outptr = text_buffer_base (buf) + text_buffer_off (buf);
356 out_bytes_left = text_buffer_space_left (buf);
357
358 iconv_ret = iconv (iconv_state, inbuf, inbytesleft,
359 &outptr, &out_bytes_left);
360 text_buffer_off (buf) = outptr - text_buffer_base (buf);
361
362 if (iconv_ret != (size_t) -1)
363 break; /* success */
364
365 /* If we ran out of space, allocate more and try again. */
366 if (errno == E2BIG)
367 text_buffer_alloc (buf, (extra_alloc *= 4));
368 else
369 break; /* let calling code deal with it */
370 }
371 return iconv_ret;
372 }
373
374 #endif /* HAVE_ICONV */
375
376 size_t
377 text_buffer_add_string (struct text_buffer *buf, const char *str, size_t len)
378 {
379 text_buffer_alloc (buf, len);
380 memcpy (buf->base + buf->off, str, len);
381 buf->off += len;
382 return len;
383 }
384
385 size_t
386 text_buffer_fill (struct text_buffer *buf, int c, size_t len)
387 {
388 char *p;
389 int i;
390
391 text_buffer_alloc (buf, len);
392
393 for (i = 0, p = buf->base + buf->off; i < len; i++)
394 *p++ = c;
395 buf->off += len;
396
397 return len;
398 }
399
400 void
401 text_buffer_add_char (struct text_buffer *buf, int c)
402 {
403 char ch = c;
404 text_buffer_add_string (buf, &ch, 1);
405 }
406
407 size_t
408 text_buffer_printf (struct text_buffer *buf, const char *format, ...)
409 {
410 va_list ap;
411 size_t n;
412
413 va_start (ap, format);
414 n = text_buffer_vprintf (buf, format, ap);
415 va_end (ap);
416 return n;
417 }
418
419 #if defined(__MSDOS__) || defined(__MINGW32__)
420 /* Cannot use FILENAME_CMP here, since that does not consider forward-
421 and back-slash characters equal. */
422 int
423 fncmp (const char *fn1, const char *fn2)
424 {
425 const char *s1 = fn1, *s2 = fn2;
426
427 while (tolower (*s1) == tolower (*s2)
428 || (IS_SLASH (*s1) && IS_SLASH (*s2)))
429 {
430 if (*s1 == 0)
431 return 0;
432 s1++;
433 s2++;
434 }
435
436 return tolower (*s1) - tolower (*s2);
437 }
438
439 #endif