2017-09-17 10:10:12 +00:00
|
|
|
/**************************************************************************
|
|
|
|
* history.c -- This file is part of GNU nano. *
|
|
|
|
* *
|
2021-01-11 13:21:03 +00:00
|
|
|
* Copyright (C) 2003-2011, 2013-2021 Free Software Foundation, Inc. *
|
2020-01-15 11:11:56 +00:00
|
|
|
* Copyright (C) 2016, 2017, 2019 Benno Schulenberg *
|
2017-09-17 10:10:12 +00:00
|
|
|
* *
|
|
|
|
* 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/. *
|
|
|
|
* *
|
|
|
|
**************************************************************************/
|
|
|
|
|
2020-06-20 10:09:31 +00:00
|
|
|
#include "prototypes.h"
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2019-10-13 16:51:15 +00:00
|
|
|
#ifdef ENABLE_HISTORIES
|
|
|
|
|
2017-09-17 10:10:12 +00:00
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2017-10-27 21:15:06 +00:00
|
|
|
#ifndef SEARCH_HISTORY
|
2020-05-29 16:45:14 +00:00
|
|
|
#define SEARCH_HISTORY "search_history"
|
2017-10-27 21:15:06 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef POSITION_HISTORY
|
2020-05-29 16:45:14 +00:00
|
|
|
#define POSITION_HISTORY "filepos_history"
|
2017-10-27 21:15:06 +00:00
|
|
|
#endif
|
|
|
|
|
2017-09-17 10:10:12 +00:00
|
|
|
static bool history_changed = FALSE;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Whether any of the history lists has changed. */
|
2017-11-02 18:31:02 +00:00
|
|
|
static char *poshistname = NULL;
|
2017-12-29 18:27:33 +00:00
|
|
|
/* The name of the positions-history file. */
|
2020-06-30 08:48:47 +00:00
|
|
|
static time_t latest_timestamp = 942927132;
|
|
|
|
/* The last time the positions-history file was written. */
|
2019-02-27 18:49:02 +00:00
|
|
|
static poshiststruct *position_history = NULL;
|
|
|
|
/* The list of filenames with their last cursor positions. */
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-09-17 18:06:04 +00:00
|
|
|
/* Initialize the lists of historical search and replace strings
|
|
|
|
* and the list of historical executed commands. */
|
2017-09-17 10:10:12 +00:00
|
|
|
void history_init(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
search_history = make_new_node(NULL);
|
2019-10-13 10:24:27 +00:00
|
|
|
search_history->data = copy_of("");
|
2017-12-29 18:27:33 +00:00
|
|
|
searchtop = search_history;
|
|
|
|
searchbot = search_history;
|
|
|
|
|
|
|
|
replace_history = make_new_node(NULL);
|
2019-10-13 10:24:27 +00:00
|
|
|
replace_history->data = copy_of("");
|
2017-12-29 18:27:33 +00:00
|
|
|
replacetop = replace_history;
|
|
|
|
replacebot = replace_history;
|
|
|
|
|
|
|
|
execute_history = make_new_node(NULL);
|
2019-10-13 10:24:27 +00:00
|
|
|
execute_history->data = copy_of("");
|
2017-12-29 18:27:33 +00:00
|
|
|
executetop = execute_history;
|
|
|
|
executebot = execute_history;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 08:11:47 +00:00
|
|
|
/* Reset the pointer into the history list that contains item to the bottom. */
|
|
|
|
void reset_history_pointer_for(const linestruct *item)
|
2017-09-17 10:10:12 +00:00
|
|
|
{
|
2021-10-10 08:11:47 +00:00
|
|
|
if (item == search_history)
|
2017-12-29 18:27:33 +00:00
|
|
|
search_history = searchbot;
|
2021-10-10 08:11:47 +00:00
|
|
|
else if (item == replace_history)
|
2017-12-29 18:27:33 +00:00
|
|
|
replace_history = replacebot;
|
2021-10-10 08:11:47 +00:00
|
|
|
else if (item == execute_history)
|
2017-12-29 18:27:33 +00:00
|
|
|
execute_history = executebot;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2017-09-17 18:06:04 +00:00
|
|
|
/* Return from the history list that starts at start and ends at end
|
|
|
|
* the first node that contains the first len characters of the given
|
|
|
|
* text, or NULL if there is no such node. */
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *find_history(const linestruct *start, const linestruct *end,
|
2017-12-29 18:27:33 +00:00
|
|
|
const char *text, size_t len)
|
2017-09-17 10:10:12 +00:00
|
|
|
{
|
2019-03-21 16:08:52 +00:00
|
|
|
const linestruct *item;
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
for (item = start; item != end->prev && item != NULL; item = item->prev) {
|
|
|
|
if (strncmp(item->data, text, len) == 0)
|
2019-03-21 16:08:52 +00:00
|
|
|
return (linestruct *)item;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return NULL;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2017-09-17 18:06:04 +00:00
|
|
|
/* Update a history list (the one in which item is the current position)
|
|
|
|
* with a fresh string text. That is: add text, or move it to the end. */
|
2019-03-21 16:08:52 +00:00
|
|
|
void update_history(linestruct **item, const char *text)
|
2017-09-17 10:10:12 +00:00
|
|
|
{
|
2021-10-13 14:45:15 +00:00
|
|
|
linestruct **htop = NULL, **hbot = NULL;
|
|
|
|
linestruct *thesame;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
if (*item == search_history) {
|
|
|
|
htop = &searchtop;
|
|
|
|
hbot = &searchbot;
|
|
|
|
} else if (*item == replace_history) {
|
|
|
|
htop = &replacetop;
|
|
|
|
hbot = &replacebot;
|
|
|
|
} else if (*item == execute_history) {
|
|
|
|
htop = &executetop;
|
|
|
|
hbot = &executebot;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* See if the string is already in the history. */
|
|
|
|
thesame = find_history(*hbot, *htop, text, HIGHEST_POSITIVE);
|
|
|
|
|
|
|
|
/* If an identical string was found, delete that item. */
|
|
|
|
if (thesame != NULL) {
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *after = thesame->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If the string is at the head of the list, move the head. */
|
|
|
|
if (thesame == *htop)
|
|
|
|
*htop = after;
|
|
|
|
|
|
|
|
unlink_node(thesame);
|
2019-04-30 08:27:10 +00:00
|
|
|
renumber_from(after);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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) {
|
2019-03-21 16:08:52 +00:00
|
|
|
linestruct *oldest = *htop;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
*htop = (*htop)->next;
|
|
|
|
unlink_node(oldest);
|
2019-04-30 08:27:10 +00:00
|
|
|
renumber_from(*htop);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Store the fresh string in the last item, then create a new item. */
|
|
|
|
(*hbot)->data = mallocstrcpy((*hbot)->data, text);
|
|
|
|
splice_node(*hbot, make_new_node(*hbot));
|
|
|
|
*hbot = (*hbot)->next;
|
2019-10-13 10:24:27 +00:00
|
|
|
(*hbot)->data = copy_of("");
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* Indicate that the history needs to be saved on exit. */
|
|
|
|
history_changed = TRUE;
|
|
|
|
|
|
|
|
/* Set the current position in the list to the bottom. */
|
|
|
|
*item = *hbot;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef ENABLE_TABCOMP
|
2021-10-13 15:04:21 +00:00
|
|
|
/* Go backward through one of three history lists, starting at item *here,
|
|
|
|
* searching for a string that is a tab completion of the given string,
|
|
|
|
* looking at only its first len characters. When found, make *here point
|
|
|
|
* at the item and return its string; otherwise, just return the string. */
|
|
|
|
char *get_history_completion(linestruct **here, char *string, size_t len)
|
2017-09-17 10:10:12 +00:00
|
|
|
{
|
2021-10-13 15:04:21 +00:00
|
|
|
linestruct *htop = NULL, *hbot = NULL;
|
|
|
|
linestruct *item;
|
2020-06-26 17:18:05 +00:00
|
|
|
|
2021-10-13 15:04:21 +00:00
|
|
|
if (*here == search_history) {
|
2020-06-26 17:18:05 +00:00
|
|
|
htop = searchtop;
|
|
|
|
hbot = searchbot;
|
2021-10-13 15:04:21 +00:00
|
|
|
} else if (*here == replace_history) {
|
2020-06-26 17:18:05 +00:00
|
|
|
htop = replacetop;
|
|
|
|
hbot = replacebot;
|
2021-10-13 15:04:21 +00:00
|
|
|
} else if (*here == execute_history) {
|
2020-06-26 17:18:05 +00:00
|
|
|
htop = executetop;
|
|
|
|
hbot = executebot;
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-26 17:18:05 +00:00
|
|
|
/* First search from the current position to the top of the list
|
|
|
|
* for a match of len characters. Skip over an exact match. */
|
2021-10-13 15:04:21 +00:00
|
|
|
item = find_history((*here)->prev, htop, string, len);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2021-10-13 15:04:21 +00:00
|
|
|
while (item != NULL && strcmp(item->data, string) == 0)
|
|
|
|
item = find_history(item->prev, htop, string, len);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2021-10-13 15:04:21 +00:00
|
|
|
if (item) {
|
|
|
|
*here = item;
|
|
|
|
return mallocstrcpy(string, item->data);
|
2020-06-26 17:18:05 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-26 17:18:05 +00:00
|
|
|
/* Now search from the bottom of the list to the original position. */
|
2021-10-13 15:04:21 +00:00
|
|
|
item = find_history(hbot, *here, string, len);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2021-10-13 15:04:21 +00:00
|
|
|
while (item != NULL && strcmp(item->data, string) == 0)
|
|
|
|
item = find_history(item->prev, *here, string, len);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2021-10-13 15:04:21 +00:00
|
|
|
if (item) {
|
|
|
|
*here = item;
|
|
|
|
return mallocstrcpy(string, item->data);
|
2020-06-26 17:18:05 +00:00
|
|
|
}
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2020-06-26 17:06:08 +00:00
|
|
|
/* When no useful match was found, simply return the given string. */
|
2021-10-13 15:04:21 +00:00
|
|
|
return (char *)string;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
2018-04-23 10:19:25 +00:00
|
|
|
#endif /* ENABLE_TABCOMP */
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-10-27 21:15:06 +00:00
|
|
|
/* Check whether we have or could make a directory for history files. */
|
|
|
|
bool have_statedir(void)
|
2017-09-17 10:10:12 +00:00
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
const char *xdgdatadir;
|
2021-10-13 14:45:15 +00:00
|
|
|
struct stat dirinfo;
|
2017-10-27 21:15:06 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
get_homedir();
|
2017-10-27 21:15:06 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (homedir != NULL) {
|
|
|
|
statedir = concatenate(homedir, "/.nano/");
|
2017-10-27 21:15:06 +00:00
|
|
|
|
2021-10-13 14:45:15 +00:00
|
|
|
if (stat(statedir, &dirinfo) == 0 && S_ISDIR(dirinfo.st_mode)) {
|
2017-12-29 18:27:33 +00:00
|
|
|
poshistname = concatenate(statedir, POSITION_HISTORY);
|
|
|
|
return TRUE;
|
|
|
|
}
|
2017-11-08 20:11:57 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
free(statedir);
|
|
|
|
xdgdatadir = getenv("XDG_DATA_HOME");
|
|
|
|
|
|
|
|
if (homedir == NULL && xdgdatadir == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
if (xdgdatadir != NULL)
|
|
|
|
statedir = concatenate(xdgdatadir, "/nano/");
|
|
|
|
else
|
|
|
|
statedir = concatenate(homedir, "/.local/share/nano/");
|
|
|
|
|
2021-10-13 14:45:15 +00:00
|
|
|
if (stat(statedir, &dirinfo) == -1) {
|
2017-12-29 18:27:33 +00:00
|
|
|
if (xdgdatadir == NULL) {
|
|
|
|
char *statepath = concatenate(homedir, "/.local");
|
|
|
|
mkdir(statepath, S_IRWXU | S_IRWXG | S_IRWXO);
|
|
|
|
free(statepath);
|
|
|
|
statepath = concatenate(homedir, "/.local/share");
|
|
|
|
mkdir(statepath, S_IRWXU);
|
|
|
|
free(statepath);
|
|
|
|
}
|
|
|
|
if (mkdir(statedir, S_IRWXU) == -1) {
|
2019-10-20 12:25:22 +00:00
|
|
|
jot_error(N_("Unable to create directory %s: %s\n"
|
2017-12-29 18:27:33 +00:00
|
|
|
"It is required for saving/loading "
|
|
|
|
"search history or cursor positions.\n"),
|
|
|
|
statedir, strerror(errno));
|
|
|
|
return FALSE;
|
|
|
|
}
|
2021-10-13 14:45:15 +00:00
|
|
|
} else if (!S_ISDIR(dirinfo.st_mode)) {
|
2019-10-20 12:25:22 +00:00
|
|
|
jot_error(N_("Path %s is not a directory and needs to be.\n"
|
2017-12-29 18:27:33 +00:00
|
|
|
"Nano will be unable to load or save "
|
|
|
|
"search history or cursor positions.\n"),
|
|
|
|
statedir);
|
|
|
|
return FALSE;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
poshistname = concatenate(statedir, POSITION_HISTORY);
|
|
|
|
return TRUE;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2020-05-08 08:21:02 +00:00
|
|
|
/* Load the histories for Search, Replace With, and Execute Command. */
|
2017-09-17 10:10:12 +00:00
|
|
|
void load_history(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *histname = concatenate(statedir, SEARCH_HISTORY);
|
2020-06-29 10:04:15 +00:00
|
|
|
FILE *histfile = fopen(histname, "rb");
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 10:39:46 +00:00
|
|
|
/* If reading an existing file failed, don't save history when we quit. */
|
|
|
|
if (histfile == NULL && errno != ENOENT) {
|
|
|
|
jot_error(N_("Error reading %s: %s"), histname, strerror(errno));
|
|
|
|
UNSET(HISTORYLOG);
|
2020-06-29 10:20:33 +00:00
|
|
|
}
|
|
|
|
|
2020-06-29 11:05:10 +00:00
|
|
|
if (histfile == NULL) {
|
|
|
|
free(histname);
|
2020-06-29 10:39:46 +00:00
|
|
|
return;
|
2020-06-29 11:05:10 +00:00
|
|
|
}
|
2020-06-29 10:39:46 +00:00
|
|
|
|
2020-06-29 10:35:17 +00:00
|
|
|
linestruct **history = &search_history;
|
2020-09-22 08:45:37 +00:00
|
|
|
char *stanza = NULL;
|
|
|
|
size_t dummy = 0;
|
2020-06-29 10:35:17 +00:00
|
|
|
ssize_t read;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 10:20:33 +00:00
|
|
|
/* Load the three history lists (first search, then replace, then execute)
|
|
|
|
* from oldest entry to newest. Between two lists there is an empty line. */
|
2020-09-22 08:45:37 +00:00
|
|
|
while ((read = getline(&stanza, &dummy, histfile)) > 0) {
|
|
|
|
stanza[--read] = '\0';
|
2020-06-29 10:35:17 +00:00
|
|
|
if (read > 0) {
|
2020-09-22 08:45:37 +00:00
|
|
|
recode_NUL_to_LF(stanza, read);
|
|
|
|
update_history(history, stanza);
|
2020-06-29 10:35:17 +00:00
|
|
|
} else if (history == &search_history)
|
|
|
|
history = &replace_history;
|
|
|
|
else
|
|
|
|
history = &execute_history;
|
|
|
|
}
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 11:05:10 +00:00
|
|
|
if (fclose(histfile) == EOF)
|
|
|
|
jot_error(N_("Error reading %s: %s"), histname, strerror(errno));
|
|
|
|
|
|
|
|
free(histname);
|
2020-09-22 08:45:37 +00:00
|
|
|
free(stanza);
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2020-05-08 08:21:02 +00:00
|
|
|
/* Reading in the lists has marked them as changed; undo this side effect. */
|
2017-12-29 18:27:33 +00:00
|
|
|
history_changed = FALSE;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2017-09-17 15:29:43 +00:00
|
|
|
/* Write the lines of a history list, starting at head, from oldest to newest,
|
|
|
|
* to the given file. Return TRUE if writing succeeded, and FALSE otherwise. */
|
2020-06-29 10:04:15 +00:00
|
|
|
bool write_list(const linestruct *head, FILE *histfile)
|
2017-09-17 10:10:12 +00:00
|
|
|
{
|
2019-03-21 16:08:52 +00:00
|
|
|
const linestruct *item;
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
for (item = head; item != NULL; item = item->next) {
|
|
|
|
size_t length = strlen(item->data);
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
/* Decode 0x0A bytes as embedded NULs. */
|
2020-04-15 15:20:03 +00:00
|
|
|
recode_LF_to_NUL(item->data);
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2020-06-29 10:04:15 +00:00
|
|
|
if (fwrite(item->data, sizeof(char), length, histfile) < length)
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
2020-06-29 10:04:15 +00:00
|
|
|
if (putc('\n', histfile) == EOF)
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2020-05-08 08:21:02 +00:00
|
|
|
/* Save the histories for Search, Replace With, and Execute Command. */
|
2017-09-17 10:10:12 +00:00
|
|
|
void save_history(void)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *histname;
|
2020-06-29 10:04:15 +00:00
|
|
|
FILE *histfile;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* If the histories are unchanged, don't bother saving them. */
|
|
|
|
if (!history_changed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
histname = concatenate(statedir, SEARCH_HISTORY);
|
2020-06-29 10:04:15 +00:00
|
|
|
histfile = fopen(histname, "wb");
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 10:20:33 +00:00
|
|
|
if (histfile == NULL) {
|
2020-09-21 17:25:07 +00:00
|
|
|
jot_error(N_("Error writing %s: %s"), histname, strerror(errno));
|
2020-06-29 10:20:33 +00:00
|
|
|
free(histname);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-29 10:35:17 +00:00
|
|
|
/* Don't allow others to read or write the history file. */
|
2021-09-07 14:44:08 +00:00
|
|
|
if (chmod(histname, S_IRUSR | S_IWUSR) < 0)
|
|
|
|
jot_error(N_("Cannot limit permissions on %s: %s"), histname, strerror(errno));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 11:05:10 +00:00
|
|
|
if (!write_list(searchtop, histfile) || !write_list(replacetop, histfile) ||
|
|
|
|
!write_list(executetop, histfile))
|
2020-09-21 17:25:07 +00:00
|
|
|
jot_error(N_("Error writing %s: %s"), histname, strerror(errno));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 11:05:10 +00:00
|
|
|
if (fclose(histfile) == EOF)
|
2020-09-21 17:25:07 +00:00
|
|
|
jot_error(N_("Error writing %s: %s"), histname, strerror(errno));
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(histname);
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2017-09-17 18:06:04 +00:00
|
|
|
/* Load the recorded cursor positions for files that were edited. */
|
2017-09-17 10:10:12 +00:00
|
|
|
void load_poshistory(void)
|
|
|
|
{
|
2020-06-29 10:04:15 +00:00
|
|
|
FILE *histfile = fopen(poshistname, "rb");
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 10:39:46 +00:00
|
|
|
/* If reading an existing file failed, don't save history when we quit. */
|
|
|
|
if (histfile == NULL && errno != ENOENT) {
|
|
|
|
jot_error(N_("Error reading %s: %s"), poshistname, strerror(errno));
|
|
|
|
UNSET(POSITIONLOG);
|
|
|
|
}
|
2020-06-29 10:20:33 +00:00
|
|
|
|
2020-06-29 10:39:46 +00:00
|
|
|
if (histfile == NULL)
|
2020-06-29 10:20:33 +00:00
|
|
|
return;
|
|
|
|
|
2021-10-13 14:38:23 +00:00
|
|
|
poshiststruct *lastitem = NULL;
|
|
|
|
poshiststruct *newitem;
|
2020-09-22 09:18:44 +00:00
|
|
|
char *lineptr, *columnptr;
|
2020-09-22 08:45:37 +00:00
|
|
|
char *stanza = NULL;
|
2021-10-13 14:45:15 +00:00
|
|
|
struct stat fileinfo;
|
2020-09-22 08:45:37 +00:00
|
|
|
size_t dummy = 0;
|
2021-10-13 14:45:15 +00:00
|
|
|
ssize_t count = 0;
|
|
|
|
ssize_t read;
|
2020-06-29 10:35:17 +00:00
|
|
|
|
|
|
|
/* Read and parse each line, and store the extracted data. */
|
2020-09-22 08:45:37 +00:00
|
|
|
while ((read = getline(&stanza, &dummy, histfile)) > 5) {
|
2020-06-29 10:35:17 +00:00
|
|
|
/* Decode NULs as embedded newlines. */
|
2020-09-22 08:45:37 +00:00
|
|
|
recode_NUL_to_LF(stanza, read);
|
2020-06-29 10:35:17 +00:00
|
|
|
|
2020-09-22 08:45:37 +00:00
|
|
|
/* Find the spaces before column number and line number. */
|
2020-09-22 09:18:44 +00:00
|
|
|
columnptr = revstrstr(stanza, " ", stanza + read - 3);
|
|
|
|
if (columnptr == NULL)
|
2020-06-29 10:35:17 +00:00
|
|
|
continue;
|
2020-09-22 09:18:44 +00:00
|
|
|
lineptr = revstrstr(stanza, " ", columnptr - 2);
|
2020-06-29 10:35:17 +00:00
|
|
|
if (lineptr == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Now separate the three elements of the line. */
|
2020-09-22 09:18:44 +00:00
|
|
|
*(columnptr++) = '\0';
|
2020-06-29 10:35:17 +00:00
|
|
|
*(lineptr++) = '\0';
|
|
|
|
|
|
|
|
/* Create a new position record. */
|
2021-10-13 14:38:23 +00:00
|
|
|
newitem = nmalloc(sizeof(poshiststruct));
|
|
|
|
newitem->filename = copy_of(stanza);
|
|
|
|
newitem->linenumber = atoi(lineptr);
|
|
|
|
newitem->columnnumber = atoi(columnptr);
|
|
|
|
newitem->next = NULL;
|
2020-06-29 10:35:17 +00:00
|
|
|
|
|
|
|
/* Add the record to the list. */
|
|
|
|
if (position_history == NULL)
|
2021-10-13 14:38:23 +00:00
|
|
|
position_history = newitem;
|
2020-06-29 10:35:17 +00:00
|
|
|
else
|
2021-10-13 14:38:23 +00:00
|
|
|
lastitem->next = newitem;
|
2020-06-29 10:35:17 +00:00
|
|
|
|
2021-10-13 14:38:23 +00:00
|
|
|
lastitem = newitem;
|
2020-06-29 10:35:17 +00:00
|
|
|
|
|
|
|
/* 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);
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
2020-06-29 10:35:17 +00:00
|
|
|
}
|
2020-06-29 10:20:33 +00:00
|
|
|
|
2020-06-29 11:05:10 +00:00
|
|
|
if (fclose(histfile) == EOF)
|
|
|
|
jot_error(N_("Error reading %s: %s"), poshistname, strerror(errno));
|
|
|
|
|
2020-09-22 08:45:37 +00:00
|
|
|
free(stanza);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-08-22 08:20:24 +00:00
|
|
|
if (stat(poshistname, &fileinfo) == 0)
|
2020-06-30 08:48:47 +00:00
|
|
|
latest_timestamp = fileinfo.st_mtime;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2017-09-17 18:06:04 +00:00
|
|
|
/* Save the recorded cursor positions for files that were edited. */
|
2017-09-17 10:10:12 +00:00
|
|
|
void save_poshistory(void)
|
|
|
|
{
|
2020-06-29 10:04:15 +00:00
|
|
|
FILE *histfile = fopen(poshistname, "wb");
|
2021-10-12 16:02:24 +00:00
|
|
|
struct stat fileinfo;
|
|
|
|
poshiststruct *item;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 10:20:33 +00:00
|
|
|
if (histfile == NULL) {
|
2020-09-21 17:25:07 +00:00
|
|
|
jot_error(N_("Error writing %s: %s"), poshistname, strerror(errno));
|
2020-06-29 10:20:33 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-29 10:35:17 +00:00
|
|
|
/* Don't allow others to read or write the history file. */
|
2021-09-07 14:44:08 +00:00
|
|
|
if (chmod(poshistname, S_IRUSR | S_IWUSR) < 0)
|
|
|
|
jot_error(N_("Cannot limit permissions on %s: %s"), poshistname, strerror(errno));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2021-10-12 16:02:24 +00:00
|
|
|
for (item = position_history; item != NULL; item = item->next) {
|
2020-06-29 10:35:17 +00:00
|
|
|
char *path_and_place;
|
|
|
|
size_t length;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 10:35:17 +00:00
|
|
|
/* Assume 20 decimal positions each for line and column number,
|
|
|
|
* plus two spaces, plus the line feed, plus the null byte. */
|
2021-10-12 16:02:24 +00:00
|
|
|
path_and_place = nmalloc(strlen(item->filename) + 44);
|
2020-06-29 10:35:17 +00:00
|
|
|
sprintf(path_and_place, "%s %zd %zd\n",
|
2021-10-12 16:02:24 +00:00
|
|
|
item->filename, item->linenumber, item->columnnumber);
|
2020-06-29 10:35:17 +00:00
|
|
|
length = strlen(path_and_place);
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 10:35:17 +00:00
|
|
|
/* Encode newlines in filenames as NULs. */
|
|
|
|
recode_LF_to_NUL(path_and_place);
|
|
|
|
/* Restore the terminating newline. */
|
|
|
|
path_and_place[length - 1] = '\n';
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-06-29 10:35:17 +00:00
|
|
|
if (fwrite(path_and_place, sizeof(char), length, histfile) < length)
|
2020-09-21 17:25:07 +00:00
|
|
|
jot_error(N_("Error writing %s: %s"), poshistname, strerror(errno));
|
2020-06-29 10:35:17 +00:00
|
|
|
|
|
|
|
free(path_and_place);
|
|
|
|
}
|
2020-06-29 10:20:33 +00:00
|
|
|
|
2020-06-29 11:05:10 +00:00
|
|
|
if (fclose(histfile) == EOF)
|
2020-09-21 17:25:07 +00:00
|
|
|
jot_error(N_("Error writing %s: %s"), poshistname, strerror(errno));
|
2017-12-29 18:27:33 +00:00
|
|
|
|
2020-08-22 08:20:24 +00:00
|
|
|
if (stat(poshistname, &fileinfo) == 0)
|
2020-06-30 08:48:47 +00:00
|
|
|
latest_timestamp = fileinfo.st_mtime;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2017-10-21 03:26:20 +00:00
|
|
|
/* Reload the position history file if it has been modified since last load. */
|
|
|
|
void reload_positions_if_needed(void)
|
|
|
|
{
|
2021-10-12 15:11:52 +00:00
|
|
|
poshiststruct *item, *nextone;
|
2020-06-30 08:48:47 +00:00
|
|
|
struct stat fileinfo;
|
2017-10-21 03:26:20 +00:00
|
|
|
|
2021-10-12 15:11:52 +00:00
|
|
|
if (stat(poshistname, &fileinfo) != 0 || fileinfo.st_mtime == latest_timestamp)
|
|
|
|
return;
|
2017-10-21 03:26:20 +00:00
|
|
|
|
2021-10-12 15:11:52 +00:00
|
|
|
for (item = position_history; item != NULL; item = nextone) {
|
|
|
|
nextone = item->next;
|
|
|
|
free(item->filename);
|
|
|
|
free(item);
|
|
|
|
}
|
2020-06-29 10:20:33 +00:00
|
|
|
|
2021-10-12 15:11:52 +00:00
|
|
|
position_history = NULL;
|
2017-10-21 03:26:20 +00:00
|
|
|
|
2021-10-12 15:11:52 +00:00
|
|
|
load_poshistory();
|
2017-10-21 03:26:20 +00:00
|
|
|
}
|
|
|
|
|
2020-05-08 08:40:03 +00:00
|
|
|
/* Update the recorded last file positions with the current position in the
|
|
|
|
* current buffer. If no existing entry is found, add a new one at the end. */
|
|
|
|
void update_poshistory(void)
|
2017-09-17 10:10:12 +00:00
|
|
|
{
|
2020-05-08 08:40:03 +00:00
|
|
|
char *fullpath = get_full_path(openfile->filename);
|
2021-10-12 16:02:24 +00:00
|
|
|
poshiststruct *previous = NULL;
|
|
|
|
poshiststruct *item, *theone;
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2020-05-08 08:40:03 +00:00
|
|
|
if (fullpath == NULL || openfile->filename[0] == '\0') {
|
2017-12-29 18:27:33 +00:00
|
|
|
free(fullpath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
reload_positions_if_needed();
|
|
|
|
|
|
|
|
/* Look for a matching filename in the list. */
|
2021-10-12 16:02:24 +00:00
|
|
|
for (item = position_history; item != NULL; item = item->next) {
|
|
|
|
if (!strcmp(item->filename, fullpath))
|
2017-12-29 18:27:33 +00:00
|
|
|
break;
|
2021-10-12 16:02:24 +00:00
|
|
|
previous = item;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Don't record files that have the default cursor position. */
|
2020-05-08 08:40:03 +00:00
|
|
|
if (openfile->current->lineno == 1 && openfile->current_x == 0) {
|
2021-10-12 16:02:24 +00:00
|
|
|
if (item != NULL) {
|
|
|
|
if (previous == NULL)
|
|
|
|
position_history = item->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
else
|
2021-10-12 16:02:24 +00:00
|
|
|
previous->next = item->next;
|
|
|
|
free(item->filename);
|
|
|
|
free(item);
|
2017-12-29 18:27:33 +00:00
|
|
|
save_poshistory();
|
|
|
|
}
|
|
|
|
free(fullpath);
|
|
|
|
return;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
2021-10-12 16:02:24 +00:00
|
|
|
theone = item;
|
2017-12-29 18:27:33 +00:00
|
|
|
|
|
|
|
/* 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) {
|
2020-08-30 02:57:45 +00:00
|
|
|
theone = nmalloc(sizeof(poshiststruct));
|
2019-10-13 10:24:27 +00:00
|
|
|
theone->filename = copy_of(fullpath);
|
2017-12-29 18:27:33 +00:00
|
|
|
if (position_history == NULL)
|
|
|
|
position_history = theone;
|
|
|
|
else
|
2021-10-12 16:02:24 +00:00
|
|
|
previous->next = theone;
|
|
|
|
} else if (item->next != NULL) {
|
|
|
|
if (previous == NULL)
|
|
|
|
position_history = item->next;
|
2017-12-29 18:27:33 +00:00
|
|
|
else
|
2021-10-12 16:02:24 +00:00
|
|
|
previous->next = item->next;
|
|
|
|
while (item->next != NULL)
|
|
|
|
item = item->next;
|
|
|
|
item->next = theone;
|
2017-12-29 18:27:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Store the last cursor position. */
|
2020-09-22 08:12:43 +00:00
|
|
|
theone->linenumber = openfile->current->lineno;
|
|
|
|
theone->columnnumber = xplustabs() + 1;
|
2017-12-29 18:27:33 +00:00
|
|
|
theone->next = NULL;
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(fullpath);
|
2017-10-21 03:26:20 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
save_poshistory();
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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)
|
|
|
|
{
|
2017-12-29 18:27:33 +00:00
|
|
|
char *fullpath = get_full_path(file);
|
2021-10-12 16:02:24 +00:00
|
|
|
poshiststruct *item;
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
if (fullpath == NULL)
|
|
|
|
return FALSE;
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
reload_positions_if_needed();
|
2017-10-21 03:26:20 +00:00
|
|
|
|
2021-10-12 16:02:24 +00:00
|
|
|
item = position_history;
|
|
|
|
while (item != NULL && strcmp(item->filename, fullpath) != 0)
|
|
|
|
item = item->next;
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2017-12-29 18:27:33 +00:00
|
|
|
free(fullpath);
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2021-10-12 16:02:24 +00:00
|
|
|
if (item == NULL)
|
2017-12-29 18:27:33 +00:00
|
|
|
return FALSE;
|
2017-09-17 10:10:12 +00:00
|
|
|
|
2021-10-12 16:02:24 +00:00
|
|
|
*line = item->linenumber;
|
|
|
|
*column = item->columnnumber;
|
2017-12-29 18:27:33 +00:00
|
|
|
return TRUE;
|
2017-09-17 10:10:12 +00:00
|
|
|
}
|
2017-10-29 18:42:12 +00:00
|
|
|
#endif /* ENABLE_HISTORIES */
|