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