1/* $Id: utils.c,v 1.124 2007/01/01 05:15:32 dolorous Exp $ */ 2/************************************************************************** 3 * utils.c * 4 * * 5 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 Chris Allegretta * 6 * Copyright (C) 2005, 2006, 2007 David Lawrence Ramsey * 7 * This program is free software; you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published by * 9 * the Free Software Foundation; either version 2, or (at your option) * 10 * any later version. * 11 * * 12 * This program is distributed in the hope that it will be useful, but * 13 * WITHOUT ANY WARRANTY; without even the implied warranty of * 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 15 * 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, write to the Free Software * 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 20 * 02110-1301, USA. * 21 * * 22 **************************************************************************/ 23 24#include "proto.h" 25 26#include <string.h> 27#include <stdio.h> 28#include <unistd.h> 29#include <pwd.h> 30#include <ctype.h> 31#include <errno.h> 32 33/* Return the number of decimal digits in n. */ 34int digits(size_t n) 35{ 36 int i; 37 38 if (n == 0) 39 i = 1; 40 else { 41 for (i = 0; n != 0; n /= 10, i++) 42 ; 43 } 44 45 return i; 46} 47 48/* Return the user's home directory. We use $HOME, and if that fails, 49 * we fall back on the home directory of the effective user ID. */ 50void get_homedir(void) 51{ 52 if (homedir == NULL) { 53 const char *homenv = getenv("HOME"); 54 55 if (homenv == NULL) { 56 const struct passwd *userage = getpwuid(geteuid()); 57 58 if (userage != NULL) 59 homenv = userage->pw_dir; 60 } 61 homedir = mallocstrcpy(NULL, homenv); 62 } 63} 64 65/* Read a ssize_t from str, and store it in *val (if val is not NULL). 66 * On error, we return FALSE and don't change *val. Otherwise, we 67 * return TRUE. */ 68bool parse_num(const char *str, ssize_t *val) 69{ 70 char *first_error; 71 ssize_t j; 72 73 assert(str != NULL); 74 75 j = (ssize_t)strtol(str, &first_error, 10); 76 77 if (errno == ERANGE || *str == '\0' || *first_error != '\0') 78 return FALSE; 79 80 if (val != NULL) 81 *val = j; 82 83 return TRUE; 84} 85 86/* Read two ssize_t's, separated by a comma, from str, and store them in 87 * *line and *column (if they're not both NULL). Return FALSE on error, 88 * or TRUE otherwise. */ 89bool parse_line_column(const char *str, ssize_t *line, ssize_t *column) 90{ 91 bool retval = TRUE; 92 const char *comma; 93 94 assert(str != NULL); 95 96 comma = strchr(str, ','); 97 98 if (comma != NULL && column != NULL) { 99 if (!parse_num(comma + 1, column)) 100 retval = FALSE; 101 } 102 103 if (line != NULL) { 104 if (comma != NULL) { 105 char *str_line = mallocstrncpy(NULL, str, comma - str + 1); 106 str_line[comma - str] = '\0'; 107 108 if (str_line[0] != '\0' && !parse_num(str_line, line)) 109 retval = FALSE; 110 111 free(str_line); 112 } else if (!parse_num(str, line)) 113 retval = FALSE; 114 } 115 116 return retval; 117} 118 119/* Fix the memory allocation for a string. */ 120void align(char **str) 121{ 122 assert(str != NULL); 123 124 if (*str != NULL) 125 *str = charealloc(*str, strlen(*str) + 1); 126} 127 128/* Null a string at a certain index and align it. */ 129void null_at(char **data, size_t index) 130{ 131 assert(data != NULL); 132 133 *data = charealloc(*data, index + 1); 134 (*data)[index] = '\0'; 135} 136 137/* For non-null-terminated lines. A line, by definition, shouldn't 138 * normally have newlines in it, so encode its nulls as newlines. */ 139void unsunder(char *str, size_t true_len) 140{ 141 assert(str != NULL); 142 143 for (; true_len > 0; true_len--, str++) { 144 if (*str == '\0') 145 *str = '\n'; 146 } 147} 148 149/* For non-null-terminated lines. A line, by definition, shouldn't 150 * normally have newlines in it, so decode its newlines as nulls. */ 151void sunder(char *str) 152{ 153 assert(str != NULL); 154 155 for (; *str != '\0'; str++) { 156 if (*str == '\n') 157 *str = '\0'; 158 } 159} 160 161/* These functions, ngetline() (originally getline()) and ngetdelim() 162 * (originally getdelim()), were adapted from GNU mailutils 0.5 163 * (mailbox/getline.c). Here is the notice from that file, after 164 * converting to the GPL via LGPL clause 3, and with the Free Software 165 * Foundation's address updated: 166 * 167 * GNU Mailutils -- a suite of utilities for electronic mail 168 * Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation, Inc. 169 * 170 * This library is free software; you can redistribute it and/or 171 * modify it under the terms of the GNU General Public License as 172 * published by the Free Software Foundation; either version 2 of the 173 * License, or (at your option) any later version. 174 * 175 * This library is distributed in the hope that it will be useful, 176 * but WITHOUT ANY WARRANTY; without even the implied warranty of 177 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 178 * General Public License for more details. 179 * 180 * You should have received a copy of the GNU General Public License 181 * along with this library; if not, write to the Free Software 182 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 183 * 02110-1301, USA. */ 184 185#if !defined(NANO_TINY) && defined(ENABLE_NANORC) 186#ifndef HAVE_GETLINE 187/* This function is equivalent to getline(). */ 188ssize_t ngetline(char **lineptr, size_t *n, FILE *stream) 189{ 190 return getdelim(lineptr, n, '\n', stream); 191} 192#endif 193 194#ifndef HAVE_GETDELIM 195/* This function is equivalent to getdelim(). */ 196ssize_t ngetdelim(char **lineptr, size_t *n, int delim, FILE *stream) 197{ 198 size_t indx = 0; 199 int c; 200 201 /* Sanity checks. */ 202 if (lineptr == NULL || n == NULL || stream == NULL || 203 fileno(stream) == -1) { 204 errno = EINVAL; 205 return -1; 206 } 207 208 /* Allocate the line the first time. */ 209 if (*lineptr == NULL) { 210 *lineptr = charalloc(MAX_BUF_SIZE); 211 *n = MAX_BUF_SIZE; 212 } 213 214 while ((c = getc(stream)) != EOF) { 215 /* Check if more memory is needed. */ 216 if (indx >= *n) { 217 *lineptr = charealloc(*lineptr, *n + MAX_BUF_SIZE); 218 *n += MAX_BUF_SIZE; 219 } 220 221 /* Put the result in the line. */ 222 (*lineptr)[indx++] = (char)c; 223 224 /* Bail out. */ 225 if (c == delim) 226 break; 227 } 228 229 /* Make room for the null character. */ 230 if (indx >= *n) { 231 *lineptr = charealloc(*lineptr, *n + MAX_BUF_SIZE); 232 *n += MAX_BUF_SIZE; 233 } 234 235 /* Null-terminate the buffer. */ 236 null_at(lineptr, indx++); 237 *n = indx; 238 239 /* The last line may not have the delimiter. We have to return what 240 * we got, and the error will be seen on the next iteration. */ 241 return (c == EOF && (indx - 1) == 0) ? -1 : indx - 1; 242} 243#endif 244#endif /* !NANO_TINY && ENABLE_NANORC */ 245 246#ifdef HAVE_REGEX_H 247/* Do the compiled regex in preg and the regex in string match the 248 * beginning or end of a line? */ 249bool regexp_bol_or_eol(const regex_t *preg, const char *string) 250{ 251 return (regexec(preg, string, 0, NULL, 0) == 0 && 252 regexec(preg, string, 0, NULL, REG_NOTBOL | REG_NOTEOL) == 253 REG_NOMATCH); 254} 255#endif 256 257#ifndef DISABLE_SPELLER 258/* Is the word starting at position pos in buf a whole word? */ 259bool is_whole_word(size_t pos, const char *buf, const char *word) 260{ 261 char *p = charalloc(mb_cur_max()), *r = charalloc(mb_cur_max()); 262 size_t word_end = pos + strlen(word); 263 bool retval; 264 265 assert(buf != NULL && pos <= strlen(buf) && word != NULL); 266 267 parse_mbchar(buf + move_mbleft(buf, pos), p, NULL); 268 parse_mbchar(buf + word_end, r, NULL); 269 270 /* If we're at the beginning of the line or the character before the 271 * word isn't a non-punctuation "word" character, and if we're at 272 * the end of the line or the character after the word isn't a 273 * non-punctuation "word" character, we have a whole word. */ 274 retval = (pos == 0 || !is_word_mbchar(p, FALSE)) && 275 (word_end == strlen(buf) || !is_word_mbchar(r, FALSE)); 276 277 free(p); 278 free(r); 279 280 return retval; 281} 282#endif /* !DISABLE_SPELLER */ 283 284/* If we are searching backwards, we will find the last match that 285 * starts no later than start. Otherwise we find the first match 286 * starting no earlier than start. If we are doing a regexp search, we 287 * fill in the global variable regmatches with at most 9 subexpression 288 * matches. Also, all .rm_so elements are relative to the start of the 289 * whole match, so regmatches[0].rm_so == 0. */ 290const char *strstrwrapper(const char *haystack, const char *needle, 291 const char *start) 292{ 293 /* start can be 1 character before the start or after the end of the 294 * line. In either case, we just say no match was found. */ 295 if ((start > haystack && *(start - 1) == '\0') || start < haystack) 296 return NULL; 297 298 assert(haystack != NULL && needle != NULL && start != NULL); 299 300#ifdef HAVE_REGEX_H 301 if (ISSET(USE_REGEXP)) { 302#ifndef NANO_TINY 303 if (ISSET(BACKWARDS_SEARCH)) { 304 if (regexec(&search_regexp, haystack, 1, regmatches, 305 0) == 0 && haystack + regmatches[0].rm_so <= start) { 306 const char *retval = haystack + regmatches[0].rm_so; 307 308 /* Search forward until there are no more matches. */ 309 while (regexec(&search_regexp, retval + 1, 1, 310 regmatches, REG_NOTBOL) == 0 && 311 retval + regmatches[0].rm_so + 1 <= start) 312 retval += regmatches[0].rm_so + 1; 313 /* Finally, put the subexpression matches in global 314 * variable regmatches. The REG_NOTBOL flag doesn't 315 * matter now. */ 316 regexec(&search_regexp, retval, 10, regmatches, 0); 317 return retval; 318 } 319 } else 320#endif /* !NANO_TINY */ 321 if (regexec(&search_regexp, start, 10, regmatches, 322 (start > haystack) ? REG_NOTBOL : 0) == 0) { 323 const char *retval = start + regmatches[0].rm_so; 324 325 regexec(&search_regexp, retval, 10, regmatches, 0); 326 return retval; 327 } 328 return NULL; 329 } 330#endif /* HAVE_REGEX_H */ 331#if !defined(NANO_TINY) || !defined(DISABLE_SPELLER) 332 if (ISSET(CASE_SENSITIVE)) { 333#ifndef NANO_TINY 334 if (ISSET(BACKWARDS_SEARCH)) 335 return revstrstr(haystack, needle, start); 336 else 337#endif 338 return strstr(start, needle); 339 } 340#endif /* !DISABLE_SPELLER || !NANO_TINY */ 341#ifndef NANO_TINY 342 else if (ISSET(BACKWARDS_SEARCH)) 343 return mbrevstrcasestr(haystack, needle, start); 344#endif 345 return mbstrcasestr(start, needle); 346} 347 348/* This is a wrapper for the perror() function. The wrapper temporarily 349 * leaves curses mode, calls perror() (which writes to stderr), and then 350 * reenters curses mode, updating the screen in the process. Note that 351 * nperror() causes the window to flicker once. */ 352void nperror(const char *s) 353{ 354 endwin(); 355 perror(s); 356 doupdate(); 357} 358 359/* This is a wrapper for the malloc() function that properly handles 360 * things when we run out of memory. Thanks, BG, many people have been 361 * asking for this... */ 362void *nmalloc(size_t howmuch) 363{ 364 void *r = malloc(howmuch); 365 366 if (r == NULL && howmuch != 0) 367 die(_("nano is out of memory!")); 368 369 return r; 370} 371 372/* This is a wrapper for the realloc() function that properly handles 373 * things when we run out of memory. */ 374void *nrealloc(void *ptr, size_t howmuch) 375{ 376 void *r = realloc(ptr, howmuch); 377 378 if (r == NULL && howmuch != 0) 379 die(_("nano is out of memory!")); 380 381 return r; 382} 383 384/* Copy the first n characters of one malloc()ed string to another 385 * pointer. Should be used as: "dest = mallocstrncpy(dest, src, 386 * n);". */ 387char *mallocstrncpy(char *dest, const char *src, size_t n) 388{ 389 if (src == NULL) 390 src = ""; 391 392 if (src != dest) 393 free(dest); 394 395 dest = charalloc(n); 396 strncpy(dest, src, n); 397 398 return dest; 399} 400 401/* Copy one malloc()ed string to another pointer. Should be used as: 402 * "dest = mallocstrcpy(dest, src);". */ 403char *mallocstrcpy(char *dest, const char *src) 404{ 405 return mallocstrncpy(dest, src, (src == NULL) ? 1 : 406 strlen(src) + 1); 407} 408 409/* Free the malloc()ed string at dest and return the malloc()ed string 410 * at src. Should be used as: "answer = mallocstrassn(answer, 411 * real_dir_from_tilde(answer));". */ 412char *mallocstrassn(char *dest, char *src) 413{ 414 free(dest); 415 return src; 416} 417 418/* nano scrolls horizontally within a line in chunks. Return the column 419 * number of the first character displayed in the edit window when the 420 * cursor is at the given column. Note that (0 <= column - 421 * get_page_start(column) < COLS). */ 422size_t get_page_start(size_t column) 423{ 424 if (column == 0 || column < COLS - 1) 425 return 0; 426 else if (COLS > 8) 427 return column - 7 - (column - 7) % (COLS - 8); 428 else 429 return column - (COLS - 2); 430} 431 432/* Return the placewewant associated with current_x, i.e. the zero-based 433 * column position of the cursor. The value will be no smaller than 434 * current_x. */ 435size_t xplustabs(void) 436{ 437 return strnlenpt(openfile->current->data, openfile->current_x); 438} 439 440/* Return the index in s of the character displayed at the given column, 441 * i.e. the largest value such that strnlenpt(s, actual_x(s, column)) <= 442 * column. */ 443size_t actual_x(const char *s, size_t column) 444{ 445 size_t i = 0; 446 /* The position in s, returned. */ 447 size_t len = 0; 448 /* The screen display width to s[i]. */ 449 450 assert(s != NULL); 451 452 while (*s != '\0') { 453 int s_len = parse_mbchar(s, NULL, &len); 454 455 if (len > column) 456 break; 457 458 i += s_len; 459 s += s_len; 460 } 461 462 return i; 463} 464 465/* A strnlen() with tabs and multicolumn characters factored in, similar 466 * to xplustabs(). How many columns wide are the first maxlen characters 467 * of s? */ 468size_t strnlenpt(const char *s, size_t maxlen) 469{ 470 size_t len = 0; 471 /* The screen display width to s[i]. */ 472 473 if (maxlen == 0) 474 return 0; 475 476 assert(s != NULL); 477 478 while (*s != '\0') { 479 int s_len = parse_mbchar(s, NULL, &len); 480 481 s += s_len; 482 483 if (maxlen <= s_len) 484 break; 485 486 maxlen -= s_len; 487 } 488 489 return len; 490} 491 492/* A strlen() with tabs and multicolumn characters factored in, similar 493 * to xplustabs(). How many columns wide is s? */ 494size_t strlenpt(const char *s) 495{ 496 return strnlenpt(s, (size_t)-1); 497} 498 499/* Append a new magicline to filebot. */ 500void new_magicline(void) 501{ 502 openfile->filebot->next = (filestruct *)nmalloc(sizeof(filestruct)); 503 openfile->filebot->next->data = mallocstrcpy(NULL, ""); 504 openfile->filebot->next->prev = openfile->filebot; 505 openfile->filebot->next->next = NULL; 506 openfile->filebot->next->lineno = openfile->filebot->lineno + 1; 507 openfile->filebot = openfile->filebot->next; 508 openfile->totsize++; 509} 510 511#ifndef NANO_TINY 512/* Remove the magicline from filebot, if there is one and it isn't the 513 * only line in the file. Assume that edittop and current are not at 514 * filebot. */ 515void remove_magicline(void) 516{ 517 if (openfile->filebot->data[0] == '\0' && 518 openfile->filebot != openfile->fileage) { 519 assert(openfile->filebot != openfile->edittop && openfile->filebot != openfile->current); 520 521 openfile->filebot = openfile->filebot->prev; 522 free_filestruct(openfile->filebot->next); 523 openfile->filebot->next = NULL; 524 openfile->totsize--; 525 } 526} 527 528/* Set top_x and bot_x to the top and bottom x-coordinates of the mark, 529 * respectively, based on the locations of top and bot. If 530 * right_side_up isn't NULL, set it to TRUE If the mark begins with 531 * (mark_begin, mark_begin_x) and ends with (current, current_x), or 532 * FALSE otherwise. */ 533void mark_order(const filestruct **top, size_t *top_x, const filestruct 534 **bot, size_t *bot_x, bool *right_side_up) 535{ 536 assert(top != NULL && top_x != NULL && bot != NULL && bot_x != NULL); 537 538 if ((openfile->current->lineno == openfile->mark_begin->lineno && 539 openfile->current_x > openfile->mark_begin_x) || 540 openfile->current->lineno > openfile->mark_begin->lineno) { 541 *top = openfile->mark_begin; 542 *top_x = openfile->mark_begin_x; 543 *bot = openfile->current; 544 *bot_x = openfile->current_x; 545 if (right_side_up != NULL) 546 *right_side_up = TRUE; 547 } else { 548 *bot = openfile->mark_begin; 549 *bot_x = openfile->mark_begin_x; 550 *top = openfile->current; 551 *top_x = openfile->current_x; 552 if (right_side_up != NULL) 553 *right_side_up = FALSE; 554 } 555} 556#endif 557 558/* Calculate the number of characters between begin and end, and return 559 * it. */ 560size_t get_totsize(const filestruct *begin, const filestruct *end) 561{ 562 size_t totsize = 0; 563 const filestruct *f; 564 565 /* Go through the lines from begin to end->prev, if we can. */ 566 for (f = begin; f != end && f != NULL; f = f->next) { 567 /* Count the number of characters on this line. */ 568 totsize += mbstrlen(f->data); 569 570 /* Count the newline if we have one. */ 571 if (f->next != NULL) 572 totsize++; 573 } 574 575 /* Go through the line at end, if we can. */ 576 if (f != NULL) { 577 /* Count the number of characters on this line. */ 578 totsize += mbstrlen(f->data); 579 580 /* Count the newline if we have one. */ 581 if (f->next != NULL) 582 totsize++; 583 } 584 585 return totsize; 586} 587 588#ifdef DEBUG 589/* Dump the filestruct inptr to stderr. */ 590void dump_filestruct(const filestruct *inptr) 591{ 592 if (inptr == openfile->fileage) 593 fprintf(stderr, "Dumping file buffer to stderr...\n"); 594 else if (inptr == cutbuffer) 595 fprintf(stderr, "Dumping cutbuffer to stderr...\n"); 596 else 597 fprintf(stderr, "Dumping a buffer to stderr...\n"); 598 599 while (inptr != NULL) { 600 fprintf(stderr, "(%ld) %s\n", (long)inptr->lineno, inptr->data); 601 inptr = inptr->next; 602 } 603} 604 605/* Dump the current buffer's filestruct to stderr in reverse. */ 606void dump_filestruct_reverse(void) 607{ 608 const filestruct *fileptr = openfile->filebot; 609 610 while (fileptr != NULL) { 611 fprintf(stderr, "(%ld) %s\n", (long)fileptr->lineno, 612 fileptr->data); 613 fileptr = fileptr->prev; 614 } 615} 616#endif /* DEBUG */ 617