diff --git a/src/Makefile.am b/src/Makefile.am index 7ac02ca3..625434c2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,6 +28,7 @@ nano_SOURCES = browser.c \ files.c \ global.c \ help.c \ + history.c \ move.c \ nano.c \ nano.h \ diff --git a/src/files.c b/src/files.c index d53c794a..37f61e33 100644 --- a/src/files.c +++ b/src/files.c @@ -2701,406 +2701,3 @@ const char *tail(const char *path) else return ++slash; } - -#ifndef DISABLE_HISTORIES -/* Return the constructed dirfile path, or NULL if we can't find the home - * directory. The string is dynamically allocated, and should be freed. */ -char *construct_filename(const char *str) -{ - char *newstr = NULL; - - if (homedir != NULL) { - size_t homelen = strlen(homedir); - - newstr = charalloc(homelen + strlen(str) + 1); - strcpy(newstr, homedir); - strcpy(newstr + homelen, str); - } - - return newstr; -} - -char *histfilename(void) -{ - return construct_filename("/.nano/search_history"); -} - -/* Construct the legacy history filename. */ -/* (To be removed in 2018.) */ -char *legacyhistfilename(void) -{ - return construct_filename("/.nano_history"); -} - -char *poshistfilename(void) -{ - return construct_filename("/.nano/filepos_history"); -} - -void history_error(const char *msg, ...) -{ - va_list ap; - - va_start(ap, msg); - vfprintf(stderr, _(msg), ap); - va_end(ap); - - fprintf(stderr, _("\nPress Enter to continue\n")); - while (getchar() != '\n') - ; -} - -/* Now that we have more than one history file, let's just rely on a - * .nano dir for this stuff. Return 1 if the dir exists or was - * successfully created, and return 0 otherwise. */ -int check_dotnano(void) -{ - int ret = 1; - struct stat dirstat; - char *nanodir = construct_filename("/.nano"); - - if (stat(nanodir, &dirstat) == -1) { - if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) { - history_error(N_("Unable to create directory %s: %s\n" - "It is required for saving/loading " - "search history or cursor positions.\n"), - nanodir, strerror(errno)); - ret = 0; - } - } else if (!S_ISDIR(dirstat.st_mode)) { - history_error(N_("Path %s is not a directory and needs to be.\n" - "Nano will be unable to load or save " - "search history or cursor positions.\n"), - nanodir); - ret = 0; - } - - free(nanodir); - return ret; -} - -/* Load the search and replace histories from ~/.nano/search_history. */ -void load_history(void) -{ - char *searchhist = histfilename(); - char *legacyhist = legacyhistfilename(); - struct stat hstat; - FILE *hist; - - /* If no home directory was found, we can't do anything. */ - if (searchhist == NULL || legacyhist == NULL) - return; - - /* If there is an old history file, migrate it. */ - /* (To be removed in 2018.) */ - if (stat(legacyhist, &hstat) != -1 && stat(searchhist, &hstat) == -1) { - if (rename(legacyhist, searchhist) == -1) - history_error(N_("Detected a legacy nano history file (%s) which I tried to move\n" - "to the preferred location (%s) but encountered an error: %s"), - legacyhist, searchhist, strerror(errno)); - else - history_error(N_("Detected a legacy nano history file (%s) which I moved\n" - "to the preferred location (%s)\n(see the nano FAQ about this change)"), - legacyhist, searchhist); - } - - hist = fopen(searchhist, "rb"); - - if (hist == NULL) { - if (errno != ENOENT) { - /* When reading failed, don't save history when we quit. */ - UNSET(HISTORYLOG); - history_error(N_("Error reading %s: %s"), searchhist, - strerror(errno)); - } - } else { - /* Load the two history lists -- first the search history, then - * the replace history -- from the oldest entry to the newest. - * The two lists are separated by an empty line. */ - filestruct **history = &search_history; - char *line = NULL; - size_t buf_len = 0; - ssize_t read; - - while ((read = getline(&line, &buf_len, hist)) > 0) { - line[--read] = '\0'; - if (read > 0) { - /* Encode any embedded NUL as 0x0A. */ - unsunder(line, read); - update_history(history, line); - } else if (history == &search_history) - history = &replace_history; - else - history = &execute_history; - - } - - fclose(hist); - free(line); - } - - free(searchhist); - free(legacyhist); -} - -/* Write the lines of a history list, starting with the line at head, to - * the open file at hist. Return TRUE if the write succeeded, and FALSE - * otherwise. */ -bool writehist(FILE *hist, const filestruct *head) -{ - const filestruct *item; - - /* Write a history list, from the oldest item to the newest. */ - for (item = head; item != NULL; item = item->next) { - size_t length = strlen(item->data); - - /* Decode 0x0A bytes as embedded NULs. */ - sunder(item->data); - - if (fwrite(item->data, sizeof(char), length, hist) < length) - return FALSE; - if (putc('\n', hist) == EOF) - return FALSE; - } - - return TRUE; -} - -/* Save the search and replace histories to ~/.nano/search_history. */ -void save_history(void) -{ - char *searchhist; - FILE *hist; - - /* If the histories are unchanged or empty, don't bother saving them. */ - if (!history_has_changed() || (searchbot->lineno == 1 && - replacebot->lineno == 1 && executebot->lineno == 1)) - return; - - searchhist = histfilename(); - - if (searchhist == NULL) - return; - - hist = fopen(searchhist, "wb"); - - if (hist == NULL) - fprintf(stderr, _("Error writing %s: %s\n"), searchhist, - strerror(errno)); - else { - /* Don't allow others to read or write the history file. */ - chmod(searchhist, S_IRUSR | S_IWUSR); - - if (!writehist(hist, searchage) || !writehist(hist, replaceage) || - !writehist(hist, executetop)) - fprintf(stderr, _("Error writing %s: %s\n"), searchhist, - strerror(errno)); - - fclose(hist); - } - - free(searchhist); -} - -/* Save the recorded last file positions to ~/.nano/filepos_history. */ -void save_poshistory(void) -{ - char *poshist = poshistfilename(); - poshiststruct *posptr; - FILE *hist; - - if (poshist == NULL) - return; - - hist = fopen(poshist, "wb"); - - if (hist == NULL) - fprintf(stderr, _("Error writing %s: %s\n"), poshist, strerror(errno)); - else { - /* Don't allow others to read or write the history file. */ - chmod(poshist, S_IRUSR | S_IWUSR); - - for (posptr = position_history; posptr != NULL; posptr = posptr->next) { - char *path_and_place; - size_t length; - - /* Assume 20 decimal positions each for line and column number, - * plus two spaces, plus the line feed, plus the null byte. */ - path_and_place = charalloc(strlen(posptr->filename) + 44); - sprintf(path_and_place, "%s %ld %ld\n", posptr->filename, - (long)posptr->lineno, (long)posptr->xno); - length = strlen(path_and_place); - - /* Encode newlines in filenames as nulls. */ - sunder(path_and_place); - /* Restore the terminating newline. */ - path_and_place[length - 1] = '\n'; - - if (fwrite(path_and_place, sizeof(char), length, hist) < length) - fprintf(stderr, _("Error writing %s: %s\n"), - poshist, strerror(errno)); - free(path_and_place); - } - fclose(hist); - } - free(poshist); -} - -/* Update the recorded last file positions, given a filename, a line - * and a column. If no entry is found, add a new one at the end. */ -void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos) -{ - poshiststruct *posptr, *theone, *posprev = NULL; - char *fullpath = get_full_path(filename); - - if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/' || inhelp) { - free(fullpath); - return; - } - - /* Look for a matching filename in the list. */ - for (posptr = position_history; posptr != NULL; posptr = posptr->next) { - if (!strcmp(posptr->filename, fullpath)) - break; - posprev = posptr; - } - - /* Don't record files that have the default cursor position. */ - if (lineno == 1 && xpos == 1) { - if (posptr != NULL) { - if (posprev == NULL) - position_history = posptr->next; - else - posprev->next = posptr->next; - free(posptr->filename); - free(posptr); - } - free(fullpath); - return; - } - - theone = posptr; - - /* If we didn't find it, make a new node; otherwise, if we're - * not at the end, move the matching one to the end. */ - if (theone == NULL) { - theone = (poshiststruct *)nmalloc(sizeof(poshiststruct)); - theone->filename = mallocstrcpy(NULL, fullpath); - if (position_history == NULL) - position_history = theone; - else - posprev->next = theone; - } else if (posptr->next != NULL) { - if (posprev == NULL) - position_history = posptr->next; - else - posprev->next = posptr->next; - while (posptr->next != NULL) - posptr = posptr->next; - posptr->next = theone; - } - - /* Store the last cursor position. */ - theone->lineno = lineno; - theone->xno = xpos; - theone->next = NULL; - - free(fullpath); -} - -/* Check whether the given file matches an existing entry in the recorded - * last file positions. If not, return FALSE. If yes, return TRUE and - * set line and column to the retrieved values. */ -bool has_old_position(const char *file, ssize_t *line, ssize_t *column) -{ - poshiststruct *posptr = position_history; - char *fullpath = get_full_path(file); - - if (fullpath == NULL) - return FALSE; - - while (posptr != NULL && strcmp(posptr->filename, fullpath) != 0) - posptr = posptr->next; - - free(fullpath); - - if (posptr == NULL) - return FALSE; - - *line = posptr->lineno; - *column = posptr->xno; - return TRUE; -} - -/* Load the recorded file positions from ~/.nano/filepos_history. */ -void load_poshistory(void) -{ - char *poshist = poshistfilename(); - FILE *hist; - - /* If the home directory is missing, do_rcfile() will have reported it. */ - if (poshist == NULL) - return; - - hist = fopen(poshist, "rb"); - - if (hist == NULL) { - if (errno != ENOENT) { - /* When reading failed, don't save history when we quit. */ - UNSET(POS_HISTORY); - history_error(N_("Error reading %s: %s"), poshist, strerror(errno)); - } - } else { - char *line = NULL, *lineptr, *xptr; - size_t buf_len = 0; - ssize_t read, count = 0; - poshiststruct *record_ptr = NULL, *newrecord; - - /* Read and parse each line, and store the extracted data. */ - while ((read = getline(&line, &buf_len, hist)) > 5) { - /* Decode nulls as embedded newlines. */ - unsunder(line, read); - - /* Find where the x index and line number are in the line. */ - xptr = revstrstr(line, " ", line + read - 3); - if (xptr == NULL) - continue; - lineptr = revstrstr(line, " ", xptr - 2); - if (lineptr == NULL) - continue; - - /* Now separate the three elements of the line. */ - *(xptr++) = '\0'; - *(lineptr++) = '\0'; - - /* Create a new position record. */ - newrecord = (poshiststruct *)nmalloc(sizeof(poshiststruct)); - newrecord->filename = mallocstrcpy(NULL, line); - newrecord->lineno = atoi(lineptr); - newrecord->xno = atoi(xptr); - newrecord->next = NULL; - - /* Add the record to the list. */ - if (position_history == NULL) - position_history = newrecord; - else - record_ptr->next = newrecord; - - record_ptr = newrecord; - - /* Impose a limit, so the file will not grow indefinitely. */ - if (++count > 200) { - poshiststruct *drop_record = position_history; - - position_history = position_history->next; - - free(drop_record->filename); - free(drop_record); - } - } - fclose(hist); - free(line); - } - free(poshist); -} -#endif /* !DISABLE_HISTORIES */ diff --git a/src/history.c b/src/history.c new file mode 100644 index 00000000..45684ac1 --- /dev/null +++ b/src/history.c @@ -0,0 +1,638 @@ +/************************************************************************** + * history.c -- This file is part of GNU nano. * + * * + * Copyright (C) 2003-2011, 2013-2017 Free Software Foundation, Inc. * + * Copyright (C) 2016 Benno Schulenberg * + * * + * GNU nano 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. * + * * + * GNU nano 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 "proto.h" + +#include +#include + +#ifndef DISABLE_HISTORIES +static bool history_changed = FALSE; + /* Have any of the history lists changed? */ + +/* Indicate whether any of the history lists have changed. */ +bool history_has_changed(void) +{ + return history_changed; +} + +/* Initialize the search and replace history lists. */ +void history_init(void) +{ + search_history = make_new_node(NULL); + search_history->data = mallocstrcpy(NULL, ""); + searchage = search_history; + searchbot = search_history; + + replace_history = make_new_node(NULL); + replace_history->data = mallocstrcpy(NULL, ""); + replaceage = replace_history; + replacebot = replace_history; + + execute_history = make_new_node(NULL); + execute_history->data = mallocstrcpy(NULL, ""); + executetop = execute_history; + executebot = execute_history; +} + +/* Set the current position in the history list h to the bottom. */ +void history_reset(const filestruct *h) +{ + if (h == search_history) + search_history = searchbot; + else if (h == replace_history) + replace_history = replacebot; + else if (h == execute_history) + execute_history = executebot; +} + +/* Return the first node containing the first len characters of the + * string s in the history list, starting at h_start and ending at + * h_end, or NULL if there isn't one. */ +filestruct *find_history(const filestruct *h_start, const filestruct + *h_end, const char *s, size_t len) +{ + const filestruct *p; + + for (p = h_start; p != h_end->prev && p != NULL; p = p->prev) { + if (strncmp(s, p->data, len) == 0) + return (filestruct *)p; + } + + return NULL; +} + +/* Update a history list (the one in which h is the current position) + * with a fresh string s. That is: add s, or move it to the end. */ +void update_history(filestruct **h, const char *s) +{ + filestruct **hage = NULL, **hbot = NULL, *thesame; + + assert(h != NULL && s != NULL); + + if (*h == search_history) { + hage = &searchage; + hbot = &searchbot; + } else if (*h == replace_history) { + hage = &replaceage; + hbot = &replacebot; + } else if (*h == execute_history) { + hage = &executetop; + hbot = &executebot; + } + + assert(hage != NULL && hbot != NULL); + + /* See if the string is already in the history. */ + thesame = find_history(*hbot, *hage, s, HIGHEST_POSITIVE); + + /* If an identical string was found, delete that item. */ + if (thesame != NULL) { + filestruct *after = thesame->next; + + /* If the string is at the head of the list, move the head. */ + if (thesame == *hage) + *hage = after; + + unlink_node(thesame); + renumber(after); + } + + /* If the history is full, delete the oldest item (the one at the + * head of the list), to make room for a new item at the end. */ + if ((*hbot)->lineno == MAX_SEARCH_HISTORY + 1) { + filestruct *oldest = *hage; + + *hage = (*hage)->next; + unlink_node(oldest); + renumber(*hage); + } + + /* Store the fresh string in the last item, then create a new item. */ + (*hbot)->data = mallocstrcpy((*hbot)->data, s); + splice_node(*hbot, make_new_node(*hbot)); + *hbot = (*hbot)->next; + (*hbot)->data = mallocstrcpy(NULL, ""); + + /* Indicate that the history needs to be saved on exit. */ + history_changed = TRUE; + + /* Set the current position in the list to the bottom. */ + *h = *hbot; +} + +/* Move h to the string in the history list just before it, and return + * that string. If there isn't one, don't move h and return NULL. */ +char *get_history_older(filestruct **h) +{ + assert(h != NULL); + + if ((*h)->prev == NULL) + return NULL; + + *h = (*h)->prev; + + return (*h)->data; +} + +/* Move h to the string in the history list just after it, and return + * that string. If there isn't one, don't move h and return NULL. */ +char *get_history_newer(filestruct **h) +{ + assert(h != NULL); + + if ((*h)->next == NULL) + return NULL; + + *h = (*h)->next; + + return (*h)->data; +} + +/* More placeholders. */ +void get_history_newer_void(void) +{ + ; +} +void get_history_older_void(void) +{ + ; +} + +#ifdef ENABLE_TABCOMP +/* Move h to the next string that's a tab completion of the string s, + * looking at only the first len characters of s, and return that + * string. If there isn't one, or if len is 0, don't move h and return + * s. */ +char *get_history_completion(filestruct **h, char *s, size_t len) +{ + assert(s != NULL); + + if (len > 0) { + filestruct *hage = NULL, *hbot = NULL, *p; + + assert(h != NULL); + + if (*h == search_history) { + hage = searchage; + hbot = searchbot; + } else if (*h == replace_history) { + hage = replaceage; + hbot = replacebot; + } else if (*h == execute_history) { + hage = executetop; + hbot = executebot; + } + + assert(hage != NULL && hbot != NULL); + + /* Search the history list from the current position to the top + * for a match of len characters. Skip over an exact match. */ + p = find_history((*h)->prev, hage, s, len); + + while (p != NULL && strcmp(p->data, s) == 0) + p = find_history(p->prev, hage, s, len); + + if (p != NULL) { + *h = p; + return mallocstrcpy(s, (*h)->data); + } + + /* Search the history list from the bottom to the current position + * for a match of len characters. Skip over an exact match. */ + p = find_history(hbot, *h, s, len); + + while (p != NULL && strcmp(p->data, s) == 0) + p = find_history(p->prev, *h, s, len); + + if (p != NULL) { + *h = p; + return mallocstrcpy(s, (*h)->data); + } + } + + /* If we're here, we didn't find a match, we didn't find an inexact + * match, or len is 0. Return s. */ + return (char *)s; +} +#endif /* ENSABLE_TABCOMP */ + +/* Return the constructed dirfile path, or NULL if we can't find the home + * directory. The string is dynamically allocated, and should be freed. */ +char *construct_filename(const char *str) +{ + char *newstr = NULL; + + if (homedir != NULL) { + size_t homelen = strlen(homedir); + + newstr = charalloc(homelen + strlen(str) + 1); + strcpy(newstr, homedir); + strcpy(newstr + homelen, str); + } + + return newstr; +} + +char *histfilename(void) +{ + return construct_filename("/.nano/search_history"); +} + +/* Construct the legacy history filename. */ +/* (To be removed in 2018.) */ +char *legacyhistfilename(void) +{ + return construct_filename("/.nano_history"); +} + +char *poshistfilename(void) +{ + return construct_filename("/.nano/filepos_history"); +} + +void history_error(const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + vfprintf(stderr, _(msg), ap); + va_end(ap); + + fprintf(stderr, _("\nPress Enter to continue\n")); + while (getchar() != '\n') + ; +} + +/* Now that we have more than one history file, let's just rely on a + * .nano dir for this stuff. Return 1 if the dir exists or was + * successfully created, and return 0 otherwise. */ +int check_dotnano(void) +{ + int ret = 1; + struct stat dirstat; + char *nanodir = construct_filename("/.nano"); + + if (stat(nanodir, &dirstat) == -1) { + if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) { + history_error(N_("Unable to create directory %s: %s\n" + "It is required for saving/loading " + "search history or cursor positions.\n"), + nanodir, strerror(errno)); + ret = 0; + } + } else if (!S_ISDIR(dirstat.st_mode)) { + history_error(N_("Path %s is not a directory and needs to be.\n" + "Nano will be unable to load or save " + "search history or cursor positions.\n"), + nanodir); + ret = 0; + } + + free(nanodir); + return ret; +} + +/* Load the search and replace histories from ~/.nano/search_history. */ +void load_history(void) +{ + char *searchhist = histfilename(); + char *legacyhist = legacyhistfilename(); + struct stat hstat; + FILE *hist; + + /* If no home directory was found, we can't do anything. */ + if (searchhist == NULL || legacyhist == NULL) + return; + + /* If there is an old history file, migrate it. */ + /* (To be removed in 2018.) */ + if (stat(legacyhist, &hstat) != -1 && stat(searchhist, &hstat) == -1) { + if (rename(legacyhist, searchhist) == -1) + history_error(N_("Detected a legacy nano history file (%s) which I tried to move\n" + "to the preferred location (%s) but encountered an error: %s"), + legacyhist, searchhist, strerror(errno)); + else + history_error(N_("Detected a legacy nano history file (%s) which I moved\n" + "to the preferred location (%s)\n(see the nano FAQ about this change)"), + legacyhist, searchhist); + } + + hist = fopen(searchhist, "rb"); + + if (hist == NULL) { + if (errno != ENOENT) { + /* When reading failed, don't save history when we quit. */ + UNSET(HISTORYLOG); + history_error(N_("Error reading %s: %s"), searchhist, + strerror(errno)); + } + } else { + /* Load the two history lists -- first the search history, then + * the replace history -- from the oldest entry to the newest. + * The two lists are separated by an empty line. */ + filestruct **history = &search_history; + char *line = NULL; + size_t buf_len = 0; + ssize_t read; + + while ((read = getline(&line, &buf_len, hist)) > 0) { + line[--read] = '\0'; + if (read > 0) { + /* Encode any embedded NUL as 0x0A. */ + unsunder(line, read); + update_history(history, line); + } else if (history == &search_history) + history = &replace_history; + else + history = &execute_history; + + } + + fclose(hist); + free(line); + } + + free(searchhist); + free(legacyhist); +} + +/* Write the lines of a history list, starting with the line at head, to + * the open file at hist. Return TRUE if the write succeeded, and FALSE + * otherwise. */ +bool writehist(FILE *hist, const filestruct *head) +{ + const filestruct *item; + + /* Write a history list, from the oldest item to the newest. */ + for (item = head; item != NULL; item = item->next) { + size_t length = strlen(item->data); + + /* Decode 0x0A bytes as embedded NULs. */ + sunder(item->data); + + if (fwrite(item->data, sizeof(char), length, hist) < length) + return FALSE; + if (putc('\n', hist) == EOF) + return FALSE; + } + + return TRUE; +} + +/* Save the search and replace histories to ~/.nano/search_history. */ +void save_history(void) +{ + char *searchhist; + FILE *hist; + + /* If the histories are unchanged or empty, don't bother saving them. */ + if (!history_has_changed() || (searchbot->lineno == 1 && + replacebot->lineno == 1 && executebot->lineno == 1)) + return; + + searchhist = histfilename(); + + if (searchhist == NULL) + return; + + hist = fopen(searchhist, "wb"); + + if (hist == NULL) + fprintf(stderr, _("Error writing %s: %s\n"), searchhist, + strerror(errno)); + else { + /* Don't allow others to read or write the history file. */ + chmod(searchhist, S_IRUSR | S_IWUSR); + + if (!writehist(hist, searchage) || !writehist(hist, replaceage) || + !writehist(hist, executetop)) + fprintf(stderr, _("Error writing %s: %s\n"), searchhist, + strerror(errno)); + + fclose(hist); + } + + free(searchhist); +} + +/* Load the recorded file positions from ~/.nano/filepos_history. */ +void load_poshistory(void) +{ + char *poshist = poshistfilename(); + FILE *hist; + + /* If the home directory is missing, do_rcfile() will have reported it. */ + if (poshist == NULL) + return; + + hist = fopen(poshist, "rb"); + + if (hist == NULL) { + if (errno != ENOENT) { + /* When reading failed, don't save history when we quit. */ + UNSET(POS_HISTORY); + history_error(N_("Error reading %s: %s"), poshist, strerror(errno)); + } + } else { + char *line = NULL, *lineptr, *xptr; + size_t buf_len = 0; + ssize_t read, count = 0; + poshiststruct *record_ptr = NULL, *newrecord; + + /* Read and parse each line, and store the extracted data. */ + while ((read = getline(&line, &buf_len, hist)) > 5) { + /* Decode nulls as embedded newlines. */ + unsunder(line, read); + + /* Find where the x index and line number are in the line. */ + xptr = revstrstr(line, " ", line + read - 3); + if (xptr == NULL) + continue; + lineptr = revstrstr(line, " ", xptr - 2); + if (lineptr == NULL) + continue; + + /* Now separate the three elements of the line. */ + *(xptr++) = '\0'; + *(lineptr++) = '\0'; + + /* Create a new position record. */ + newrecord = (poshiststruct *)nmalloc(sizeof(poshiststruct)); + newrecord->filename = mallocstrcpy(NULL, line); + newrecord->lineno = atoi(lineptr); + newrecord->xno = atoi(xptr); + newrecord->next = NULL; + + /* Add the record to the list. */ + if (position_history == NULL) + position_history = newrecord; + else + record_ptr->next = newrecord; + + record_ptr = newrecord; + + /* Impose a limit, so the file will not grow indefinitely. */ + if (++count > 200) { + poshiststruct *drop_record = position_history; + + position_history = position_history->next; + + free(drop_record->filename); + free(drop_record); + } + } + fclose(hist); + free(line); + } + free(poshist); +} + +/* Save the recorded last file positions to ~/.nano/filepos_history. */ +void save_poshistory(void) +{ + char *poshist = poshistfilename(); + poshiststruct *posptr; + FILE *hist; + + if (poshist == NULL) + return; + + hist = fopen(poshist, "wb"); + + if (hist == NULL) + fprintf(stderr, _("Error writing %s: %s\n"), poshist, strerror(errno)); + else { + /* Don't allow others to read or write the history file. */ + chmod(poshist, S_IRUSR | S_IWUSR); + + for (posptr = position_history; posptr != NULL; posptr = posptr->next) { + char *path_and_place; + size_t length; + + /* Assume 20 decimal positions each for line and column number, + * plus two spaces, plus the line feed, plus the null byte. */ + path_and_place = charalloc(strlen(posptr->filename) + 44); + sprintf(path_and_place, "%s %ld %ld\n", posptr->filename, + (long)posptr->lineno, (long)posptr->xno); + length = strlen(path_and_place); + + /* Encode newlines in filenames as nulls. */ + sunder(path_and_place); + /* Restore the terminating newline. */ + path_and_place[length - 1] = '\n'; + + if (fwrite(path_and_place, sizeof(char), length, hist) < length) + fprintf(stderr, _("Error writing %s: %s\n"), + poshist, strerror(errno)); + free(path_and_place); + } + fclose(hist); + } + free(poshist); +} + +/* Update the recorded last file positions, given a filename, a line + * and a column. If no entry is found, add a new one at the end. */ +void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos) +{ + poshiststruct *posptr, *theone, *posprev = NULL; + char *fullpath = get_full_path(filename); + + if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/' || inhelp) { + free(fullpath); + return; + } + + /* Look for a matching filename in the list. */ + for (posptr = position_history; posptr != NULL; posptr = posptr->next) { + if (!strcmp(posptr->filename, fullpath)) + break; + posprev = posptr; + } + + /* Don't record files that have the default cursor position. */ + if (lineno == 1 && xpos == 1) { + if (posptr != NULL) { + if (posprev == NULL) + position_history = posptr->next; + else + posprev->next = posptr->next; + free(posptr->filename); + free(posptr); + } + free(fullpath); + return; + } + + theone = posptr; + + /* If we didn't find it, make a new node; otherwise, if we're + * not at the end, move the matching one to the end. */ + if (theone == NULL) { + theone = (poshiststruct *)nmalloc(sizeof(poshiststruct)); + theone->filename = mallocstrcpy(NULL, fullpath); + if (position_history == NULL) + position_history = theone; + else + posprev->next = theone; + } else if (posptr->next != NULL) { + if (posprev == NULL) + position_history = posptr->next; + else + posprev->next = posptr->next; + while (posptr->next != NULL) + posptr = posptr->next; + posptr->next = theone; + } + + /* Store the last cursor position. */ + theone->lineno = lineno; + theone->xno = xpos; + theone->next = NULL; + + free(fullpath); +} + +/* Check whether the given file matches an existing entry in the recorded + * last file positions. If not, return FALSE. If yes, return TRUE and + * set line and column to the retrieved values. */ +bool has_old_position(const char *file, ssize_t *line, ssize_t *column) +{ + poshiststruct *posptr = position_history; + char *fullpath = get_full_path(file); + + if (fullpath == NULL) + return FALSE; + + while (posptr != NULL && strcmp(posptr->filename, fullpath) != 0) + posptr = posptr->next; + + free(fullpath); + + if (posptr == NULL) + return FALSE; + + *line = posptr->lineno; + *column = posptr->xno; + return TRUE; +} +#endif /* !DISABLE_HISTORIES */ diff --git a/src/proto.h b/src/proto.h index b7a279c4..ef940544 100644 --- a/src/proto.h +++ b/src/proto.h @@ -322,15 +322,6 @@ char *input_tab(char *buf, bool allow_files, size_t *place, bool *lastwastab, void (*refresh_func)(void), bool *listed); #endif const char *tail(const char *path); -#ifndef DISABLE_HISTORIES -void load_history(void); -void save_history(void); -int check_dotnano(void); -void load_poshistory(void); -void save_poshistory(void); -void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos); -bool has_old_position(const char *file, ssize_t *line, ssize_t *column); -#endif /* Some functions in global.c. */ size_t length_of_list(int menu); @@ -363,6 +354,27 @@ size_t help_line_len(const char *ptr); #endif void do_help_void(void); +/* Most functions in history.c. */ +#ifndef DISABLE_HISTORIES +void history_init(void); +void history_reset(const filestruct *h); +void update_history(filestruct **h, const char *s); +char *get_history_older(filestruct **h); +char *get_history_newer(filestruct **h); +void get_history_older_void(void); +void get_history_newer_void(void); +#ifdef ENABLE_TABCOMP +char *get_history_completion(filestruct **h, char *s, size_t len); +#endif +int check_dotnano(void); +void load_history(void); +void save_history(void); +void load_poshistory(void); +void save_poshistory(void); +void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos); +bool has_old_position(const char *file, ssize_t *line, ssize_t *column); +#endif + /* Most functions in move.c. */ void do_first_line(void); void do_last_line(void); @@ -505,19 +517,6 @@ void do_gotolinecolumn(ssize_t line, ssize_t column, bool use_answer, void do_gotolinecolumn_void(void); #ifndef NANO_TINY void do_find_bracket(void); -#ifdef ENABLE_TABCOMP -char *get_history_completion(filestruct **h, char *s, size_t len); -#endif -#endif -#ifndef DISABLE_HISTORIES -bool history_has_changed(void); -void history_init(void); -void history_reset(const filestruct *h); -void update_history(filestruct **h, const char *s); -char *get_history_older(filestruct **h); -char *get_history_newer(filestruct **h); -void get_history_older_void(void); -void get_history_newer_void(void); #endif /* Most functions in text.c. */ diff --git a/src/search.c b/src/search.c index b04e9a37..c37813ea 100644 --- a/src/search.c +++ b/src/search.c @@ -28,10 +28,6 @@ static bool came_full_circle = FALSE; /* Have we reached the starting line again while searching? */ -#ifndef DISABLE_HISTORIES -static bool history_changed = FALSE; - /* Have any of the history lists changed? */ -#endif static bool regexp_compiled = FALSE; /* Have we compiled any regular expressions? */ @@ -1087,212 +1083,3 @@ void do_find_bracket(void) } } #endif /* !NANO_TINY */ - -#ifndef DISABLE_HISTORIES -/* Indicate whether any of the history lists have changed. */ -bool history_has_changed(void) -{ - return history_changed; -} - -/* Initialize the search and replace history lists. */ -void history_init(void) -{ - search_history = make_new_node(NULL); - search_history->data = mallocstrcpy(NULL, ""); - searchage = search_history; - searchbot = search_history; - - replace_history = make_new_node(NULL); - replace_history->data = mallocstrcpy(NULL, ""); - replaceage = replace_history; - replacebot = replace_history; - - execute_history = make_new_node(NULL); - execute_history->data = mallocstrcpy(NULL, ""); - executetop = execute_history; - executebot = execute_history; -} - -/* Set the current position in the history list h to the bottom. */ -void history_reset(const filestruct *h) -{ - if (h == search_history) - search_history = searchbot; - else if (h == replace_history) - replace_history = replacebot; - else if (h == execute_history) - execute_history = executebot; -} - -/* Return the first node containing the first len characters of the - * string s in the history list, starting at h_start and ending at - * h_end, or NULL if there isn't one. */ -filestruct *find_history(const filestruct *h_start, const filestruct - *h_end, const char *s, size_t len) -{ - const filestruct *p; - - for (p = h_start; p != h_end->prev && p != NULL; p = p->prev) { - if (strncmp(s, p->data, len) == 0) - return (filestruct *)p; - } - - return NULL; -} - -/* Update a history list (the one in which h is the current position) - * with a fresh string s. That is: add s, or move it to the end. */ -void update_history(filestruct **h, const char *s) -{ - filestruct **hage = NULL, **hbot = NULL, *thesame; - - assert(h != NULL && s != NULL); - - if (*h == search_history) { - hage = &searchage; - hbot = &searchbot; - } else if (*h == replace_history) { - hage = &replaceage; - hbot = &replacebot; - } else if (*h == execute_history) { - hage = &executetop; - hbot = &executebot; - } - - assert(hage != NULL && hbot != NULL); - - /* See if the string is already in the history. */ - thesame = find_history(*hbot, *hage, s, HIGHEST_POSITIVE); - - /* If an identical string was found, delete that item. */ - if (thesame != NULL) { - filestruct *after = thesame->next; - - /* If the string is at the head of the list, move the head. */ - if (thesame == *hage) - *hage = after; - - unlink_node(thesame); - renumber(after); - } - - /* If the history is full, delete the oldest item (the one at the - * head of the list), to make room for a new item at the end. */ - if ((*hbot)->lineno == MAX_SEARCH_HISTORY + 1) { - filestruct *oldest = *hage; - - *hage = (*hage)->next; - unlink_node(oldest); - renumber(*hage); - } - - /* Store the fresh string in the last item, then create a new item. */ - (*hbot)->data = mallocstrcpy((*hbot)->data, s); - splice_node(*hbot, make_new_node(*hbot)); - *hbot = (*hbot)->next; - (*hbot)->data = mallocstrcpy(NULL, ""); - - /* Indicate that the history needs to be saved on exit. */ - history_changed = TRUE; - - /* Set the current position in the list to the bottom. */ - *h = *hbot; -} - -/* Move h to the string in the history list just before it, and return - * that string. If there isn't one, don't move h and return NULL. */ -char *get_history_older(filestruct **h) -{ - assert(h != NULL); - - if ((*h)->prev == NULL) - return NULL; - - *h = (*h)->prev; - - return (*h)->data; -} - -/* Move h to the string in the history list just after it, and return - * that string. If there isn't one, don't move h and return NULL. */ -char *get_history_newer(filestruct **h) -{ - assert(h != NULL); - - if ((*h)->next == NULL) - return NULL; - - *h = (*h)->next; - - return (*h)->data; -} - -/* More placeholders. */ -void get_history_newer_void(void) -{ - ; -} -void get_history_older_void(void) -{ - ; -} - -#ifdef ENABLE_TABCOMP -/* Move h to the next string that's a tab completion of the string s, - * looking at only the first len characters of s, and return that - * string. If there isn't one, or if len is 0, don't move h and return - * s. */ -char *get_history_completion(filestruct **h, char *s, size_t len) -{ - assert(s != NULL); - - if (len > 0) { - filestruct *hage = NULL, *hbot = NULL, *p; - - assert(h != NULL); - - if (*h == search_history) { - hage = searchage; - hbot = searchbot; - } else if (*h == replace_history) { - hage = replaceage; - hbot = replacebot; - } else if (*h == execute_history) { - hage = executetop; - hbot = executebot; - } - - assert(hage != NULL && hbot != NULL); - - /* Search the history list from the current position to the top - * for a match of len characters. Skip over an exact match. */ - p = find_history((*h)->prev, hage, s, len); - - while (p != NULL && strcmp(p->data, s) == 0) - p = find_history(p->prev, hage, s, len); - - if (p != NULL) { - *h = p; - return mallocstrcpy(s, (*h)->data); - } - - /* Search the history list from the bottom to the current position - * for a match of len characters. Skip over an exact match. */ - p = find_history(hbot, *h, s, len); - - while (p != NULL && strcmp(p->data, s) == 0) - p = find_history(p->prev, *h, s, len); - - if (p != NULL) { - *h = p; - return mallocstrcpy(s, (*h)->data); - } - } - - /* If we're here, we didn't find a match, we didn't find an inexact - * match, or len is 0. Return s. */ - return (char *)s; -} -#endif /* ENSABLE_TABCOMP */ -#endif /* !DISABLE_HISTORIES */