Skip to main content

Overview

The Workbench Layer (src/vs/workbench/) implements the complete VS Code application UI. It orchestrates the editor, panels, sidebars, and all user-facing features into a cohesive IDE experience.

Directory Structure

src/vs/workbench/
├── browser/              # Core workbench UI
│   ├── parts/            # UI parts (editor, sidebar, panel, etc.)
│   │   ├── editor/        # Editor area with tab management
│   │   ├── sidebar/       # Primary sidebar
│   │   ├── panel/         # Bottom panel
│   │   ├── auxiliarybar/  # Secondary sidebar
│   │   ├── statusbar/     # Status bar
│   │   ├── titlebar/      # Title bar
│   │   └── activitybar/   # Activity bar
│   ├── layout.ts         # Layout management
│   └── workbench.ts      # Main workbench class
├── services/            # Workbench services (94+ services)
│   ├── editor/           # Editor management
│   ├── viewlet/          # Viewlet service
│   ├── panel/            # Panel service
│   ├── search/           # Search service
│   └── ...
├── contrib/             # Feature contributions (94+ features)
│   ├── debug/            # Debug functionality
│   ├── files/            # File explorer
│   ├── search/           # Search functionality
│   ├── scm/              # Source control
│   ├── terminal/         # Integrated terminal
│   ├── extensions/       # Extension management
│   └── ...
├── api/                 # Extension host and VS Code API
│   ├── browser/          # Main thread API implementation
│   └── common/           # Extension host
├── common/              # Workbench common code
└── electron-browser/    # Electron-specific workbench code

Workbench Parts

The workbench is divided into physical UI parts:
┌───────────────────────────────────────────────────────┐
│                     TITLEBAR (Part.TITLEBAR)                  │
├───────┬───────────────────────────────────────┬───────┤
│ ACTIV │                                        │  AUX  │
│  ITY  │                                        │ ILIAR│
│  BAR  │           EDITOR AREA                │   Y   │
│       │        (Part.EDITOR)                │  BAR  │
│       ├────────────────────────────────────────┤       │
│       │                                        │       │
│       │           PANEL AREA                 │       │
│       │        (Part.PANEL)                 │       │
├───────┴───────────────────────────────────────┴───────┤
│                  STATUSBAR (Part.STATUSBAR)                 │
└───────────────────────────────────────────────────────┘

┌──────────────────────┐
│      SIDEBAR      │  SIDEBAR can be on left or right
│  (Part.SIDEBAR)   │  Contains viewlets (Explorer, Search, etc.)
│                    │
└──────────────────────┘

Workbench Main Class

Located in: src/vs/workbench/browser/workbench.ts
import { Workbench } from 'vs/workbench/browser/workbench';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';

export class Workbench extends Layout {
	private readonly _onWillShutdown = this._register(new Emitter<WillShutdownEvent>());
	readonly onWillShutdown = this._onWillShutdown.event;

	private readonly _onDidShutdown = this._register(new Emitter<void>());
	readonly onDidShutdown = this._onDidShutdown.event;

	constructor(
		parent: HTMLElement,
		options: IWorkbenchOptions | undefined,
		serviceCollection: ServiceCollection,
		logService: ILogService
	) {
		super(parent, { resetLayout: Boolean(options?.resetLayout) });
		this.registerErrorHandler(logService);
	}

	async startup(): Promise<void> {
		// Initialize services
		// Restore workbench state
		// Render UI parts
		// Load contributions
	}
}

Layout Service

Located in: src/vs/workbench/services/layout/browser/layoutService.ts
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';

export class MyComponent {
	constructor(
		@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
	) { }

	toggleSidebar(): void {
		const visible = this.layoutService.isVisible(Parts.SIDEBAR_PART);
		this.layoutService.setPartHidden(!visible, Parts.SIDEBAR_PART);
	}

	movePanelToSide(): void {
		// Move panel to left, right, or bottom
		this.layoutService.setPanelPosition(Position.RIGHT);
	}

	getContainerDimensions(): void {
		const dimension = this.layoutService.getContainer(Parts.EDITOR_PART);
		console.log('Editor area:', dimension.width, 'x', dimension.height);
	}

	focusPart(part: Parts): void {
		this.layoutService.focusPart(part);
	}
}
Available parts:
  • Parts.TITLEBAR_PART - Title bar
  • Parts.ACTIVITYBAR_PART - Activity bar
  • Parts.SIDEBAR_PART - Primary sidebar
  • Parts.EDITOR_PART - Editor area
  • Parts.PANEL_PART - Bottom/side panel
  • Parts.AUXILIARYBAR_PART - Secondary sidebar
  • Parts.STATUSBAR_PART - Status bar

