Skip to content

Commit 043f37a

Browse files
committed
Support OSC for setting the terminal window title
1 parent 52760c7 commit 043f37a

File tree

4 files changed

+43
-4
lines changed

4 files changed

+43
-4
lines changed

src/components/elementselection/ElementSelection.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export default defineComponent({
4242
*/
4343
pushToPrompt(index: number) {
4444
prompt.state().push(new PromptElement(PROMPT_ELEMENT_TYPES[index]));
45+
46+
// append an additional bell element which ends the operating system command setting the window title
47+
if (PROMPT_ELEMENT_TYPES[index].name === 'Set Window Title') {
48+
this.pushToPrompt(PROMPT_ELEMENT_TYPES.findIndex((element) => element.name === 'Bell'));
49+
}
4550
},
4651
/**
4752
* Whether to add a separator before the given element type, as specified in `PROMPT_ELEMENT_TYPES_SEPARATORS`

src/components/output/PS1Variable.vue

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
<div class="ps1" :class="{ dark: darkMode }">
1010
<span>{{ ps1 }}</span>
1111
</div>
12+
<div class="hint" v-if="hasElement('Set Window Title')">
13+
Elements between the <code>Set Window Title</code> and the next <code>Bell</code> element are used to modify the
14+
window title of the terminal, if supported.
15+
Any styling properties of those elements are ignored.
16+
</div>
1217
<div class="hint" v-if="hasElement('Advanced Git Prompt')">
1318
The <code>Advanced Git Prompt</code> element requires some extra work: Copy the
1419
<a
@@ -179,13 +184,24 @@ function generatePS1(elements: PromptElement[]): string {
179184
// the initial state of the properties is that all colors and attributes are not set
180185
// this might actually be false if any escape codes are printed before the prompt but we ignore that
181186
let propertiesState: PropertiesState = defaultPropertiesState();
187+
// whether an operating system command ('Set Window Title') has been encountered and awaits an ending bell
188+
let operatingSystemCommand: boolean = false;
182189
183190
const commands: string[] = [];
184191
const outputElements: string[] = [];
185192
186193
elements.forEach((element) => {
187194
if (!element.type.visible) {
188195
outputElements.push(element.type.char(element.parameters));
196+
197+
if (element.type.name === 'Set Window Title') {
198+
operatingSystemCommand = true;
199+
} else if (element.type.name === 'Bell' && operatingSystemCommand) {
200+
operatingSystemCommand = false;
201+
// end of non-printable operating system command
202+
outputElements.push('\\]');
203+
}
204+
189205
// skip any handling of escape sequences for invisible elements as they are not affected by them
190206
// for instance, if two elements with identical properties are separated by only invisible elements, we do not
191207
// need to insert any reset escape codes in between them
@@ -209,15 +225,20 @@ function generatePS1(elements: PromptElement[]): string {
209225
};
210226
211227
const escapeCodes = generateEscapeCodes(propertiesState, newPropertiesState);
228+
229+
// ignore escape codes within operating system commands
230+
if (!operatingSystemCommand) {
231+
outputElements.push(escapeCodes);
232+
propertiesState = newPropertiesState;
233+
}
234+
212235
if (element.type.command) {
213236
const commandVariable = `PS1_CMD${commands.length + 1}`;
214237
commands.push(`${commandVariable}=$(${element.type.char(element.parameters)})`);
215-
outputElements.push(`${escapeCodes}$\{${commandVariable}}`);
238+
outputElements.push(`$\{${commandVariable}}`);
216239
} else {
217-
outputElements.push(`${escapeCodes}${element.type.char(element.parameters)}`);
240+
outputElements.push(`${element.type.char(element.parameters)}`);
218241
}
219-
220-
propertiesState = newPropertiesState;
221242
});
222243
223244
// reset all attributes at the end if there are any set

src/lib/enum/promptElementType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ export const PROMPT_ELEMENT_TYPES = [
289289
return `${variable} value`;
290290
},
291291
),
292+
new PromptElementType('Set Window Title', '\\[\\e]0;', [], false, false, 'Set the terminal window title.', ''),
292293
new PromptElementType('␣', ' ', [], true, false, 'Space.', ' '),
293294
new PromptElementType('~', '~', [], true, false, 'Tilde.', '~'),
294295
new PromptElementType('!', '!', [], true, false, 'Exclamation mark.', '!'),

src/lib/promptParser.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,13 +358,25 @@ export function parsePrompt(ps1: string, promptCommand: string): PromptElement[]
358358

359359
let cursor = 0;
360360
let propertiesState: PropertiesState = defaultPropertiesState();
361+
// whether an operating system command ('Set Window Title') has been encountered and awaits an ending bell
362+
let operatingSystemCommand: boolean = false;
361363

362364
while (cursor < ps1.length) {
363365
// unparameterized elements are the most common, so we check them first
364366
const unparameterizedElement = readUnparameterized(ps1, cursor);
365367
if (unparameterizedElement !== null) {
366368
elements.push(applyState(unparameterizedElement.element, propertiesState));
367369
cursor = unparameterizedElement.newCursor;
370+
371+
if (unparameterizedElement.element.type.name === 'Set Window Title') {
372+
operatingSystemCommand = true;
373+
} else if (unparameterizedElement.element.type.name === 'Bell' && operatingSystemCommand) {
374+
operatingSystemCommand = false;
375+
// skip '\]'
376+
if (ps1.startsWith('\\]', cursor)) {
377+
cursor += 2;
378+
}
379+
}
368380
}
369381
// handling of escape sequences that will affect the following elements
370382
else if (

0 commit comments

Comments
 (0)