Skip to content

Commit fbd2cd7

Browse files
authored
Terminal and Execution APIs (#10)
1 parent 816a92a commit fbd2cd7

File tree

14 files changed

+856
-287
lines changed

14 files changed

+856
-287
lines changed

.vscode/settings.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
},
1111
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
1212
"typescript.tsc.autoDetect": "off",
13+
"editor.formatOnSave": true,
1314
"[typescript]": {
14-
"editor.defaultFormatter": "esbenp.prettier-vscode",
15-
"editor.formatOnSave": true
15+
"editor.defaultFormatter": "esbenp.prettier-vscode"
1616
},
1717
"prettier.tabWidth": 4
18-
}
18+
}

package.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,18 @@
179179
"title": "Run as Task",
180180
"category": "Python Envs",
181181
"icon": "$(play)"
182+
},
183+
{
184+
"command": "python-envs.terminal.activate",
185+
"title": "Activate Environment in Current Terminal",
186+
"category": "Python Envs",
187+
"icon": "$(zap)"
188+
},
189+
{
190+
"command": "python-envs.terminal.deactivate",
191+
"title": "Deactivate Environment in Current Terminal",
192+
"category": "Python Envs",
193+
"icon": "$(circle-slash)"
182194
}
183195
],
184196
"menus": {
@@ -234,6 +246,14 @@
234246
{
235247
"command": "python-envs.runAsTask",
236248
"when": "true"
249+
},
250+
{
251+
"command": "python-envs.terminal.activate",
252+
"when": "false"
253+
},
254+
{
255+
"command": "python-envs.terminal.deactivate",
256+
"when": "false"
237257
}
238258
],
239259
"view/item/context": [
@@ -315,6 +335,16 @@
315335
"command": "python-envs.refreshAllManagers",
316336
"group": "navigation",
317337
"when": "view == env-managers"
338+
},
339+
{
340+
"command": "python-envs.terminal.activate",
341+
"group": "navigation",
342+
"when": "view == terminal && pythonTerminalActivation && !pythonTerminalActivated"
343+
},
344+
{
345+
"command": "python-envs.terminal.deactivate",
346+
"group": "navigation",
347+
"when": "view == terminal && pythonTerminalActivation && pythonTerminalActivated"
318348
}
319349
],
320350
"explorer/context": [
@@ -335,6 +365,16 @@
335365
"group": "Python",
336366
"when": "editorLangId == python"
337367
}
368+
],
369+
"terminal/title/context": [
370+
{
371+
"command": "python-envs.terminal.activate",
372+
"when": "pythonTerminalActivation && !pythonTerminalActivated"
373+
},
374+
{
375+
"command": "python-envs.terminal.deactivate",
376+
"when": "pythonTerminalActivation && pythonTerminalActivated"
377+
}
338378
]
339379
},
340380
"viewsContainers": {

src/api.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Uri, Disposable, MarkdownString, Event, LogOutputChannel, ThemeIcon } from 'vscode';
1+
import { Uri, Disposable, MarkdownString, Event, LogOutputChannel, ThemeIcon, Terminal } from 'vscode';
22

