organization: move all history-related stuff to its own file
parent
a75bf0a1d1
commit
a1c73317f7
|
@ -28,6 +28,7 @@ nano_SOURCES = browser.c \
|
|||
files.c \
|
||||
global.c \
|
||||
help.c \
|
||||
history.c \
|
||||
move.c \
|
||||
nano.c \
|
||||
nano.h \
|
||||
|
|
403
src/files.c
403
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 */
|
||||
|
|
|
@ -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 */
|
43
src/proto.h
43
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. */
|
||||
|
|
213
src/search.c
213
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 */
|
||||
|
|
Loading…
Reference in New Issue