Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * psql - the PostgreSQL interactive terminal
3 : : *
4 : : * Copyright (c) 2000-2024, PostgreSQL Global Development Group
5 : : *
6 : : * src/bin/psql/prompt.c
7 : : */
8 : : #include "postgres_fe.h"
9 : :
10 : : #ifdef WIN32
11 : : #include <io.h>
12 : : #include <win32.h>
13 : : #endif
14 : :
15 : : #include "common.h"
16 : : #include "common/string.h"
17 : : #include "input.h"
18 : : #include "libpq/pqcomm.h"
19 : : #include "prompt.h"
20 : : #include "settings.h"
21 : :
22 : : /*--------------------------
23 : : * get_prompt
24 : : *
25 : : * Returns a statically allocated prompt made by interpolating certain
26 : : * tcsh style escape sequences into pset.vars "PROMPT1|2|3".
27 : : * (might not be completely multibyte safe)
28 : : *
29 : : * Defined interpolations are:
30 : : * %M - database server "hostname.domainname", "[local]" for AF_UNIX
31 : : * sockets, "[local:/dir/name]" if not default
32 : : * %m - like %M, but hostname only (before first dot), or always "[local]"
33 : : * %p - backend pid
34 : : * %> - database server port number
35 : : * %n - database user name
36 : : * %/ - current database
37 : : * %~ - like %/ but "~" when database name equals user name
38 : : * %w - whitespace of the same width as the most recent output of PROMPT1
39 : : * %# - "#" if superuser, ">" otherwise
40 : : * %R - in prompt1 normally =, or ^ if single line mode,
41 : : * or a ! if session is not connected to a database;
42 : : * in prompt2 -, *, ', or ";
43 : : * in prompt3 nothing
44 : : * %x - transaction status: empty, *, !, ? (unknown or no connection)
45 : : * %l - The line number inside the current statement, starting from 1.
46 : : * %? - the error code of the last query (not yet implemented)
47 : : * %% - a percent sign
48 : : *
49 : : * %[0-9] - the character with the given decimal code
50 : : * %0[0-7] - the character with the given octal code
51 : : * %0x[0-9A-Fa-f] - the character with the given hexadecimal code
52 : : *
53 : : * %`command` - The result of executing command in /bin/sh with trailing
54 : : * newline stripped.
55 : : * %:name: - The value of the psql variable 'name'
56 : : * (those will not be rescanned for more escape sequences!)
57 : : *
58 : : * %[ ... %] - tell readline that the contained text is invisible
59 : : *
60 : : * If the application-wide prompts become NULL somehow, the returned string
61 : : * will be empty (not NULL!).
62 : : *--------------------------
63 : : */
64 : :
65 : : char *
2572 tgl@sss.pgh.pa.us 66 :CBC 54 : get_prompt(promptStatus_t status, ConditionalStack cstack)
67 : : {
68 : : #define MAX_PROMPT_SIZE 256
69 : : static char destination[MAX_PROMPT_SIZE + 1];
70 : : char buf[MAX_PROMPT_SIZE + 1];
8928 bruce@momjian.us 71 : 54 : bool esc = false;
72 : : const char *p;
7696 73 : 54 : const char *prompt_string = "? ";
74 : : static size_t last_prompt1_width = 0;
75 : :
76 [ + + - - ]: 54 : switch (status)
77 : : {
78 : 52 : case PROMPT_READY:
6438 tgl@sss.pgh.pa.us 79 : 52 : prompt_string = pset.prompt1;
7696 bruce@momjian.us 80 : 52 : break;
81 : :
82 : 2 : case PROMPT_CONTINUE:
83 : : case PROMPT_SINGLEQUOTE:
84 : : case PROMPT_DOUBLEQUOTE:
85 : : case PROMPT_DOLLARQUOTE:
86 : : case PROMPT_COMMENT:
87 : : case PROMPT_PAREN:
6438 tgl@sss.pgh.pa.us 88 : 2 : prompt_string = pset.prompt2;
7696 bruce@momjian.us 89 : 2 : break;
90 : :
7696 bruce@momjian.us 91 :UBC 0 : case PROMPT_COPY:
6438 tgl@sss.pgh.pa.us 92 : 0 : prompt_string = pset.prompt3;
7696 bruce@momjian.us 93 : 0 : break;
94 : : }
95 : :
8928 bruce@momjian.us 96 :CBC 54 : destination[0] = '\0';
97 : :
98 : 54 : for (p = prompt_string;
6275 peter_e@gmx.net 99 [ + + + - ]: 540 : *p && strlen(destination) < sizeof(destination) - 1;
8928 bruce@momjian.us 100 : 486 : p++)
101 : : {
6275 peter_e@gmx.net 102 : 486 : memset(buf, 0, sizeof(buf));
8928 bruce@momjian.us 103 [ + + ]: 486 : if (esc)
104 : : {
105 [ + - - - : 216 : switch (*p)
- - - - +
+ - - + -
- - - ]
106 : : {
107 : : /* Current database */
108 : 54 : case '/':
8857 peter_e@gmx.net 109 [ + - ]: 54 : if (pset.db)
6275 110 : 54 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
8928 bruce@momjian.us 111 : 54 : break;
8928 bruce@momjian.us 112 :UBC 0 : case '~':
7559 113 [ # # ]: 0 : if (pset.db)
114 : : {
115 : : const char *var;
116 : :
117 [ # # # # ]: 0 : if (strcmp(PQdb(pset.db), PQuser(pset.db)) == 0 ||
118 [ # # ]: 0 : ((var = getenv("PGDATABASE")) && strcmp(var, PQdb(pset.db)) == 0))
6275 peter_e@gmx.net 119 : 0 : strlcpy(buf, "~", sizeof(buf));
120 : : else
121 : 0 : strlcpy(buf, PQdb(pset.db), sizeof(buf));
122 : : }
7559 bruce@momjian.us 123 : 0 : break;
124 : :
125 : : /* Whitespace of the same width as the last PROMPT1 */
1608 tmunro@postgresql.or 126 : 0 : case 'w':
127 [ # # ]: 0 : if (pset.db)
128 : 0 : memset(buf, ' ',
129 : 0 : Min(last_prompt1_width, sizeof(buf) - 1));
130 : 0 : break;
131 : :
132 : : /* DB server hostname (long/short) */
8928 bruce@momjian.us 133 : 0 : case 'M':
134 : : case 'm':
8857 peter_e@gmx.net 135 [ # # ]: 0 : if (pset.db)
136 : : {
8207 bruce@momjian.us 137 : 0 : const char *host = PQhost(pset.db);
138 : :
139 : : /* INET socket */
1236 peter@eisentraut.org 140 [ # # # # : 0 : if (host && host[0] && !is_unixsock_path(host))
# # ]
141 : : {
6275 peter_e@gmx.net 142 : 0 : strlcpy(buf, host, sizeof(buf));
8928 bruce@momjian.us 143 [ # # ]: 0 : if (*p == 'm')
144 : 0 : buf[strcspn(buf, ".")] = '\0';
145 : : }
146 : : /* UNIX socket */
147 : : else
148 : : {
8379 peter_e@gmx.net 149 [ # # ]: 0 : if (!host
8207 bruce@momjian.us 150 [ # # ]: 0 : || strcmp(host, DEFAULT_PGSOCKET_DIR) == 0
8379 peter_e@gmx.net 151 [ # # ]: 0 : || *p == 'm')
6275 152 : 0 : strlcpy(buf, "[local]", sizeof(buf));
153 : : else
154 : 0 : snprintf(buf, sizeof(buf), "[local:%s]", host);
155 : : }
156 : : }
8928 bruce@momjian.us 157 : 0 : break;
158 : : /* DB server port number */
159 : 0 : case '>':
8857 peter_e@gmx.net 160 [ # # # # ]: 0 : if (pset.db && PQport(pset.db))
6275 161 : 0 : strlcpy(buf, PQport(pset.db), sizeof(buf));
8928 bruce@momjian.us 162 : 0 : break;
163 : : /* DB server user name */
164 : 0 : case 'n':
8857 peter_e@gmx.net 165 [ # # ]: 0 : if (pset.db)
6275 166 : 0 : strlcpy(buf, session_username(), sizeof(buf));
8928 bruce@momjian.us 167 : 0 : break;
168 : : /* backend pid */
3204 andres@anarazel.de 169 : 0 : case 'p':
170 [ # # ]: 0 : if (pset.db)
171 : : {
2866 rhaas@postgresql.org 172 : 0 : int pid = PQbackendPID(pset.db);
173 : :
3204 andres@anarazel.de 174 [ # # ]: 0 : if (pid)
175 : 0 : snprintf(buf, sizeof(buf), "%d", pid);
176 : : }
177 : 0 : break;
178 : :
8928 bruce@momjian.us 179 : 0 : case '0':
180 : : case '1':
181 : : case '2':
182 : : case '3':
183 : : case '4':
184 : : case '5':
185 : : case '6':
186 : : case '7':
1902 peter@eisentraut.org 187 : 0 : *buf = (char) strtol(p, unconstify(char **, &p), 8);
6894 bruce@momjian.us 188 : 0 : --p;
7596 tgl@sss.pgh.pa.us 189 : 0 : break;
8928 bruce@momjian.us 190 [ + + - - :CBC 54 : case 'R':
- - + - ]
191 : : switch (status)
192 : : {
193 : 52 : case PROMPT_READY:
2572 tgl@sss.pgh.pa.us 194 [ + - - + ]: 52 : if (cstack != NULL && !conditional_active(cstack))
2572 tgl@sss.pgh.pa.us 195 :UBC 0 : buf[0] = '@';
2572 tgl@sss.pgh.pa.us 196 [ - + ]:CBC 52 : else if (!pset.db)
8928 bruce@momjian.us 197 :UBC 0 : buf[0] = '!';
6438 tgl@sss.pgh.pa.us 198 [ + - ]:CBC 52 : else if (!pset.singleline)
8928 bruce@momjian.us 199 : 52 : buf[0] = '=';
200 : : else
8928 bruce@momjian.us 201 :UBC 0 : buf[0] = '^';
8928 bruce@momjian.us 202 :CBC 52 : break;
203 : 1 : case PROMPT_CONTINUE:
204 : 1 : buf[0] = '-';
205 : 1 : break;
8928 bruce@momjian.us 206 :UBC 0 : case PROMPT_SINGLEQUOTE:
207 : 0 : buf[0] = '\'';
208 : 0 : break;
209 : 0 : case PROMPT_DOUBLEQUOTE:
210 : 0 : buf[0] = '"';
211 : 0 : break;
7355 tgl@sss.pgh.pa.us 212 : 0 : case PROMPT_DOLLARQUOTE:
213 : 0 : buf[0] = '$';
214 : 0 : break;
8928 bruce@momjian.us 215 : 0 : case PROMPT_COMMENT:
216 : 0 : buf[0] = '*';
217 : 0 : break;
8768 bruce@momjian.us 218 :CBC 1 : case PROMPT_PAREN:
219 : 1 : buf[0] = '(';
220 : 1 : break;
8928 bruce@momjian.us 221 :UBC 0 : default:
222 : 0 : buf[0] = '\0';
223 : 0 : break;
224 : : }
7596 tgl@sss.pgh.pa.us 225 :CBC 54 : break;
226 : :
7498 peter_e@gmx.net 227 : 54 : case 'x':
7596 tgl@sss.pgh.pa.us 228 [ - + ]: 54 : if (!pset.db)
7596 tgl@sss.pgh.pa.us 229 :UBC 0 : buf[0] = '?';
230 : : else
7559 bruce@momjian.us 231 :CBC 54 : switch (PQtransactionStatus(pset.db))
232 : : {
233 : 54 : case PQTRANS_IDLE:
234 : 54 : buf[0] = '\0';
235 : 54 : break;
7559 bruce@momjian.us 236 :UBC 0 : case PQTRANS_ACTIVE:
237 : : case PQTRANS_INTRANS:
238 : 0 : buf[0] = '*';
239 : 0 : break;
240 : 0 : case PQTRANS_INERROR:
241 : 0 : buf[0] = '!';
242 : 0 : break;
243 : 0 : default:
244 : 0 : buf[0] = '?';
245 : 0 : break;
246 : : }
7596 tgl@sss.pgh.pa.us 247 :CBC 54 : break;
248 : :
3512 andres@anarazel.de 249 :UBC 0 : case 'l':
250 : 0 : snprintf(buf, sizeof(buf), UINT64_FORMAT, pset.stmt_lineno);
251 : 0 : break;
252 : :
8928 bruce@momjian.us 253 : 0 : case '?':
254 : : /* not here yet */
255 : 0 : break;
256 : :
8928 bruce@momjian.us 257 :CBC 54 : case '#':
7596 tgl@sss.pgh.pa.us 258 [ + - ]: 54 : if (is_superuser())
259 : 54 : buf[0] = '#';
260 : : else
7596 tgl@sss.pgh.pa.us 261 :UBC 0 : buf[0] = '>';
7596 tgl@sss.pgh.pa.us 262 :CBC 54 : break;
263 : :
264 : : /* execute command */
8928 bruce@momjian.us 265 :UBC 0 : case '`':
266 : : {
1593 alvherre@alvh.no-ip. 267 : 0 : int cmdend = strcspn(p + 1, "`");
268 : 0 : char *file = pnstrdup(p + 1, cmdend);
269 : : FILE *fd;
270 : :
594 tgl@sss.pgh.pa.us 271 : 0 : fflush(NULL);
272 : 0 : fd = popen(file, "r");
8928 bruce@momjian.us 273 [ # # ]: 0 : if (fd)
274 : : {
5098 tgl@sss.pgh.pa.us 275 [ # # ]: 0 : if (fgets(buf, sizeof(buf), fd) == NULL)
276 : 0 : buf[0] = '\0';
8928 bruce@momjian.us 277 : 0 : pclose(fd);
278 : : }
279 : :
280 : : /* strip trailing newline and carriage return */
1710 michael@paquier.xyz 281 : 0 : (void) pg_strip_crlf(buf);
282 : :
8928 bruce@momjian.us 283 : 0 : free(file);
284 : 0 : p += cmdend + 1;
285 : 0 : break;
286 : : }
287 : :
288 : : /* interpolate variable */
8833 peter_e@gmx.net 289 : 0 : case ':':
290 : : {
1593 alvherre@alvh.no-ip. 291 : 0 : int nameend = strcspn(p + 1, ":");
292 : 0 : char *name = pnstrdup(p + 1, nameend);
293 : : const char *val;
294 : :
8857 peter_e@gmx.net 295 : 0 : val = GetVariable(pset.vars, name);
8928 bruce@momjian.us 296 [ # # ]: 0 : if (val)
6275 peter_e@gmx.net 297 : 0 : strlcpy(buf, val, sizeof(buf));
8928 bruce@momjian.us 298 : 0 : free(name);
299 : 0 : p += nameend + 1;
300 : 0 : break;
301 : : }
302 : :
7168 303 : 0 : case '[':
304 : : case ']':
305 : : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
306 : :
307 : : /*
308 : : * readline >=4.0 undocumented feature: non-printing
309 : : * characters in prompt strings must be marked as such, in
310 : : * order to properly display the line during editing.
311 : : */
6676 tgl@sss.pgh.pa.us 312 [ # # ]: 0 : buf[0] = (*p == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
313 : 0 : buf[1] = '\0';
314 : : #endif /* USE_READLINE */
7168 bruce@momjian.us 315 : 0 : break;
316 : :
8928 317 : 0 : default:
318 : 0 : buf[0] = *p;
319 : 0 : buf[1] = '\0';
7390 tgl@sss.pgh.pa.us 320 : 0 : break;
321 : : }
8928 bruce@momjian.us 322 :CBC 216 : esc = false;
323 : : }
324 [ + + ]: 270 : else if (*p == '%')
325 : 216 : esc = true;
326 : : else
327 : : {
328 : 54 : buf[0] = *p;
329 : 54 : buf[1] = '\0';
330 : 54 : esc = false;
331 : : }
332 : :
333 [ + + ]: 486 : if (!esc)
6275 peter_e@gmx.net 334 : 270 : strlcat(destination, buf, sizeof(destination));
335 : : }
336 : :
337 : : /* Compute the visible width of PROMPT1, for PROMPT2's %w */
1608 tmunro@postgresql.or 338 [ + + ]: 54 : if (prompt_string == pset.prompt1)
339 : : {
340 : 52 : char *p = destination;
341 : 52 : char *end = p + strlen(p);
342 : 52 : bool visible = true;
343 : :
344 : 52 : last_prompt1_width = 0;
345 [ + + ]: 624 : while (*p)
346 : : {
347 : : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
348 [ - + ]: 572 : if (*p == RL_PROMPT_START_IGNORE)
349 : : {
1608 tmunro@postgresql.or 350 :UBC 0 : visible = false;
351 : 0 : ++p;
352 : : }
1608 tmunro@postgresql.or 353 [ - + ]:CBC 572 : else if (*p == RL_PROMPT_END_IGNORE)
354 : : {
1608 tmunro@postgresql.or 355 :UBC 0 : visible = true;
356 : 0 : ++p;
357 : : }
358 : : else
359 : : #endif
360 : : {
361 : : int chlen,
362 : : chwidth;
363 : :
1608 tmunro@postgresql.or 364 :CBC 572 : chlen = PQmblen(p, pset.encoding);
365 [ - + ]: 572 : if (p + chlen > end)
1608 tmunro@postgresql.or 366 :UBC 0 : break; /* Invalid string */
367 : :
1608 tmunro@postgresql.or 368 [ + - ]:CBC 572 : if (visible)
369 : : {
370 : 572 : chwidth = PQdsplen(p, pset.encoding);
371 : :
1525 372 [ - + ]: 572 : if (*p == '\n')
1525 tmunro@postgresql.or 373 :UBC 0 : last_prompt1_width = 0;
1525 tmunro@postgresql.or 374 [ + - ]:CBC 572 : else if (chwidth > 0)
1608 375 : 572 : last_prompt1_width += chwidth;
376 : : }
377 : :
378 : 572 : p += chlen;
379 : : }
380 : : }
381 : : }
382 : :
8928 bruce@momjian.us 383 : 54 : return destination;
384 : : }
|