summaryrefslogtreecommitdiff
path: root/src/utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils.c')
-rw-r--r--src/utils.c674
1 files changed, 674 insertions, 0 deletions
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..ff13a41
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,674 @@
+/* $Id: utils.c 4453 2009-12-02 03:36:22Z astyanax $ */
+/**************************************************************************
+ * utils.c *
+ * *
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
+ * 2008, 2009 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, 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, write to the Free Software *
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
+ * 02110-1301, USA. *
+ * *
+ **************************************************************************/
+
+#include "proto.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <ctype.h>
+#include <errno.h>
+
+/* Return the number of decimal digits in n. */
+int digits(size_t n)
+{
+ int i;
+
+ if (n == 0)
+ i = 1;
+ else {
+ for (i = 0; n != 0; n /= 10, i++)
+ ;
+ }
+
+ return i;
+}
+
+/* Return the user's home directory. We use $HOME, and if that fails,
+ * we fall back on the home directory of the effective user ID. */
+void get_homedir(void)
+{
+ if (homedir == NULL) {
+ const char *homenv = getenv("HOME");
+
+ if (homenv == NULL) {
+ const struct passwd *userage = getpwuid(geteuid());
+
+ if (userage != NULL)
+ homenv = userage->pw_dir;
+ }
+ homedir = mallocstrcpy(NULL, homenv);
+ }
+}
+
+/* Read a ssize_t from str, and store it in *val (if val is not NULL).
+ * On error, we return FALSE and don't change *val. Otherwise, we
+ * return TRUE. */
+bool parse_num(const char *str, ssize_t *val)
+{
+ char *first_error;
+ ssize_t j;
+
+ assert(str != NULL);
+
+ j = (ssize_t)strtol(str, &first_error, 10);
+
+ if (errno == ERANGE || *str == '\0' || *first_error != '\0')
+ return FALSE;
+
+ if (val != NULL)
+ *val = j;
+
+ return TRUE;
+}
+
+/* Read two ssize_t's, separated by a comma, from str, and store them in
+ * *line and *column (if they're not both NULL). Return FALSE on error,
+ * or TRUE otherwise. */
+bool parse_line_column(const char *str, ssize_t *line, ssize_t *column)
+{
+ bool retval = TRUE;
+ const char *comma;
+
+ assert(str != NULL);
+
+ comma = strchr(str, ',');
+
+ if (comma != NULL && column != NULL) {
+ if (!parse_num(comma + 1, column))
+ retval = FALSE;
+ }
+
+ if (line != NULL) {
+ if (comma != NULL) {
+ char *str_line = mallocstrncpy(NULL, str, comma - str + 1);
+ str_line[comma - str] = '\0';
+
+ if (str_line[0] != '\0' && !parse_num(str_line, line))
+ retval = FALSE;
+
+ free(str_line);
+ } else if (!parse_num(str, line))
+ retval = FALSE;
+ }
+
+ return retval;
+}
+
+/* Fix the memory allocation for a string. */
+void align(char **str)
+{
+ assert(str != NULL);
+
+ if (*str != NULL)
+ *str = charealloc(*str, strlen(*str) + 1);
+}
+
+/* Null a string at a certain index and align it. */
+void null_at(char **data, size_t index)
+{
+ assert(data != NULL);
+
+ *data = charealloc(*data, index + 1);
+ (*data)[index] = '\0';
+}
+
+/* For non-null-terminated lines. A line, by definition, shouldn't
+ * normally have newlines in it, so encode its nulls as newlines. */
+void unsunder(char *str, size_t true_len)
+{
+ assert(str != NULL);
+
+ for (; true_len > 0; true_len--, str++) {
+ if (*str == '\0')
+ *str = '\n';
+ }
+}
+
+/* For non-null-terminated lines. A line, by definition, shouldn't
+ * normally have newlines in it, so decode its newlines as nulls. */
+void sunder(char *str)
+{
+ assert(str != NULL);
+
+ for (; *str != '\0'; str++) {
+ if (*str == '\n')
+ *str = '\0';
+ }
+}
+
+/* These functions, ngetline() (originally getline()) and ngetdelim()
+ * (originally getdelim()), were adapted from GNU mailutils 0.5
+ * (mailbox/getline.c). Here is the notice from that file, after
+ * converting to the GPL via LGPL clause 3, and with the Free Software
+ * Foundation's address and the copyright years updated:
+ *
+ * GNU Mailutils -- a suite of utilities for electronic mail
+ * Copyright (C) 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007
+ * Free Software Foundation, Inc.
+ *
+ * This library 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 library 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301, USA. */
+
+#ifdef ENABLE_NANORC
+
+#ifndef HAVE_GETDELIM
+/* This function is equivalent to getdelim(). */
+ssize_t ngetdelim(char **lineptr, size_t *n, int delim, FILE *stream)
+{
+ size_t indx = 0;
+ int c;
+
+ /* Sanity checks. */
+ if (lineptr == NULL || n == NULL || stream == NULL ||
+ fileno(stream) == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Allocate the line the first time. */
+ if (*lineptr == NULL) {
+ *n = MAX_BUF_SIZE;
+ *lineptr = charalloc(*n);
+ }
+
+ while ((c = getc(stream)) != EOF) {
+ /* Check if more memory is needed. */
+ if (indx >= *n) {
+ *n += MAX_BUF_SIZE;
+ *lineptr = charealloc(*lineptr, *n);
+ }
+
+ /* Put the result in the line. */
+ (*lineptr)[indx++] = (char)c;
+
+ /* Bail out. */
+ if (c == delim)
+ break;
+ }
+
+ /* Make room for the null character. */
+ if (indx >= *n) {
+ *n += MAX_BUF_SIZE;
+ *lineptr = charealloc(*lineptr, *n);
+ }
+
+ /* Null-terminate the buffer. */
+ null_at(lineptr, indx++);
+ *n = indx;
+
+ /* The last line may not have the delimiter. We have to return what
+ * we got, and the error will be seen on the next iteration. */
+ return (c == EOF && (indx - 1) == 0) ? -1 : indx - 1;
+}
+#endif
+
+#ifndef HAVE_GETLINE
+/* This function is equivalent to getline(). */
+ssize_t ngetline(char **lineptr, size_t *n, FILE *stream)
+{
+ return getdelim(lineptr, n, '\n', stream);
+}
+#endif
+#endif /* ENABLE_NANORC */
+
+#ifdef HAVE_REGEX_H
+/* Do the compiled regex in preg and the regex in string match the
+ * beginning or end of a line? */
+bool regexp_bol_or_eol(const regex_t *preg, const char *string)
+{
+ return (regexec(preg, string, 0, NULL, 0) == 0 &&
+ regexec(preg, string, 0, NULL, REG_NOTBOL | REG_NOTEOL) ==
+ REG_NOMATCH);
+}
+
+/* Fix the regex if we're on platforms which requires an adjustment
+ * from GNU-style to BSD-style word boundaries. */
+const char *fixbounds(const char *r) {
+#ifndef GNU_WORDBOUNDS
+ int i, j = 0;
+ char *r2 = charalloc(strlen(r) * 5);
+ char *r3;
+
+#ifdef DEBUG
+ fprintf(stderr, "fixbounds(): Start string = \"%s\"\n", r);
+#endif
+
+ for (i = 0; i < strlen(r); i++) {
+ if (r[i] != '\0' && r[i] == '\\' && (r[i+1] == '>' || r[i+1] == '<')) {
+ strcpy(&r2[j], "[[:");
+ r2[j+3] = r[i+1];
+ strcpy(&r2[j+4], ":]]");
+ i++;
+ j += 6;
+ } else
+ r2[j] = r[i];
+ j++;
+ }
+ r2[j] = '\0';
+ r3 = mallocstrcpy(NULL, r2);
+ free(r2);
+#ifdef DEBUG
+ fprintf(stderr, "fixbounds(): Ending string = \"%s\"\n", r3);
+#endif
+ return (const char *) r3;
+#endif
+
+ return r;
+}
+
+#endif
+
+#ifndef DISABLE_SPELLER
+/* Is the word starting at position pos in buf a whole word? */
+bool is_whole_word(size_t pos, const char *buf, const char *word)
+{
+ char *p = charalloc(mb_cur_max()), *r = charalloc(mb_cur_max());
+ size_t word_end = pos + strlen(word);
+ bool retval;
+
+ assert(buf != NULL && pos <= strlen(buf) && word != NULL);
+
+ parse_mbchar(buf + move_mbleft(buf, pos), p, NULL);
+ parse_mbchar(buf + word_end, r, NULL);
+
+ /* If we're at the beginning of the line or the character before the
+ * word isn't a non-punctuation "word" character, and if we're at
+ * the end of the line or the character after the word isn't a
+ * non-punctuation "word" character, we have a whole word. */
+ retval = (pos == 0 || !is_word_mbchar(p, FALSE)) &&
+ (word_end == strlen(buf) || !is_word_mbchar(r, FALSE));
+
+ free(p);
+ free(r);
+
+ return retval;
+}
+#endif /* !DISABLE_SPELLER */
+
+/* If we are searching backwards, we will find the last match that
+ * starts no later than start. Otherwise we find the first match
+ * starting no earlier than start. If we are doing a regexp search, we
+ * fill in the global variable regmatches with at most 9 subexpression
+ * matches. Also, all .rm_so elements are relative to the start of the
+ * whole match, so regmatches[0].rm_so == 0. */
+const char *strstrwrapper(const char *haystack, const char *needle,
+ const char *start)
+{
+ /* start can be 1 character before the start or after the end of the
+ * line. In either case, we just say no match was found. */
+ if ((start > haystack && *(start - 1) == '\0') || start < haystack)
+ return NULL;
+
+ assert(haystack != NULL && needle != NULL && start != NULL);
+
+#ifdef HAVE_REGEX_H
+ if (ISSET(USE_REGEXP)) {
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH)) {
+ if (regexec(&search_regexp, haystack, 1, regmatches,
+ 0) == 0 && haystack + regmatches[0].rm_so <= start) {
+ const char *retval = haystack + regmatches[0].rm_so;
+
+ /* Search forward until there are no more matches. */
+ while (regexec(&search_regexp, retval + 1, 1,
+ regmatches, REG_NOTBOL) == 0 &&
+ retval + regmatches[0].rm_so + 1 <= start)
+ retval += regmatches[0].rm_so + 1;
+ /* Finally, put the subexpression matches in global
+ * variable regmatches. The REG_NOTBOL flag doesn't
+ * matter now. */
+ regexec(&search_regexp, retval, 10, regmatches, 0);
+ return retval;
+ }
+ } else
+#endif /* !NANO_TINY */
+ if (regexec(&search_regexp, start, 10, regmatches,
+ (start > haystack) ? REG_NOTBOL : 0) == 0) {
+ const char *retval = start + regmatches[0].rm_so;
+
+ regexec(&search_regexp, retval, 10, regmatches, 0);
+ return retval;
+ }
+ return NULL;
+ }
+#endif /* HAVE_REGEX_H */
+#if !defined(NANO_TINY) || !defined(DISABLE_SPELLER)
+ if (ISSET(CASE_SENSITIVE)) {
+#ifndef NANO_TINY
+ if (ISSET(BACKWARDS_SEARCH))
+ return revstrstr(haystack, needle, start);
+ else
+#endif
+ return strstr(start, needle);
+ }
+#endif /* !DISABLE_SPELLER || !NANO_TINY */
+#ifndef NANO_TINY
+ else if (ISSET(BACKWARDS_SEARCH))
+ return mbrevstrcasestr(haystack, needle, start);
+#endif
+ return mbstrcasestr(start, needle);
+}
+
+/* This is a wrapper for the perror() function. The wrapper temporarily
+ * leaves curses mode, calls perror() (which writes to stderr), and then
+ * reenters curses mode, updating the screen in the process. Note that
+ * nperror() causes the window to flicker once. */
+void nperror(const char *s)
+{
+ endwin();
+ perror(s);
+ doupdate();
+}
+
+/* This is a wrapper for the malloc() function that properly handles
+ * things when we run out of memory. Thanks, BG, many people have been
+ * asking for this... */
+void *nmalloc(size_t howmuch)
+{
+ void *r = malloc(howmuch);
+
+ if (r == NULL && howmuch != 0)
+ die(_("nano is out of memory!"));
+
+ return r;
+}
+
+/* This is a wrapper for the realloc() function that properly handles
+ * things when we run out of memory. */
+void *nrealloc(void *ptr, size_t howmuch)
+{
+ void *r = realloc(ptr, howmuch);
+
+ if (r == NULL && howmuch != 0)
+ die(_("nano is out of memory!"));
+
+ return r;
+}
+
+/* Copy the first n characters of one malloc()ed string to another
+ * pointer. Should be used as: "dest = mallocstrncpy(dest, src,
+ * n);". */
+char *mallocstrncpy(char *dest, const char *src, size_t n)
+{
+ if (src == NULL)
+ src = "";
+
+ if (src != dest)
+ free(dest);
+
+ dest = charalloc(n);
+ strncpy(dest, src, n);
+
+ return dest;
+}
+
+/* Copy one malloc()ed string to another pointer. Should be used as:
+ * "dest = mallocstrcpy(dest, src);". */
+char *mallocstrcpy(char *dest, const char *src)
+{
+ return mallocstrncpy(dest, src, (src == NULL) ? 1 :
+ strlen(src) + 1);
+}
+
+/* Free the malloc()ed string at dest and return the malloc()ed string
+ * at src. Should be used as: "answer = mallocstrassn(answer,
+ * real_dir_from_tilde(answer));". */
+char *mallocstrassn(char *dest, char *src)
+{
+ free(dest);
+ return src;
+}
+
+/* nano scrolls horizontally within a line in chunks. Return the column
+ * number of the first character displayed in the edit window when the
+ * cursor is at the given column. Note that (0 <= column -
+ * get_page_start(column) < COLS). */
+size_t get_page_start(size_t column)
+{
+ if (column == 0 || column < COLS - 1)
+ return 0;
+ else if (COLS > 8)
+ return column - 7 - (column - 7) % (COLS - 8);
+ else
+ return column - (COLS - 2);
+}
+
+/* Return the placewewant associated with current_x, i.e. the zero-based
+ * column position of the cursor. The value will be no smaller than
+ * current_x. */
+size_t xplustabs(void)
+{
+ return strnlenpt(openfile->current->data, openfile->current_x);
+}
+
+/* Return the index in s of the character displayed at the given column,
+ * i.e. the largest value such that strnlenpt(s, actual_x(s, column)) <=
+ * column. */
+size_t actual_x(const char *s, size_t column)
+{
+ size_t i = 0;
+ /* The position in s, returned. */
+ size_t len = 0;
+ /* The screen display width to s[i]. */
+
+ assert(s != NULL);
+
+ while (*s != '\0') {
+ int s_len = parse_mbchar(s, NULL, &len);
+
+ if (len > column)
+ break;
+
+ i += s_len;
+ s += s_len;
+ }
+
+ return i;
+}
+
+/* A strnlen() with tabs and multicolumn characters factored in, similar
+ * to xplustabs(). How many columns wide are the first maxlen characters
+ * of s? */
+size_t strnlenpt(const char *s, size_t maxlen)
+{
+ size_t len = 0;
+ /* The screen display width to s[i]. */
+
+ if (maxlen == 0)
+ return 0;
+
+ assert(s != NULL);
+
+ while (*s != '\0') {
+ int s_len = parse_mbchar(s, NULL, &len);
+
+ s += s_len;
+
+ if (maxlen <= s_len)
+ break;
+
+ maxlen -= s_len;
+ }
+
+ return len;
+}
+
+/* A strlen() with tabs and multicolumn characters factored in, similar
+ * to xplustabs(). How many columns wide is s? */
+size_t strlenpt(const char *s)
+{
+ return strnlenpt(s, (size_t)-1);
+}
+
+/* Append a new magicline to filebot. */
+void new_magicline(void)
+{
+ openfile->filebot->next = (filestruct *)nmalloc(sizeof(filestruct));
+ openfile->filebot->next->data = mallocstrcpy(NULL, "");
+ openfile->filebot->next->prev = openfile->filebot;
+ openfile->filebot->next->next = NULL;
+ openfile->filebot->next->lineno = openfile->filebot->lineno + 1;
+#ifdef ENABLE_COLOR
+ openfile->filebot->next->multidata = NULL;
+#endif
+ openfile->filebot = openfile->filebot->next;
+ openfile->totsize++;
+}
+
+#ifndef NANO_TINY
+/* Remove the magicline from filebot, if there is one and it isn't the
+ * only line in the file. Assume that edittop and current are not at
+ * filebot. */
+void remove_magicline(void)
+{
+ if (openfile->filebot->data[0] == '\0' &&
+ openfile->filebot != openfile->fileage) {
+ assert(openfile->filebot != openfile->edittop && openfile->filebot != openfile->current);
+
+ openfile->filebot = openfile->filebot->prev;
+ free_filestruct(openfile->filebot->next);
+ openfile->filebot->next = NULL;
+ openfile->totsize--;
+ }
+}
+
+/* Set top_x and bot_x to the top and bottom x-coordinates of the mark,
+ * respectively, based on the locations of top and bot. If
+ * right_side_up isn't NULL, set it to TRUE if the mark begins with
+ * (mark_begin, mark_begin_x) and ends with (current, current_x), or
+ * FALSE otherwise. */
+void mark_order(const filestruct **top, size_t *top_x, const filestruct
+ **bot, size_t *bot_x, bool *right_side_up)
+{
+ assert(top != NULL && top_x != NULL && bot != NULL && bot_x != NULL);
+
+ if ((openfile->current->lineno == openfile->mark_begin->lineno &&
+ openfile->current_x > openfile->mark_begin_x) ||
+ openfile->current->lineno > openfile->mark_begin->lineno) {
+ *top = openfile->mark_begin;
+ *top_x = openfile->mark_begin_x;
+ *bot = openfile->current;
+ *bot_x = openfile->current_x;
+ if (right_side_up != NULL)
+ *right_side_up = TRUE;
+ } else {
+ *bot = openfile->mark_begin;
+ *bot_x = openfile->mark_begin_x;
+ *top = openfile->current;
+ *top_x = openfile->current_x;
+ if (right_side_up != NULL)
+ *right_side_up = FALSE;
+ }
+}
+#endif
+
+/* Calculate the number of characters between begin and end, and return
+ * it. */
+size_t get_totsize(const filestruct *begin, const filestruct *end)
+{
+ size_t totsize = 0;
+ const filestruct *f;
+
+ /* Go through the lines from begin to end->prev, if we can. */
+ for (f = begin; f != end && f != NULL; f = f->next) {
+ /* Count the number of characters on this line. */
+ totsize += mbstrlen(f->data);
+
+ /* Count the newline if we have one. */
+ if (f->next != NULL)
+ totsize++;
+ }
+
+ /* Go through the line at end, if we can. */
+ if (f != NULL) {
+ /* Count the number of characters on this line. */
+ totsize += mbstrlen(f->data);
+
+ /* Count the newline if we have one. */
+ if (f->next != NULL)
+ totsize++;
+ }
+
+ return totsize;
+}
+
+/* Get back a pointer given a line number in the current openfilestruct */
+filestruct *fsfromline(ssize_t lineno)
+{
+ filestruct *f = openfile->current;
+
+ if (lineno <= openfile->current->lineno)
+ for (; f->lineno != lineno && f != openfile->fileage; f = f->prev)
+ ;
+ else
+ for (; f->lineno != lineno && f->next != NULL; f = f->next)
+ ;
+
+ if (f->lineno != lineno)
+ f = NULL;
+ return f;
+}
+
+#ifdef DEBUG
+/* Dump the filestruct inptr to stderr. */
+void dump_filestruct(const filestruct *inptr)
+{
+ if (inptr == openfile->fileage)
+ fprintf(stderr, "Dumping file buffer to stderr...\n");
+ else if (inptr == cutbuffer)
+ fprintf(stderr, "Dumping cutbuffer to stderr...\n");
+ else
+ fprintf(stderr, "Dumping a buffer to stderr...\n");
+
+ while (inptr != NULL) {
+ fprintf(stderr, "(%ld) %s\n", (long)inptr->lineno, inptr->data);
+ inptr = inptr->next;
+ }
+}
+
+/* Dump the current buffer's filestruct to stderr in reverse. */
+void dump_filestruct_reverse(void)
+{
+ const filestruct *fileptr = openfile->filebot;
+
+ while (fileptr != NULL) {
+ fprintf(stderr, "(%ld) %s\n", (long)fileptr->lineno,
+ fileptr->data);
+ fileptr = fileptr->prev;
+ }
+}
+#endif /* DEBUG */