Index | Thread | Search

From:
ori@eigenstate.org
Subject:
ksh: add OSC7 support
To:
tech@openbsd.org
Date:
Thu, 06 Nov 2025 20:23:26 -0500

Download raw body.

Thread
I use OSC7 in order to advertise the working directory to the
terminal emulator I use. While I can hack this together in ksh
with some scripts, it feels like it would be a better fit to
have directly in the shell, though I'm uncertain about that.

It seems a bit odd to have support for this in tmux, but not
emit it in our own shell.

The patch below adds a '\O' option to PS1. It needs to be
enabled explicitly, as:

	PS1=\O\h\$

I'd also considered printing it after every 'cd' command,
either unconditionally or behind a knob.

Is there interest in this? If there is, is this the right
place to do it, or should we put it somewhere else?

diff b0824ea26cb2a850adf7ef5a41c70bb06f83050c uncommitted
--- a/bin/ksh/ksh.1
+++ b/bin/ksh/ksh.1
@@ -1618,6 +1618,10 @@
 .Dv $HOME
 is abbreviated as
 .Sq ~ .
+.It Sy \eO
+The OSC-7 escape code.
+This advertises the current working directory to
+terminal emulator under wish ksh is running.
 .It Sy \e!
 The current history number.
 An unescaped
@@ -4282,7 +4286,7 @@
 .Ar n
 blocks on files written by the shell and its child processes (files of any
 size may be read).
-.It Fl H
+.It Fl//
 Set the hard limit only (the default is to set both hard and soft limits).
 .It Fl l Ar n
 Impose a limit of
--- a/bin/ksh/lex.c
+++ b/bin/ksh/lex.c
@@ -1207,10 +1207,37 @@
 	}
 }
 
+static char*
+osc7encode(char *p, char *e, char *s, int raw)
+{
+	static const char hex[] = "0123456789abcdef";
+
+	while(*s){
+		if((*s >= 'a' && *s <= 'z')
+		|| (*s >= 'A' && *s <= 'f')
+		|| (*s >= '0' && *s <= '9')
+		|| (strchr("/-_.~", *s) != NULL)
+		|| raw){
+			if(e - p < 2)
+				break;
+			*p++ = *s++;
+		}else{
+			if(e - p < 4)
+				break;
+			*p++ = '%';
+			*p++ = hex[(*s>>4) & 0xf];
+			*p++ = hex[(*s>>0) & 0xf];
+			s++;
+		}
+	}
+	*p = 0;
+	return p;
+}
+
 static int
 dopprompt(const char *sp, int ntruncate, const char **spp, int doprint)
 {
-	char strbuf[1024], tmpbuf[1024], *p, *str, nbuf[32], delimiter = '\0';
+	char strbuf[1024], tmpbuf[1024], *p, *e, *str, nbuf[32], delimiter = '\0';
 	int len, c, n, totlen = 0, indelimit = 0, counting = 1, delimitthis;
 	const char *cp = sp;
 	struct tm *tm;
@@ -1398,6 +1425,20 @@
 					strbuf[1] = '\0';
 				} else
 					strlcpy(strbuf, basename(p), sizeof strbuf);
+				break;
+			case 'O':
+				str = str_val(global("PWD"));
+				gethostname(tmpbuf, sizeof strbuf);
+				if((e = strchr(tmpbuf, '.')) != NULL)
+					*e = '\0';
+
+				p = strbuf;
+				e = strbuf + sizeof(strbuf);
+				p = osc7encode(p, e, "\033]7;file://", 1);
+				p = osc7encode(p, e, tmpbuf, 0);
+				p = osc7encode(p, e, "/", 0);
+				p = osc7encode(p, e, str, 0);
+				p = osc7encode(p, e, "\033", 1);
 				break;
 			case '!':	/* '\' '!' history line number */
 				snprintf(strbuf, sizeof strbuf, "%d",