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
8 changes: 8 additions & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@
{
"command": "solana.reloadDetectors",
"title": "solana: Reload Security Detectors"
},
{
"command": "solana.showStatusDetails",
"title": "solana: Show Status Details"
},
{
"command": "solana.installNightly",
"title": "solana: Install Nightly Rust Toolchain"
}
],
"keybindings": [
Expand Down
52 changes: 52 additions & 0 deletions extension/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as vscode from "vscode";
import { ExtensionFeatureManagers } from "./extensionFeatureManagers";
import { CLOSE_COVERAGE, SHOW_COVERAGE } from "./coverage/commands";
import { RELOAD_DETECTORS, SCAN_WORKSPACE, SHOW_SCAN_OUTPUT } from "./detectors/commands";
import { INSTALL_NIGHTLY, SHOW_STATUS_DETAILS } from "./statusBar/commands";
import { StatusBarState } from "./statusBar/statusBarManager";

function registerCommands(
context: vscode.ExtensionContext,
Expand Down Expand Up @@ -39,6 +41,56 @@ function registerCommands(
await extensionFeatureManagers.detectorsManager.reloadDetectors();
})
);

// Add command to show status details
context.subscriptions.push(
vscode.commands.registerCommand(SHOW_STATUS_DETAILS, async () => {
const state = extensionFeatureManagers.statusBarManager.getCurrentState();
const isNightly = extensionFeatureManagers.statusBarManager.isNightlyRustAvailable();

let message = `Solana Extension Status\n\n`;
message += `State: ${state}\n`;
message += `Nightly Rust: ${isNightly ? 'Available' : 'Not available'}\n`;

if (state === 'error') {
message += `\nThe language server encountered an error. Check the output channel for details.`;
extensionFeatureManagers.detectorsManager.showOutput();
}

vscode.window.showInformationMessage(message);
})
);

// Add command to install nightly Rust
context.subscriptions.push(
vscode.commands.registerCommand(INSTALL_NIGHTLY, async () => {
const action = await vscode.window.showInformationMessage(
'Install nightly Rust toolchain? This will run: rustup toolchain install nightly',
'Install',
'Cancel'
);

if (action === 'Install') {
const terminal = vscode.window.createTerminal('Install Nightly Rust');
terminal.sendText('rustup toolchain install nightly');
terminal.show();

// Recheck toolchain after a delay
setTimeout(async () => {
await extensionFeatureManagers.statusBarManager.recheckRustToolchain();
if (extensionFeatureManagers.statusBarManager.isNightlyRustAvailable()) {
const currentState = extensionFeatureManagers.statusBarManager.getCurrentState();
extensionFeatureManagers.statusBarManager.updateStatus(
currentState === StatusBarState.Warn
? StatusBarState.Chill
: currentState,
'Nightly Rust is now available'
);
}
}, 5000);
}
})
);
}

export default registerCommands;
55 changes: 54 additions & 1 deletion extension/src/detectors/detectorsManager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { OutputChannel, window, workspace } from 'vscode';
import { LanguageClient, LanguageClientOptions, RevealOutputChannelOn, ServerOptions, StateChangeEvent, TransportKind } from 'vscode-languageclient/node';
import { LanguageClient, LanguageClientOptions, RevealOutputChannelOn, ServerOptions, StateChangeEvent, TransportKind, State } from 'vscode-languageclient/node';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
import * as vscode from 'vscode';
import { SOLANA_OUTPUT_CHANNEL } from '../output';
import { StatusBarState } from '../statusBar/statusBarManager';