33
/**
44
* The path to an icon, or a theme-specific configuration of icons.
@@ -767,6 +767,40 @@ export interface Installable {
767767
readonly uri?: Uri;
768768
}
769769

770+
export interface PythonTaskResult {}
771+
772+
export interface PythonProcess {
773+
/**
774+
* The process ID of the Python process.
775+
*/
776+
readonly pid: number;
777+
778+
/**
779+
* The standard input of the Python process.
780+
*/
781+
readonly stdin: NodeJS.WritableStream;
782+
783+
/**
784+
* The standard output of the Python process.
785+
*/
786+
readonly stdout: NodeJS.ReadableStream;
787+
788+
/**
789+
* The standard error of the Python process.
790+
*/
791+
readonly stderr: NodeJS.ReadableStream;
792+
793+
/**
794+
* Kills the Python process.
795+
*/
796+
kill(): void;
797+
798+
/**
799+
* Event that is fired when the Python process exits.
800+
*/
801+
onExit: Event<number>;
802+
}
803+
770804
export interface PythonEnvironmentManagerApi {
771805
/**
772806
* Register an environment manager implementation.
@@ -963,6 +997,40 @@ export interface PythonProjectApi {
963997
registerPythonProjectCreator(creator: PythonProjectCreator): Disposable;
964998
}
965999

1000+
export interface PythonExecutionApi {
1001+
createTerminal(
1002+
cwd: string | Uri | PythonProject,
1003+
environment?: PythonEnvironment,
1004+
envVars?: { [key: string]: string },
1005+
): Promise<Terminal>;
1006+
runInTerminal(
1007+
environment: PythonEnvironment,
1008+
cwd: string | Uri | PythonProject,
1009+
command: string,
1010+
args?: string[],
1011+
): Promise<Terminal>;
1012+
runInDedicatedTerminal(
1013+
terminalKey: string | Uri,
1014+
environment: PythonEnvironment,
1015+
cwd: string | Uri | PythonProject,
1016+
command: string,
1017+
args?: string[],
1018+
): Promise<Terminal>;
1019+
runAsTask(
1020+
environment: PythonEnvironment,
1021+
cwd: string | Uri | PythonProject,
1022+
command: string,
1023+
args?: string[],
1024+
envVars?: { [key: string]: string },
1025+
): Promise<PythonTaskResult>;
1026+
runInBackground(
1027+
environment: PythonEnvironment,
1028+
cwd: string | Uri | PythonProject,
1029+
command: string,
1030+
args?: string[],
1031+
envVars?: { [key: string]: string },
1032+
): Promise<PythonProcess>;
1033+
}
9661034
/**
9671035
* The API for interacting with Python environments, package managers, and projects.
9681036
*/

src/common/command.api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { commands } from 'vscode';
2+
3+
export function executeCommand<T = unknown>(command: string, ...rest: any[]): Thenable<T> {
4+
return commands.executeCommand(command, ...rest);
5+
}

src/common/window.apis.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
Terminal,
1919
TerminalOptions,
2020
TerminalShellExecutionEndEvent,
21+
TerminalShellExecutionStartEvent,
2122
TerminalShellIntegrationChangeEvent,
2223
TextEditor,
2324
Uri,
@@ -53,6 +54,34 @@ export function activeTerminal(): Terminal | undefined {
5354
return window.activeTerminal;
5455
}
5556

57+
export function activeTextEditor(): TextEditor | undefined {
58+
return window.activeTextEditor;
59+
}
60+
61+
export function onDidChangeActiveTerminal(
62+
listener: (e: Terminal | undefined) => any,
63+
thisArgs?: any,
64+
disposables?: Disposable[],
65+
): Disposable {
66+
return window.onDidChangeActiveTerminal(listener, thisArgs, disposables);
67+
}
68+
69+
export function onDidChangeActiveTextEditor(
70+
listener: (e: TextEditor | undefined) => any,
71+
thisArgs?: any,
72+
disposables?: Disposable[],
73+
): Disposable {
74+
return window.onDidChangeActiveTextEditor(listener, thisArgs, disposables);
75+
}
76+
77+
export function onDidStartTerminalShellExecution(
78+
listener: (e: TerminalShellExecutionStartEvent) => any,
79+
thisArgs?: any,
80+
disposables?: Disposable[],
81+
): Disposable {
82+
return window.onDidStartTerminalShellExecution(listener, thisArgs, disposables);
83+
}
84+
5685
export function onDidEndTerminalShellExecution(
5786
listener: (e: TerminalShellExecutionEndEvent) => any,
5887
thisArgs?: any,

src/extension.ts

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
resetEnvironmentCommand,
2121
refreshPackagesCommand,
2222
createAnyEnvironmentCommand,
23+
runInDedicatedTerminalCommand,
2324
} from './features/envCommands';
2425
import { registerCondaFeatures } from './managers/conda/main';
2526
import { registerSystemPythonFeatures } from './managers/sysPython/main';
@@ -37,6 +38,13 @@ import {
3738
} from './features/projectCreators';
3839
import { WorkspaceView } from './features/views/projectView';
3940
import { registerCompletionProvider } from './features/settings/settingCompletions';
41+
import { TerminalManager, TerminalManagerImpl } from './features/terminal/terminalManager';
42+
import { activeTerminal, onDidChangeActiveTerminal, onDidChangeActiveTextEditor } from './common/window.apis';
43+
import {
44+
getEnvironmentForTerminal,
45+
setActivateMenuButtonContext,
46+
updateActivateMenuButtonContext,
47+
} from './features/terminal/activateMenuButton';
4048

4149
export async function activate(context: ExtensionContext): Promise<PythonEnvironmentApi> {
4250
// Logging should be set up before anything else.
@@ -46,6 +54,9 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
4654
// Setup the persistent state for the extension.
4755
setPersistentState(context);
4856

57+
const terminalManager: TerminalManager = new TerminalManagerImpl();
58+
context.subscriptions.push(terminalManager);
59+
4960
const projectManager: PythonProjectManager = new PythonProjectManagerImpl();
5061
context.subscriptions.push(projectManager);
5162

@@ -104,23 +115,25 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
104115
if (result) {
105116
const projects: PythonProject[] = [];
106117
result.forEach((r) => {
107-
if (r.projects) {
108-
projects.push(r.projects);
118+
if (r.project) {
119+
projects.push(r.project);
109120
}
110121
});
111122
workspaceView.updateProject(projects);
123+
await updateActivateMenuButtonContext(terminalManager, projectManager, envManagers);
112124
}
113125
}),
114126
commands.registerCommand('python-envs.setEnv', async (item) => {
115127
const result = await setEnvironmentCommand(item, envManagers, projectManager);
116128
if (result) {
117129
const projects: PythonProject[] = [];
118130
result.forEach((r) => {
119-
if (r.projects) {
120-
projects.push(r.projects);
131+
if (r.project) {
132+
projects.push(r.project);
121133
}
122134
});
123135
workspaceView.updateProject(projects);
136+
await updateActivateMenuButtonContext(terminalManager, projectManager, envManagers);
124137
}
125138
}),
126139
commands.registerCommand('python-envs.reset', async (item) => {
@@ -143,15 +156,44 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
143156
await envManagers.clearCache(undefined);
144157
}),
145158
commands.registerCommand('python-envs.runInTerminal', (item) => {
146-
return runInTerminalCommand(item, api);
159+
return runInTerminalCommand(item, api, terminalManager);
160+
}),
161+
commands.registerCommand('python-envs.runInDedicatedTerminal', (item) => {
162+
return runInDedicatedTerminalCommand(item, api, terminalManager);
147163
}),
148164
commands.registerCommand('python-envs.runAsTask', (item) => {
149165
return runAsTaskCommand(item, api);
150166
}),
151167
commands.registerCommand('python-envs.createTerminal', (item) => {
152-
return createTerminalCommand(item, api);
168+
return createTerminalCommand(item, api, terminalManager);
169+
}),
170+
commands.registerCommand('python-envs.terminal.activate', async () => {
171+
const terminal = activeTerminal();
172+
if (terminal) {
173+
const env = await getEnvironmentForTerminal(terminalManager, projectManager, envManagers, terminal);
174+
if (env) {
175+
await terminalManager.activate(terminal, env);
176+
await setActivateMenuButtonContext(terminalManager, terminal, env);
177+
}
178+
}
179+
}),
180+
commands.registerCommand('python-envs.terminal.deactivate', async () => {
181+
const terminal = activeTerminal();
182+
if (terminal) {
183+
await terminalManager.deactivate(terminal);
184+
const env = await getEnvironmentForTerminal(terminalManager, projectManager, envManagers, terminal);
185+
if (env) {
186+
await setActivateMenuButtonContext(terminalManager, terminal, env);
187+
}
188+
}
189+
}),
190+
envManagers.onDidChangeEnvironmentManager(async () => {
191+
await updateActivateMenuButtonContext(terminalManager, projectManager, envManagers);
192+
}),
193+
onDidChangeActiveTerminal(async (t) => {
194+
await updateActivateMenuButtonContext(terminalManager, projectManager, envManagers, t);
153195
}),
154-
window.onDidChangeActiveTextEditor(async (e: TextEditor | undefined) => {
196+
onDidChangeActiveTextEditor(async (e: TextEditor | undefined) => {
155197
if (e && !e.document.isUntitled && e.document.uri.scheme === 'file') {
156198
if (
157199
e.document.languageId === 'python' ||

src/features/execution/activation.ts renamed to src/features/common/activation.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,24 @@ export function getActivationCommand(
3030

3131
return activation;
3232
}
33+
34+
export function getDeactivationCommand(
35+
terminal: Terminal,
36+
environment: PythonEnvironment,
37+
): PythonCommandRunConfiguration[] | undefined {
38+
const shell = identifyTerminalShell(terminal);
39+
40+
let deactivation: PythonCommandRunConfiguration[] | undefined;
41+
if (environment.execInfo?.shellDeactivation) {
42+
deactivation = environment.execInfo.shellDeactivation.get(shell);
43+
if (!deactivation) {
44+
deactivation = environment.execInfo.shellDeactivation.get(TerminalShellType.unknown);
45+
}
46+
}
47+
48+
if (!deactivation) {
49+
deactivation = environment.execInfo?.deactivation;
50+
}
51+
52+
return deactivation;
53+
}
File renamed without changes.

0 commit comments

Comments
 (0)