Editor Management

Located in: src/vs/workbench/services/editor/common/editorGroupsService.ts
import { IEditorGroupsService, GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';

export class EditorManager {
	constructor(
		@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
		@IEditorService private readonly editorService: IEditorService
	) { }

	splitEditor(): void {
		// Split active editor to the right
		const group = this.editorGroupsService.activeGroup;
		this.editorGroupsService.addGroup(group, GroupDirection.RIGHT);
	}

	closeAllEditors(): void {
		// Close all editors in all groups
		for (const group of this.editorGroupsService.groups) {
			group.closeAllEditors();
		}
	}

	getOpenEditors(): void {
		for (const group of this.editorGroupsService.groups) {
			for (const editor of group.editors) {
				console.log('Open editor:', editor.resource?.fsPath);
			}
		}
	}

	async openEditor(resource: URI): Promise<void> {
		await this.editorService.openEditor({
			resource,
			options: {
				pinned: true,
				revealIfOpened: true
			}
		});
	}
}
Located in: src/vs/workbench/browser/parts/editor/editorPart.ts
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';

// EditorPart manages the grid of editor groups
export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
	private readonly _onDidFocus = this._register(new Emitter<void>());
	readonly onDidFocus = this._onDidFocus.event;

	private readonly _onDidActiveGroupChange = this._register(new Emitter<IEditorGroupView>());
	readonly onDidActiveGroupChange = this._onDidActiveGroupChange.event;

	// Editor groups organized in a grid
	private gridWidget: SerializableGrid<IEditorGroupView>;

	// Methods for group management
	addGroup(location: IEditorGroupView, direction: GroupDirection): IEditorGroupView;
	removeGroup(group: IEditorGroupView): void;
	moveGroup(group: IEditorGroupView, location: IEditorGroupView, direction: GroupDirection): void;
	mergeGroup(group: IEditorGroupView, target: IEditorGroupView): void;
}

Contribution Model

The workbench uses a contribution-based architecture where features register themselves:
Located in: src/vs/workbench/common/contributions.ts
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';

// Define a contribution
class MyWorkbenchContribution {
	constructor(
		@IFileService private readonly fileService: IFileService,
		@INotificationService private readonly notificationService: INotificationService
	) {
		// Initialize when workbench is ready
		this.initialize();
	}

	private async initialize(): Promise<void> {
		// Contribution logic
		this.notificationService.info('My feature initialized!');
	}
}

// Register the contribution
const registry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
registry.registerWorkbenchContribution(MyWorkbenchContribution, LifecyclePhase.Ready);
Lifecycle phases:
  • LifecyclePhase.Starting - Very early, before window opens
  • LifecyclePhase.Ready - Window is ready, services available
  • LifecyclePhase.Restored - Workbench state restored
  • LifecyclePhase.Eventually - After everything else

Viewlets and Views

import { Registry } from 'vs/platform/registry/common/platform';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';

// Define a custom viewlet
class MyViewlet extends Viewlet {
	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IWorkspaceContextService contextService: IWorkspaceContextService,
		@IStorageService storageService: IStorageService,
		@IConfigurationService configurationService: IConfigurationService,
		@IInstantiationService instantiationService: IInstantiationService,
		@IThemeService themeService: IThemeService,
		@IContextMenuService contextMenuService: IContextMenuService
	) {
		super('my.viewlet', telemetryService, /* ... */);
	}

	protected createViewPaneContainer(parent: HTMLElement): ViewPaneContainer {
		// Create your viewlet content
		return this.instantiationService.createInstance(MyViewPaneContainer, /* ... */);
	}
}

// Register the viewlet
const viewletRegistry = Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets);
viewletRegistry.registerViewlet(new ViewletDescriptor(
	MyViewlet,
	'my.viewlet',
	'My Viewlet',
	'myViewlet',
	1 // Order
));
import { IViewDescriptorService, IViewsRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views';

// Register a view
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);

viewsRegistry.registerViews([
	{
		id: 'myView',
		name: 'My View',
		containerIcon: Codicon.code,
		ctorDescriptor: new SyncDescriptor(MyTreeView),
		canToggleVisibility: true,
		canMoveView: true,
		weight: 100
	}
], MY_VIEW_CONTAINER);

// Implement the view
class MyTreeView extends ViewPane {
	private tree: WorkbenchObjectTree<ITreeNode>;

	protected renderBody(container: HTMLElement): void {
		super.renderBody(container);

		// Create tree widget
		this.tree = this.instantiationService.createInstance(
			WorkbenchObjectTree,
			container,
			delegate,
			renderers,
			options
		);

		// Set initial data
		this.updateTreeData();
	}

