diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index b803ef9..85adf55 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1338,7 +1338,7 @@ testdb=>
- \edit (or \e) filename
+ \edit (or \e) filename linenumber
@@ -1368,12 +1368,19 @@ testdb=>
systems, notepad.exe on Windows systems.
+
+
+ If a line number is specified, psql will
+ attempt to position the cursor on the specified line of the file.
+ An error will occur if EDITOR_LINENUMBER_SWITCH
+ is not set.
+
- \ef function_description
+ \ef function_description linenumber
@@ -1396,6 +1403,14 @@ testdb=>
If no function is specified, a blank CREATE FUNCTION>
template is presented for editing.
+
+
+ If a line number is specified, psql will
+ attempt to 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). An error will occur if
+ EDITOR_LINENUMBER_SWITCH is not set.
+
@@ -2458,6 +2473,25 @@ bar
+ EDITOR_LINENUMBER_SWITCH
+
+
+ When \edit or \ef is used with
+ a line number argument, this variable specifies the 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 single plus sign. In some cases, it might be necessary
+ to include a trailing space in the value assigned to this variable.
+ For example:
+
+
+\set EDIT_LINENUMBER_SWITCH '--line '
+
+
+
+
+
+
ENCODING
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a3e55c5..697a906 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -57,7 +57,7 @@ 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);
@@ -66,6 +66,9 @@ static void minimal_error_message(PGresult *res);
static void printSSLInfo(void);
+static int strip_lineno_from_funcdesc(char *func);
+static char *get_functiondef_dollarquote_tag(char *line);
+
#ifdef WIN32
static void checkWin32Codepage(void);
#endif
@@ -510,17 +513,39 @@ exec_command(const char *cmd,
else
{
char *fname;
+ char *ln;
+ int lineno = -1;
fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
- expand_tilde(&fname);
+
+ /* try to get lineno */
if (fname)
- canonicalize_path(fname);
- if (do_edit(fname, query_buf, NULL))
- status = PSQL_CMD_NEWEDIT;
- else
- status = PSQL_CMD_ERROR;
- free(fname);
+ {
+ ln = psql_scan_slash_option(scan_state,
+ OT_NORMAL, NULL, true);
+ if (ln)
+ {
+ lineno = atoi(ln);
+ if (lineno < 1)
+ {
+ psql_error("invalid line number\n");
+ 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);
}
}
@@ -530,6 +555,8 @@ exec_command(const char *cmd,
*/
else if (strcmp(cmd, "ef") == 0)
{
+ int lineno = -1; /* keep compiler quiet */
+
if (!query_buf)
{
psql_error("no query buffer\n");
@@ -542,7 +569,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,
@@ -571,7 +604,7 @@ exec_command(const char *cmd,
{
bool edited = false;
- if (!do_edit(0, query_buf, &edited))
+ if (!do_edit(0, query_buf, lineno, &edited))
status = PSQL_CMD_ERROR;
else if (!edited)
puts(_("No changes"));
@@ -1543,11 +1576,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; /* be compiler quiet */
char *sys;
int result;
@@ -1562,6 +1595,25 @@ 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 is not set\n");
+ return false;
+ }
+ }
+
+ /* Allocate sufficient memory for command line. */
+ if (lineno > 1)
+ sys = pg_malloc(strlen(editorName) + strlen(editor_lineno_switch) + 10
+ + 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 +1621,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 +1649,7 @@ 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;
@@ -1678,9 +1739,34 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
error = true;
}
+ /* adjust line number based on start of actual function body */
+ if (!error && lineno != -1)
+ {
+ char *lines = query_buf->data;
+ char *dqtag;
+
+ /* skip header lines */
+ while (*lines != '\0')
+ {
+ dqtag = get_functiondef_dollarquote_tag(lines);
+ if (dqtag)
+ {
+ free(dqtag);
+ break;
+ }
+ lineno++;
+
+ /* find start of next line */
+ lines = strchr(lines, '\n');
+ if (!lines)
+ break;
+ lines++;
+ }
+ }
+
/* call editor */
if (!error)
- error = !editFile(fname);
+ error = !editFile(fname, lineno);
if (!error && stat(fname, &after) != 0)
{
@@ -2236,3 +2322,75 @@ minimal_error_message(PGresult *res)
destroyPQExpBuffer(msg);
}
+
+
+/*
+ * Strips an optional trailing line number off of a backslash command.
+ *
+ * Retuns -1 if no line number is present, 0 on error, or a positive value
+ * otherwise.
+ */
+static int
+strip_lineno_from_funcdesc(char *func)
+{
+ char *endfunc;
+ char *c;
+ int lineno;
+
+ if (!func)
+ return -1;
+ endfunc = func + strlen(func) - 1;
+ c = endfunc;
+
+ /* skip trailing whitespace */
+ while (c >= func && isblank(*c))
+ c--;
+ if (c == func || !isdigit(*c))
+ return -1;
+
+ /* find start of digit string */
+ while (c >= func && isdigit(*c))
+ c--;
+
+ /* digits must be separated from identifier by blank or closing paren */
+ if (c == func || (!isblank(*c) && *c != ')'))
+ return -1;
+
+ /* parse digit string */
+ lineno = atoi(c + 1);
+ if (lineno < 1)
+ {
+ psql_error("invalid line number\n");
+ return 0;
+ }
+ c[1] = '\0';
+ return lineno;
+}
+
+/*
+ * Returns tag of dollar quoted string used as function body. We assume
+ * that the function body is returned by pg_get_functiondef() and therefore
+ * must be begin on a line that starts with "AS $function$". If the line is
+ * not of that format, we return NULL.
+ */
+static char *
+get_functiondef_dollarquote_tag(char *line)
+{
+ char *starttag;
+ char *endtag;
+ int len;
+ char *result;
+
+ if (strncmp(line, "AS $function", 12) != 0)
+ return NULL;
+ starttag = line + 3;
+ endtag = strchr(line + 12, '$');
+ psql_assert(endtag != NULL);
+
+ len = endtag - starttag + 1;
+ result = pg_malloc(len + 1);
+ memcpy(result, starttag, len);
+ result[len] = '\0';
+
+ return result;
+}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 19c807d..6a00a1f 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -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