Skip to content

Commit aa095f8

Browse files
authored
Merge pull request microsoft#250670 from microsoft/copilot/fix-250669
Consolidate write data to terminal and send sequence commands
2 parents 13ff454 + ea74aa6 commit aa095f8

File tree

6 files changed

+57
-66
lines changed

6 files changed

+57
-66
lines changed

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

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,20 +121,49 @@ export async function getCwdForSplit(
121121
}
122122

123123
export const terminalSendSequenceCommand = async (accessor: ServicesAccessor, args: unknown) => {
124-
const instance = accessor.get(ITerminalService).activeInstance;
125-
if (instance) {
126-
const text = isObject(args) && 'text' in args ? toOptionalString(args.text) : undefined;
124+
const configurationResolverService = accessor.get(IConfigurationResolverService);
125+
const historyService = accessor.get(IHistoryService);
126+
const quickInputService = accessor.get(IQuickInputService);
127+
const terminalService = accessor.get(ITerminalService);
128+
const workspaceContextService = accessor.get(IWorkspaceContextService);
129+
130+
const instance = terminalService.activeInstance || await terminalService.getActiveOrCreateInstance();
131+
if (!instance) {
132+
return;
133+
}
134+
135+
let text = isObject(args) && 'text' in args ? toOptionalString(args.text) : undefined;
136+
137+
// If no text provided, prompt user for input
138+
if (!text) {
139+
text = await quickInputService.input({
140+
value: '',
141+
placeHolder: 'Enter sequence to send (supports \\n, \\r, \\x escape sequences)',
142+
prompt: localize('workbench.action.terminal.sendSequence.prompt', "Enter sequence to send to the terminal"),
143+
});
127144
if (!text) {
128145
return;
129146
}
130-
const configurationResolverService = accessor.get(IConfigurationResolverService);
131-
const workspaceContextService = accessor.get(IWorkspaceContextService);
132-
const historyService = accessor.get(IHistoryService);
133-
const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(instance.isRemote ? Schemas.vscodeRemote : Schemas.file);
134-
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined;
135-
const resolvedText = await configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, text);
136-
instance.sendText(resolvedText, false);
137147
}
148+
149+
// Process escape sequences
150+
let processedText = text
151+
.replace(/\\n/g, '\n')
152+
.replace(/\\r/g, '\r');
153+
154+
// Process hex escape sequences (\xNN)
155+
while (true) {
156+
const match = processedText.match(/\\x([0-9a-fA-F]{2})/);
157+
if (match === null || match.index === undefined || match.length < 2) {
158+
break;
159+
}
160+
processedText = processedText.slice(0, match.index) + String.fromCharCode(parseInt(match[1], 16)) + processedText.slice(match.index + 4);
161+
}
162+
163+
const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(instance.isRemote ? Schemas.vscodeRemote : Schemas.file);
164+
const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) ?? undefined : undefined;
165+
const resolvedText = await configurationResolverService.resolveAsync(lastActiveWorkspaceRoot, processedText);
166+
instance.sendText(resolvedText, false);
138167
};
139168

