Skip to content

Commit 58757c0

Browse files
authored
Use bracketed paste for multiline executed terminal text (#302526)
1 parent 08316e4 commit 58757c0

2 files changed

Lines changed: 65 additions & 5 deletions

File tree

src/vs/workbench/contrib/terminal/browser/terminalInstance.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1343,9 +1343,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
13431343
}
13441344

13451345
async sendText(text: string, shouldExecute: boolean, bracketedPasteMode?: boolean): Promise<void> {
1346+
const useBracketedPasteMode = (bracketedPasteMode || /[\r\n]/.test(text)) && this.xterm?.raw.modes.bracketedPasteMode;
1347+
13461348
// Apply bracketed paste sequences if the terminal has the mode enabled, this will prevent
13471349
// the text from triggering keybindings and ensure new lines are handled properly
1348-
if (bracketedPasteMode && this.xterm?.raw.modes.bracketedPasteMode) {
1350+
if (useBracketedPasteMode) {
13491351
text = `\x1b[200~${text}\x1b[201~`;
13501352
}
13511353

src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { deepStrictEqual, strictEqual } from 'assert';
7+
import { timeout } from '../../../../../base/common/async.js';
78
import { Event } from '../../../../../base/common/event.js';
89
import { Disposable } from '../../../../../base/common/lifecycle.js';
910
import { Schemas } from '../../../../../base/common/network.js';
@@ -123,7 +124,8 @@ suite('Workbench - TerminalInstance', () => {
123124

124125
suite('TerminalInstance', () => {
125126
let terminalInstance: ITerminalInstance;
126-
test('should create an instance of TerminalInstance with env from default profile', async () => {
127+
128+
async function createTerminalInstance(): Promise<TerminalInstance> {
127129
const instantiationService = workbenchInstantiationService({
128130
configurationService: () => new TestConfigurationService({
129131
files: {},
@@ -146,9 +148,25 @@ suite('Workbench - TerminalInstance', () => {
146148
instantiationService.stub(IEnvironmentVariableService, store.add(instantiationService.createInstance(EnvironmentVariableService)));
147149
instantiationService.stub(ITerminalInstanceService, store.add(new TestTerminalInstanceService()));
148150
instantiationService.stub(ITerminalService, { setNextCommandId: async () => { } } as Partial<ITerminalService>);
149-
terminalInstance = store.add(instantiationService.createInstance(TerminalInstance, terminalShellTypeContextKey, {}));
150-
// //Wait for the teminalInstance._xtermReadyPromise to resolve
151-
await new Promise(resolve => setTimeout(resolve, 100));
151+
const instance = store.add(instantiationService.createInstance(TerminalInstance, terminalShellTypeContextKey, {}));
152+
await instance.xtermReadyPromise;
153+
return instance;
154+
}
155+
156+
async function waitForShellLaunchConfigEnv(instance: ITerminalInstance): Promise<void> {
157+
for (let i = 0; i < 50; i++) {
158+
if (instance.shellLaunchConfig.env) {
159+
return;
160+
}
161+
await timeout(0);
162+
}
163+
164+
throw new Error('Timed out waiting for shell launch config env');
165+
}
166+
167+
test('should create an instance of TerminalInstance with env from default profile', async () => {
168+
terminalInstance = await createTerminalInstance();
169+
await waitForShellLaunchConfigEnv(terminalInstance);
152170
deepStrictEqual(terminalInstance.shellLaunchConfig.env, { TEST: 'TEST' });
153171
});
154172

@@ -192,6 +210,46 @@ suite('Workbench - TerminalInstance', () => {
192210
// Verify that the task name is preserved
193211
strictEqual(taskTerminal.title, 'Test Task Name', 'Task terminal should preserve API-set title');
194212
});
213+
214+
test('should use bracketed paste mode for multiline executed text when available', async () => {
215+
const instance = await createTerminalInstance();
216+
const writes: string[] = [];
217+
const processManager = (instance as unknown as { _processManager: { write(data: string): Promise<void> } })._processManager;
218+
const originalWrite = processManager.write;
219+
const originalXterm = instance.xterm!;
220+
const testRaw = Object.create(originalXterm.raw) as typeof originalXterm.raw;
221+
Object.defineProperty(testRaw, 'modes', {
222+
value: {
223+
...originalXterm.raw.modes,
224+
bracketedPasteMode: true
225+
},
226+
configurable: true
227+
});
228+
const testXterm = Object.create(originalXterm) as typeof originalXterm;
229+
Object.defineProperty(testXterm, 'raw', {
230+
value: testRaw,
231+
configurable: true
232+
});
233+
Object.defineProperty(testXterm, 'scrollToBottom', {
234+
value: () => { },
235+
configurable: true
236+
});
237+
238+
processManager.write = async (data: string) => {
239+
writes.push(data);
240+
};
241+
instance.xterm = testXterm;
242+
243+
try {
244+
await instance.sendText('echo hello\nworld', true);
245+
} finally {
246+
processManager.write = originalWrite;
247+
instance.xterm = originalXterm;
248+
}
249+
250+
strictEqual(writes.length, 1);
251+
strictEqual(writes[0].replace(/\x1b/g, '\\x1b').replace(/\r/g, '\\r'), '\\x1b[200~echo hello\\rworld\\x1b[201~\\r');
252+
});
195253
});
196254
suite('parseExitResult', () => {
197255
test('should return no message for exit code = undefined', () => {

0 commit comments

Comments
 (0)