Commit 55873a00 authored by Tom Lane's avatar Tom Lane

Improve psql's behavior when the editor is exited without saving.

When editing the previous query buffer, if the editor is exited
without modifying the temp file then clear the query buffer,
rather than re-loading (and probably re-executing) the previous
query buffer.  This reduces the probability of accidentally
re-executing something you didn't intend to.

Similarly, in "\e file", if the file isn't actually modified
then don't load it into the query buffer.  And in "\ef" and
"\ev", if no changes are made then clear the query buffer
instead of loading the function or view definition into it.

Cases where we fail to invoke the editor at all, or it returns
a nonzero status, are treated like the no-file-modification case.

Laurenz Albe, reviewed by Jacob Champion

Discussion: https://postgr.es/m/0ba3f2a658bac6546d9934ab6ba63a805d46a49b.camel@cybertec.at
parent 225a22b1
...@@ -1970,7 +1970,9 @@ testdb=> ...@@ -1970,7 +1970,9 @@ testdb=>
</para> </para>
<para> <para>
The new contents of the query buffer are then re-parsed according to If you edit a file or the previous query, and you quit the editor without
modifying the file, the query buffer is cleared.
Otherwise, the new contents of the query buffer are re-parsed according to
the normal rules of <application>psql</application>, treating the the normal rules of <application>psql</application>, treating the
whole buffer as a single line. Any complete queries are immediately whole buffer as a single line. Any complete queries are immediately
executed; that is, if the query buffer contains or ends with a executed; that is, if the query buffer contains or ends with a
...@@ -2039,7 +2041,8 @@ Tue Oct 26 21:40:57 CEST 1999 ...@@ -2039,7 +2041,8 @@ Tue Oct 26 21:40:57 CEST 1999
in the form of a <command>CREATE OR REPLACE FUNCTION</command> or in the form of a <command>CREATE OR REPLACE FUNCTION</command> or
<command>CREATE OR REPLACE PROCEDURE</command> command. <command>CREATE OR REPLACE PROCEDURE</command> command.
Editing is done in the same way as for <literal>\edit</literal>. Editing is done in the same way as for <literal>\edit</literal>.
After the editor exits, the updated command is executed immediately If you quit the editor without saving, the statement is discarded.
If you save and exit the editor, the updated command is executed immediately
if you added a semicolon to it. Otherwise it is redisplayed; if you added a semicolon to it. Otherwise it is redisplayed;
type semicolon or <literal>\g</literal> to send it, or <literal>\r</literal> type semicolon or <literal>\g</literal> to send it, or <literal>\r</literal>
to cancel. to cancel.
...@@ -2115,7 +2118,8 @@ Tue Oct 26 21:40:57 CEST 1999 ...@@ -2115,7 +2118,8 @@ Tue Oct 26 21:40:57 CEST 1999
This command fetches and edits the definition of the named view, This command fetches and edits the definition of the named view,
in the form of a <command>CREATE OR REPLACE VIEW</command> command. in the form of a <command>CREATE OR REPLACE VIEW</command> command.
Editing is done in the same way as for <literal>\edit</literal>. Editing is done in the same way as for <literal>\edit</literal>.
After the editor exits, the updated command is executed immediately If you quit the editor without saving, the statement is discarded.
If you save and exit the editor, the updated command is executed immediately
if you added a semicolon to it. Otherwise it is redisplayed; if you added a semicolon to it. Otherwise it is redisplayed;
type semicolon or <literal>\g</literal> to send it, or <literal>\r</literal> type semicolon or <literal>\g</literal> to send it, or <literal>\r</literal>
to cancel. to cancel.
......
...@@ -148,11 +148,11 @@ static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cst ...@@ -148,11 +148,11 @@ static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cst
PQExpBuffer query_buf); PQExpBuffer query_buf);
static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack, static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
PQExpBuffer query_buf); PQExpBuffer query_buf);
static void copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf); static bool copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf);
static bool do_connect(enum trivalue reuse_previous_specification, static bool do_connect(enum trivalue reuse_previous_specification,
char *dbname, char *user, char *host, char *port); char *dbname, char *user, char *host, char *port);
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
int lineno, bool *edited); int lineno, bool discard_on_quit, bool *edited);
static bool do_shell(const char *command); static bool do_shell(const char *command);
static bool do_watch(PQExpBuffer query_buf, double sleep); static bool do_watch(PQExpBuffer query_buf, double sleep);
static bool lookup_object_oid(EditableObjectType obj_type, const char *desc, static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
...@@ -418,7 +418,7 @@ exec_command(const char *cmd, ...@@ -418,7 +418,7 @@ exec_command(const char *cmd,
* the individual command subroutines. * the individual command subroutines.
*/ */
if (status == PSQL_CMD_SEND) if (status == PSQL_CMD_SEND)
copy_previous_query(query_buf, previous_buf); (void) copy_previous_query(query_buf, previous_buf);
return status; return status;
} }
...@@ -1004,14 +1004,27 @@ exec_command_edit(PsqlScanState scan_state, bool active_branch, ...@@ -1004,14 +1004,27 @@ exec_command_edit(PsqlScanState scan_state, bool active_branch,
} }
if (status != PSQL_CMD_ERROR) if (status != PSQL_CMD_ERROR)
{ {
bool discard_on_quit;
expand_tilde(&fname); expand_tilde(&fname);
if (fname) if (fname)
{
canonicalize_path(fname); canonicalize_path(fname);
/* Always clear buffer if the file isn't modified */
discard_on_quit = true;
}
else
{
/*
* If query_buf is empty, recall previous query for
* editing. But in that case, the query buffer should be
* emptied if editing doesn't modify the file.
*/
discard_on_quit = copy_previous_query(query_buf,
previous_buf);
}
/* If query_buf is empty, recall previous query for editing */ if (do_edit(fname, query_buf, lineno, discard_on_quit, NULL))
copy_previous_query(query_buf, previous_buf);
if (do_edit(fname, query_buf, lineno, NULL))
status = PSQL_CMD_NEWEDIT; status = PSQL_CMD_NEWEDIT;
else else
status = PSQL_CMD_ERROR; status = PSQL_CMD_ERROR;
...@@ -1134,7 +1147,7 @@ exec_command_ef_ev(PsqlScanState scan_state, bool active_branch, ...@@ -1134,7 +1147,7 @@ exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
{ {
bool edited = false; bool edited = false;
if (!do_edit(NULL, query_buf, lineno, &edited)) if (!do_edit(NULL, query_buf, lineno, true, &edited))
status = PSQL_CMD_ERROR; status = PSQL_CMD_ERROR;
else if (!edited) else if (!edited)
puts(_("No changes")); puts(_("No changes"));
...@@ -2637,7 +2650,7 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch, ...@@ -2637,7 +2650,7 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch,
} }
/* If query_buf is empty, recall and execute previous query */ /* If query_buf is empty, recall and execute previous query */
copy_previous_query(query_buf, previous_buf); (void) copy_previous_query(query_buf, previous_buf);
success = do_watch(query_buf, sleep); success = do_watch(query_buf, sleep);
...@@ -2961,12 +2974,19 @@ discard_query_text(PsqlScanState scan_state, ConditionalStack cstack, ...@@ -2961,12 +2974,19 @@ discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
* This is used by various slash commands for which re-execution of a * This is used by various slash commands for which re-execution of a
* previous query is a common usage. For convenience, we allow the * previous query is a common usage. For convenience, we allow the
* case of query_buf == NULL (and do nothing). * case of query_buf == NULL (and do nothing).
*
* Returns "true" if the previous query was copied into the query
* buffer, else "false".
*/ */
static void static bool
copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf) copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf)
{ {
if (query_buf && query_buf->len == 0) if (query_buf && query_buf->len == 0)
{
appendPQExpBufferStr(query_buf, previous_buf->data); appendPQExpBufferStr(query_buf, previous_buf->data);
return true;
}
return false;
} }
/* /*
...@@ -3647,10 +3667,11 @@ UnsyncVariables(void) ...@@ -3647,10 +3667,11 @@ UnsyncVariables(void)
/* /*
* do_edit -- handler for \e * helper for do_edit(): actually invoke the editor
* *
* If you do not specify a filename, the current query buffer will be copied * Returns true on success, false if we failed to invoke the editor or
* into a temporary one. * it returned nonzero status. (An error message is printed for failed-
* to-invoke cases, but not if the editor returns nonzero status.)
*/ */
static bool static bool
editFile(const char *fname, int lineno) editFile(const char *fname, int lineno)
...@@ -3719,10 +3740,23 @@ editFile(const char *fname, int lineno) ...@@ -3719,10 +3740,23 @@ editFile(const char *fname, int lineno)
} }
/* call this one */ /*
* do_edit -- handler for \e
*
* If you do not specify a filename, the current query buffer will be copied
* into a temporary file.
*
* After this function is done, the resulting file will be copied back into the
* query buffer. As an exception to this, the query buffer will be emptied
* if the file was not modified (or the editor failed) and the caller passes
* "discard_on_quit" = true.
*
* If "edited" isn't NULL, *edited will be set to true if the query buffer
* is successfully replaced.
*/
static bool static bool
do_edit(const char *filename_arg, PQExpBuffer query_buf, do_edit(const char *filename_arg, PQExpBuffer query_buf,
int lineno, bool *edited) int lineno, bool discard_on_quit, bool *edited)
{ {
char fnametmp[MAXPGPATH]; char fnametmp[MAXPGPATH];
FILE *stream = NULL; FILE *stream = NULL;
...@@ -3870,6 +3904,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, ...@@ -3870,6 +3904,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
{ {
pg_log_error("%s: %m", fname); pg_log_error("%s: %m", fname);
error = true; error = true;
resetPQExpBuffer(query_buf);
} }
else if (edited) else if (edited)
{ {
...@@ -3879,6 +3914,15 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, ...@@ -3879,6 +3914,15 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
fclose(stream); fclose(stream);
} }
} }
else
{
/*
* If the file was not modified, and the caller requested it, discard
* the query buffer.
*/
if (discard_on_quit)
resetPQExpBuffer(query_buf);
}
/* remove temp file */ /* remove temp file */
if (!filename_arg) if (!filename_arg)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment