Invisible PROMPT2
Hello hackers,
From the advanced bikeshedding department: I'd like my psql
transcripts to have the usual alignment, but be easier to copy and
paste later without having weird prompt stuff in the middle. How
about a prompt format directive %w that means "whitespace of the same
width as %/"? Then you can make set your PROMPT2 to '%w ' and it
becomes invisible:
pgdu=# create table foo (
i int,
j int
);
CREATE TABLE
pgdu=#
Attachments:
invisible-database.patchapplication/octet-stream; name=invisible-database.patchDownload
diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c
index 195192a95d..eb8953aec0 100644
--- a/src/bin/psql/prompt.c
+++ b/src/bin/psql/prompt.c
@@ -39,6 +39,7 @@
* %n - database user name
* %/ - current database
* %~ - like %/ but "~" when database name equals user name
+ * %w - whitespace of the same width as the current database
* %# - "#" if superuser, ">" otherwise
* %R - in prompt1 normally =, or ^ if single line mode,
* or a ! if session is not connected to a database;
@@ -123,6 +124,14 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
strlcpy(buf, PQdb(pset.db), sizeof(buf));
}
break;
+ case 'w':
+ if (pset.db)
+ {
+ size_t len = strlen(PQdb(pset.db));
+
+ memset(buf, ' ', Min(len, sizeof(buf) - 1));
+ }
+ break;
/* DB server hostname (long/short) */
case 'M':
st 13. 11. 2019 v 4:15 odesílatel Thomas Munro <thomas.munro@gmail.com>
napsal:
Hello hackers,
From the advanced bikeshedding department: I'd like my psql
transcripts to have the usual alignment, but be easier to copy and
paste later without having weird prompt stuff in the middle. How
about a prompt format directive %w that means "whitespace of the same
width as %/"? Then you can make set your PROMPT2 to '%w ' and it
becomes invisible:pgdu=# create table foo (
i int,
j int
);
CREATE TABLE
pgdu=#
+1
Pavel
Thomas Munro <thomas.munro@gmail.com> writes:
Hello hackers,
From the advanced bikeshedding department: I'd like my psql
transcripts to have the usual alignment, but be easier to copy and
paste later without having weird prompt stuff in the middle. How
about a prompt format directive %w that means "whitespace of the same
width as %/"? Then you can make set your PROMPT2 to '%w ' and it
becomes invisible:
That only lines up nicely if %/ is the only variable-width directive in
PROMPT1. How about a circumfix directive (like the existing %[ ... %])
that replaces everything inside with whitespace, but keeps the width?
- ilmari
--
"The surreality of the universe tends towards a maximum" -- Skud's Law
"Never formulate a law or axiom that you're not prepared to live with
the consequences of." -- Skud's Meta-Law
ilmari@ilmari.org (Dagfinn Ilmari =?utf-8?Q?Manns=C3=A5ker?=) writes:
Thomas Munro <thomas.munro@gmail.com> writes:
From the advanced bikeshedding department: I'd like my psql
transcripts to have the usual alignment, but be easier to copy and
paste later without having weird prompt stuff in the middle. How
about a prompt format directive %w that means "whitespace of the same
width as %/"? Then you can make set your PROMPT2 to '%w ' and it
becomes invisible:
That only lines up nicely if %/ is the only variable-width directive in
PROMPT1.
Yeah, that was my first reaction too.
How about a circumfix directive (like the existing %[ ... %])
that replaces everything inside with whitespace, but keeps the width?
Or just define %w as meaning "whitespace of the same width as
PROMPT1". You couldn't use it *in* PROMPT1, then, but I see
no use-case for that anyway.
regards, tom lane
On Wed, Nov 13, 2019 at 09:47:01AM -0500, Tom Lane wrote:
ilmari@ilmari.org (Dagfinn Ilmari =?utf-8?Q?Manns=C3=A5ker?=) writes:
Thomas Munro <thomas.munro@gmail.com> writes:
From the advanced bikeshedding department: I'd like my psql
transcripts to have the usual alignment, but be easier to copy and
paste later without having weird prompt stuff in the middle. How
about a prompt format directive %w that means "whitespace of the same
width as %/"? Then you can make set your PROMPT2 to '%w ' and it
becomes invisible:That only lines up nicely if %/ is the only variable-width directive in
PROMPT1.Yeah, that was my first reaction too.
How about a circumfix directive (like the existing %[ ... %])
that replaces everything inside with whitespace, but keeps the width?Or just define %w as meaning "whitespace of the same width as
PROMPT1". You couldn't use it *in* PROMPT1, then, but I see
no use-case for that anyway.
+1 for doing it this way. Would it make more sense to error out if
somebody tried to set that in PROMPT1, or ignore it, or...?
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
On 11/13/19 12:49 PM, David Fetter wrote:
Or just define %w as meaning "whitespace of the same width as
PROMPT1". You couldn't use it *in* PROMPT1, then, but I see
no use-case for that anyway.+1 for doing it this way. Would it make more sense to error out if
somebody tried to set that in PROMPT1, or ignore it, or...?
Define it as "difference between PROMPT1's width and the total width
of non-%w elements in this prompt". Then it has a defined meaning in
PROMPT1 too (which could be arbitrary if it appears only once, but
has to be zero in case it appears more than once).
Easter egg: expand it to backspaces if used in PROMPT2 among other
stuff that's already wider than PROMPT1. ;)
Regards,
-Chap
On 2019-Nov-13, David Fetter wrote:
On Wed, Nov 13, 2019 at 09:47:01AM -0500, Tom Lane wrote:
How about a circumfix directive (like the existing %[ ... %])
that replaces everything inside with whitespace, but keeps the width?Or just define %w as meaning "whitespace of the same width as
PROMPT1". You couldn't use it *in* PROMPT1, then, but I see
no use-case for that anyway.+1 for doing it this way. Would it make more sense to error out if
somebody tried to set that in PROMPT1, or ignore it, or...?
This seems way too specific to me. I like the "circumfix" directive
better, because it allows one to do more things. I don't have any
immediate use for it, but it doesn't seem completely far-fetched that
there are some.
BTW the psql manual says that %[ and %] were plagiarized from tcsh, but
that's a lie: tcsh does not contain such a feature. Bash does, however.
(I guess not many people read the tcsh manual.)
Neither bash nor tcsh have a feature to return whitespace of anything;
we're in a green field here ISTM.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Nov 13, 2019 at 03:06:08PM -0300, Alvaro Herrera wrote:
On 2019-Nov-13, David Fetter wrote:
On Wed, Nov 13, 2019 at 09:47:01AM -0500, Tom Lane wrote:
How about a circumfix directive (like the existing %[ ... %])
that replaces everything inside with whitespace, but keeps the width?Or just define %w as meaning "whitespace of the same width as
PROMPT1". You couldn't use it *in* PROMPT1, then, but I see
no use-case for that anyway.+1 for doing it this way. Would it make more sense to error out if
somebody tried to set that in PROMPT1, or ignore it, or...?This seems way too specific to me. I like the "circumfix" directive
better, because it allows one to do more things. I don't have any
immediate use for it, but it doesn't seem completely far-fetched that
there are some.BTW the psql manual says that %[ and %] were plagiarized from tcsh, but
that's a lie: tcsh does not contain such a feature. Bash does, however.
(I guess not many people read the tcsh manual.)Neither bash nor tcsh have a feature to return whitespace of anything;
we're in a green field here ISTM.
So something like %w[...%w] where people could put things like PROMPT1
inside?
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
On 2019-Nov-13, David Fetter wrote:
On Wed, Nov 13, 2019 at 03:06:08PM -0300, Alvaro Herrera wrote:
On 2019-Nov-13, David Fetter wrote:
On Wed, Nov 13, 2019 at 09:47:01AM -0500, Tom Lane wrote:
How about a circumfix directive (like the existing %[ ... %])
that replaces everything inside with whitespace, but keeps the width?
This seems way too specific to me. I like the "circumfix" directive
better, because it allows one to do more things. I don't have any
immediate use for it, but it doesn't seem completely far-fetched that
there are some.
So something like %w[...%w] where people could put things like PROMPT1
inside?
Hmm, (I'm not sure your proposed syntax works, but let's assume that
it does.) I'm saying you'd define
\set PROMPT1 '%a%b%c '
\set PROMPT2 '%w[%a%b%c %w]'
and you'd end up with matching indentation on multiline queries.
I'm not sure that we'd need to make something like this work:
PROMPT1="%w[$PROMPT1%w]"
which I think is what you're saying.
We already have "%:PROMPT1:" but that expands to the literal value of
prompt1, not to the value that prompt1 would expand to:
55432 13devel 11214=# \set PROMPT2 'hello %:PROMPT1: bye'
55432 13devel 11214=# select<Enter>
hello %[%033[35m%]%> %:VERSION_NAME: %p%[%033[0m%]%R%# bye
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Nov 13, 2019 at 03:58:38PM -0300, Alvaro Herrera wrote:
On 2019-Nov-13, David Fetter wrote:
On Wed, Nov 13, 2019 at 03:06:08PM -0300, Alvaro Herrera wrote:
On 2019-Nov-13, David Fetter wrote:
On Wed, Nov 13, 2019 at 09:47:01AM -0500, Tom Lane wrote:
How about a circumfix directive (like the existing %[ ... %])
that replaces everything inside with whitespace, but keeps the width?This seems way too specific to me. I like the "circumfix" directive
better, because it allows one to do more things. I don't have any
immediate use for it, but it doesn't seem completely far-fetched that
there are some.So something like %w[...%w] where people could put things like PROMPT1
inside?Hmm, (I'm not sure your proposed syntax works, but let's assume that
it does.) I'm saying you'd define
\set PROMPT1 '%a%b%c '
\set PROMPT2 '%w[%a%b%c %w]'and you'd end up with matching indentation on multiline queries.
I'm not sure that we'd need to make something like this work:
PROMPT1="%w[$PROMPT1%w]"
which I think is what you're saying.
PROMPT2="%w[$PROMPT1%w]", and basically yes.
We already have "%:PROMPT1:" but that expands to the literal value of
prompt1, not to the value that prompt1 would expand to:
Yeah, that's not so great for this usage. I guess "expand variables"
could be a separate useful feature (and patch) all by itself...
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
At Wed, 13 Nov 2019 20:57:04 +0100, David Fetter <david@fetter.org> wrote in
On Wed, Nov 13, 2019 at 03:58:38PM -0300, Alvaro Herrera wrote:
On 2019-Nov-13, David Fetter wrote:
On Wed, Nov 13, 2019 at 03:06:08PM -0300, Alvaro Herrera wrote:
On 2019-Nov-13, David Fetter wrote:
On Wed, Nov 13, 2019 at 09:47:01AM -0500, Tom Lane wrote:
How about a circumfix directive (like the existing %[ ... %])
that replaces everything inside with whitespace, but keeps the width?This seems way too specific to me. I like the "circumfix" directive
better, because it allows one to do more things. I don't have any
immediate use for it, but it doesn't seem completely far-fetched that
there are some.So something like %w[...%w] where people could put things like PROMPT1
inside?Hmm, (I'm not sure your proposed syntax works, but let's assume that
it does.) I'm saying you'd define
\set PROMPT1 '%a%b%c '
\set PROMPT2 '%w[%a%b%c %w]'and you'd end up with matching indentation on multiline queries.
This seems assuming %x are a kind of stable (until semicolon)
function. But at least %`..` can be volatile. So, I think the %w
thing in PROMPT2 should be able to refer the actual prompt string
resulted from PROMPT1.
I'm not sure that we'd need to make something like this work:
PROMPT1="%w[$PROMPT1%w]"
which I think is what you're saying.PROMPT2="%w[$PROMPT1%w]", and basically yes.
Like this. Or may be a bit too-much and I don't came up with a
lialistic use-case, but I think of the following syntax.
\set PROMPT1 '%w[%a%b%c%w] '
\set PROMPT2 '%w '
where %w in PROMPT2 is replaced by a whitespace with the same length
to the output of %w[..%w] part in PROMPT1.
We already have "%:PROMPT1:" but that expands to the literal value of
prompt1, not to the value that prompt1 would expand to:Yeah, that's not so great for this usage. I guess "expand variables"
could be a separate useful feature (and patch) all by itself...
+1.
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
Kyotaro Horiguchi <horikyota.ntt@gmail.com> writes:
This seems assuming %x are a kind of stable (until semicolon)
function. But at least %`..` can be volatile. So, I think the %w
thing in PROMPT2 should be able to refer the actual prompt string
resulted from PROMPT1.
Oh, that's a good point. But it actually leads to a much simpler
definition and implementation than the other ideas we've kicked
around: define %w as "whitespace equal to the length of the
last-generated PROMPT1 string (initially empty)", and we just
have to save PROMPT1 each time we generate it.
Except ... I'm not sure how to deal with hidden escape sequences.
We should probably assume that anything inside %[...%] has width
zero, but how would we remember that?
Maybe count the width of non-escape characters whenever we
generate PROMPT1, and just save that number not the string?
It'd add overhead that's useless when there's no %w, but
probably not enough to care about.
regards, tom lane
On Fri, Nov 15, 2019 at 3:58 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Kyotaro Horiguchi <horikyota.ntt@gmail.com> writes:
This seems assuming %x are a kind of stable (until semicolon)
function. But at least %`..` can be volatile. So, I think the %w
thing in PROMPT2 should be able to refer the actual prompt string
resulted from PROMPT1.Oh, that's a good point. But it actually leads to a much simpler
definition and implementation than the other ideas we've kicked
around: define %w as "whitespace equal to the length of the
last-generated PROMPT1 string (initially empty)", and we just
have to save PROMPT1 each time we generate it.Except ... I'm not sure how to deal with hidden escape sequences.
We should probably assume that anything inside %[...%] has width
zero, but how would we remember that?Maybe count the width of non-escape characters whenever we
generate PROMPT1, and just save that number not the string?
It'd add overhead that's useless when there's no %w, but
probably not enough to care about.
Nice idea. Here's one like that, that just does the counting at the
end and looks out for readline control codes. It's pretty naive about
what "width" means though: you'll get two spaces for UTF-8 encoded é,
and I suppose a complete implementation would know about the half
width/full width thing for Chinese and Japanese etc.
Attachments:
0001-Allow-invisible-PROMPT2-in-psql.patchapplication/octet-stream; name=0001-Allow-invisible-PROMPT2-in-psql.patchDownload
From a415f6a2591a785abac22ac9bddebb0caefa9d49 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Mon, 18 Nov 2019 09:49:16 +1300
Subject: [PATCH] Allow invisible PROMPT2 in psql.
Keep track of the visible width of PROMPT1, and provide %w as a way
for PROMPT2 to generate the same number of spaces.
Discussion: https://postgr.es/m/CA%2BhUKG%2BzGd7RigjWbxwhzGW59gUpf76ydQECeGdEdodH6nd__A%40mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 12 ++++++++++++
src/bin/psql/prompt.c | 36 ++++++++++++++++++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 7789fc6177..e9fde65d72 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -4310,6 +4310,18 @@ testdb=> \set PROMPT1 '%[%033[1;33;40m%]%n@%/%R%[%033[0m%]%# '
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>%w</literal></term>
+ <listitem>
+ <para>
+ Whitespace of the same width as <varname>PROMPT1</varname>. This can
+ be used as a <varname>PROMPT2</varname> setting, so that multi-line
+ statements are aligned with the first line, but there is no visible
+ secondary prompt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
To insert a percent sign into your prompt, write
diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c
index 195192a95d..8013b329ac 100644
--- a/src/bin/psql/prompt.c
+++ b/src/bin/psql/prompt.c
@@ -39,6 +39,7 @@
* %n - database user name
* %/ - current database
* %~ - like %/ but "~" when database name equals user name
+ * %w - whitespace of the same width as the most recent output of PROMPT1
* %# - "#" if superuser, ">" otherwise
* %R - in prompt1 normally =, or ^ if single line mode,
* or a ! if session is not connected to a database;
@@ -74,6 +75,7 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
bool esc = false;
const char *p;
const char *prompt_string = "? ";
+ static size_t last_prompt1_width;
switch (status)
{
@@ -124,6 +126,13 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
}
break;
+ /* Whitespace of the same width as the last PROMPT1 */
+ case 'w':
+ if (pset.db)
+ memset(buf, ' ',
+ Min(last_prompt1_width, sizeof(buf) - 1));
+ break;
+
/* DB server hostname (long/short) */
case 'M':
case 'm':
@@ -336,5 +345,32 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
strlcat(destination, buf, sizeof(destination));
}
+ if (prompt_string == pset.prompt1)
+ {
+ char *p = destination;
+ bool visible = true;
+ char c;
+
+ /* Count the visible characters, for use by %w in PROMPT2. */
+ last_prompt1_width = 0;
+ while ((c = *p++))
+ {
+#if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
+ if (c == RL_PROMPT_START_IGNORE)
+ {
+ visible = false;
+ continue;
+ }
+ else if (c == RL_PROMPT_END_IGNORE)
+ {
+ visible = true;
+ continue;
+ }
+#endif
+ if (visible)
+ ++last_prompt1_width;
+ }
+ }
+
return destination;
}
--
2.23.0
On 2019-Nov-18, Thomas Munro wrote:
Nice idea. Here's one like that, that just does the counting at the
end and looks out for readline control codes. It's pretty naive about
what "width" means though: you'll get two spaces for UTF-8 encoded �,
and I suppose a complete implementation would know about the half
width/full width thing for Chinese and Japanese etc.
Hmm ... is this related to what Juan Jos� posted at
/messages/by-id/CAC+AXB28ADgwdNRA=aAoWDYPqO1DZR+5NTO8iXGSsFrXyVpqYQ@mail.gmail.com
? That's backend code of course, though.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Mon, Nov 18, 2019 at 1:49 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
On 2019-Nov-18, Thomas Munro wrote:
Nice idea. Here's one like that, that just does the counting at the
end and looks out for readline control codes. It's pretty naive about
what "width" means though: you'll get two spaces for UTF-8 encoded é,
and I suppose a complete implementation would know about the half
width/full width thing for Chinese and Japanese etc.Hmm ... is this related to what Juan José posted at
/messages/by-id/CAC+AXB28ADgwdNRA=aAoWDYPqO1DZR+5NTO8iXGSsFrXyVpqYQ@mail.gmail.com
? That's backend code of course, though.
Yeah. Maybe pg_wcswidth() would be OK though, and it's available in
psql, though I guess you'd have to make a copy with the escaped bits
stripped out.
Thomas Munro <thomas.munro@gmail.com> writes:
On Mon, Nov 18, 2019 at 1:49 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
On 2019-Nov-18, Thomas Munro wrote:
Nice idea. Here's one like that, that just does the counting at the
end and looks out for readline control codes. It's pretty naive about
what "width" means though: you'll get two spaces for UTF-8 encoded é,
and I suppose a complete implementation would know about the half
width/full width thing for Chinese and Japanese etc.
Yeah. Maybe pg_wcswidth() would be OK though, and it's available in
psql, though I guess you'd have to make a copy with the escaped bits
stripped out.
Right, you should use pg_wcswidth() or the underlying PQdsplen() function
to compute display width. The latter might be more convenient since
you could apply it character by character rather than making a copy
of the string.
regards, tom lane
On Tue, Nov 19, 2019 at 6:21 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Thomas Munro <thomas.munro@gmail.com> writes:
Yeah. Maybe pg_wcswidth() would be OK though, and it's available in
psql, though I guess you'd have to make a copy with the escaped bits
stripped out.Right, you should use pg_wcswidth() or the underlying PQdsplen() function
to compute display width. The latter might be more convenient since
you could apply it character by character rather than making a copy
of the string.
Right, a PQdsplen()/PQmblen() loop works nicely, as attached.
I spotted a potential problem: I suppose I could write a PROMPT1 that
includes an invalid multibyte sequence at the end of the buffer and
trick PQmblen() or PQdsplen() into reading a few bytes past the end.
Two defences against that would be (1) use pg_encoding_verifymb()
instead of PQmblen() and (2) use pg_encoding_max_length() to make sure
you can't get close enough to the end of the buffer, but neither of
those functions are available to psql.
Attachments:
v2-0001-Allow-invisible-PROMPT2-in-psql.patchapplication/octet-stream; name=v2-0001-Allow-invisible-PROMPT2-in-psql.patchDownload
From 592a2e8a94ef06befe42e50fa42901482f2bf5fc Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 19 Nov 2019 09:36:06 +1300
Subject: [PATCH v2] Allow invisible PROMPT2 in psql.
Keep track of the visible width of PROMPT1, and provide %w as a way
for PROMPT2 to generate the same number of spaces.
Author: Thomas Munro, using suggestions from others
Reviewed-by: Tom Lane
Discussion: https://postgr.es/m/CA%2BhUKG%2BzGd7RigjWbxwhzGW59gUpf76ydQECeGdEdodH6nd__A%40mail.gmail.com
---
doc/src/sgml/ref/psql-ref.sgml | 12 ++++++++++
src/bin/psql/prompt.c | 40 ++++++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 7789fc6177..e9fde65d72 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -4310,6 +4310,18 @@ testdb=> \set PROMPT1 '%[%033[1;33;40m%]%n@%/%R%[%033[0m%]%# '
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>%w</literal></term>
+ <listitem>
+ <para>
+ Whitespace of the same width as <varname>PROMPT1</varname>. This can
+ be used as a <varname>PROMPT2</varname> setting, so that multi-line
+ statements are aligned with the first line, but there is no visible
+ secondary prompt.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
To insert a percent sign into your prompt, write
diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c
index 195192a95d..edb57a4cf7 100644
--- a/src/bin/psql/prompt.c
+++ b/src/bin/psql/prompt.c
@@ -39,6 +39,7 @@
* %n - database user name
* %/ - current database
* %~ - like %/ but "~" when database name equals user name
+ * %w - whitespace of the same width as the most recent output of PROMPT1
* %# - "#" if superuser, ">" otherwise
* %R - in prompt1 normally =, or ^ if single line mode,
* or a ! if session is not connected to a database;
@@ -74,6 +75,7 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
bool esc = false;
const char *p;
const char *prompt_string = "? ";
+ static size_t last_prompt1_width;
switch (status)
{
@@ -124,6 +126,13 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
}
break;
+ /* Whitespace of the same width as the last PROMPT1 */
+ case 'w':
+ if (pset.db)
+ memset(buf, ' ',
+ Min(last_prompt1_width, sizeof(buf) - 1));
+ break;
+
/* DB server hostname (long/short) */
case 'M':
case 'm':
@@ -336,5 +345,36 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
strlcat(destination, buf, sizeof(destination));
}
+ /* Compute the visible width of PROMPT1, for PROMPT2's %w */
+ if (prompt_string == pset.prompt1)
+ {
+ char *p = destination;
+ char *end = p + strlen(p);
+ bool visible = true;
+
+ last_prompt1_width = 0;
+ while (p < end && *p)
+ {
+#if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
+ if (*p == RL_PROMPT_START_IGNORE)
+ {
+ visible = false;
+ ++p;
+ }
+ else if (*p == RL_PROMPT_END_IGNORE)
+ {
+ visible = true;
+ ++p;
+ }
+ else
+#endif
+ {
+ if (visible)
+ last_prompt1_width += PQdsplen(p, pset.encoding);
+ p += PQmblen(p, pset.encoding);
+ }
+ }
+ }
+
return destination;
}
--
2.23.0
Thomas Munro <thomas.munro@gmail.com> writes:
Right, a PQdsplen()/PQmblen() loop works nicely, as attached.
I spotted a potential problem: I suppose I could write a PROMPT1 that
includes an invalid multibyte sequence at the end of the buffer and
trick PQmblen() or PQdsplen() into reading a few bytes past the end.
Two defences against that would be (1) use pg_encoding_verifymb()
instead of PQmblen() and (2) use pg_encoding_max_length() to make sure
you can't get close enough to the end of the buffer, but neither of
those functions are available to psql.
You should follow the logic in pg_wcswidth: compute PQmblen() first,
and bail out if it's more than the remaining string length, otherwise
it's ok to apply PQdsplen().
It might be a good idea to explicitly initialize last_prompt1_width to
zero, for clarity.
Should the user docs explicitly say "of the same width as the most recent
output of PROMPT1", as you have in the comments? That seems a more
precise specification, and it will eliminate some questions people will
otherwise ask.
LGTM otherwise.
regards, tom lane
On Tue, Nov 19, 2019 at 12:09 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
You should follow the logic in pg_wcswidth: compute PQmblen() first,
and bail out if it's more than the remaining string length, otherwise
it's ok to apply PQdsplen().
Got it. I was worried that it wasn't safe to call even PQmblen(),
because I didn't know a fact about all encodings: as described in the
comment of pg_gb18030_mblen(), all implementations read only the first
byte to determine the length, except for GB18030 which reads the
second byte too, and that's OK because there's always a null
terminator.
It might be a good idea to explicitly initialize last_prompt1_width to
zero, for clarity.Should the user docs explicitly say "of the same width as the most recent
output of PROMPT1", as you have in the comments? That seems a more
precise specification, and it will eliminate some questions people will
otherwise ask.LGTM otherwise.
Done, and pushed. I also skipped negative results from PQdsplen like
pg_wcswidth() does (that oversight explained why a non-readline build
showed the correct alignment for PROMPT1 '%[%033[1m%]%M
%n@%/%R%[%033[0m%]%# ' by strange concindence).
Thanks all for the feedback. I think the new bikeshed colour looks good.
On Tue, Nov 19, 2019 at 04:02:48PM +1300, Thomas Munro wrote:
On Tue, Nov 19, 2019 at 12:09 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
You should follow the logic in pg_wcswidth: compute PQmblen() first,
and bail out if it's more than the remaining string length, otherwise
it's ok to apply PQdsplen().Got it. I was worried that it wasn't safe to call even PQmblen(),
because I didn't know a fact about all encodings: as described in the
comment of pg_gb18030_mblen(), all implementations read only the first
byte to determine the length, except for GB18030 which reads the
second byte too, and that's OK because there's always a null
terminator.It might be a good idea to explicitly initialize last_prompt1_width to
zero, for clarity.Should the user docs explicitly say "of the same width as the most recent
output of PROMPT1", as you have in the comments? That seems a more
precise specification, and it will eliminate some questions people will
otherwise ask.LGTM otherwise.
Done, and pushed. I also skipped negative results from PQdsplen like
pg_wcswidth() does (that oversight explained why a non-readline build
showed the correct alignment for PROMPT1 '%[%033[1m%]%M
%n@%/%R%[%033[0m%]%# ' by strange concindence).Thanks all for the feedback. I think the new bikeshed colour looks good.
Please find attached some polka dots for the bike shed :)
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Attachments:
v1-0001-Make-visible_length-available-to-the-rest-of-psql.patchtext/x-diff; charset=us-asciiDownload
From fdbb6272338ed71f8cecf2a755ef53b037acc251 Mon Sep 17 00:00:00 2001
From: David Fetter <david.fetter@onelogin.com>
Date: Tue, 19 Nov 2019 11:39:47 -0800
Subject: [PATCH v1] Make visible_length() available to the rest of psql
To: hackers
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="------------2.23.0"
This is a multi-part message in MIME format.
--------------2.23.0
Content-Type: text/plain; charset=UTF-8; format=fixed
Content-Transfer-Encoding: 8bit
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 90f6380170..3245f3ebae 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -2443,3 +2443,50 @@ recognized_connection_string(const char *connstr)
{
return uri_prefix_length(connstr) != 0 || strchr(connstr, '=') != NULL;
}
+
+/*
+ * Return visible width of the string
+ */
+int
+visible_length(char *string)
+{
+ int visible_length = 0;
+ char *p = string;
+ char *end = p + strlen(p);
+ bool visible = true;
+
+ while(*string)
+ {
+#if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
+ if (*string == RL_PROMPT_START_IGNORE)
+ {
+ visible = false;
+ ++p;
+ }
+ else if (*p == RL_PROMPT_END_IGNORE)
+ {
+ visible = true;
+ ++p;
+ }
+ else
+#endif
+ {
+ int chlen,
+ chwidth;
+
+ chlen = PQmblen(p, pset.encoding);
+ if (p + chlen > end)
+ break;
+
+ if (visible)
+ {
+ chwidth = PQdsplen(p, pset.encoding);
+ if (chwidth > 0)
+ visible_length += chwidth;
+ }
+
+ p += chlen;
+ }
+ }
+ return visible_length;
+}
diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h
index 282a520116..56e28915ce 100644
--- a/src/bin/psql/common.h
+++ b/src/bin/psql/common.h
@@ -44,4 +44,5 @@ extern void expand_tilde(char **filename);
extern bool recognized_connection_string(const char *connstr);
+extern int visible_length(char *string);
#endif /* COMMON_H */
diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c
index 41c6f21ecf..9c0e2c7567 100644
--- a/src/bin/psql/prompt.c
+++ b/src/bin/psql/prompt.c
@@ -347,46 +347,7 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
/* Compute the visible width of PROMPT1, for PROMPT2's %w */
if (prompt_string == pset.prompt1)
- {
- char *p = destination;
- char *end = p + strlen(p);
- bool visible = true;
-
- last_prompt1_width = 0;
- while (*p)
- {
-#if defined(USE_READLINE) && defined(RL_PROMPT_START_IGNORE)
- if (*p == RL_PROMPT_START_IGNORE)
- {
- visible = false;
- ++p;
- }
- else if (*p == RL_PROMPT_END_IGNORE)
- {
- visible = true;
- ++p;
- }
- else
-#endif
- {
- int chlen,
- chwidth;
-
- chlen = PQmblen(p, pset.encoding);
- if (p + chlen > end)
- break; /* Invalid string */
-
- if (visible)
- {
- chwidth = PQdsplen(p, pset.encoding);
- if (chwidth > 0)
- last_prompt1_width += chwidth;
- }
-
- p += chlen;
- }
- }
- }
+ last_prompt1_width = visible_length(destination);
return destination;
}
--------------2.23.0--
Hi,
I noticed that this patch does not work when PROMPT1 contains a new line,
since the whole length of PROMPT1 is taken into account for the length of
%w.
Attached screenshot shows the issue on my psql, with the following PROMPT
variables (colors edited out for readability):
\set PROMPT1 '\n[pid:%p] %n :: %`hostname`:%> ‹%/› \n› '
\set PROMPT2 '%w'
Notice in the screenshot that just after inputting a newline, my cursor is
far to the right.
The length of %w should probably be computed starting from the last newline
in PROMPT1.
I could technically get rid of my newline, but since my prompt can get
pretty long, i like the comfort of having my first line of sql start right
at the left of my terminal.
Also attached is a trivial patch to fix this issue, which I have not
extensively tested (works for me at least), and might not be the right way
to do it, but it's a start.
Otherwise, nice feature, I like it!
Regards,
Maxence
Attachments:
psql_prompt_with_newline.pngimage/png; name=psql_prompt_with_newline.pngDownload
�PNG
IHDR � E 0��� pHYs � ��+ IDATx���y�eWU/��s�}���W���*I�* ��&�� �D��|�
� ����{y6��{��OQ���D��M���*}HS}s�n�{�^k���9�:'
������� ���g7k�5�o�f�h�;�H���D� 1�( @�PU ��+U�9C�� �T����"�L���J!@�s ��D @DL~G����vq; ���$��1�'�P�x��R�XT ��TUE�� 9��R)��*C���*P��
3��H��#�[1�"j��������rN�J �R�h����=1qJ"R���Rb@M��}x)q�u��L����*u�e�BQkeN��jW&��8������HD"@�R�2��"-���*���R����B<(f��91��
'f�R�'