commit a1c73317f74a741e724a62b9758a787f6e288e9d
parent a75bf0a1d17151ea96d674b80294bfe57a00aef6
Author: Benno Schulenberg <bensberg@telfort.nl>
Date: Sun, 17 Sep 2017 12:10:12 +0200
organization: move all history-related stuff to its own file
Diffstat:
M | src/Makefile.am | | | 1 | + |
M | src/files.c | | | 403 | ------------------------------------------------------------------------------- |
A | src/history.c | | | 638 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/proto.h | | | 43 | +++++++++++++++++++++---------------------- |
M | src/search.c | | | 213 | ------------------------------------------------------------------------------- |
5 files changed, 660 insertions(+), 638 deletions(-)
diff --git 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
@@ -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
@@ -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 <errno.h>
+#include <string.h>
+
+#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
@@ -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
@@ -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 */