Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ The extension relies on the Python extension for locating your Python
environment. In some cases, this appears to default to your globally-installed
environment, even when a virtual environment exists.

When the extension detects a Mojo project (a workspace containing `.mojo`
files, or with a `.mojo` file open), the SDK status appears in the bottom-left
status bar, showing details of the detected SDK or a clickable notice if no
SDK was detected.

If the Mojo extension cannot find your SDK installation, try invoking the
`Python: Select Interpreter` command and selecting your virtual
environment.
Expand Down
25 changes: 25 additions & 0 deletions extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { RpcServer } from './server/RpcServer';
import { Mutex } from 'async-mutex';
import { TelemetryReporter } from './telemetry';
import { PythonEnvironmentManager } from './pyenv';
import { SDKStatusBar } from './statusBar';

/**
* This class provides an entry point for the Mojo extension, managing the
Expand Down Expand Up @@ -72,6 +73,30 @@ Activating the Mojo Extension
this.pushSubscription(this.pyenvManager);
await this.pyenvManager.init();

// Set up the SDK status bar.
const showOutputCommand = 'mojo.showOutput';
this.pushSubscription(
vscode.commands.registerCommand(showOutputCommand, () => {
this.logger.main.show();
}),
);
const statusBar = new SDKStatusBar(showOutputCommand);
this.pushSubscription(statusBar);

const updateStatusBar = async () => {
statusBar.showLoading();
const sdk = await this.pyenvManager!.findActiveSDK();
statusBar.update(sdk);
};

this.pushSubscription(
statusBar.onRefreshRequested(() => updateStatusBar()),
);
this.pushSubscription(
this.pyenvManager.onEnvironmentChange(() => updateStatusBar()),
);
await statusBar.checkVisibility();

this.pushSubscription(
await configWatcher.activate({
settings: ['SDK.additionalSDKs'],
Expand Down
127 changes: 127 additions & 0 deletions extension/statusBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//===----------------------------------------------------------------------===//
// Copyright (c) 2026, Modular Inc. All rights reserved.
//
// Licensed under the Apache License v2.0 with LLVM Exceptions:
// https://llvm.org/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//

import * as vscode from 'vscode';
import { SDK, SDKKind } from './pyenv';

const SDK_KIND_LABELS: Record<SDKKind, string> = {
[SDKKind.Environment]: 'env',
[SDKKind.Custom]: 'custom',
[SDKKind.Internal]: 'dev',
};

function editorHasMojoFile(): boolean {
return vscode.window.visibleTextEditors.some(
(editor) => editor.document.languageId === 'mojo',
);
}

export class SDKStatusBar implements vscode.Disposable {
private statusBarItem: vscode.StatusBarItem;
private disposables: vscode.Disposable[] = [];
private visible = false;
private workspaceHasMojo: boolean | undefined = undefined;
private onShouldRefresh: vscode.EventEmitter<void> =
new vscode.EventEmitter();
readonly onRefreshRequested: vscode.Event<void> = this.onShouldRefresh.event;

constructor(showOutputCommand: string) {
this.statusBarItem = vscode.window.createStatusBarItem(
'mojo-sdk-status',
vscode.StatusBarAlignment.Left,
50,
);
this.statusBarItem.name = 'Mojo SDK';
this.statusBarItem.command = showOutputCommand;

// Watch for mojo files being opened.
this.disposables.push(
vscode.window.onDidChangeVisibleTextEditors(() => this.checkVisibility()),
);

// Watch for mojo files being created/deleted in the workspace.
const watcher = vscode.workspace.createFileSystemWatcher('**/*.mojo');
this.disposables.push(watcher);
this.disposables.push(
watcher.onDidCreate(() => {
this.workspaceHasMojo = true;
this.checkVisibility();
}),
);
this.disposables.push(
watcher.onDidDelete(() => {
this.workspaceHasMojo = undefined; // invalidate cache
this.checkVisibility();
}),
);
}

async checkVisibility() {
if (editorHasMojoFile()) {
this.show();
return;
}

if (this.workspaceHasMojo === undefined) {
const results = await vscode.workspace.findFiles('**/*.mojo', null, 1);
this.workspaceHasMojo = results.length > 0;
}

if (this.workspaceHasMojo) {
this.show();
} else if (this.visible) {
this.visible = false;
this.statusBarItem.hide();
}
}

private show() {
if (!this.visible) {
this.visible = true;
this.statusBarItem.show();
this.onShouldRefresh.fire();
}
}

showLoading() {
this.statusBarItem.text = '$(loading~spin) Mojo';
this.statusBarItem.tooltip = 'Detecting Mojo SDK...';
this.statusBarItem.backgroundColor = undefined;
}

update(sdk: SDK | undefined) {
if (sdk) {
const version = sdk.version.replace(/^mojo\s*/i, '').trim();
const kindLabel = SDK_KIND_LABELS[sdk.kind];
this.statusBarItem.text = `$(check) Mojo ${version} (${kindLabel})`;
this.statusBarItem.tooltip = new vscode.MarkdownString(
`**Mojo SDK** (${kindLabel})\n\nVersion: ${version}\n\nPath: ${sdk.mojoPath}`,
);
this.statusBarItem.backgroundColor = undefined;
} else {
this.statusBarItem.text = '$(warning) Mojo: No SDK';
this.statusBarItem.tooltip = 'No Mojo SDK detected. Click to view logs.';
this.statusBarItem.backgroundColor = new vscode.ThemeColor(
'statusBarItem.warningBackground',
);
}
}

dispose() {
this.statusBarItem.dispose();
this.onShouldRefresh.dispose();
for (const d of this.disposables) {
d.dispose();
}
}
}
Loading