diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 4510923d0d..47b1e99e05 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1,5 +1,5 @@
@@ -1338,48 +1338,60 @@ testdb=>
- \edit (or \e) filename
+ \edit> (or \e>) filename> line_number>
If filename is
specified, the file is edited; after the editor exits, its
- content is copied back to the query buffer. If no argument is
- given, the current query buffer is copied to a temporary file
- which is then edited in the same fashion.
+ content is copied back to the query buffer. If no filename is given, the current query
+ buffer is copied to a temporary file which is then edited in the same
+ fashion.
The new query buffer is then re-parsed according to the normal
rules of psql, where the whole buffer
is treated as a single line. (Thus you cannot make scripts this
- way. Use \i for that.) This means also that
- if the query ends with (or rather contains) a semicolon, it is
- immediately executed. In other cases it will merely wait in the
- query buffer.
+ way. Use \i for that.) This means that
+ if the query ends with (or contains) a semicolon, it is
+ immediately executed. Otherwise it will merely wait in the
+ query buffer; type semicolon or \g> to send it, or
+ \r> to cancel.
- psql searches the environment
+ psql checks the environment
variables PSQL_EDITOR, EDITOR, and
VISUAL (in that order) for an editor to use. If
all of them are unset, vi is used on Unix
systems, notepad.exe on Windows systems.
+
+
+ If a line number is specified, psql will
+ position the cursor on the specified line of the file or query buffer.
+ This feature requires the EDITOR_LINENUMBER_SWITCH
+ variable to be set, so that psql knows how
+ to specify the line number to the editor. Note that if a single
+ all-digits argument is given, psql assumes
+ it is a line number not a file name.
+
- \ef function_description
+ \ef function_description> line_number>
This command fetches and edits the definition of the named function,
in the form of a CREATE OR REPLACE FUNCTION> command.
- Editing is done in the same way as for \e>.
+ Editing is done in the same way as for \edit>.
After the editor exits, the updated command waits in the query buffer;
type semicolon or \g> to send it, or \r>
to cancel.
@@ -1396,6 +1408,16 @@ testdb=>
If no function is specified, a blank CREATE FUNCTION>
template is presented for editing.
+
+
+ If a line number is specified, psql will
+ position the cursor on the specified line of the function body
+ (note that the function body typically does not begin on the
+ first line of the file).
+ This feature requires the EDITOR_LINENUMBER_SWITCH
+ variable to be set, so that psql knows how
+ to specify the line number to the editor.
+
@@ -2457,6 +2479,27 @@ bar
+
+ EDITOR_LINENUMBER_SWITCH
+
+
+ When \edit or \ef is used with a
+ line number argument, this variable specifies the command-line switch
+ used to pass the line number to the user's editor. For editors such
+ as emacs> or vi>, you can simply set
+ this variable to a plus sign. Include a trailing space in the value
+ of the variable if there needs to be space between the switch name and
+ the line number.
+ Examples:
+
+
+\set EDITOR_LINENUMBER_SWITCH +
+\set EDITOR_LINENUMBER_SWITCH '--line '
+
+
+
+
+
ENCODING
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0887c705f5..655d3f890c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.330 2010/08/03 19:24:04 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.331 2010/08/12 00:40:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1620,6 +1620,10 @@ pg_get_serial_sequence(PG_FUNCTION_ARGS)
* pg_get_functiondef
* Returns the complete "CREATE OR REPLACE FUNCTION ..." statement for
* the specified function.
+ *
+ * Note: if you change the output format of this function, be careful not
+ * to break psql's rules (in \ef) for identifying the start of the function
+ * body.
*/
Datum
pg_get_functiondef(PG_FUNCTION_ARGS)
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 90cd813f1e..687cbca2ca 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3,7 +3,7 @@
*
* Copyright (c) 2000-2010, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.225 2010/08/03 18:33:09 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.226 2010/08/12 00:40:59 tgl Exp $
*/
#include "postgres_fe.h"
#include "command.h"
@@ -57,11 +57,12 @@ static backslashResult exec_command(const char *cmd,
PsqlScanState scan_state,
PQExpBuffer query_buf);
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
- bool *edited);
+ int lineno, bool *edited);
static bool do_connect(char *dbname, char *user, char *host, char *port);
static bool do_shell(const char *command);
static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
static bool get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf);
+static int strip_lineno_from_funcdesc(char *func);
static void minimal_error_message(PGresult *res);
static void printSSLInfo(void);
@@ -497,8 +498,8 @@ exec_command(const char *cmd,
/*
- * \e or \edit -- edit the current query buffer (or a file and make it the
- * query buffer
+ * \e or \edit -- edit the current query buffer, or edit a file and make
+ * it the query buffer
*/
else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
{
@@ -510,17 +511,51 @@ exec_command(const char *cmd,
else
{
char *fname;
+ char *ln = NULL;
+ int lineno = -1;
fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
- expand_tilde(&fname);
if (fname)
- canonicalize_path(fname);
- if (do_edit(fname, query_buf, NULL))
- status = PSQL_CMD_NEWEDIT;
- else
- status = PSQL_CMD_ERROR;
- free(fname);
+ {
+ /* try to get separate lineno arg */
+ ln = psql_scan_slash_option(scan_state,
+ OT_NORMAL, NULL, true);
+ if (ln == NULL)
+ {
+ /* only one arg; maybe it is lineno not fname */
+ if (fname[0] &&
+ strspn(fname, "0123456789") == strlen(fname))
+ {
+ /* all digits, so assume it is lineno */
+ ln = fname;
+ fname = NULL;
+ }
+ }
+ }
+ if (ln)
+ {
+ lineno = atoi(ln);
+ if (lineno < 1)
+ {
+ psql_error("invalid line number: %s\n", ln);
+ status = PSQL_CMD_ERROR;
+ }
+ }
+ if (status != PSQL_CMD_ERROR)
+ {
+ expand_tilde(&fname);
+ if (fname)
+ canonicalize_path(fname);
+ if (do_edit(fname, query_buf, lineno, NULL))
+ status = PSQL_CMD_NEWEDIT;
+ else
+ status = PSQL_CMD_ERROR;
+ }
+ if (fname)
+ free(fname);
+ if (ln)
+ free(ln);
}
}
@@ -530,6 +565,8 @@ exec_command(const char *cmd,
*/
else if (strcmp(cmd, "ef") == 0)
{
+ int lineno = -1;
+
if (!query_buf)
{
psql_error("no query buffer\n");
@@ -542,7 +579,13 @@ exec_command(const char *cmd,
func = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, true);
- if (!func)
+ lineno = strip_lineno_from_funcdesc(func);
+ if (lineno == 0)
+ {
+ /* error already reported */
+ status = PSQL_CMD_ERROR;
+ }
+ else if (!func)
{
/* set up an empty command to fill in */
printfPQExpBuffer(query_buf,
@@ -563,6 +606,32 @@ exec_command(const char *cmd,
/* error already reported */
status = PSQL_CMD_ERROR;
}
+ else if (lineno > 0)
+ {
+ /*
+ * lineno "1" should correspond to the first line of the
+ * function body. We expect that pg_get_functiondef() will
+ * emit that on a line beginning with "AS $function", and that
+ * there can be no such line before the real start of the
+ * function body. Increment lineno by the number of lines
+ * before that line, so that it becomes relative to the first
+ * line of the function definition.
+ */
+ const char *lines = query_buf->data;
+
+ while (*lines != '\0')
+ {
+ if (strncmp(lines, "AS $function", 12) == 0)
+ break;
+ lineno++;
+ /* find start of next line */
+ lines = strchr(lines, '\n');
+ if (!lines)
+ break;
+ lines++;
+ }
+ }
+
if (func)
free(func);
}
@@ -571,7 +640,7 @@ exec_command(const char *cmd,
{
bool edited = false;
- if (!do_edit(0, query_buf, &edited))
+ if (!do_edit(NULL, query_buf, lineno, &edited))
status = PSQL_CMD_ERROR;
else if (!edited)
puts(_("No changes"));
@@ -1543,11 +1612,11 @@ UnsyncVariables(void)
* If you do not specify a filename, the current query buffer will be copied
* into a temporary one.
*/
-
static bool
-editFile(const char *fname)
+editFile(const char *fname, int lineno)
{
const char *editorName;
+ const char *editor_lineno_switch = NULL;
char *sys;
int result;
@@ -1562,6 +1631,26 @@ editFile(const char *fname)
if (!editorName)
editorName = DEFAULT_EDITOR;
+ /* Get line number switch, if we need it. */
+ if (lineno > 0)
+ {
+ editor_lineno_switch = GetVariable(pset.vars,
+ "EDITOR_LINENUMBER_SWITCH");
+ if (editor_lineno_switch == NULL)
+ {
+ psql_error("EDITOR_LINENUMBER_SWITCH variable must be set to specify a line number\n");
+ return false;
+ }
+ }
+
+ /* Allocate sufficient memory for command line. */
+ if (lineno > 0)
+ sys = pg_malloc(strlen(editorName)
+ + strlen(editor_lineno_switch) + 10 /* for integer */
+ + 1 + strlen(fname) + 10 + 1);
+ else
+ sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+
/*
* On Unix the EDITOR value should *not* be quoted, since it might include
* switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
@@ -1569,11 +1658,20 @@ editFile(const char *fname)
* severe brain damage in their command shell plus the fact that standard
* program paths include spaces.
*/
- sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
#ifndef WIN32
- sprintf(sys, "exec %s '%s'", editorName, fname);
+ if (lineno > 0)
+ sprintf(sys, "exec %s %s%d '%s'",
+ editorName, editor_lineno_switch, lineno, fname);
+ else
+ sprintf(sys, "exec %s '%s'",
+ editorName, fname);
#else
- sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
+ if (lineno > 0)
+ sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE,
+ editorName, editor_lineno_switch, lineno, fname);
+ else
+ sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE,
+ editorName, fname);
#endif
result = system(sys);
if (result == -1)
@@ -1588,7 +1686,8 @@ editFile(const char *fname)
/* call this one */
static bool
-do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
+do_edit(const char *filename_arg, PQExpBuffer query_buf,
+ int lineno, bool *edited)
{
char fnametmp[MAXPGPATH];
FILE *stream = NULL;
@@ -1680,7 +1779,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
/* call editor */
if (!error)
- error = !editFile(fname);
+ error = !editFile(fname, lineno);
if (!error && stat(fname, &after) != 0)
{
@@ -2208,6 +2307,68 @@ get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf)
return result;
}
+/*
+ * If the given argument of \ef ends with a line number, delete the line
+ * number from the argument string and return it as an integer. (We need
+ * this kluge because we're too lazy to parse \ef's function name argument
+ * carefully --- we just slop it up in OT_WHOLE_LINE mode.)
+ *
+ * Returns -1 if no line number is present, 0 on error, or a positive value
+ * on success.
+ */
+static int
+strip_lineno_from_funcdesc(char *func)
+{
+ char *c;
+ int lineno;
+
+ if (!func || func[0] == '\0')
+ return -1;
+
+ c = func + strlen(func) - 1;
+
+ /*
+ * This business of parsing backwards is dangerous as can be in a
+ * multibyte environment: there is no reason to believe that we are
+ * looking at the first byte of a character, nor are we necessarily
+ * working in a "safe" encoding. Fortunately the bitpatterns we are
+ * looking for are unlikely to occur as non-first bytes, but beware
+ * of trying to expand the set of cases that can be recognized. We must
+ * guard the macros by using isascii() first, too.
+ */
+
+ /* skip trailing whitespace */
+ while (c > func && isascii(*c) && isspace(*c))
+ c--;
+
+ /* must have a digit as last non-space char */
+ if (c == func || !isascii(*c) || !isdigit(*c))
+ return -1;
+
+ /* find start of digit string */
+ while (c > func && isascii(*c) && isdigit(*c))
+ c--;
+
+ /* digits must be separated from func name by space or closing paren */
+ /* notice also that we are not allowing an empty func name ... */
+ if (c == func || !isascii(*c) || !(isspace(*c) || *c == ')'))
+ return -1;
+
+ /* parse digit string */
+ c++;
+ lineno = atoi(c);
+ if (lineno < 1)
+ {
+ psql_error("invalid line number: %s\n", c);
+ return 0;
+ }
+
+ /* strip digit string from func */
+ *c = '\0';
+
+ return lineno;
+}
+
/*
* Report just the primary error; this is to avoid cluttering the output
* with, for instance, a redisplay of the internally generated query
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 8351c5fb02..69a073a2b3 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -3,7 +3,7 @@
*
* Copyright (c) 2000-2010, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.160 2010/07/20 03:54:19 rhaas Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.161 2010/08/12 00:40:59 tgl Exp $
*/
#include "postgres_fe.h"
@@ -162,7 +162,7 @@ slashUsage(unsigned short int pager)
{
FILE *output;
- output = PageOutput(87, pager);
+ output = PageOutput(89, pager);
/* if you add/remove a line here, change the row count above */
@@ -174,8 +174,8 @@ slashUsage(unsigned short int pager)
fprintf(output, "\n");
fprintf(output, _("Query Buffer\n"));
- fprintf(output, _(" \\e [FILE] edit the query buffer (or file) with external editor\n"));
- fprintf(output, _(" \\ef [FUNCNAME] edit function definition with external editor\n"));
+ fprintf(output, _(" \\e [FILE] [LINE] edit the query buffer (or file) with external editor\n"));
+ fprintf(output, _(" \\ef [FUNCNAME [LINE]] edit function definition with external editor\n"));
fprintf(output, _(" \\p show the contents of the query buffer\n"));
fprintf(output, _(" \\r reset (clear) the query buffer\n"));
#ifdef USE_READLINE