From: Tom Lane Date: Thu, 12 Aug 2010 00:40:59 +0000 (+0000) Subject: Extend psql's \e and \ef commands so that a line number can be specified, X-Git-Tag: REL9_1_ALPHA1~98 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=568e709372abad30075c299be28956b5922219b4;p=postgresql Extend psql's \e and \ef commands so that a line number can be specified, and the editor's cursor will be initially placed on that line. In \e the lines are counted with respect to the query buffer, while in \ef they are counted with line 1 = first line of function body. These choices are useful for positioning the cursor on the line of a previously-reported error. To avoid assumptions about what switch the user's editor takes for this purpose, invent a new psql variable EDITOR_LINENUMBER_SWITCH with (at present) no default value. One incompatibility from previous behavior is that "\e 1234" will now take "1234" as a line number not a file name. There are at least two ways to select a numerically-named file if you really want to. Pavel Stehule, reviewed by Jan Urbanski, with further editing by Robert Haas and Tom Lane --- 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