I've confirmed that the attached patches work as expected in both the
oldest and newest readline and libedit versions available to me.
Barring further objections, I plan to commit and back-patch these
changes.

                        regards, tom lane

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 74d4618..6033dfd 100644
*** a/doc/src/sgml/ref/psql-ref.sgml
--- b/doc/src/sgml/ref/psql-ref.sgml
*************** EOF
*** 277,283 ****
        <term><option>--no-readline</></term>
        <listitem>
        <para>
!        Do not use <application>readline</application> for line editing and do not use the history.
         This can be useful to turn off tab expansion when cutting and pasting.
        </para>
        </listitem>
--- 277,284 ----
        <term><option>--no-readline</></term>
        <listitem>
        <para>
!        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>
*************** lo_import 152801
*** 2357,2368 ****
          <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.
          </para>
          </listitem>
        </varlistentry>
--- 2358,2370 ----
          <term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
          <listitem>
          <para>
!         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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index e16b4d5..e1949d8 100644
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
*************** exec_command(const char *cmd,
*** 1088,1107 ****
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, true);
  
- #if defined(WIN32) && !defined(__CYGWIN__)
- 
- 		/*
- 		 * XXX This does not work for all terminal environments or for output
- 		 * containing non-ASCII characters; see comments in simple_prompt().
- 		 */
- #define DEVTTY	"con"
- #else
- #define DEVTTY	"/dev/tty"
- #endif
- 
  		expand_tilde(&fname);
! 		/* This scrolls off the screen when using /dev/tty */
! 		success = saveHistory(fname ? fname : DEVTTY, -1, false, false);
  		if (success && !pset.quiet && fname)
  			printf(_("Wrote history to file \"%s\".\n"), fname);
  		if (!fname)
--- 1088,1095 ----
  		char	   *fname = psql_scan_slash_option(scan_state,
  												   OT_NORMAL, NULL, true);
  
  		expand_tilde(&fname);
! 		success = printHistory(fname, pset.popt.topt.pager);
  		if (success && !pset.quiet && fname)
  			printf(_("Wrote history to file \"%s\".\n"), fname);
  		if (!fname)
diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c
index aa32a3f..2e01eb1 100644
*** a/src/bin/psql/input.c
--- b/src/bin/psql/input.c
***************
*** 11,16 ****
--- 11,17 ----
  #include <unistd.h>
  #endif
  #include <fcntl.h>
+ #include <limits.h>
  
  #include "input.h"
  #include "settings.h"
*************** initializeInput(int flags)
*** 319,340 ****
  
  
  /*
!  * This function saves the readline history when user
!  * runs \s command or 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)
  {
  #ifdef USE_READLINE
  
--- 320,334 ----
  
  
  /*
!  * 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.
   */
! static bool
! saveHistory(char *fname, int max_lines)
  {
  #ifdef USE_READLINE
  
*************** saveHistory(char *fname, int max_lines, 
*** 344,354 ****
  	 * where write_history will fail because it tries to chmod the target
  	 * file.
  	 */
! 	if (useHistory && fname &&
! 		strcmp(fname, DEVNULL) != 0)
  	{
! 		if (encodeFlag)
! 			encode_history();
  
  		/*
  		 * On newer versions of libreadline, truncate the history file as
--- 338,352 ----
  	 * where write_history will fail because it tries to chmod the target
  	 * file.
  	 */
! 	if (strcmp(fname, DEVNULL) != 0)
  	{
! 		/*
! 		 * 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
*************** saveHistory(char *fname, int max_lines, 
*** 362,368 ****
  		 * 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;
--- 360,365 ----
*************** saveHistory(char *fname, int max_lines, 
*** 387,394 ****
  			if (errno == 0)
  				return true;
  		}
! 		else
! #endif
  		{
  			/* truncate what we have ... */
  			if (max_lines >= 0)
--- 384,390 ----
  			if (errno == 0)
  				return true;
  		}
! #else							/* don't have append support */
  		{
  			/* truncate what we have ... */
  			if (max_lines >= 0)
*************** saveHistory(char *fname, int max_lines, 
*** 399,417 ****
  			if (errno == 0)
  				return true;
  		}
  
  		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;
  }
  
  
  static void
  finishInput(void)
  {
--- 395,468 ----
  			if (errno == 0)
  				return true;
  		}
+ #endif
  
  		psql_error("could not save history to file \"%s\": %s\n",
  				   fname, strerror(errno));
  	}
  #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;
+ 	int			i;
+ 	HIST_ENTRY *cur_hist;
+ 
+ 	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;
+ 	}
+ 
+ 	for (i = history_base; (cur_hist = history_get(i)) != NULL; i++)
+ 	{
+ 		fprintf(output, "%s\n", cur_hist->line);
+ 	}
+ 
+ 	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
  finishInput(void)
  {
*************** finishInput(void)
*** 421,427 ****
  		int			hist_size;
  
  		hist_size = GetVariableNum(pset.vars, "HISTSIZE", 500, -1, true);
! 		saveHistory(psql_history, hist_size, true, true);
  		free(psql_history);
  		psql_history = NULL;
  	}
--- 472,478 ----
  		int			hist_size;
  
  		hist_size = GetVariableNum(pset.vars, "HISTSIZE", 500, -1, true);
! 		(void) saveHistory(psql_history, hist_size);
  		free(psql_history);
  		psql_history = NULL;
  	}
diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h
index 1d10a22..1b22399 100644
*** a/src/bin/psql/input.h
--- b/src/bin/psql/input.h
*************** char	   *gets_interactive(const char *pr
*** 42,48 ****
  char	   *gets_fromFile(FILE *source);
  
  void		initializeInput(int flags);
! bool		saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag);
  
  void		pg_append_history(const char *s, PQExpBuffer history_buf);
  void		pg_send_history(PQExpBuffer history_buf);
--- 42,49 ----
  char	   *gets_fromFile(FILE *source);
  
  void		initializeInput(int flags);
! 
! bool		printHistory(const char *fname, unsigned short int pager);
  
  void		pg_append_history(const char *s, PQExpBuffer history_buf);
  void		pg_send_history(PQExpBuffer history_buf);
diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c
index 2e01eb1..f3193a2 100644
*** a/src/bin/psql/input.c
--- b/src/bin/psql/input.c
*************** gets_fromFile(FILE *source)
*** 229,244 ****
  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())
  	{
  		/* 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;
  	}
  }
  
--- 229,246 ----
  static void
  encode_history(void)
  {
+ 	int			i;
  	HIST_ENTRY *cur_hist;
  	char	   *cur_ptr;
  
! 	for (i = history_base; (cur_hist = history_get(i)) != NULL; i++)
  	{
  		/* 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;
+ 		}
  	}
  }
  
*************** encode_history(void)
*** 248,263 ****
  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())
  	{
  		/* 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';
  	}
  }
  #endif   /* USE_READLINE */
--- 250,267 ----
  static void
  decode_history(void)
  {
+ 	int			i;
  	HIST_ENTRY *cur_hist;
  	char	   *cur_ptr;
  
! 	for (i = history_base; (cur_hist = history_get(i)) != NULL; i++)
  	{
  		/* 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';
+ 		}
  	}
  }
  #endif   /* USE_READLINE */
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to