From: john@arissoft.COM
Subject: "Philosophical" bug:  Bash needs an "invisiblity" sequence inside PS1
Organization: GNUs Not Usenet
Distribution: gnu
Date: Sun, 6 Sep 1992 00:53:13 GMT

I use GNU Bash, version 1.12.9, and the terminal program I use is Stuart
2.4 on a NeXT, which has title-bar-setting escape sequences that are a
superset of those understood by Xterm.  A problem I was experiencing was
that the Readline library used by Bash doesn't know what part of the prompt
is actually visible.  So, if I have a big, nasty PS1 variable that puts
lots of things in the title bar, Readline decides I've hit the 80th column
and breaks the line way too early.  (About the 15th character, with my
current PS1 setting.)  Things just get worse when you start doing cursor
motion commands, redraws, etc.

Very bad.

I thought of a reasonably simple fix.  Introduce two new sequences into
the PS[124] variables:  "\i" to say that the following characters are
invisible and shouldn't get put into the prompt that Readline knows
about, and (naturally) "\I" to turn off invisibility for the following
characters.  Now, a PS1 sequence to set the titlebar for an Xterm (or
a Stuart, in my case) might be

	PS1="\i^[]0;\u@\h:\w^G\I% '

I had originally planned just to send in a whiney message complaining
about this and sit around and wait until somebody did something, but
I figured I'd like this feature right away, and besides, you probably
wouldn't stick this feature in future versions anyway ... :-)  So, a
little diddling around produced the following apparently-operational
diff.

The code works like this:  it introduces a global variable called
invisible_readline_prompt that is effectively a second return value
from decode_prompt_string().  When prompt_again() calls
decode_prompt_string(), it prints invisible_readline_prompt to stderr.

decode_prompt_string() was modified to have two states.  One state is
the "invisible" state, and the other is the "visible" state.  In the
visible state, operation is as it was before.  The string result is
built up a little bit at a time.  However, the sequences \i and \I
switch to/from the invisible state, and in the invisible state, the
little chunks get added to a new local variable called invisible_result
(use of invisible_result was a cut 'n' paste from the code that dealt
with result).  When the function is done, it copies invisible_result
out to invisible_readline_prompt.

I wouldn't say this was the greatest implementation in the world, but
it was quick and easy, and I think the \i and \I are easy to use.  (And
probably won't break existing code.)  Feel free (of course) to throw
this code out and do it like it should have been done.  But please --
put SOMETHING like this in the next Bash.  The problem with invisible
prompt characters messing up the line breaking was terribly annoying.

Incidentally, to Brian Fox and whoever else has contributed code to
Bash:  I'm extremely impressed with Bash, both from a user standpoint
and from a standpoint of a guy who just had to hack it to add a
little feature.  The code is extremely readable and maintainable.
Good work.

--
john@arissoft.com (John Dawson)

Enclosure:
--------------------------------------------------------------------
*** parse.y.old	Sat Sep  5 16:20:42 1992
--- parse.y	Sat Sep  5 19:18:29 1992
***************
*** 601,606 ****
--- 601,607 ----
  

  #if defined (READLINE)
  char *current_readline_prompt = (char *)NULL;
+ char *invisible_readline_prompt = (char *)NULL;
  char *current_readline_line = (char *)NULL;
  int current_readline_line_index = 0;
  

***************
*** 2328,2334 ****
    ps2_prompt = get_string_value ("PS2");
  

    if (!prompt_string_pointer)
!     prompt_string_pointer = &ps1_prompt;
  

    if (*prompt_string_pointer)
      temp_prompt = decode_prompt_string (*prompt_string_pointer);
--- 2329,2337 ----
    ps2_prompt = get_string_value ("PS2");
  

    if (!prompt_string_pointer)
!     {
!       prompt_string_pointer = &ps1_prompt;
!     }
  

    if (*prompt_string_pointer)
      temp_prompt = decode_prompt_string (*prompt_string_pointer);
***************
*** 2335,2340 ****
--- 2338,2349 ----
    else
      temp_prompt = savestring ("");
  

+   if (invisible_readline_prompt)
+     {
+       fprintf(stderr, "%s", invisible_readline_prompt);
+       fflush(stderr);
+     }
+ 

    current_prompt_string = *prompt_string_pointer;
    prompt_string_pointer = &ps2_prompt;
  

***************
*** 2356,2361 ****
--- 2365,2371 ----
  	}
        free (temp_prompt);
      }
