Skip to content

Commit 51a2d41

Browse files
authored
Connect: Close terminal tab if last input was Ctrl+D, even on non-zero exit code (#59836)
* Include the last input in the exit event * Close terminal tab if last input was Ctrl+D * Remove use of create functions
1 parent 5932fd4 commit 51a2d41

File tree

10 files changed

+93
-63
lines changed

10 files changed

+93
-63
lines changed

gen/proto/ts/teleport/web/teleterm/ptyhost/v1/pty_host_service_pb.ts

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/teleport/web/teleterm/ptyhost/v1/pty_host_service.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ message PtyEventOpen {}
9393
message PtyEventExit {
9494
uint32 exit_code = 1;
9595
optional uint32 signal = 2;
96+
string last_input = 3;
9697
}
9798

9899
// PtyEventStartError is sent by the PTY process when the shared process fails to start it.

web/packages/teleterm/src/services/pty/ptyHost/ptyEventsStreamHandler.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ import { DuplexStreamingCall } from '@protobuf-ts/runtime-rpc';
2121
import {
2222
ManagePtyProcessRequest,
2323
ManagePtyProcessResponse,
24-
PtyEventData,
25-
PtyEventResize,
26-
PtyEventStart,
2724
} from 'gen-proto-ts/teleport/web/teleterm/ptyhost/v1/pty_host_service_pb';
2825

2926
import {
@@ -56,7 +53,7 @@ export class PtyEventsStreamHandler {
5653
await this.send({
5754
event: {
5855
oneofKind: 'start',
59-
start: PtyEventStart.create({ columns, rows }),
56+
start: { columns, rows },
6057
},
6158
});
6259
}
@@ -65,7 +62,7 @@ export class PtyEventsStreamHandler {
6562
await this.send({
6663
event: {
6764
oneofKind: 'data',
68-
data: PtyEventData.create({ message: data }),
65+
data: { message: data },
6966
},
7067
});
7168
}
@@ -74,7 +71,7 @@ export class PtyEventsStreamHandler {
7471
await this.send({
7572
event: {
7673
oneofKind: 'resize',
77-
resize: PtyEventResize.create({ columns, rows }),
74+
resize: { columns, rows },
7875
},
7976
});
8077
}
@@ -110,7 +107,11 @@ export class PtyEventsStreamHandler {
110107
}
111108

112109
onExit(
113-
callback: (reason: { exitCode: number; signal?: number }) => void
110+
callback: (reason: {
111+
exitCode: number;
112+
signal?: number;
113+
lastInput: string;
114+
}) => void
114115
): RemoveListenerFunction {
115116
return this.addDataListenerAndReturnRemovalFunction(
116117
(event: ManagePtyProcessResponse) => {

web/packages/teleterm/src/services/pty/ptyHost/ptyProcess.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@ export function createPtyProcess(
6161
return stream.onOpen(callback);
6262
},
6363

64-
onExit(callback: (reason: { exitCode: number; signal?: number }) => void) {
64+
onExit(
65+
callback: (reason: {
66+
exitCode: number;
67+
signal?: number;
68+
lastInput: string;
69+
}) => void
70+
) {
6571
return stream.onExit(callback);
6672
},
6773

web/packages/teleterm/src/sharedProcess/ptyHost/ptyEventsStreamHandler.ts

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@ import {
2222
ManagePtyProcessRequest,
2323
ManagePtyProcessResponse,
2424
PtyEventData,
25-
PtyEventExit,
26-
PtyEventOpen,
2725
PtyEventResize,
2826
PtyEventStart,
29-
PtyEventStartError,
3027
} from 'gen-proto-ts/teleport/web/teleterm/ptyhost/v1/pty_host_service_pb';
3128

3229
import {
@@ -75,44 +72,36 @@ export class PtyEventsStreamHandler {
7572

7673
private handleStartEvent(event: PtyEventStart): void {
7774
this.ptyProcess.onData(data =>
78-
this.stream.write(
79-
ManagePtyProcessResponse.create({
80-
event: {
81-
oneofKind: 'data',
82-
data: PtyEventData.create({ message: data }),
83-
},
84-
})
85-
)
75+
this.stream.write({
76+
event: {
77+
oneofKind: 'data',
78+
data: { message: data },
79+
},
80+
})
8681
);
8782
this.ptyProcess.onOpen(() =>
88-
this.stream.write(
89-
ManagePtyProcessResponse.create({
90-
event: {
91-
oneofKind: 'open',
92-
open: PtyEventOpen.create(),
93-
},
94-
})
95-
)
83+
this.stream.write({
84+
event: {
85+
oneofKind: 'open',
86+
open: {},
87+
},
88+
})
9689
);
97-
this.ptyProcess.onExit(({ exitCode, signal }) =>
98-
this.stream.write(
99-
ManagePtyProcessResponse.create({
100-
event: {
101-
oneofKind: 'exit',
102-
exit: PtyEventExit.create({ exitCode, signal }),
103-
},
104-
})
105-
)
90+
this.ptyProcess.onExit(payload =>
91+
this.stream.write({
92+
event: {
93+
oneofKind: 'exit',
94+
exit: payload,
95+
},
96+
})
10697
);
10798
this.ptyProcess.onStartError(message => {
108-
this.stream.write(
109-
ManagePtyProcessResponse.create({
110-
event: {
111-
oneofKind: 'startError',
112-
startError: PtyEventStartError.create({ message }),
113-
},
114-
})
115-
);
99+
this.stream.write({
100+
event: {
101+
oneofKind: 'startError',
102+
startError: { message },
103+
},
104+
});
116105
});
117106
// PtyProcess.prototype.start always returns a fulfilled promise. If an error is caught during
118107
// start, it's reported through PtyProcess.prototype.onStartError. Similarly, the information

web/packages/teleterm/src/sharedProcess/ptyHost/ptyHostService.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@
1717
*/
1818

1919
import { Struct } from 'gen-proto-ts/google/protobuf/struct_pb';
20-
import {
21-
CreatePtyProcessResponse,
22-
GetCwdResponse,
23-
} from 'gen-proto-ts/teleport/web/teleterm/ptyhost/v1/pty_host_service_pb';
2420
import { IPtyHostService } from 'gen-proto-ts/teleport/web/teleterm/ptyhost/v1/pty_host_service_pb.grpc-server';
2521

2622
import Logger from 'teleterm/logger';
@@ -55,7 +51,7 @@ export function createPtyHostService(): IPtyHostService & {
5551
callback(error);
5652
return;
5753
}
58-
callback(null, CreatePtyProcessResponse.create({ id: ptyId }));
54+
callback(null, { id: ptyId });
5955
logger.info(`created PTY process for id ${ptyId}`);
6056
},
6157
getCwd: (call, callback) => {
@@ -69,8 +65,7 @@ export function createPtyHostService(): IPtyHostService & {
6965
ptyProcess
7066
.getCwd()
7167
.then(cwd => {
72-
const response = GetCwdResponse.create({ cwd });
73-
callback(null, response);
68+
callback(null, { cwd });
7469
})
7570
.catch(error => {
7671
logger.error(`could not read CWD for id: ${id}`, error);

web/packages/teleterm/src/sharedProcess/ptyHost/ptyProcess.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,29 @@ describe('PtyProcess', () => {
5252
);
5353
});
5454
});
55+
56+
if (process.platform !== 'win32') {
57+
test('including the last input in the exit event', async () => {
58+
const pty = new PtyProcess({
59+
path: 'sh',
60+
env: {},
61+
args: [],
62+
useConpty: true,
63+
ptyId: '1234',
64+
});
65+
await pty.start(80, 24);
66+
const listener = jest.fn();
67+
pty.onExit(listener);
68+
69+
pty.write('\x04');
70+
71+
await expect(() => listener.mock.calls.length > 0).toEventuallyBeTrue({
72+
waitFor: 2000,
73+
tick: 10,
74+
});
75+
expect(listener).toHaveBeenCalledWith(
76+
expect.objectContaining({ lastInput: '\x04' })
77+
);
78+
});
79+
}
5580
});

web/packages/teleterm/src/sharedProcess/ptyHost/ptyProcess.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class PtyProcess extends EventEmitter implements IPtyProcess {
4242
private _logger: Logger;
4343
private _status: Status = 'not_initialized';
4444
private _disposed = false;
45+
private _lastInput = '';
4546

4647
constructor(private options: PtyProcessOptions & { ptyId: string }) {
4748
super();
@@ -115,6 +116,7 @@ export class PtyProcess extends EventEmitter implements IPtyProcess {
115116
return;
116117
}
117118

119+
this._lastInput = data;
118120
this._process.write(data);
119121
}
120122

@@ -184,7 +186,9 @@ export class PtyProcess extends EventEmitter implements IPtyProcess {
184186
return this.addListenerAndReturnRemovalFunction(TermEventEnum.Open, cb);
185187
}
186188

187-
onExit(cb: (ev: { exitCode: number; signal?: number }) => void) {
189+
onExit(
190+
cb: (ev: { exitCode: number; signal?: number; lastInput: string }) => void
191+
) {
188192
return this.addListenerAndReturnRemovalFunction(TermEventEnum.Exit, cb);
189193
}
190194

@@ -229,7 +233,7 @@ export class PtyProcess extends EventEmitter implements IPtyProcess {
229233
}
230234

231235
private _handleExit(e: { exitCode: number; signal?: number }) {
232-
this.emit(TermEventEnum.Exit, e);
236+
this.emit(TermEventEnum.Exit, { ...e, lastInput: this._lastInput });
233237
this._logger.info(`pty has been terminated with exit code: ${e.exitCode}`);
234238
this._setStatus('terminated');
235239
}

web/packages/teleterm/src/sharedProcess/ptyHost/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export type IPtyProcess = {
4545
onOpen(cb: () => void): RemoveListenerFunction;
4646
onStartError(cb: (message: string) => void): RemoveListenerFunction;
4747
onExit(
48-
cb: (ev: { exitCode: number; signal?: number }) => void
48+
cb: (ev: { exitCode: number; signal?: number; lastInput: string }) => void
4949
): RemoveListenerFunction;
5050
};
5151

web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,15 +209,12 @@ async function setUpPtyProcess(
209209

210210
ptyProcess.onExit(event => {
211211
// Not closing the tab on non-zero exit code lets us show the error to the user if, for example,
212-
// tsh ssh cannot connect to the given node.
212+
// tsh ssh couldn't connect to the given node.
213213
//
214-
// The downside of this is that if you open a local shell, then execute a command that fails
215-
// (for example, `cd` to a nonexistent directory), and then try to execute `exit` or press
216-
// Ctrl + D, the tab won't automatically close, because the last exit code is not zero.
217-
//
218-
// We can look up how the terminal in vscode handles this problem, since in the scenario
219-
// described above they do close the tab correctly.
220-
if (event.exitCode === 0) {
214+
// We also have to account for Ctrl+D, as executing it makes the shell exit with the last
215+
// reported exit code. If we depended on the exit code alone, it'd mean that the terminal tab
216+
// wouldn't close if Ctrl+D followed a command that failed, say cd to a nonexistent directory.
217+
if (event.exitCode === 0 || event.lastInput === /* Ctrl+D */ '\x04') {
221218
documentsService.close(doc.uri);
222219
}
223220
});

0 commit comments

Comments
 (0)