deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
10
packages/remote-wsl/.eslintrc.js
Normal file
10
packages/remote-wsl/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
extends: [
|
||||
'../../configs/build.eslintrc.json'
|
||||
],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: 'tsconfig.json'
|
||||
}
|
||||
};
|
||||
31
packages/remote-wsl/README.md
Normal file
31
packages/remote-wsl/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
<div align='center'>
|
||||
|
||||
<br />
|
||||
|
||||
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
|
||||
|
||||
<h2>ECLIPSE THEIA - REMOTE WSL</h2>
|
||||
|
||||
<hr />
|
||||
|
||||
</div>
|
||||
|
||||
## Description
|
||||
|
||||
This package extends the @theia/remote feature with functionality to connect to Windows Subsystem for Linux.
|
||||
|
||||
## Additional Information
|
||||
|
||||
- [API documentation for `@theia/remote-wsl`](https://eclipse-theia.github.io/theia/docs/next/modules/_theia_remote-wsl.html)
|
||||
- [Theia - GitHub](https://github.com/eclipse-theia/theia)
|
||||
- [Theia - Website](https://theia-ide.org/)
|
||||
|
||||
## License
|
||||
|
||||
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
|
||||
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
|
||||
|
||||
## Trademark
|
||||
|
||||
"Theia" is a trademark of the Eclipse Foundation
|
||||
<https://www.eclipse.org/theia>
|
||||
48
packages/remote-wsl/package.json
Normal file
48
packages/remote-wsl/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@theia/remote-wsl",
|
||||
"version": "1.68.0",
|
||||
"description": "Theia - Remote WSL",
|
||||
"dependencies": {
|
||||
"@theia/core": "1.68.0",
|
||||
"@theia/remote": "1.68.0",
|
||||
"@theia/workspace": "1.68.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"theiaExtensions": [
|
||||
{
|
||||
"frontendElectron": "lib/electron-browser/remote-wsl-frontend-module",
|
||||
"backendElectron": "lib/electron-node/remote-wsl-backend-module"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"theia-extension"
|
||||
],
|
||||
"license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/eclipse-theia/theia.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/theia-ide/theia/issues"
|
||||
},
|
||||
"homepage": "https://github.com/theia-ide/theia",
|
||||
"files": [
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "theiaext build",
|
||||
"clean": "theiaext clean",
|
||||
"compile": "theiaext compile",
|
||||
"lint": "theiaext lint",
|
||||
"test": "theiaext test",
|
||||
"watch": "theiaext watch"
|
||||
},
|
||||
"nyc": {
|
||||
"extends": "../../configs/nyc.json"
|
||||
},
|
||||
"gitHead": "21358137e41342742707f660b8e222f940a27652"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { RemoteRegistryContribution } from '@theia/remote/lib/electron-browser/remote-registry-contribution';
|
||||
import { RemoteWslConnectionProvider, RemoteWslConnectionProviderPath } from '../electron-common/remote-wsl-connection-provider';
|
||||
import { WslConnectionContribution } from './wsl-connection-contribution';
|
||||
import { ServiceConnectionProvider } from '@theia/core/lib/browser/messaging/service-connection-provider';
|
||||
import { WorkspaceOpenHandlerContribution } from '@theia/workspace/lib/browser/workspace-service';
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
bind(WslConnectionContribution).toSelf().inSingletonScope();
|
||||
bind(RemoteRegistryContribution).toService(WslConnectionContribution);
|
||||
bind(WorkspaceOpenHandlerContribution).toService(WslConnectionContribution);
|
||||
|
||||
bind(RemoteWslConnectionProvider).toDynamicValue(ctx =>
|
||||
ServiceConnectionProvider.createLocalProxy<RemoteWslConnectionProvider>(ctx.container, RemoteWslConnectionProviderPath)
|
||||
).inSingletonScope();
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { AbstractRemoteRegistryContribution, RemoteRegistry } from '@theia/remote/lib/electron-browser/remote-registry-contribution';
|
||||
import { WorkspaceStorageService } from '@theia/workspace/lib/browser/workspace-storage-service';
|
||||
import { Command, MessageService, QuickInputService, URI, isWindows, nls } from '@theia/core';
|
||||
import { WorkspaceInput, WorkspaceOpenHandlerContribution, WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { WorkspaceServer } from '@theia/workspace/lib/common';
|
||||
import { RemoteWslConnectionProvider, WslDistribution } from '../electron-common/remote-wsl-connection-provider';
|
||||
import { WSL_WORKSPACE_SCHEME } from '../electron-common/wsl-workspaces';
|
||||
import { RemotePreferences } from '@theia/remote/lib/electron-common/remote-preferences';
|
||||
|
||||
export namespace RemoteWslCommands {
|
||||
export const CONNECT_TO_WSL = Command.toLocalizedCommand({
|
||||
id: 'remote-wsl.connect-to-wsl',
|
||||
label: 'Connect to WSL',
|
||||
category: 'WSL'
|
||||
}, 'theia/remote/wsl/connectToWsl');
|
||||
|
||||
export const CONNECT_TO_WSL_WITH_DISTRO = Command.toLocalizedCommand({
|
||||
id: 'remote-wsl.connect-to-wsl-with-distro',
|
||||
label: 'Connect to WSL using Distro...',
|
||||
category: 'WSL'
|
||||
}, 'theia/remote/wsl/connectToWslUsingDistro');
|
||||
|
||||
export const OPEN_CURRENT_FOLDER_IN_WSL = Command.toLocalizedCommand({
|
||||
id: 'remote-wsl.open-current-folder-in-wsl',
|
||||
label: 'Reopen Folder in WSL',
|
||||
category: 'WSL'
|
||||
}, 'theia/remote/wsl/reopenInWsl');
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class WslConnectionContribution extends AbstractRemoteRegistryContribution implements
|
||||
WorkspaceOpenHandlerContribution, WorkspaceOpenHandlerContribution {
|
||||
|
||||
@inject(RemoteWslConnectionProvider)
|
||||
protected readonly connectionProvider: RemoteWslConnectionProvider;
|
||||
|
||||
@inject(RemotePreferences)
|
||||
protected readonly remotePreferences: RemotePreferences;
|
||||
|
||||
@inject(WorkspaceStorageService)
|
||||
protected readonly workspaceStorageService: WorkspaceStorageService;
|
||||
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
|
||||
@inject(WorkspaceServer)
|
||||
protected readonly workspaceServer: WorkspaceServer;
|
||||
|
||||
@inject(QuickInputService)
|
||||
protected readonly quickInputService: QuickInputService;
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
registerRemoteCommands(registry: RemoteRegistry): void {
|
||||
if (!isWindows) {
|
||||
// ignore this feature on non-Windows platforms
|
||||
return;
|
||||
}
|
||||
registry.registerCommand(RemoteWslCommands.CONNECT_TO_WSL, {
|
||||
execute: () => {
|
||||
this.connectionProvider.getWslDistributions().then(distributions => {
|
||||
const defaultDistro = distributions.find(dist => dist.default);
|
||||
if (defaultDistro) {
|
||||
this.connectToWSL(defaultDistro);
|
||||
} else {
|
||||
this.getOrSelectWslDistribution().then(distribution => {
|
||||
if (distribution) {
|
||||
this.connectToWSL(distribution);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
registry.registerCommand(RemoteWslCommands.CONNECT_TO_WSL_WITH_DISTRO, {
|
||||
execute: () => this.getOrSelectWslDistribution().then(distribution => {
|
||||
if (distribution) {
|
||||
this.connectToWSL(distribution);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
registry.registerCommand(RemoteWslCommands.OPEN_CURRENT_FOLDER_IN_WSL, {
|
||||
execute: () => this.getOrSelectWslDistribution().then(distribution => {
|
||||
if (distribution) {
|
||||
const workspacePath = this.workspaceService.workspace?.resource.path.fsPath();
|
||||
if (workspacePath) {
|
||||
this.connectToWSL(distribution, this.toWSLMountPath(workspacePath));
|
||||
}
|
||||
}
|
||||
}),
|
||||
isVisible: () => !!this.workspaceService.workspace
|
||||
});
|
||||
}
|
||||
|
||||
async connectToWSL(distribution: WslDistribution, workspace?: string, preserveWindow = true): Promise<void> {
|
||||
const connectionResult = await this.connectionProvider.connectToWsl({
|
||||
nodeDownloadTemplate: this.remotePreferences['remote.nodeDownloadTemplate'],
|
||||
distribution: distribution.name,
|
||||
workspacePath: this.workspaceService.workspace?.resource.path?.fsPath()
|
||||
});
|
||||
|
||||
if (workspace) {
|
||||
this.workspaceServer.setMostRecentlyUsedWorkspace(
|
||||
`${WSL_WORKSPACE_SCHEME}:${workspace}?distro=${distribution.name}`
|
||||
);
|
||||
}
|
||||
|
||||
this.openRemote(connectionResult.port.toString(), !preserveWindow, workspace);
|
||||
}
|
||||
|
||||
async getOrSelectWslDistribution(): Promise<WslDistribution | undefined> {
|
||||
const distributions = await this.connectionProvider.getWslDistributions();
|
||||
|
||||
if (distributions.length === 0) {
|
||||
this.messageService.error(nls.localize('theia/remote/wsl/noWslDistroFound', 'No WSL distributions found. Please install a WSL distribution first.'));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (distributions.length === 1) {
|
||||
return distributions[0];
|
||||
}
|
||||
|
||||
return (await this.quickInputService.pick(distributions.map(dist => ({
|
||||
type: 'item',
|
||||
label: dist.name,
|
||||
description: dist.default ? nls.localizeByDefault('Default') : dist.version,
|
||||
distribution: dist,
|
||||
})), {
|
||||
title: nls.localize('theia/remote/wsl/selectWSLDistro', 'Select a WSL distribution')
|
||||
}))?.distribution;
|
||||
}
|
||||
|
||||
canHandle(uri: URI): boolean {
|
||||
return uri.scheme === WSL_WORKSPACE_SCHEME; // WSL doesn't use a special URI scheme
|
||||
}
|
||||
|
||||
async openWorkspace(uri: URI, options?: WorkspaceInput | undefined): Promise<void> {
|
||||
const workspacePath = uri.path.toString();
|
||||
const distroName = new URLSearchParams(uri.query).get('distro');
|
||||
if (distroName) {
|
||||
const distros = await this.connectionProvider.getWslDistributions();
|
||||
const distro = distros.find(d => d.name === distroName);
|
||||
if (!distro) {
|
||||
throw new Error(`Invalid WSL workspace URI. Distribution ${distroName} not found.`);
|
||||
}
|
||||
this.connectToWSL(distro, workspacePath, options?.preserveWindow);
|
||||
}
|
||||
throw new Error('Invalid WSL workspace URI. No distrubution specified.');
|
||||
}
|
||||
|
||||
async getWorkspaceLabel(uri: URI): Promise<string | undefined> {
|
||||
return `[WSL] ${uri.path.base}`;
|
||||
}
|
||||
|
||||
protected toWSLMountPath(path: string): string {
|
||||
const driveLetter = path.charAt(0).toLowerCase();
|
||||
const wslPath = path.replace(/^[a-zA-Z]:\\/, `/mnt/${driveLetter}/`);
|
||||
return wslPath.replace(/\\/g, '/');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
export interface WslDistribution {
|
||||
name: string;
|
||||
default: boolean;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface WslConnectionOptions {
|
||||
nodeDownloadTemplate: string;
|
||||
distribution: string;
|
||||
workspacePath?: string;
|
||||
}
|
||||
|
||||
export interface WslConnectionResult {
|
||||
port: number;
|
||||
}
|
||||
|
||||
export const RemoteWslConnectionProviderPath = '/remote/wsl';
|
||||
|
||||
export const RemoteWslConnectionProvider = Symbol('RemoteWslConnectionProvider');
|
||||
export interface RemoteWslConnectionProvider {
|
||||
getWslDistributions(): Promise<WslDistribution[]>;
|
||||
connectToWsl(options: WslConnectionOptions): Promise<WslConnectionResult>;
|
||||
}
|
||||
17
packages/remote-wsl/src/electron-common/wsl-workspaces.ts
Normal file
17
packages/remote-wsl/src/electron-common/wsl-workspaces.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
export const WSL_WORKSPACE_SCHEME = 'wsl';
|
||||
@@ -0,0 +1,41 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { RemoteWslConnectionProviderImpl } from './remote-wsl-connection-provider';
|
||||
import { RemoteWslConnectionProvider, RemoteWslConnectionProviderPath } from '../electron-common/remote-wsl-connection-provider';
|
||||
import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connection-container-module';
|
||||
import { ConnectionHandler, RpcConnectionHandler } from '@theia/core';
|
||||
import { WslWorkspaceHandler } from './wsl-workspace-handler';
|
||||
import { WorkspaceHandlerContribution } from '@theia/workspace/lib/node/default-workspace-server';
|
||||
|
||||
export const wslRemoteConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
|
||||
bind(RemoteWslConnectionProviderImpl).toSelf().inSingletonScope();
|
||||
bind(RemoteWslConnectionProvider).toService(RemoteWslConnectionProviderImpl);
|
||||
bind(ConnectionHandler).toDynamicValue(ctx =>
|
||||
new RpcConnectionHandler<RemoteWslConnectionProvider>(RemoteWslConnectionProviderPath, client => {
|
||||
const server = ctx.container.get<RemoteWslConnectionProvider>(RemoteWslConnectionProvider);
|
||||
return server;
|
||||
}));
|
||||
});
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
bind(ConnectionContainerModule).toConstantValue(wslRemoteConnectionModule);
|
||||
|
||||
bind(WslWorkspaceHandler).toSelf().inSingletonScope();
|
||||
bind(WorkspaceHandlerContribution).toService(WslWorkspaceHandler);
|
||||
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { RemoteWslConnectionProvider, WslDistribution, WslConnectionOptions, WslConnectionResult } from '../electron-common/remote-wsl-connection-provider';
|
||||
import { RemoteConnectionService } from '@theia/remote/lib/electron-node/remote-connection-service';
|
||||
import { RemoteSetupService } from '@theia/remote/lib/electron-node/setup/remote-setup-service';
|
||||
import { exec } from 'child_process';
|
||||
import { MessageService, generateUuid } from '@theia/core';
|
||||
import { RemoteWslConnection } from './remote-wsl-connection';
|
||||
|
||||
@injectable()
|
||||
export class RemoteWslConnectionProviderImpl implements RemoteWslConnectionProvider {
|
||||
|
||||
@inject(RemoteConnectionService)
|
||||
protected readonly remoteConnectionService: RemoteConnectionService;
|
||||
|
||||
@inject(RemoteSetupService)
|
||||
protected readonly remoteSetup: RemoteSetupService;
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
dispose(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* executes `wsl.exe --list` to get the list of WSL distributions.
|
||||
* The Output format look like this:
|
||||
* ```
|
||||
* NAME STATE VERSION
|
||||
* * Ubuntu Stopped 2
|
||||
* Other Distro Stopped 2
|
||||
* ```
|
||||
* so we split the output by lines and then by whitespace. The * indicates the default distribution so this has to be handled slightly different.
|
||||
*
|
||||
* @returns a list of WslDistribution objects, each containing the name, default status, and version.
|
||||
*/
|
||||
async getWslDistributions(): Promise<WslDistribution[]> {
|
||||
return new Promise<WslDistribution[]>((resolve, reject) => {
|
||||
exec('wsl.exe --list --verbose --all', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
const errorMessage = `Error executing wsl.exe: ${error} \n ${stderr}`;
|
||||
console.error(errorMessage);
|
||||
reject(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = stdout
|
||||
.replace(/\0/g, '')
|
||||
.split('\n')
|
||||
.map(line => line.replace('\r', '').trim())
|
||||
.filter(line => line.length > 0)
|
||||
.slice(1); // Skip header line
|
||||
|
||||
resolve(lines.map(line => {
|
||||
const parts = line.split(/\s+/);
|
||||
const isDefault = parts[0] === '*';
|
||||
const name = isDefault ? parts[1] : parts[0];
|
||||
const version = isDefault ? parts[3] : parts[2];
|
||||
return {
|
||||
name,
|
||||
default: isDefault,
|
||||
version
|
||||
};
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async connectToWsl(options: WslConnectionOptions): Promise<WslConnectionResult> {
|
||||
const progress = await this.messageService.showProgress({
|
||||
text: 'Connecting to WSL'
|
||||
});
|
||||
|
||||
try {
|
||||
const connection = new RemoteWslConnection({
|
||||
id: generateUuid(),
|
||||
name: options.distribution,
|
||||
type: 'WSL',
|
||||
distribution: options.distribution,
|
||||
});
|
||||
|
||||
const report: (message: string) => void = message => progress.report({ message });
|
||||
report('Setting up remote environment...');
|
||||
|
||||
await this.remoteSetup.setup({
|
||||
connection,
|
||||
report,
|
||||
nodeDownloadTemplate: options.nodeDownloadTemplate
|
||||
});
|
||||
|
||||
const registration = this.remoteConnectionService.register(connection);
|
||||
|
||||
connection.onDidDisconnect(() => {
|
||||
registration.dispose();
|
||||
});
|
||||
|
||||
return {
|
||||
port: connection.remotePort,
|
||||
};
|
||||
} catch (e) {
|
||||
this.messageService.error(`Failed to connect to WSL: ${e.message}`);
|
||||
throw e;
|
||||
} finally {
|
||||
progress.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
161
packages/remote-wsl/src/electron-node/remote-wsl-connection.ts
Normal file
161
packages/remote-wsl/src/electron-node/remote-wsl-connection.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { Emitter, Event } from '@theia/core';
|
||||
import { RemoteConnection, RemoteExecOptions, RemoteExecResult, RemoteExecTester } from '@theia/remote/lib/electron-node/remote-types';
|
||||
import { Socket } from 'net';
|
||||
import { exec, spawn } from 'child_process';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export interface RemoteWslConnectionOptions {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
distribution: string;
|
||||
}
|
||||
|
||||
export class RemoteWslConnection implements RemoteConnection {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
remotePort: number;
|
||||
distribution: string;
|
||||
|
||||
get localPort(): number {
|
||||
return this.remotePort;
|
||||
}
|
||||
|
||||
protected readonly onDidDisconnectEmitter = new Emitter<void>();
|
||||
onDidDisconnect: Event<void> = this.onDidDisconnectEmitter.event;
|
||||
|
||||
constructor(options: RemoteWslConnectionOptions) {
|
||||
this.id = options.id;
|
||||
this.name = options.name;
|
||||
this.type = options.type;
|
||||
this.distribution = options.distribution;
|
||||
}
|
||||
|
||||
async forwardOut(socket: Socket, port: number): Promise<void> {
|
||||
new Socket().connect(port, 'localhost', () => {
|
||||
socket.pipe(new Socket().connect(port, 'localhost'));
|
||||
});
|
||||
}
|
||||
|
||||
async exec(cmd: string, args?: string[], options?: RemoteExecOptions): Promise<RemoteExecResult> {
|
||||
const deferred = new Deferred<RemoteExecResult>();
|
||||
const fullCommand = `wsl -d ${this.distribution} ${cmd} ${args?.join(' ') ?? ''}`;
|
||||
|
||||
try {
|
||||
exec(fullCommand, { env: options?.env }, (error, stdout, stderr) => {
|
||||
deferred.resolve({ stdout, stderr });
|
||||
});
|
||||
} catch (e) {
|
||||
deferred.reject(e);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
async execPartial(cmd: string, tester: RemoteExecTester, args?: string[], options?: RemoteExecOptions): Promise<RemoteExecResult> {
|
||||
const deferred = new Deferred<RemoteExecResult>();
|
||||
|
||||
try {
|
||||
let cdPath = undefined;
|
||||
if (cmd.startsWith('cd') && cmd.includes(';')) {
|
||||
const parts = cmd.split(';');
|
||||
cdPath = parts[0].replace('cd', '').trim();
|
||||
cmd = parts[1];
|
||||
}
|
||||
const fullCommand = `wsl -d ${this.distribution} ${cdPath ? `--cd ${cdPath} ${cmd}` : cmd} ${args?.join(' ') ?? ''}`;
|
||||
|
||||
const process = spawn(fullCommand, { env: options?.env, shell: 'powershell' });
|
||||
|
||||
let stdoutBuffer = '';
|
||||
let stderrBuffer = '';
|
||||
|
||||
process.stdout.on('data', (data: Buffer) => {
|
||||
if (deferred.state === 'unresolved') {
|
||||
stdoutBuffer += data.toString();
|
||||
if (tester(stdoutBuffer, stderrBuffer)) {
|
||||
deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
process.stderr.on('data', (data: Buffer) => {
|
||||
if (deferred.state === 'unresolved') {
|
||||
stderrBuffer += data.toString();
|
||||
if (tester(stdoutBuffer, stderrBuffer)) {
|
||||
deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
process.on('close', () => {
|
||||
if (deferred.state === 'unresolved') {
|
||||
deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer });
|
||||
}
|
||||
});
|
||||
|
||||
process.on('error', error => {
|
||||
deferred.reject(error);
|
||||
});
|
||||
} catch (e) {
|
||||
deferred.reject(e);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
async copy(localPath: string | Buffer | NodeJS.ReadableStream, remotePath: string): Promise<void> {
|
||||
const deferred = new Deferred<void>();
|
||||
const wslPath = `\\\\wsl$\\${this.distribution}\\${remotePath}`;
|
||||
|
||||
if (typeof localPath === 'string') {
|
||||
exec(`copy "${localPath}" "${wslPath}"`, error => {
|
||||
if (error) {
|
||||
deferred.reject(error);
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
} else if (Buffer.isBuffer(localPath)) {
|
||||
fs.writeFile(wslPath, localPath, (error: Error) => {
|
||||
if (error) {
|
||||
deferred.reject(error);
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const writeStream = fs.createWriteStream(wslPath);
|
||||
localPath.pipe(writeStream);
|
||||
writeStream.on('finish', () => deferred.resolve());
|
||||
writeStream.on('error', (error: Error) => deferred.reject(error));
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.onDidDisconnectEmitter.dispose();
|
||||
}
|
||||
|
||||
disposeSync(): void {
|
||||
// No special cleanup needed for WSL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2025 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// ****************************************************************************
|
||||
import { URI } from '@theia/core';
|
||||
import * as fs from '@theia/core/shared/fs-extra';
|
||||
import { WorkspaceHandlerContribution } from '@theia/workspace/lib/node/default-workspace-server';
|
||||
|
||||
export class WslWorkspaceHandler implements WorkspaceHandlerContribution {
|
||||
canHandle(uri: URI): boolean {
|
||||
return uri.scheme === 'wsl';
|
||||
}
|
||||
workspaceStillExists(uri: URI): Promise<boolean> {
|
||||
return fs.pathExists(this.toWindowsPath(uri.path.toString()));
|
||||
}
|
||||
|
||||
private toWindowsPath(path: string): string {
|
||||
const match = path.match(/^\/mnt\/([a-z])\/(.*)/i);
|
||||
if (match) {
|
||||
const driveLetter = match[1].toUpperCase();
|
||||
const windowsPath = match[2].replace(/\//g, '\\');
|
||||
return `${driveLetter}:\\${windowsPath}`;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
||||
29
packages/remote-wsl/src/package.spec.ts
Normal file
29
packages/remote-wsl/src/package.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2023 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* note: this bogus test file is required so that
|
||||
we are able to run mocha unit tests on this
|
||||
package, without having any actual unit tests in it.
|
||||
This way a coverage report will be generated,
|
||||
showing 0% coverage, instead of no report.
|
||||
This file can be removed once we have real unit
|
||||
tests in place. */
|
||||
|
||||
describe('remote-wsl package', () => {
|
||||
|
||||
it('support code coverage statistics', () => true);
|
||||
|
||||
});
|
||||
22
packages/remote-wsl/tsconfig.json
Normal file
22
packages/remote-wsl/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "../../configs/base.tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../core"
|
||||
},
|
||||
{
|
||||
"path": "../remote"
|
||||
},
|
||||
{
|
||||
"path": "../workspace"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user