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