	private updateTreeData(): void {
		const nodes = this.getNodes();
		this.tree.setChildren(null, nodes);
	}
}

Feature Contributions

The contrib/ directory contains major features:
Located in: src/vs/workbench/contrib/files/File explorer, file operations, and file management.
import { IExplorerService } from 'vs/workbench/contrib/files/browser/files';

export class FileOperations {
    constructor(
        @IExplorerService private readonly explorerService: IExplorerService,
        @IFileService private readonly fileService: IFileService
    ) { }

    async revealInExplorer(resource: URI): Promise<void> {
        await this.explorerService.select(resource, true);
    }
}
Located in: src/vs/workbench/contrib/debug/
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';

export class DebugOperations {
	constructor(
		@IDebugService private readonly debugService: IDebugService
	) { }

	async startDebugging(): Promise<void> {
		await this.debugService.startDebugging(undefined, {
			type: 'node',
			request: 'launch',
			name: 'Launch Program',
			program: '${workspaceFolder}/index.js'
		});
	}

	getActiveSession(): void {
		const session = this.debugService.getViewModel().focusedSession;
		if (session) {
			console.log('Active session:', session.name);
		}
	}

	addBreakpoint(uri: URI, lineNumber: number): void {
		this.debugService.addBreakpoints(uri, [{ lineNumber }]);
	}
}
Located in: src/vs/workbench/contrib/scm/
import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm';

export class SCMOperations {
	constructor(
		@ISCMService private readonly scmService: ISCMService
	) { }

	getRepositories(): ISCMRepository[] {
		return this.scmService.repositories;
	}

	async commit(repository: ISCMRepository, message: string): Promise<void> {
		const input = repository.input;
		input.value = message;
		await repository.provider.commit(message);
	}

	getChanges(repository: ISCMRepository): void {
		for (const group of repository.provider.groups) {
			for (const resource of group.resources) {
				console.log('Changed file:', resource.sourceUri.fsPath);
			}
		}
	}
}

Extension API Implementation

The api/ directory implements the VS Code Extension API:
┌─────────────────────────────┐
│  Main Thread (Workbench)  │
│  src/vs/workbench/api/    │
│  browser/                 │
│                           │
│  - MainThreadEditors      │
│  - MainThreadCommands     │
│  - MainThreadLanguages    │
│  - MainThreadWorkspace    │
└────────────┬────────────────┘
             │ IPC

┌────────────┴────────────────┐
│   Extension Host Process   │
│   src/vs/workbench/api/    │
│   common/                  │
│                            │
│   - ExtHostEditors         │
│   - ExtHostCommands        │
│   - ExtHostLanguages       │
│   - ExtHostWorkspace       │
│                            │
│   Extensions run here      │
└────────────────────────────┘
Extensions run in a separate process and communicate with the main thread via IPC.

Commands and Actions

import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';

// Register a command
CommandsRegistry.registerCommand({
	id: 'myExtension.doSomething',
	handler: async (accessor: ServicesAccessor, ...args: any[]) => {
		// Get services
		const fileService = accessor.get(IFileService);
		const notificationService = accessor.get(INotificationService);

		// Execute command logic
		await fileService.resolve(URI.file('/path'));
		notificationService.info('Command executed!');
	}
});

Lifecycle Management

import { ILifecycleService, ShutdownReason, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle';

export class LifecycleAwareComponent extends Disposable {
	constructor(
		@ILifecycleService private readonly lifecycleService: ILifecycleService
	) {
		super();

		// Listen for shutdown
		this._register(this.lifecycleService.onWillShutdown(e => {
			this.handleShutdown(e);
		}));

		// Listen for phase changes
		this._register(this.lifecycleService.onDidChangePhase(phase => {
			console.log('Lifecycle phase:', phase);
		}));
	}

	private handleShutdown(event: WillShutdownEvent): void {
		// Save state before shutdown
		event.join(
			this.saveState(),
			{ id: 'myComponent.saveState', label: 'Saving state' }
		);
	}

	private async saveState(): Promise<void> {
		// Async save logic
	}
}

Key Takeaways

  • The workbench orchestrates all UI parts (editor, sidebar, panel, etc.)
  • Features register as contributions and are loaded at specific lifecycle phases
  • Editor management supports multiple editor groups in a grid layout
  • Views and viewlets provide extensible UI containers
  • The Extension API bridges the main thread and extension host process
  • Commands and menus are registered in central registries

Next Steps

Electron Main Process

Learn about the Electron main process

Editor Layer

Review the editor implementation

Build docs developers (and LLMs) love