// Interface for scan summary data from language server
interface ScanSummary {
Expand All @@ -28,6 +29,7 @@ interface FileIssueInfo {
export class DetectorsManager {
private client?: LanguageClient;
private outputChannel: OutputChannel;
private statusBarUpdateCallback?: (state: StatusBarState, message?: string) => void;

constructor() {
console.log('Security Server initialized');
Expand All @@ -38,6 +40,7 @@ export class DetectorsManager {

if (!serverPath) {
this.handleServerNotFound();
this.updateStatusBar(StatusBarState.Error, 'Language server binary not found');
return;
}

Expand Down Expand Up @@ -186,6 +189,7 @@ export class DetectorsManager {

this.client.onDidChangeState((e: StateChangeEvent) => {
this.outputChannel.appendLine(`Server state changed: ${e.newState}`);
this.updateStatusBarFromServerState(e.newState);
});

this.client.onNotification('window/logMessage', (params) => {
Expand All @@ -195,6 +199,10 @@ export class DetectorsManager {
// Listen for scan complete notifications
this.client.onNotification('solana/scanComplete', (scanSummary: ScanSummary) => {
this.handleScanComplete(scanSummary);
// Update status bar after scan completes (unless it was an error)
if (this.statusBarUpdateCallback && this.client?.state === State.Running) {
this.statusBarUpdateCallback(StatusBarState.Chill, 'Scan completed');
}
});
}

Expand Down Expand Up @@ -252,10 +260,12 @@ export class DetectorsManager {
async scanWorkspace(): Promise<void> {
if (!this.client) {
window.showErrorMessage('Language server not running');
this.updateStatusBar(StatusBarState.Error, 'Language server not running');
return;
}

this.outputChannel.appendLine('Manually triggering workspace scan...');
this.updateStatusBar(StatusBarState.Running, 'Scanning workspace...');

try {
const result = await this.client.sendRequest('workspace/executeCommand', {
Expand All @@ -264,20 +274,24 @@ export class DetectorsManager {
});

this.outputChannel.appendLine(`Scan request result: ${JSON.stringify(result)}`);
this.updateStatusBar(StatusBarState.Chill, 'Workspace scan completed');
} catch (error) {
this.outputChannel.appendLine(`Error triggering scan: ${error}`);
window.showErrorMessage(`Failed to scan workspace: ${error}`);
this.updateStatusBar(StatusBarState.Error, `Failed to scan workspace: ${error}`);
}
}

// Method to reload all detectors
async reloadDetectors(): Promise<void> {
if (!this.client) {
window.showErrorMessage('Language server not running');
this.updateStatusBar(StatusBarState.Error, 'Language server not running');
return;
}

this.outputChannel.appendLine('Reloading security detectors...');
this.updateStatusBar(StatusBarState.Running, 'Reloading security detectors...');

try {
const result = await this.client.sendRequest('workspace/executeCommand', {
Expand All @@ -286,9 +300,48 @@ export class DetectorsManager {
});

this.outputChannel.appendLine(`Reload detectors result: ${JSON.stringify(result)}`);
this.updateStatusBar(StatusBarState.Chill, 'Detectors reloaded successfully');
} catch (error) {
this.outputChannel.appendLine(`Error reloading detectors: ${error}`);
window.showErrorMessage(`Failed to reload detectors: ${error}`);
this.updateStatusBar(StatusBarState.Error, `Failed to reload detectors: ${error}`);
}
}

/**
* Set callback for status bar updates
*/
setStatusBarUpdateCallback(callback: (state: StatusBarState, message?: string) => void): void {
this.statusBarUpdateCallback = callback;
}

/**
* Update status bar based on server state
*/
private updateStatusBarFromServerState(state: State): void {
if (!this.statusBarUpdateCallback) {
return;
}

switch (state) {
case State.Starting:
this.statusBarUpdateCallback(StatusBarState.Running, 'Starting language server...');
break;
case State.Running:
this.statusBarUpdateCallback(StatusBarState.Chill, 'Language server is ready');
break;
case State.Stopped:
this.statusBarUpdateCallback(StatusBarState.Error, 'Language server stopped');
break;
}
}

/**
* Update status bar
*/
private updateStatusBar(state: StatusBarState, message?: string): void {
if (this.statusBarUpdateCallback) {
this.statusBarUpdateCallback(state, message);
}
}
}
21 changes: 20 additions & 1 deletion extension/src/extensionFeatureManagers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import { CoverageManager } from "./coverage/coverageManager";
import { DetectorsManager } from "./detectors/detectorsManager";
import { StatusBarManager, StatusBarState } from "./statusBar/statusBarManager";

function initExtensionFeatureManagers(): ExtensionFeatureManagers {
console.log('Initializing extension feature managers');

// Create status bar manager first
const statusBarManager = new StatusBarManager();

// Create detectors manager
const detectorsManager = new DetectorsManager();

// Connect status bar manager to detectors manager
detectorsManager.setStatusBarUpdateCallback((state: StatusBarState, message?: string) => {
statusBarManager.updateStatus(state, message);
});

// After connecting, check if we need to update status based on current state
// This handles the case where DetectorsManager set an error state before callback was connected
// The server state will be updated via onDidChangeState event, so we don't need to manually check here

let extensionFeatureManagers: ExtensionFeatureManagers = {
coverageManager: new CoverageManager(),
detectorsManager: new DetectorsManager(),
detectorsManager: detectorsManager,
statusBarManager: statusBarManager,
// add other managers here ...
};

Expand All @@ -15,6 +33,7 @@ function initExtensionFeatureManagers(): ExtensionFeatureManagers {
interface ExtensionFeatureManagers {
coverageManager: CoverageManager;
detectorsManager: DetectorsManager;
statusBarManager: StatusBarManager;
// add other managers here ...
}

Expand Down
8 changes: 8 additions & 0 deletions extension/src/statusBar/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Status bar command identifiers
* These constants define all available commands for status bar interactions
*/

// Status bar-related commands
export const SHOW_STATUS_DETAILS = "solana.showStatusDetails";
export const INSTALL_NIGHTLY = "solana.installNightly";
Loading
Loading