diff --git a/editors/vscode/client/extension.ts b/editors/vscode/client/extension.ts index bb78da0e0..1257c4129 100644 --- a/editors/vscode/client/extension.ts +++ b/editors/vscode/client/extension.ts @@ -1,4 +1,13 @@ -import { ExtensionContext, window, commands, workspace } from "vscode"; +import { + ExtensionContext, + window, + commands, + workspace, + StatusBarItem, + StatusBarAlignment, + ConfigurationTarget, + ThemeColor, +} from "vscode"; import { Executable, @@ -19,10 +28,13 @@ const enum OxcCommands { ApplyAllFixes = "oxc.applyAllFixes", ShowOutputChannel = "oxc.showOutputChannel", ShowTraceOutputChannel = "oxc.showTraceOutputChannel", + ToggleEnable = "oxc.toggleEnable", } let client: LanguageClient; +let myStatusBarItem: StatusBarItem; + export async function activate(context: ExtensionContext) { const restartCommand = commands.registerCommand( OxcCommands.RestartServer, @@ -60,10 +72,22 @@ export async function activate(context: ExtensionContext) { }, ); + const toggleEnable = commands.registerCommand( + OxcCommands.ToggleEnable, + () => { + let enabled = workspace.getConfiguration("oxc-client").get("enable"); + let nextState = !enabled; + workspace + .getConfiguration("oxc-client") + .update("enable", nextState, ConfigurationTarget.Global); + }, + ); + context.subscriptions.push( restartCommand, showOutputCommand, showTraceOutputCommand, + toggleEnable, ); const outputChannel = window.createOutputChannel(outputChannelName); @@ -101,7 +125,10 @@ export async function activate(context: ExtensionContext) { "javascript", "typescriptreact", "javascriptreact", - ].map((lang) => ({ language: lang, scheme: "file" })), + ].map((lang) => ({ + language: lang, + scheme: "file", + })), synchronize: { // Notify the server about file changes to '.clientrc files contained in the workspace fileEvents: workspace.createFileSystemWatcher("**/.clientrc"), @@ -121,15 +148,34 @@ export async function activate(context: ExtensionContext) { clientOptions, ); workspace.onDidChangeConfiguration((e) => { - let settings: any = {}; - if (e.affectsConfiguration("oxc-client.run")) { - settings.run = workspace.getConfiguration("oxc-client").get("run"); - } + let settings: any = JSON.parse( + JSON.stringify(workspace.getConfiguration("oxc-client")), + ); + updateStatsBar(settings.enable); client.sendNotification("workspace/didChangeConfiguration", { settings, }); }); + function updateStatsBar(enable: boolean) { + if (!myStatusBarItem) { + myStatusBarItem = window.createStatusBarItem( + StatusBarAlignment.Right, + 100, + ); + myStatusBarItem.command = OxcCommands.ToggleEnable; + context.subscriptions.push(myStatusBarItem); + myStatusBarItem.show(); + } + let bgColor = new ThemeColor( + enable + ? "statusBarItem.activeBackground" + : "statusBarItem.errorBackground", + ); + myStatusBarItem.text = `oxc: ${enable ? "on" : "off"}`; + myStatusBarItem.backgroundColor = bgColor; + } + updateStatsBar(clientConfig.enable); client.start(); } diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 81c203afc..eb69f2461 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -42,6 +42,11 @@ "title": "Restart Oxc Server", "category": "Oxc" }, + { + "command": "oxc.toggleEnable", + "title": "toggle enable", + "category": "Oxc" + }, { "command": "oxc.showOutputChannel", "title": "Show Output Channel", @@ -67,6 +72,11 @@ "default": "onType", "description": "Run the linter on save (onSave) or on type (onType)" }, + "oxc-client.enable": { + "type": "boolean", + "default": true, + "description": "enable oxc linter" + }, "oxc-client.trace.server": { "type": "string", "scope": "window", diff --git a/editors/vscode/server/src/main.rs b/editors/vscode/server/src/main.rs index 93b52c42c..1d8cfe746 100644 --- a/editors/vscode/server/src/main.rs +++ b/editors/vscode/server/src/main.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; use std::path::PathBuf; +use std::str::FromStr; use dashmap::DashMap; use futures::future::join_all; @@ -17,11 +18,11 @@ use tower_lsp::jsonrpc::{Error, ErrorCode, Result}; use tower_lsp::lsp_types::{ CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams, CodeActionProviderCapability, CodeActionResponse, Diagnostic, DidChangeConfigurationParams, - DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, - InitializeParams, InitializeResult, InitializedParams, MessageType, OneOf, Registration, - ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, - Url, WorkDoneProgressOptions, WorkspaceEdit, WorkspaceFoldersServerCapabilities, - WorkspaceServerCapabilities, + DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, + DidSaveTextDocumentParams, InitializeParams, InitializeResult, InitializedParams, MessageType, + OneOf, Registration, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, + TextDocumentSyncKind, TextEdit, Url, WorkDoneProgressOptions, WorkspaceEdit, + WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, }; use tower_lsp::{Client, LanguageServer, LspService, Server}; @@ -43,6 +44,27 @@ enum Run { #[derive(Debug, Serialize, Deserialize, Default, Clone)] struct Options { run: Run, + enable: bool, +} + +impl Options { + fn get_lint_level(&self) -> SyntheticRunLevel { + if self.enable { + match self.run { + Run::OnSave => SyntheticRunLevel::OnSave, + Run::OnType => SyntheticRunLevel::OnType, + } + } else { + SyntheticRunLevel::Disable + } + } +} + +#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] +enum SyntheticRunLevel { + Disable, + OnSave, + OnType, } #[tower_lsp::async_trait] @@ -94,6 +116,25 @@ impl LanguageServer for Backend { return; } }; + debug!("{:?}", &changed_options.get_lint_level()); + if changed_options.get_lint_level() == SyntheticRunLevel::Disable { + // clear all exists diagnostics when linter is disabled + let opened_files = self.diagnostics_report_map.iter().map(|k| k.key().to_string()); + let cleared_diagnostics = opened_files + .into_iter() + .map(|uri| { + ( + // should convert successfully, case the key is from `params.document.uri` + Url::from_str(&uri) + .ok() + .and_then(|url| url.to_file_path().ok()) + .expect("should convert to path"), + vec![], + ) + }) + .collect::>(); + self.publish_all_diagnostics(&cleared_diagnostics).await; + } *self.options.lock().await = changed_options; } @@ -121,8 +162,8 @@ impl LanguageServer for Backend { async fn did_save(&self, params: DidSaveTextDocumentParams) { debug!("oxc server did save"); // drop as fast as possible - let options = { self.options.lock().await.run }; - if options < Run::OnSave { + let run_level = { self.options.lock().await.get_lint_level() }; + if run_level < SyntheticRunLevel::OnSave { return; } self.handle_file_update(params.text_document.uri, None).await; @@ -131,8 +172,8 @@ impl LanguageServer for Backend { /// When the document changed, it may not be written to disk, so we should /// get the file context from the language client async fn did_change(&self, params: DidChangeTextDocumentParams) { - let options = { self.options.lock().await.run }; - if options < Run::OnType { + let run_level = { self.options.lock().await.get_lint_level() }; + if run_level < SyntheticRunLevel::OnType { return; } let content = params.content_changes.first().map(|c| c.text.clone()); @@ -140,9 +181,18 @@ impl LanguageServer for Backend { } async fn did_open(&self, params: DidOpenTextDocumentParams) { + let run_level = { self.options.lock().await.get_lint_level() }; + if run_level < SyntheticRunLevel::OnType { + return; + } self.handle_file_update(params.text_document.uri, None).await; } + async fn did_close(&self, params: DidCloseTextDocumentParams) { + let uri = params.text_document.uri.to_string(); + self.diagnostics_report_map.remove(&uri); + } + async fn code_action(&self, params: CodeActionParams) -> Result> { let uri = params.text_document.uri; @@ -199,6 +249,7 @@ impl Backend { #[allow(clippy::ptr_arg)] async fn publish_all_diagnostics(&self, result: &Vec<(PathBuf, Vec)>) { + debug!("{:?}", result); join_all(result.iter().map(|(path, diagnostics)| { self.client.publish_diagnostics( Url::from_file_path(path).unwrap(),