+ 

  }
  

  #include "maxpath.h"
***************
*** 2376,2381 ****
--- 2386,2393 ----
  	\$	a $ or a # if you are root
  	\<octal> character code in octal
  	\\	a backslash
+ 	\i	mark beginning of invisible sequence
+ 	\I	mark end of invisible sequence
  */
  #include <sys/param.h>
  #include <time.h>
***************
*** 2388,2397 ****
--- 2400,2414 ----
    int result_size = PROMPT_GROWTH;
    int result_index = 0;
    char *result = (char *)xmalloc (PROMPT_GROWTH);
+   int invisible_result_size = PROMPT_GROWTH;
+   int invisible_result_index = 0;
+   char *invisible_result = (char *)xmalloc (PROMPT_GROWTH);
    int c;
    char *temp = (char *)NULL;
+   int invisible = 0;
  

    result[0] = 0;
+   invisible_result[0] = 0;
    while (c = *string++)
      {
        if (c == '\\')
***************
*** 2545,2573 ****
  	    case '\\':
  	      temp = savestring ("\\");
  	      goto add_string;
  

  	    default:
  	      temp = savestring ("\\ ");
  	      temp[1] = c;
  

  	    add_string:
! 	      if (c)
! 		string++;
! 	      result =
! 		(char *)sub_append_string (temp, result,
! 					   &result_index, &result_size);
! 	      temp = (char *)NULL; /* Free ()'ed in sub_append_string (). */
! 	      result[result_index] = '\0';
! 	      break;
  	    }
  	}
        else
  	{
! 	  while (3 + result_index > result_size)
! 	    result = (char *)xrealloc (result, result_size += PROMPT_GROWTH);
  

! 	  result[result_index++] = c;
! 	  result[result_index] = '\0';
  	}
      }
  

--- 2562,2628 ----
  	    case '\\':
  	      temp = savestring ("\\");
  	      goto add_string;
+ 	    

+ 	    case 'i':
+ 	      invisible = 1;
+ 	      temp = savestring ("");
+ 	      goto add_string;
  

+ 	    case 'I':
+ 	      invisible = 0;
+ 	      temp = savestring ("");
+ 	      goto add_string;
+ 

  	    default:
  	      temp = savestring ("\\ ");
  	      temp[1] = c;
  

  	    add_string:
! 	      if (invisible) 

! 		{
! 		  if (c)
! 		    string++;
! 		  invisible_result =
! 		    (char *)sub_append_string (temp, invisible_result,
! 					       &invisible_result_index,
! 					       &invisible_result_size);
! 		  temp = (char *)NULL; /* Free ()'ed in sub_append_string (). */
! 		  invisible_result[invisible_result_index] = '\0';
! 		  break;
! 		}
! 	      else
! 		{
! 		  if (c)
! 		    string++;
! 		  result =
! 		    (char *)sub_append_string (temp, result,
! 					       &result_index, &result_size);
! 		  temp = (char *)NULL; /* Free ()'ed in sub_append_string (). */
! 		  result[result_index] = '\0';
! 		  break;
! 		}
  	    }
  	}
        else
  	{
! 	  if (invisible)
! 	    {
! 	      while (3 + invisible_result_index > invisible_result_size)
! 		result = (char *)
! 		    xrealloc(invisible_result,
! 		             invisible_result_size += PROMPT_GROWTH);
  

! 	      invisible_result[invisible_result_index++] = c;
! 	      invisible_result[invisible_result_index] = '\0';
! 	    }
! 	  else
! 	    {
! 	      while (3 + result_index > result_size)
! 		result = (char *)xrealloc (result, result_size+=PROMPT_GROWTH);
! 

! 	      result[result_index++] = c;
! 	      result[result_index] = '\0';
! 	    }
  	}
      }
  

***************
*** 2582,2587 ****
--- 2637,2646 ----
        result = string_list (list);
        dispose_words (list);
      }
+ 

+   if (invisible_readline_prompt)
+     free(invisible_readline_prompt);
+   invisible_readline_prompt = invisible_result;
  

    return (result);
  }
--------------------------------------------------------------------

Newsgroups: gnu.bash.bug