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 : {
173 0 : const char *service_name = GetVariable(pset.vars, "SERVICE");
174 :
175 0 : if (service_name)
176 0 : strlcpy(buf, service_name, sizeof(buf));
177 : }
178 0 : break;
179 : /* backend pid */
180 0 : case 'p':
181 0 : if (pset.db)
182 : {
183 0 : int pid = PQbackendPID(pset.db);
184 :
185 0 : if (pid)
186 0 : snprintf(buf, sizeof(buf), "%d", pid);
187 : }
188 0 : break;
189 : /* pipeline status */
190 0 : case 'P':
191 : {
192 0 : PGpipelineStatus status = PQpipelineStatus(pset.db);
193 :
194 0 : if (status == PQ_PIPELINE_ON)
195 0 : strlcpy(buf, "on", sizeof(buf));
196 0 : else if (status == PQ_PIPELINE_ABORTED)
197 0 : strlcpy(buf, "abort", sizeof(buf));
198 : else
199 0 : strlcpy(buf, "off", sizeof(buf));
200 0 : break;
201 : }
202 :
203 0 : case '0':
204 : case '1':
205 : case '2':
206 : case '3':
207 : case '4':
208 : case '5':
209 : case '6':
210 : case '7':
211 0 : *buf = (char) strtol(p, unconstify(char **, &p), 8);
212 0 : --p;
213 0 : break;
214 116 : case 'R':
215 : switch (status)
216 : {
217 112 : case PROMPT_READY:
218 112 : if (cstack != NULL && !conditional_active(cstack))
219 0 : buf[0] = '@';
220 112 : else if (!pset.db)
221 0 : buf[0] = '!';
222 112 : else if (!pset.singleline)
223 112 : buf[0] = '=';
224 : else
225 0 : buf[0] = '^';
226 112 : break;
227 2 : case PROMPT_CONTINUE:
228 2 : buf[0] = '-';
229 2 : break;
230 0 : case PROMPT_SINGLEQUOTE:
231 0 : buf[0] = '\'';
232 0 : break;
233 0 : case PROMPT_DOUBLEQUOTE:
234 0 : buf[0] = '"';
235 0 : break;
236 0 : case PROMPT_DOLLARQUOTE:
237 0 : buf[0] = '$';
238 0 : break;
239 0 : case PROMPT_COMMENT:
240 0 : buf[0] = '*';
241 0 : break;
242 2 : case PROMPT_PAREN:
243 2 : buf[0] = '(';
244 2 : break;
245 0 : default:
246 0 : buf[0] = '\0';
247 0 : break;
248 : }
249 116 : break;
250 :
251 116 : case 'x':
252 116 : if (!pset.db)
253 0 : buf[0] = '?';
254 : else
255 116 : switch (PQtransactionStatus(pset.db))
256 : {
257 116 : case PQTRANS_IDLE:
258 116 : buf[0] = '\0';
259 116 : break;
260 0 : case PQTRANS_ACTIVE:
261 : case PQTRANS_INTRANS:
262 0 : buf[0] = '*';
263 0 : break;
264 0 : case PQTRANS_INERROR:
265 0 : buf[0] = '!';
266 0 : break;
267 0 : default:
268 0 : buf[0] = '?';
269 0 : break;
270 : }
271 116 : break;
272 :
273 0 : case 'l':
274 0 : snprintf(buf, sizeof(buf), UINT64_FORMAT, pset.stmt_lineno);
275 0 : break;
276 :
277 0 : case '?':
278 : /* not here yet */
279 0 : break;
280 :
281 116 : case '#':
282 116 : if (is_superuser())
283 116 : buf[0] = '#';
284 : else
285 0 : buf[0] = '>';
286 116 : break;
287 :
288 : /* execute command */
289 0 : case '`':
290 : {
291 0 : int cmdend = strcspn(p + 1, "`");
292 0 : char *file = pnstrdup(p + 1, cmdend);
293 : FILE *fd;
294 :
295 0 : fflush(NULL);
296 0 : fd = popen(file, "r");
297 0 : if (fd)
298 : {
299 0 : if (fgets(buf, sizeof(buf), fd) == NULL)
300 0 : buf[0] = '\0';
301 0 : pclose(fd);
302 : }
303 :
304 : /* strip trailing newline and carriage return */
305 0 : (void) pg_strip_crlf(buf);
306 :
307 0 : free(file);
308 0 : p += cmdend + 1;
309 0 : break;
310 : }
311 :
312 : /* interpolate variable */
313 0 : case ':':
314 : {
315 0 : int nameend = strcspn(p + 1, ":");
316 0 : char *name = pnstrdup(p + 1, nameend);
317 : const char *val;
318 :
319 0 : val = GetVariable(pset.vars, name);
320 0 : if (val)
321 0 : strlcpy(buf, val, sizeof(buf));
322 0 : free(name);
323 0 : p += nameend + 1;
324 0 : break;
325 : }
326 :
327 0 : case '[':
328 : case ']':
329 : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
330 :
331 : /*
332 : * readline >=4.0 undocumented feature: non-printing
333 : * characters in prompt strings must be marked as such, in
334 : * order to properly display the line during editing.
335 : */
336 0 : buf[0] = (*p == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
337 0 : buf[1] = '\0';
338 : #endif /* USE_READLINE */
339 0 : break;
340 :
341 0 : default:
342 0 : buf[0] = *p;
343 0 : buf[1] = '\0';
344 0 : break;
345 : }
346 464 : esc = false;
347 : }
348 580 : else if (*p == '%')
349 464 : esc = true;
350 : else
351 : {
352 116 : buf[0] = *p;
353 116 : buf[1] = '\0';
354 116 : esc = false;
355 : }
356 :
357 1044 : if (!esc)
358 580 : strlcat(destination, buf, sizeof(destination));
359 : }
360 :
361 : /* Compute the visible width of PROMPT1, for PROMPT2's %w */
362 116 : if (prompt_string == pset.prompt1)
363 : {
364 112 : char *p = destination;
365 112 : char *end = p + strlen(p);
366 112 : bool visible = true;
367 :
368 112 : last_prompt1_width = 0;
369 1344 : while (*p)
370 : {
371 : #if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
372 1232 : if (*p == RL_PROMPT_START_IGNORE)
373 : {
374 0 : visible = false;
375 0 : ++p;
376 : }
377 1232 : else if (*p == RL_PROMPT_END_IGNORE)
378 : {
379 0 : visible = true;
380 0 : ++p;
381 : }
382 : else
383 : #endif
384 : {
385 : int chlen,
386 : chwidth;
387 :
388 1232 : chlen = PQmblen(p, pset.encoding);
389 1232 : if (p + chlen > end)
390 0 : break; /* Invalid string */
391 :
392 1232 : if (visible)
393 : {
394 1232 : chwidth = PQdsplen(p, pset.encoding);
395 :
396 1232 : if (*p == '\n')
397 0 : last_prompt1_width = 0;
398 1232 : else if (chwidth > 0)
399 1232 : last_prompt1_width += chwidth;
400 : }
401 :
402 1232 : p += chlen;
403 : }
404 : }
405 : }
406 :
407 116 : return destination;
408 : }
|