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