Theia workspace extension providing workspace functionality and services for Eclipse Theia IDE framework
—
The workspace server provides backend workspace management functionality including workspace persistence, recent workspace tracking, and server-side workspace operations. It operates over JSON-RPC and handles workspace state across application sessions.
The main server interface for workspace operations exposed over JSON-RPC.
/**
* JSON-RPC endpoint path for workspace service
*/
const workspacePath = '/services/workspace';
/**
* Workspace server interface for backend workspace operations
*/
interface WorkspaceServer {
/**
* Returns the most recently used workspace folder URI as a string
*/
getMostRecentlyUsedWorkspace(): Promise<string | undefined>;
/**
* Sets the desired URI as the most recently used workspace folder
* @param uri - String representation of the workspace URI
*/
setMostRecentlyUsedWorkspace(uri: string): Promise<void>;
/**
* Removes a workspace from the list of recently opened workspaces
* @param uri - The workspace URI to remove
*/
removeRecentWorkspace(uri: string): Promise<void>;
/**
* Returns list of recently opened workspaces as an array
*/
getRecentWorkspaces(): Promise<string[]>;
}Usage Example:
import { injectable, inject } from "@theia/core/shared/inversify";
import { WorkspaceServer } from "@theia/workspace/lib/common";
@injectable()
export class MyWorkspaceClient {
@inject(WorkspaceServer)
protected readonly workspaceServer: WorkspaceServer;
async displayRecentWorkspaces(): Promise<void> {
// Get recent workspaces
const recentWorkspaces = await this.workspaceServer.getRecentWorkspaces();
console.log("Recent workspaces:", recentWorkspaces);
// Get most recently used
const mostRecent = await this.workspaceServer.getMostRecentlyUsedWorkspace();
if (mostRecent) {
console.log("Most recent workspace:", mostRecent);
}
}
async setCurrentWorkspace(workspaceUri: string): Promise<void> {
// Set as most recently used
await this.workspaceServer.setMostRecentlyUsedWorkspace(workspaceUri);
console.log(`Set ${workspaceUri} as most recent workspace`);
}
async cleanupOldWorkspace(workspaceUri: string): Promise<void> {
// Remove from recent list
await this.workspaceServer.removeRecentWorkspace(workspaceUri);
console.log(`Removed ${workspaceUri} from recent workspaces`);
}
}Backend implementation that handles persistence and workspace lifecycle management.
class DefaultWorkspaceServer implements WorkspaceServer, BackendApplicationContribution {
/**
* Threshold for untitled workspace cleanup (days)
*/
protected readonly untitledWorkspaceStaleThreshold: number;
/**
* Backend application lifecycle - called on server start
*/
onStart(): void;
/**
* Get most recently used workspace (implements WorkspaceServer)
*/
getMostRecentlyUsedWorkspace(): Promise<string | undefined>;
/**
* Set most recently used workspace and persist to storage
*/
setMostRecentlyUsedWorkspace(rawUri: string): Promise<void>;
/**
* Remove workspace from recent list and update storage
*/
removeRecentWorkspace(rawUri: string): Promise<void>;
/**
* Get filtered list of recent workspaces that still exist
*/
getRecentWorkspaces(): Promise<string[]>;
}Usage Example:
import { injectable, inject } from "@theia/core/shared/inversify";
import { DefaultWorkspaceServer } from "@theia/workspace/lib/node";
@injectable()
export class MyWorkspaceManager {
@inject(DefaultWorkspaceServer)
protected readonly workspaceServer: DefaultWorkspaceServer;
async initializeWorkspace(): Promise<void> {
// The server automatically handles:
// - Loading recent workspaces from ~/.config/theia/recentworkspace.json
// - Validating that workspaces still exist
// - Cleaning up old untitled workspaces (older than 10 days by default)
// - Processing CLI workspace arguments
const mostRecent = await this.workspaceServer.getMostRecentlyUsedWorkspace();
if (mostRecent) {
console.log(`Restoring workspace: ${mostRecent}`);
}
}
}Service for handling workspace arguments from the command line.
class WorkspaceCliContribution implements CliContribution {
/**
* Deferred workspace root from CLI arguments
*/
readonly workspaceRoot: Deferred<string | undefined>;
/**
* Configure CLI argument parsing for workspace options
*/
configure(conf: yargs.Argv): void;
/**
* Process parsed CLI arguments and set workspace
*/
setArguments(args: yargs.Arguments): Promise<void>;
/**
* Normalize workspace argument to proper format
*/
protected normalizeWorkspaceArg(raw: string): string;
/**
* Build untitled workspace for multiple directory arguments
*/
protected buildWorkspaceForMultipleArguments(workspaceArguments: string[]): Promise<string | undefined>;
}Usage Example:
import { injectable, inject } from "@theia/core/shared/inversify";
import { WorkspaceCliContribution } from "@theia/workspace/lib/node";
@injectable()
export class MyApplicationContribution {
@inject(WorkspaceCliContribution)
protected readonly cliContribution: WorkspaceCliContribution;
async handleStartup(): Promise<void> {
// Wait for CLI processing to complete
const workspaceFromCli = await this.cliContribution.workspaceRoot.promise;
if (workspaceFromCli) {
console.log(`Opening workspace from CLI: ${workspaceFromCli}`);
// The CLI contribution handles:
// - Single directory arguments
// - Multiple directory arguments (creates untitled workspace)
// - Workspace file arguments
// - Legacy --root-dir flag
}
}
}
// CLI usage examples:
// theia /path/to/workspace
// theia /path/to/project1 /path/to/project2 # Creates untitled workspace
// theia workspace.theia-workspace
// theia --root-dir=/legacy/path # Deprecated but supportedInterfaces for extending workspace server functionality with custom handlers.
/**
* Extension point for custom workspace handlers
*/
interface WorkspaceHandlerContribution {
/**
* Check if this handler can handle the given URI scheme/format
*/
canHandle(uri: URI): boolean;
/**
* Check if the workspace still exists and is accessible
*/
workspaceStillExists(uri: URI): Promise<boolean>;
}
/**
* Default file system workspace handler
*/
class FileWorkspaceHandlerContribution implements WorkspaceHandlerContribution {
/**
* Handles file:// scheme URIs
*/
canHandle(uri: URI): boolean;
/**
* Check if file/directory exists on disk
*/
workspaceStillExists(uri: URI): Promise<boolean>;
}Usage Example:
import { injectable } from "@theia/core/shared/inversify";
import { WorkspaceHandlerContribution } from "@theia/workspace/lib/node";
import URI from "@theia/core/lib/common/uri";
@injectable()
export class RemoteWorkspaceHandler implements WorkspaceHandlerContribution {
canHandle(uri: URI): boolean {
// Handle custom remote workspace schemes
return uri.scheme === 'ssh' || uri.scheme === 'ftp';
}
async workspaceStillExists(uri: URI): Promise<boolean> {
// Custom logic to check remote workspace availability
if (uri.scheme === 'ssh') {
return this.checkSshWorkspace(uri);
} else if (uri.scheme === 'ftp') {
return this.checkFtpWorkspace(uri);
}
return false;
}
private async checkSshWorkspace(uri: URI): Promise<boolean> {
// Implementation for SSH workspace validation
try {
// SSH connection logic here
return true;
} catch {
return false;
}
}
private async checkFtpWorkspace(uri: URI): Promise<boolean> {
// Implementation for FTP workspace validation
try {
// FTP connection logic here
return true;
} catch {
return false;
}
}
}
// Register the custom handler in your module:
// bind(WorkspaceHandlerContribution).to(RemoteWorkspaceHandler).inSingletonScope();Data structures and utilities for managing workspace persistence.
interface RecentWorkspacePathsData {
/** Array of recent workspace URI strings */
recentRoots: string[];
}
namespace RecentWorkspacePathsData {
/**
* Create RecentWorkspacePathsData from unknown data with validation
*/
function create(data: unknown): RecentWorkspacePathsData | undefined;
}Usage Example:
import { RecentWorkspacePathsData } from "@theia/workspace/lib/node";
import * as fs from "fs-extra";
export class WorkspaceStorageManager {
private readonly recentWorkspacesPath = "~/.config/theia/recentworkspace.json";
async loadRecentWorkspaces(): Promise<string[]> {
try {
const data = await fs.readJson(this.recentWorkspacesPath);
const workspaceData = RecentWorkspacePathsData.create(data);
return workspaceData?.recentRoots || [];
} catch {
return [];
}
}
async saveRecentWorkspaces(workspaces: string[]): Promise<void> {
const data: RecentWorkspacePathsData = {
recentRoots: workspaces
};
await fs.ensureDir(path.dirname(this.recentWorkspacesPath));
await fs.writeJson(this.recentWorkspacesPath, data, { spaces: 2 });
}
async addRecentWorkspace(workspace: string): Promise<void> {
const recent = await this.loadRecentWorkspaces();
// Remove if already exists (to move to front)
const filtered = recent.filter(w => w !== workspace);
// Add to front and limit to reasonable number
const updated = [workspace, ...filtered].slice(0, 10);
await this.saveRecentWorkspaces(updated);
}
}The workspace server integrates with Theia's backend application lifecycle and provides automatic cleanup and maintenance.
Key Features:
interface RecentWorkspacePathsData {
recentRoots: string[];
}
interface WorkspaceHandlerContribution {
canHandle(uri: URI): boolean;
workspaceStillExists(uri: URI): Promise<boolean>;
}
interface CliContribution {
configure(conf: yargs.Argv): void;
setArguments(args: yargs.Arguments): Promise<void>;
}
interface BackendApplicationContribution {
initialize?(): void;
configure?(app: Application): void;
onStart?(server?: http.Server): void;
onStop?(app?: Application): void;
}
const workspacePath: string; // '/services/workspace'
const WorkspaceServer: symbol;
const WorkspaceHandlerContribution: symbol;
type Deferred<T> = {
readonly promise: Promise<T>;
resolve(value: T): void;
reject(reason?: any): void;
};Install with Tessl CLI
npx tessl i tessl/npm-theia--workspace