Skip to content

Commit 6ea7f82

Browse files
committed
Add more approach, to make experience faster
1 parent e346387 commit 6ea7f82

File tree

5 files changed

+71
-11
lines changed

5 files changed

+71
-11
lines changed

src/client/common/application/terminalManager.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export class TerminalManager implements ITerminalManager {
3838
public onDidEndTerminalShellExecution(handler: (e: TerminalShellExecutionEndEvent) => void): Disposable {
3939
return window.onDidEndTerminalShellExecution(handler);
4040
}
41+
public onDidChangeTerminalState(handler: (e: Terminal) => void): Disposable {
42+
return window.onDidChangeTerminalState(handler);
43+
}
4144
}
4245

4346
/**

src/client/common/application/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,8 @@ export interface ITerminalManager {
939939
onDidChangeTerminalShellIntegration(handler: (e: TerminalShellIntegrationChangeEvent) => void): Disposable;
940940

941941
onDidEndTerminalShellExecution(handler: (e: TerminalShellExecutionEndEvent) => void): Disposable;
942+
943+
onDidChangeTerminalState(handler: (e: Terminal) => void): Disposable;
942944
}
943945

944946
export const IDebugService = Symbol('IDebugManager');

src/client/common/terminal/service.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ export class TerminalService implements ITerminalService, Disposable {
3838
private _terminalFirstLaunched: boolean = true;
3939
private pythonReplCommandQueue: string[] = [];
4040
private isReplReady: boolean = false;
41-
private replDataListener?: Disposable;
41+
private replPromptListener?: Disposable;
42+
private replShellTypeListener?: Disposable;
4243
public get onDidCloseTerminal(): Event<void> {
4344
return this.terminalClosed.event.bind(this.terminalClosed);
4445
}
@@ -105,28 +106,48 @@ export class TerminalService implements ITerminalService, Disposable {
105106
}
106107

107108
private startReplListener(): void {
108-
if (this.replDataListener) {
109+
if (this.replPromptListener || this.replShellTypeListener) {
109110
return;
110111
}
111112

113+
this.replShellTypeListener = this.terminalManager.onDidChangeTerminalState((terminal) => {
114+
if (this.terminal && terminal === this.terminal) {
115+
if (terminal.state.shell == 'python') {
116+
traceVerbose('Python REPL ready from terminal shell api');
117+
this.onReplReady();
118+
}
119+
}
120+
});
121+
112122
let terminalData = '';
113-
this.replDataListener = this.applicationShell.onDidWriteTerminalData((e) => {
123+
this.replPromptListener = this.applicationShell.onDidWriteTerminalData((e) => {
114124
if (this.terminal && e.terminal === this.terminal) {
115125
terminalData += e.data;
116126
if (/>>>\s*$/.test(terminalData)) {
117-
traceVerbose('Python REPL ready, detected >>> prompt');
118-
this.isReplReady = true;
119-
this.disposeReplListener();
120-
this.flushReplQueue();
127+
traceVerbose('Python REPL ready, from >>> prompt detection');
128+
this.onReplReady();
121129
}
122130
}
123131
});
124132
}
125133

134+
private onReplReady(): void {
135+
if (this.isReplReady) {
136+
return;
137+
}
138+
this.isReplReady = true;
139+
this.flushReplQueue();
140+
this.disposeReplListener();
141+
}
142+
126143
private disposeReplListener(): void {
127-
if (this.replDataListener) {
128-
this.replDataListener.dispose();
129-
this.replDataListener = undefined;
144+
if (this.replPromptListener) {
145+
this.replPromptListener.dispose();
146+
this.replPromptListener = undefined;
147+
}
148+
if (this.replShellTypeListener) {
149+
this.replShellTypeListener.dispose();
150+
this.replShellTypeListener = undefined;
130151
}
131152
}
132153

src/client/common/vscodeApis/windowApis.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
NotebookDocument,
2626
NotebookEditor,
2727
NotebookDocumentShowOptions,
28+
Terminal,
2829
} from 'vscode';
2930
import { createDeferred, Deferred } from '../utils/async';
3031
import { Resource } from '../types';
@@ -124,6 +125,10 @@ export function onDidStartTerminalShellExecution(handler: (e: TerminalShellExecu
124125
return window.onDidStartTerminalShellExecution(handler);
125126
}
126127

128+
export function onDidChangeTerminalState(handler: (e: Terminal) => void): Disposable {
129+
return window.onDidChangeTerminalState(handler);
130+
}
131+
127132
export enum MultiStepAction {
128133
Back = 'Back',
129134
Cancel = 'Cancel',

src/test/common/terminals/service.unit.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ suite('Terminal Service', () => {
5959
let options: TypeMoq.IMock<TerminalCreationOptions>;
6060
let applicationShell: TypeMoq.IMock<IApplicationShell>;
6161
let onDidWriteTerminalDataEmitter: EventEmitter<TerminalDataWriteEvent>;
62+
let onDidChangeTerminalStateEmitter: EventEmitter<VSCodeTerminal>;
6263

6364
setup(() => {
6465
useEnvExtensionStub = sinon.stub(extapi, 'useEnvExtension');
@@ -122,12 +123,16 @@ suite('Terminal Service', () => {
122123
mockServiceContainer.setup((c) => c.get(ITerminalAutoActivation)).returns(() => terminalAutoActivator.object);
123124
mockServiceContainer.setup((c) => c.get(IInterpreterService)).returns(() => interpreterService.object);
124125

125-
// Setup IApplicationShell mock with onDidWriteTerminalData
126126
applicationShell = TypeMoq.Mock.ofType<IApplicationShell>();
127127
onDidWriteTerminalDataEmitter = new EventEmitter<TerminalDataWriteEvent>();
128128
applicationShell.setup((a) => a.onDidWriteTerminalData).returns(() => onDidWriteTerminalDataEmitter.event);
129129
mockServiceContainer.setup((c) => c.get(IApplicationShell)).returns(() => applicationShell.object);
130130

131+
onDidChangeTerminalStateEmitter = new EventEmitter<VSCodeTerminal>();
132+
terminalManager
133+
.setup((t) => t.onDidChangeTerminalState(TypeMoq.It.isAny()))
134+
.returns((handler) => onDidChangeTerminalStateEmitter.event(handler));
135+
131136
getConfigurationStub = sinon.stub(workspaceApis, 'getConfiguration');
132137
isWindowsStub = sinon.stub(platform, 'isWindows');
133138
pythonConfig = TypeMoq.Mock.ofType<WorkspaceConfiguration>();
@@ -352,6 +357,30 @@ suite('Terminal Service', () => {
352357
terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1));
353358
});
354359

360+
test('Ensure REPL ready when onDidChangeTerminalState fires with python shell', async () => {
361+
pythonConfig
362+
.setup((p) => p.get('terminal.shellIntegration.enabled'))
363+
.returns(() => false)
364+
.verifiable(TypeMoq.Times.once());
365+
366+
terminalHelper
367+
.setup((helper) => helper.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
368+
.returns(() => Promise.resolve(undefined));
369+
service = new TerminalService(mockServiceContainer.object);
370+
const textToSend = 'Some Text';
371+
terminalHelper.setup((h) => h.identifyTerminalShell(TypeMoq.It.isAny())).returns(() => TerminalShellType.bash);
372+
373+
terminal.setup((t) => t.state).returns(() => ({ isInteractedWith: true, shell: 'python' }));
374+
terminalManager.setup((t) => t.createTerminal(TypeMoq.It.isAny())).returns(() => terminal.object);
375+
376+
await service.ensureTerminal();
377+
const executePromise = service.executeCommand(textToSend, true);
378+
onDidChangeTerminalStateEmitter.fire(terminal.object);
379+
await executePromise;
380+
381+
terminal.verify((t) => t.sendText(TypeMoq.It.isValue(textToSend)), TypeMoq.Times.exactly(1));
382+
});
383+
355384
test('Ensure terminal is not shown if `hideFromUser` option is set to `true`', async () => {
356385
terminalHelper
357386
.setup((helper) => helper.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny()))

0 commit comments

Comments
 (0)