utils.c (14314B)
1 /************************************************************************** 2 * utils.c -- This file is part of GNU nano. * 3 * * 4 * Copyright (C) 1999-2011, 2013-2025 Free Software Foundation, Inc. * 5 * Copyright (C) 2016, 2017, 2019 Benno Schulenberg * 6 * * 7 * GNU nano is free software: you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published * 9 * by the Free Software Foundation, either version 3 of the License, * 10 * or (at your option) any later version. * 11 * * 12 * GNU nano is distributed in the hope that it will be useful, * 13 * but WITHOUT ANY WARRANTY; without even the implied warranty * 14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * 15 * See the GNU General Public License for more details. * 16 * * 17 * You should have received a copy of the GNU General Public License * 18 * along with this program. If not, see https://gnu.org/licenses/. * 19 * * 20 **************************************************************************/ 21 22 #include "prototypes.h" 23 24 #include <errno.h> 25 #ifdef HAVE_PWD_H 26 #include <pwd.h> 27 #endif 28 #include <string.h> 29 #include <unistd.h> 30 31 /* Return the user's home directory. We use $HOME, and if that fails, 32 * we fall back on the home directory of the effective user ID. */ 33 void get_homedir(void) 34 { 35 if (homedir == NULL) { 36 const char *homenv = getenv("HOME"); 37 38 #ifdef HAVE_PWD_H 39 /* When HOME isn't set, or when we're root, get the home directory 40 * from the password file instead. */ 41 if (homenv == NULL || geteuid() == ROOT_UID) { 42 const struct passwd *userage = getpwuid(geteuid()); 43 44 if (userage != NULL) 45 homenv = userage->pw_dir; 46 } 47 #endif 48 49 /* Only set homedir if some home directory could be determined, 50 * otherwise keep homedir NULL. */ 51 if (homenv != NULL && *homenv != '\0') 52 homedir = copy_of(homenv); 53 } 54 } 55 56 /* Return the filename part of the given path. */ 57 const char *tail(const char *path) 58 { 59 const char *slash = strrchr(path, '/'); 60 61 if (slash == NULL) 62 return path; 63 else 64 return slash + 1; 65 } 66 67 /* Return a copy of the two given strings, welded together. */ 68 char *concatenate(const char *path, const char *name) 69 { 70 size_t pathlen = strlen(path); 71 char *joined = nmalloc(pathlen + strlen(name) + 1); 72 73 strcpy(joined, path); 74 strcpy(joined + pathlen, name); 75 76 return joined; 77 } 78 79 /* Return the number of digits that the given integer n takes up. */ 80 int digits(ssize_t n) 81 { 82 if (n < 100000) { 83 if (n < 1000) { 84 if (n < 100) 85 return 2; 86 else 87 return 3; 88 } else { 89 if (n < 10000) 90 return 4; 91 else 92 return 5; 93 } 94 } else { 95 if (n < 10000000) { 96 if (n < 1000000) 97 return 6; 98 else 99 return 7; 100 } else { 101 if (n < 100000000) 102 return 8; 103 else 104 return 9; 105 } 106 } 107 } 108 109 /* Read an integer from the given string. If it parses okay, 110 * store it in *result and return TRUE; otherwise, return FALSE. */ 111 bool parse_num(const char *string, ssize_t *result) 112 { 113 ssize_t value; 114 char *excess; 115 116 /* Clear the error number so that we can check it afterward. */ 117 errno = 0; 118 119 value = (ssize_t)strtol(string, &excess, 10); 120 121 if (errno == ERANGE || *string == '\0' || *excess != '\0') 122 return FALSE; 123 124 *result = value; 125 126 return TRUE; 127 } 128 129 /* Read one number (or two numbers separated by comma, period, or colon) 130 * from the given string and store the number(s) in *line (and *column). 131 * Return FALSE on a failed parsing, and TRUE otherwise. */ 132 bool parse_line_column(const char *string, ssize_t *line, ssize_t *column) 133 { 134 const char *comma; 135 char *firstpart; 136 bool retval; 137 138 while (*string == ' ') 139 string++; 140 141 comma = strpbrk(string, ",.:"); 142 143 if (comma == NULL) 144 return parse_num(string, line); 145 146 retval = parse_num(comma + 1, column); 147 148 if (comma == string) 149 return retval; 150 151 firstpart = copy_of(string); 152 firstpart[comma - string] = '\0'; 153 154 retval = parse_num(firstpart, line) && retval; 155 156 free(firstpart); 157 158 return retval; 159 } 160 161 /* In the given string, recode each embedded NUL as a newline. */ 162 void recode_NUL_to_LF(char *string, size_t length) 163 { 164 while (length > 0) { 165 if (*string == '\0') 166 *string = '\n'; 167 length--; 168 string++; 169 } 170 } 171 172 /* In the given string, recode each embedded newline as a NUL, 173 * and return the number of bytes in the string. */ 174 size_t recode_LF_to_NUL(char *string) 175 { 176 char *beginning = string; 177 178 while (*string != '\0') { 179 if (*string == '\n') 180 *string = '\0'; 181 string++; 182 } 183 184 return (string - beginning); 185 } 186 187 #if !defined(ENABLE_TINY) || defined(ENABLE_TABCOMP) || defined(ENABLE_BROWSER) 188 /* Free the memory of the given array, which should contain len elements. */ 189 void free_chararray(char **array, size_t len) 190 { 191 if (array == NULL) 192 return; 193 194 while (len > 0) 195 free(array[--len]); 196 197 free(array); 198 } 199 #endif 200 201 #ifdef ENABLE_SPELLER 202 /* Is the word starting at the given position in 'text' and of the given 203 * length a separate word? That is: is it not part of a longer word? */ 204 bool is_separate_word(size_t position, size_t length, const char *text) 205 { 206 const char *before = text + step_left(text, position); 207 const char *after = text + position + length; 208 209 /* If the word starts at the beginning of the line OR the character before 210 * the word isn't a letter, and if the word ends at the end of the line OR 211 * the character after the word isn't a letter, we have a whole word. */ 212 return ((position == 0 || !is_alpha_char(before)) && 213 (*after == '\0' || !is_alpha_char(after))); 214 } 215 #endif /* ENABLE_SPELLER */ 216 217 /* Return the position of the needle in the haystack, or NULL if not found. 218 * When searching backwards, we will find the last match that starts no later 219 * than the given start; otherwise, we find the first match starting no earlier 220 * than start. If we are doing a regexp search, and we find a match, we fill 221 * in the global variable regmatches with at most 9 subexpression matches. */ 222 const char *strstrwrapper(const char *haystack, const char *needle, 223 const char *start) 224 { 225 if (ISSET(USE_REGEXP)) { 226 if (ISSET(BACKWARDS_SEARCH)) { 227 size_t last_find, ceiling, far_end; 228 size_t floor = 0, next_rung = 0; 229 /* The start of the search range, and the next start. */ 230 231 if (regexec(&search_regexp, haystack, 1, regmatches, 0) != 0) 232 return NULL; 233 234 far_end = strlen(haystack); 235 ceiling = start - haystack; 236 last_find = regmatches[0].rm_so; 237 238 /* A result beyond the search range also means: no match. */ 239 if (last_find > ceiling) 240 return NULL; 241 242 /* Move the start-of-range forward until there is no more match; 243 * then the last match found is the first match backwards. */ 244 while (regmatches[0].rm_so <= ceiling) { 245 floor = next_rung; 246 last_find = regmatches[0].rm_so; 247 /* If this is the last possible match, don't try to advance. */ 248 if (last_find == ceiling) 249 break; 250 next_rung = step_right(haystack, last_find); 251 regmatches[0].rm_so = next_rung; 252 regmatches[0].rm_eo = far_end; 253 if (regexec(&search_regexp, haystack, 1, regmatches, 254 REG_STARTEND) != 0) 255 break; 256 } 257 258 /* Find the last match again, to get possible submatches. */ 259 regmatches[0].rm_so = floor; 260 regmatches[0].rm_eo = far_end; 261 if (regexec(&search_regexp, haystack, 10, regmatches, 262 REG_STARTEND) != 0) 263 return NULL; 264 265 return haystack + regmatches[0].rm_so; 266 } 267 268 /* Do a forward regex search from the starting point. */ 269 regmatches[0].rm_so = start - haystack; 270 regmatches[0].rm_eo = strlen(haystack); 271 if (regexec(&search_regexp, haystack, 10, regmatches, 272 REG_STARTEND) != 0) 273 return NULL; 274 else 275 return haystack + regmatches[0].rm_so; 276 } 277 278 if (ISSET(CASE_SENSITIVE)) { 279 if (ISSET(BACKWARDS_SEARCH)) 280 return revstrstr(haystack, needle, start); 281 else 282 return strstr(start, needle); 283 } 284 285 if (ISSET(BACKWARDS_SEARCH)) 286 return mbrevstrcasestr(haystack, needle, start); 287 else 288 return mbstrcasestr(start, needle); 289 } 290 291 /* Allocate the given amount of memory and return a pointer to it. */ 292 void *nmalloc(size_t howmuch) 293 { 294 void *section = malloc(howmuch); 295 296 if (section == NULL) 297 die(_("Nano is out of memory!\n")); 298 299 return section; 300 } 301 302 /* Reallocate the given section of memory to have the given size. */ 303 void *nrealloc(void *section, size_t howmuch) 304 { 305 section = realloc(section, howmuch); 306 307 if (section == NULL) 308 die(_("Nano is out of memory!\n")); 309 310 return section; 311 } 312 313 /* Return an appropriately reallocated dest string holding a copy of src. 314 * Usage: "dest = mallocstrcpy(dest, src);". */ 315 char *mallocstrcpy(char *dest, const char *src) 316 { 317 size_t count = strlen(src) + 1; 318 319 dest = nrealloc(dest, count); 320 strncpy(dest, src, count); 321 322 return dest; 323 } 324 325 /* Return an allocated copy of the first count characters 326 * of the given string, and NUL-terminate the copy. */ 327 char *measured_copy(const char *string, size_t count) 328 { 329 char *thecopy = nmalloc(count + 1); 330 331 memcpy(thecopy, string, count); 332 thecopy[count] = '\0'; 333 334 return thecopy; 335 } 336 337 /* Return an allocated copy of the given string. */ 338 char *copy_of(const char *string) 339 { 340 return measured_copy(string, strlen(string)); 341 } 342 343 /* Free the string at dest and return the string at src. */ 344 char *free_and_assign(char *dest, char *src) 345 { 346 free(dest); 347 return src; 348 } 349 350 /* When not softwrapping, nano scrolls the current line horizontally by 351 * chunks ("pages"). Return the column number of the first character 352 * displayed in the edit window when the cursor is at the given column. */ 353 size_t get_page_start(size_t column) 354 { 355 if (column == 0 || column + 2 < editwincols || ISSET(SOFTWRAP)) 356 return 0; 357 else if (editwincols > 8) 358 return column - 6 - (column - 6) % (editwincols - 8); 359 else 360 return column - (editwincols - 2); 361 } 362 363 /* Return the placewewant associated with current_x, i.e. the zero-based 364 * column position of the cursor. */ 365 size_t xplustabs(void) 366 { 367 return wideness(openfile->current->data, openfile->current_x); 368 } 369 370 /* Return the index in text of the character that (when displayed) will 371 * not overshoot the given column. */ 372 size_t actual_x(const char *text, size_t column) 373 { 374 const char *start = text; 375 /* From where we start walking through the text. */ 376 size_t width = 0; 377 /* The current accumulated span, in columns. */ 378 379 while (*text != '\0') { 380 int charlen = advance_over(text, &width); 381 382 if (width > column) 383 break; 384 385 text += charlen; 386 } 387 388 return (text - start); 389 } 390 391 /* A strnlen() with tabs and multicolumn characters factored in: 392 * how many columns wide are the first maxlen bytes of text? */ 393 size_t wideness(const char *text, size_t maxlen) 394 { 395 size_t width = 0; 396 397 if (maxlen == 0) 398 return 0; 399 400 while (*text != '\0') { 401 size_t charlen = advance_over(text, &width); 402 403 if (maxlen <= charlen) 404 break; 405 406 maxlen -= charlen; 407 text += charlen; 408 } 409 410 return width; 411 } 412 413 /* Return the number of columns that the given text occupies. */ 414 size_t breadth(const char *text) 415 { 416 size_t span = 0; 417 418 while (*text != '\0') 419 text += advance_over(text, &span); 420 421 return span; 422 } 423 424 /* Append a new magic line to the end of the buffer. */ 425 void new_magicline(void) 426 { 427 openfile->filebot->next = make_new_node(openfile->filebot); 428 openfile->filebot->next->data = copy_of(""); 429 openfile->filebot = openfile->filebot->next; 430 openfile->totsize++; 431 } 432 433 #if !defined(NANO_TINY) || defined(ENABLE_HELP) 434 /* Remove the magic line from the end of the buffer, if there is one and 435 * it isn't the only line in the file. */ 436 void remove_magicline(void) 437 { 438 if (openfile->filebot->data[0] == '\0' && 439 openfile->filebot != openfile->filetop) { 440 if (openfile->current == openfile->filebot) 441 openfile->current = openfile->current->prev; 442 openfile->filebot = openfile->filebot->prev; 443 delete_node(openfile->filebot->next); 444 openfile->filebot->next = NULL; 445 openfile->totsize--; 446 } 447 } 448 #endif 449 450 #ifndef NANO_TINY 451 /* Return TRUE when the mark is before or at the cursor, and FALSE otherwise. */ 452 bool mark_is_before_cursor(void) 453 { 454 return (openfile->mark->lineno < openfile->current->lineno || 455 (openfile->mark == openfile->current && 456 openfile->mark_x <= openfile->current_x)); 457 } 458 459 /* Return in (top, top_x) and (bot, bot_x) the start and end "coordinates" 460 * of the marked region. */ 461 void get_region(linestruct **top, size_t *top_x, linestruct **bot, size_t *bot_x) 462 { 463 if (mark_is_before_cursor()) { 464 *top = openfile->mark; 465 *top_x = openfile->mark_x; 466 *bot = openfile->current; 467 *bot_x = openfile->current_x; 468 } else { 469 *bot = openfile->mark; 470 *bot_x = openfile->mark_x; 471 *top = openfile->current; 472 *top_x = openfile->current_x; 473 } 474 } 475 476 /* Get the set of lines to work on -- either just the current line, or the 477 * first to last lines of the marked region. When the cursor (or mark) is 478 * at the start of the last line of the region, exclude that line. */ 479 void get_range(linestruct **top, linestruct **bot) 480 { 481 if (!openfile->mark) { 482 *top = openfile->current; 483 *bot = openfile->current; 484 } else { 485 size_t top_x, bot_x; 486 487 get_region(top, &top_x, bot, &bot_x); 488 489 if (bot_x == 0 && *bot != *top && !also_the_last) 490 *bot = (*bot)->prev; 491 else 492 also_the_last = TRUE; 493 } 494 } 495 #endif /* !NANO_TINY */ 496 497 #if !defined(NANO_TINY) || defined(ENABLE_SPELLER) || defined (ENABLE_LINTER) || defined (ENABLE_FORMATTER) 498 /* Return a pointer to the line that has the given line number. */ 499 linestruct *line_from_number(ssize_t number) 500 { 501 linestruct *line = openfile->current; 502 503 if (line->lineno > number) 504 while (line->lineno != number) 505 line = line->prev; 506 else 507 while (line->lineno != number) 508 line = line->next; 509 510 return line; 511 } 512 #endif 513 514 /* Count the number of characters from begin to end, and return it. */ 515 size_t number_of_characters_in(const linestruct *begin, const linestruct *end) 516 { 517 const linestruct *line; 518 size_t count = 0; 519 520 /* Sum the number of characters (plus a newline) in each line. */ 521 for (line = begin; line != end->next; line = line->next) 522 count += mbstrlen(line->data) + 1; 523 524 /* Do not count the final newline. */ 525 return (count - 1); 526 }