<term><option>--no-readline</></term>
<listitem>
<para>
- Do not use readline for line editing and do not use the history.
+ Do not use <application>Readline</application> for line editing and do
+ not use the command history.
This can be useful to turn off tab expansion when cutting and pasting.
</para>
</listitem>
<term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
<listitem>
<para>
- Print or save the command line history to <replaceable
- class="parameter">filename</replaceable>. If <replaceable
- class="parameter">filename</replaceable> is omitted, the history
- is written to the standard output. This option is only available
- if <application>psql</application> is configured to use the
- <acronym>GNU</acronym> <application>Readline</application> library.
+ Print <application>psql</application>'s command line history
+ to <replaceable class="parameter">filename</replaceable>.
+ If <replaceable class="parameter">filename</replaceable> is omitted,
+ the history is written to the standard output (using the pager if
+ appropriate). This command is not available
+ if <application>psql</application> was built
+ without <application>Readline</application> support.
</para>
</listitem>
</varlistentry>
#include <unistd.h>
#endif
#include <fcntl.h>
+#include <limits.h>
#include "input.h"
#include "settings.h"
#ifdef USE_READLINE
+
+/*
+ * Macros to iterate over each element of the history list in order
+ *
+ * You would think this would be simple enough, but in its inimitable fashion
+ * libedit has managed to break it: in libreadline we must use next_history()
+ * to go from oldest to newest, but in libedit we must use previous_history().
+ * To detect what to do, we make a trial call of previous_history(): if it
+ * fails, then either next_history() is what to use, or there's zero or one
+ * history entry so that it doesn't matter which direction we go.
+ *
+ * In case that wasn't disgusting enough: the code below is not as obvious as
+ * it might appear. In some libedit releases history_set_pos(0) fails until
+ * at least one add_history() call has been done. This is not an issue for
+ * printHistory() or encode_history(), which cannot be invoked before that has
+ * happened. In decode_history(), that's not so, and what actually happens is
+ * that we are sitting on the newest entry to start with, previous_history()
+ * fails, and we iterate over all the entries using next_history(). So the
+ * decode_history() loop iterates over the entries in the wrong order when
+ * using such a libedit release, and if there were another attempt to use
+ * BEGIN_ITERATE_HISTORY() before some add_history() call had happened, it
+ * wouldn't work. Fortunately we don't care about either of those things.
+ *
+ * Usage pattern is:
+ *
+ * BEGIN_ITERATE_HISTORY(varname);
+ * {
+ * loop body referencing varname->line;
+ * }
+ * END_ITERATE_HISTORY();
+ */
+#define BEGIN_ITERATE_HISTORY(VARNAME) \
+ do { \
+ HIST_ENTRY *VARNAME; \
+ bool use_prev_; \
+ \
+ history_set_pos(0); \
+ use_prev_ = (previous_history() != NULL); \
+ history_set_pos(0); \
+ for (VARNAME = current_history(); VARNAME != NULL; \
+ VARNAME = use_prev_ ? previous_history() : next_history()) \
+ { \
+ (void) 0
+
+#define END_ITERATE_HISTORY() \
+ } \
+ } while(0)
+
+
/*
* Convert newlines to NL_IN_HISTORY for safe saving in readline history file
*/
static void
encode_history(void)
{
- HIST_ENTRY *cur_hist;
- char *cur_ptr;
-
- history_set_pos(0);
- for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
+ BEGIN_ITERATE_HISTORY(cur_hist);
{
+ char *cur_ptr;
+
/* some platforms declare HIST_ENTRY.line as const char * */
for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
+ {
if (*cur_ptr == '\n')
*cur_ptr = NL_IN_HISTORY;
+ }
}
+ END_ITERATE_HISTORY();
}
/*
static void
decode_history(void)
{
- HIST_ENTRY *cur_hist;
- char *cur_ptr;
-
- history_set_pos(0);
- for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
+ BEGIN_ITERATE_HISTORY(cur_hist);
{
+ char *cur_ptr;
+
/* some platforms declare HIST_ENTRY.line as const char * */
for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
+ {
if (*cur_ptr == NL_IN_HISTORY)
*cur_ptr = '\n';
+ }
}
+ END_ITERATE_HISTORY();
}
#endif /* USE_READLINE */
/*
- * This function saves the readline history when user
- * runs \s command or when psql exits.
+ * This function saves the readline history when psql exits.
*
* fname: pathname of history file. (Should really be "const char *",
* but some ancient versions of readline omit the const-decoration.)
*
* max_lines: if >= 0, limit history file to that many entries.
- *
- * appendFlag: if true, try to append just our new lines to the file.
- * If false, write the whole available history.
- *
- * encodeFlag: whether to encode \n as \x01. For \s calls we don't wish
- * to do that, but must do so when saving the final history file.
*/
-bool
-saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
+static bool
+saveHistory(char *fname, int max_lines)
{
#ifdef USE_READLINE
* where write_history will fail because it tries to chmod the target
* file.
*/
- if (useHistory && fname &&
- strcmp(fname, DEVNULL) != 0)
+ if (strcmp(fname, DEVNULL) != 0)
{
- if (encodeFlag)
- encode_history();
+ /*
+ * Encode \n, since otherwise readline will reload multiline history
+ * entries as separate lines. (libedit doesn't really need this, but
+ * we do it anyway since it's too hard to tell which implementation we
+ * are using.)
+ */
+ encode_history();
/*
* On newer versions of libreadline, truncate the history file as
* see if the write failed. Similarly for append_history.
*/
#if defined(HAVE_HISTORY_TRUNCATE_FILE) && defined(HAVE_APPEND_HISTORY)
- if (appendFlag)
{
int nlines;
int fd;
if (errno == 0)
return true;
}
- else
-#endif
+#else /* don't have append support */
{
/* truncate what we have ... */
if (max_lines >= 0)
if (errno == 0)
return true;
}
+#endif
psql_error("could not save history to file \"%s\": %s\n",
fname, strerror(errno));
}
-#else
- /* only get here in \s case, so complain */
- psql_error("history is not supported by this installation\n");
#endif
return false;
}
+/*
+ * Print history to the specified file, or to the console if fname is NULL
+ * (psql \s command)
+ *
+ * We used to use saveHistory() for this purpose, but that doesn't permit
+ * use of a pager; moreover libedit's implementation behaves incompatibly
+ * (preferring to encode its output) and may fail outright when the target
+ * file is specified as /dev/tty.
+ */
+bool
+printHistory(const char *fname, unsigned short int pager)
+{
+#ifdef USE_READLINE
+ FILE *output;
+ bool is_pager;
+
+ if (!useHistory)
+ return false;
+
+ if (fname == NULL)
+ {
+ /* use pager, if enabled, when printing to console */
+ output = PageOutput(INT_MAX, pager);
+ is_pager = true;
+ }
+ else
+ {
+ output = fopen(fname, "w");
+ if (output == NULL)
+ {
+ psql_error("could not save history to file \"%s\": %s\n",
+ fname, strerror(errno));
+ return false;
+ }
+ is_pager = false;
+ }
+
+ BEGIN_ITERATE_HISTORY(cur_hist);
+ {
+ fprintf(output, "%s\n", cur_hist->line);
+ }
+ END_ITERATE_HISTORY();
+
+ if (is_pager)
+ ClosePager(output);
+ else
+ fclose(output);
+
+ return true;
+#else
+ psql_error("history is not supported by this installation\n");
+ return false;
+#endif
+}
+
+
static void
#ifdef HAVE_ATEXIT
finishInput(void)
int hist_size;
hist_size = GetVariableNum(pset.vars, "HISTSIZE", 500, -1, true);
- saveHistory(psql_history, hist_size, true, true);
+ (void) saveHistory(psql_history, hist_size);
free(psql_history);
psql_history = NULL;
}