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