140169
export class TerminalLaunchHelpAction extends Action {
@@ -957,14 +986,13 @@ export function registerTerminalActions() {
957986
registerTerminalAction({
958987
id: TerminalCommandId.SendSequence,
959988
title: terminalStrings.sendSequence,
960-
f1: false,
989+
f1: true,
961990
metadata: {
962991
description: terminalStrings.sendSequence.value,
963992
args: [{
964993
name: 'args',
965994
schema: {
966995
type: 'object',
967-
required: ['text'],
968996
properties: {
969997
text: {
970998
description: localize('sendSequence', "The sequence of text to send to the terminal"),

src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ import { IConfigurationService } from '../../../../../platform/configuration/com
1717
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
1818
import { IFileService } from '../../../../../platform/files/common/files.js';
1919
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
20-
import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js';
2120
import { ITerminalCommand, TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js';
2221
import { ITerminalLogService, TerminalSettingId } from '../../../../../platform/terminal/common/terminal.js';
2322
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
2423
import { IStatusbarService, StatusbarAlignment, type IStatusbarEntry } from '../../../../services/statusbar/browser/statusbar.js';
25-
import { IInternalXtermTerminal, ITerminalContribution, ITerminalInstance, IXtermTerminal } from '../../../terminal/browser/terminal.js';
24+
import { ITerminalContribution, ITerminalInstance, IXtermTerminal } from '../../../terminal/browser/terminal.js';
2625
import { registerTerminalAction } from '../../../terminal/browser/terminalActions.js';
2726
import { registerTerminalContribution, type ITerminalContributionContext } from '../../../terminal/browser/terminalExtensions.js';
2827
import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js';
@@ -61,41 +60,6 @@ registerTerminalAction({
6160
}
6261
});
6362

64-
registerTerminalAction({
65-
id: TerminalDeveloperCommandId.WriteDataToTerminal,
66-
title: localize2('workbench.action.terminal.writeDataToTerminal', 'Write Data to Terminal'),
67-
category: Categories.Developer,
68-
run: async (c, accessor) => {
69-
const quickInputService = accessor.get(IQuickInputService);
70-
const instance = await c.service.getActiveOrCreateInstance();
71-
await c.service.revealActiveTerminal();
72-
await instance.processReady;
73-
if (!instance.xterm) {
74-
throw new Error('Cannot write data to terminal if xterm isn\'t initialized');
75-
}
76-
const data = await quickInputService.input({
77-
value: '',
78-
placeHolder: 'Enter data, use \\x to escape',
79-
prompt: localize('workbench.action.terminal.writeDataToTerminal.prompt', "Enter data to write directly to the terminal, bypassing the pty"),
80-
});
81-
if (!data) {
82-
return;
83-
}
84-
let escapedData = data
85-
.replace(/\\n/g, '\n')
86-
.replace(/\\r/g, '\r');
87-
while (true) {
88-
const match = escapedData.match(/\\x([0-9a-fA-F]{2})/);
89-
if (match === null || match.index === undefined || match.length < 2) {
90-
break;
91-
}
92-
escapedData = escapedData.slice(0, match.index) + String.fromCharCode(parseInt(match[1], 16)) + escapedData.slice(match.index + 4);
93-
}
94-
const xterm = instance.xterm as any as IInternalXtermTerminal;
95-
xterm._writeText(escapedData);
96-
}
97-
});
98-
9963
registerTerminalAction({
10064
id: TerminalDeveloperCommandId.RecordSession,
10165
title: localize2('workbench.action.terminal.recordSession', 'Record Terminal Session'),

src/vs/workbench/contrib/terminalContrib/developer/common/terminal.developer.ts

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

66
export const enum TerminalDeveloperCommandId {
7-
WriteDataToTerminal = 'workbench.action.terminal.writeDataToTerminal',
87
RecordSession = 'workbench.action.terminal.recordSession',
98
ShowTextureAtlas = 'workbench.action.terminal.showTextureAtlas',
109
RestartPtyHost = 'workbench.action.terminal.restartPtyHost',

test/automation/src/terminal.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export enum TerminalCommandIdWithValue {
4141
NewWithProfile = 'workbench.action.terminal.newWithProfile',
4242
SelectDefaultProfile = 'workbench.action.terminal.selectDefaultShell',
4343
AttachToSession = 'workbench.action.terminal.attachToSession',
44-
WriteDataToTerminal = 'workbench.action.terminal.writeDataToTerminal'
44+
SendSequence = 'workbench.action.terminal.sendSequence'
4545
}
4646

4747
/**
@@ -156,7 +156,7 @@ export class Terminal {
156156
/**
157157
* Creates an empty terminal by opening a regular terminal and resetting its state such that it
158158
* essentially acts like an Pseudoterminal extension API-based terminal. This can then be paired
159-
* with `TerminalCommandIdWithValue.WriteDataToTerminal` to make more reliable tests.
159+
* with `TerminalCommandIdWithValue.SendSequence` to make more reliable tests.
160160
*/
161161
async createEmptyTerminal(expectedLocation?: 'editor' | 'panel'): Promise<void> {
162162
await this.createTerminal(expectedLocation);
@@ -167,11 +167,11 @@ export class Terminal {
167167
await this.waitForTerminalText(buffer => buffer.some(line => line.startsWith('initialized')));
168168

169169
// Erase all content and reset cursor to top
170-
await this.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${csi('2J')}${csi('H')}`);
170+
await this.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `${csi('2J')}${csi('H')}`);
171171

172172
// Force windows pty mode off; assume all sequences are rendered in correct position
173173
if (process.platform === 'win32') {
174-
await this.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${vsc('P;IsWindows=False')}`);
174+
await this.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `${vsc('P;IsWindows=False')}`);
175175
}
176176
}
177177

test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -98,33 +98,33 @@ export function setup(options?: { skipSuite: boolean }) {
9898
// Use the simplest profile to get as little process interaction as possible
9999
await terminal.createEmptyTerminal();
100100
// Erase all content and reset cursor to top
101-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${csi('2J')}${csi('H')}`);
101+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `${csi('2J')}${csi('H')}`);
102102
});
103103
describe('VS Code sequences', () => {
104104
it('should handle the simple case', async () => {
105-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${vsc('A')}Prompt> ${vsc('B')}exitcode 0`);
105+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `${vsc('A')}Prompt> ${vsc('B')}exitcode 0`);
106106
await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 0 });
107-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `\\r\\n${vsc('C')}Success\\r\\n${vsc('D;0')}`);
107+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `\\r\\n${vsc('C')}Success\\r\\n${vsc('D;0')}`);
108108
await terminal.assertCommandDecorations({ placeholder: 0, success: 1, error: 0 });
109-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${vsc('A')}Prompt> ${vsc('B')}exitcode 1`);
109+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `${vsc('A')}Prompt> ${vsc('B')}exitcode 1`);
110110
await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 0 });
111-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `\\r\\n${vsc('C')}Failure\\r\\n${vsc('D;1')}`);
111+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `\\r\\n${vsc('C')}Failure\\r\\n${vsc('D;1')}`);
112112
await terminal.assertCommandDecorations({ placeholder: 0, success: 1, error: 1 });
113-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${vsc('A')}Prompt> ${vsc('B')}`);
113+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `${vsc('A')}Prompt> ${vsc('B')}`);
114114
await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 1 });
115115
});
116116
});
117117
describe('Final Term sequences', () => {
118118
it('should handle the simple case', async () => {
119-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${ft('A')}Prompt> ${ft('B')}exitcode 0`);
119+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `${ft('A')}Prompt> ${ft('B')}exitcode 0`);
120120
await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 0 });
121-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `\\r\\n${ft('C')}Success\\r\\n${ft('D;0')}`);
121+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `\\r\\n${ft('C')}Success\\r\\n${ft('D;0')}`);
122122
await terminal.assertCommandDecorations({ placeholder: 0, success: 1, error: 0 });
123-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${ft('A')}Prompt> ${ft('B')}exitcode 1`);
123+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `${ft('A')}Prompt> ${ft('B')}exitcode 1`);
124124
await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 0 });
125-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `\\r\\n${ft('C')}Failure\\r\\n${ft('D;1')}`);
125+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `\\r\\n${ft('C')}Failure\\r\\n${ft('D;1')}`);
126126
await terminal.assertCommandDecorations({ placeholder: 0, success: 1, error: 1 });
127-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, `${ft('A')}Prompt> ${ft('B')}exitcode 1`);
127+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, `${ft('A')}Prompt> ${ft('B')}exitcode 1`);
128128
await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 1 });
129129
});
130130
});

test/smoke/src/areas/terminal/terminal-stickyScroll.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function setup(options?: { skipSuite: boolean }) {
3535
expectedLineCount: number = 1
3636
): Promise<void> {
3737
const data = generateCommandAndOutput(prompt, command, exitCode);
38-
await terminal.runCommandWithValue(TerminalCommandIdWithValue.WriteDataToTerminal, data);
38+
await terminal.runCommandWithValue(TerminalCommandIdWithValue.SendSequence, data);
3939
// Verify line count
4040
await app.code.waitForElements('.terminal-sticky-scroll .xterm-rows > *', true, e => e.length === expectedLineCount);
4141
// Verify content

0 commit comments

Comments
 (0)