summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--linenoise/linenoise.c25
-rw-r--r--linenoise/linenoise.h3
-rw-r--r--parser.c15
-rw-r--r--txr.127
4 files changed, 60 insertions, 10 deletions
diff --git a/linenoise/linenoise.c b/linenoise/linenoise.c
index bdb0fedc..92a5b0e7 100644
--- a/linenoise/linenoise.c
+++ b/linenoise/linenoise.c
@@ -106,6 +106,7 @@ struct lino_state {
int mlmode; /* Multi line mode. Default is single line. */
int history_max_len;
int history_len;
+ int loaded_lines; /* How many lines come from load. */
wchar_t **history;
wchar_t *clip; /* Selection */
wchar_t *result; /* Previous command result. */
@@ -2680,6 +2681,7 @@ int lino_hist_add(lino_t *ls, const wchar_t *line) {
ls->history = coerce(wchar_t **, lino_os.alloc_fn(size));
if (ls->history == NULL) return 0;
memset(ls->history, 0, size);
+ ls->loaded_lines = 0;
}
/* Don't add duplicated lines, unless we are resubmitting historic lines. */
@@ -2695,6 +2697,8 @@ int lino_hist_add(lino_t *ls, const wchar_t *line) {
lino_os.free_fn(ls->history[0]);
memmove(ls->history,ls->history+1,(ls->history_max_len-1)*sizeof *ls->history);
ls->history_len--;
+ if (ls->loaded_lines > 0)
+ ls->loaded_lines--;
}
ls->history[ls->history_len] = linecopy;
ls->history_len++;
@@ -2723,6 +2727,10 @@ int lino_hist_set_max_len(lino_t *ls, int len) {
for (j = 0; j < tocopy-len; j++)
lino_os.free_fn(ls->history[j]);
tocopy = len;
+
+ ls->loaded_lines -= (tocopy - len);
+ if (ls->loaded_lines < 0)
+ ls->loaded_lines = 0;
}
memset(nsv, 0, sizeof *nsv * len);
memcpy(nsv, ls->history+(ls->history_len-tocopy), sizeof *ls->history * tocopy);
@@ -2736,8 +2744,10 @@ int lino_hist_set_max_len(lino_t *ls, int len) {
/* Save the history in the specified file. On success 0 is returned
* otherwise -1 is returned. */
-int lino_hist_save(lino_t *ls, const wchar_t *filename) {
- mem_t *fp = lino_os.open_fn(filename, lino_overwrite);
+int lino_hist_save(lino_t *ls, const wchar_t *filename, int new_only) {
+ int from = new_only ? ls->loaded_lines : 0;
+ mem_t *fp = lino_os.open_fn(filename,
+ new_only ? lino_append : lino_overwrite);
int j;
if (fp == NULL) {
@@ -2745,11 +2755,13 @@ int lino_hist_save(lino_t *ls, const wchar_t *filename) {
return -1;
}
- for (j = 0; j < ls->history_len; j++) {
+ for (j = from; j < ls->history_len; j++) {
lino_os.puts_file_fn(fp, ls->history[j]);
lino_os.puts_file_fn(fp, L"\n");
}
+ ls->loaded_lines = ls->history_len;
+
lino_os.close_fn(fp);
return 0;
}
@@ -2768,6 +2780,12 @@ int lino_hist_load(lino_t *ls, const wchar_t *filename) {
return -1;
}
+ if (ls->history) {
+ ls->error = lino_error;
+ lino_os.close_fn(fp);
+ return -1;
+ }
+
while (lino_os.getl_fn(fp, buf, LINENOISE_MAX_LINE) != NULL) {
wchar_t *p = wcschr(buf, '\n');
if (p)
@@ -2776,6 +2794,7 @@ int lino_hist_load(lino_t *ls, const wchar_t *filename) {
}
lino_os.close_fn(fp);
+ ls->loaded_lines = ls->history_len;
return 0;
}
diff --git a/linenoise/linenoise.h b/linenoise/linenoise.h
index 178df11c..05f66632 100644
--- a/linenoise/linenoise.h
+++ b/linenoise/linenoise.h
@@ -58,6 +58,7 @@ typedef unsigned char mem_t;
typedef enum lino_file_mode {
lino_read,
lino_overwrite,
+ lino_append,
} lino_file_mode_t;
typedef struct lino_os {
@@ -112,7 +113,7 @@ lino_error_t lino_get_error(lino_t *);
lino_error_t lino_set_error(lino_t *, lino_error_t); /* returns old */
int lino_hist_add(lino_t *, const wchar_t *line);
int lino_hist_set_max_len(lino_t *, int len);
-int lino_hist_save(lino_t *, const wchar_t *filename);
+int lino_hist_save(lino_t *, const wchar_t *filename, int new_only);
int lino_hist_load(lino_t *, const wchar_t *filename);
void lino_set_result(lino_t *, wchar_t *); /* takes ownership of malloced mem; modifies it */
int lino_clear_screen(lino_t *);
diff --git a/parser.c b/parser.c
index 8746f4c8..0c6067e7 100644
--- a/parser.c
+++ b/parser.c
@@ -1505,12 +1505,19 @@ val repl(val bindings, val in_stream, val out_stream, val env)
dyn_env = saved_dyn_env;
if (histfile_w) {
- val histfile_tmp = format(nil, lit("~a/.txr_history.tmp"), home, nao);
- if (lino_hist_save(ls, c_str(histfile_tmp)) == 0)
+ val histfile_tmp = format(nil, lit("~a.tmp"), histfile, nao);
+ const wchar_t *histfile_tmp_w = c_str(histfile_tmp);
+ lino_t *ltmp = lino_make(coerce(mem_t *, in_stream),
+ coerce(mem_t *, out_stream));
+ lino_hist_set_max_len(ltmp, c_num(cdr(hist_len_var)));
+ lino_hist_load(ltmp, histfile_w);
+ lino_hist_save(ltmp, histfile_tmp_w, 0);
+ if (lino_hist_save(ls, histfile_tmp_w, 1) == 0)
rename_path(histfile_tmp, histfile);
else
put_line(lit("** unable to save history file"), out_stream);
gc_hint(histfile_tmp);
+ lino_free(ltmp);
}
free(line_w);
@@ -1649,7 +1656,7 @@ static int lino_feof(mem_t *stream_in)
}
static const wchli_t *lino_mode_str[] = {
- wli("r"), wli("w")
+ wli("r"), wli("w"), wli("a")
};
static mem_t *lino_open(const wchar_t *name_in, lino_file_mode_t mode_in)
@@ -1660,7 +1667,7 @@ static mem_t *lino_open(const wchar_t *name_in, lino_file_mode_t mode_in)
ignerr_begin;
ret = open_file(name, mode);
#if HAVE_CHMOD
- if (mode_in == lino_overwrite)
+ if (mode_in == lino_overwrite || mode_in == lino_append)
(void) fchmod(c_num(stream_fd(ret)), S_IRUSR | S_IWUSR);
#endif
ignerr_end;
diff --git a/txr.1 b/txr.1
index 6c032c05..0d2c3c09 100644
--- a/txr.1
+++ b/txr.1
@@ -72025,9 +72025,32 @@ option isn't present.
The history is maintained in a text file called
.code .txr_history
in the user's home directory. Whenever the interactive listener terminates,
-this file is overwritten with the history contents stored in the listener's
+this file is updated with the history contents stored in the listener's
memory. The next time the listener starts, it first re-loads the history from
-this file, making the commands of a previous session available for recall.
+this file, making the most recent
+.code *listener-hist-len*
+expressions of a previous session available for recall.
+
+The history file is maintained in a way that is somewhat
+robust against the loss of history arising from the situation that a user
+manages multiple simultaneous \*(TX sessions. When a session terminates, it
+doesn't blindly overwrite the history file, which may have already been updated
+with new history produced by another session. Rather, it appends new entries
+to the history file. New entries are those that had not been previously read
+from the history file, but have been newly entered into the listener.
+
+An effort is made to keep the history file trimmed to no more than
+twice the number of entries specified in
+.codn *listener-hist-len* .
+The terminating session first makes a temporary copy of the existing
+history, which is trimmed to the most recent
+.code *listener-hist-len*
+entries. New entries are then appended to this temporary file.
+Finally, the actual history file is replaced with this temporary file by a
+.code rename-path
+a rename operation. This algorithm doesn't use locking, and is therefore not
+robust against the situation when a two or more multiple interactive \*(TX
+sessions belonging to the same user terminate at around the same time.
The home directory is determined from the
contents of the