feat(vscode): add a option to control oxc lint timing (#1659)

1. Closed https://github.com/oxc-project/oxc/issues/1653
2. Now you use `oxc-client.run` to control the lint timing.


[录屏 2023年12月12日
23时39分16秒.webm](https://github.com/oxc-project/oxc/assets/17974631/123326dc-6110-4f2e-9d48-ef1a3b0a4536)
This commit is contained in:
IWANABETHATGUY 2023-12-13 11:40:39 +08:00 committed by GitHub
parent 117d95f0ae
commit e63576d03c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 8 deletions

2
Cargo.lock generated
View file

@ -1956,6 +1956,8 @@ dependencies = [
"oxc_span", "oxc_span",
"rayon", "rayon",
"ropey", "ropey",
"serde",
"serde_json",
"tokio", "tokio",
"tower-lsp", "tower-lsp",
] ]

View file

@ -88,6 +88,7 @@ export async function activate(context: ExtensionContext) {
// If the extension is launched in debug mode then the debug server options are used // If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used // Otherwise the run options are used
// Options to control the language client // Options to control the language client
let clientConfig: any = JSON.parse(JSON.stringify(workspace.getConfiguration('oxc-client')));
let clientOptions: LanguageClientOptions = { let clientOptions: LanguageClientOptions = {
// Register the server for plain text documents // Register the server for plain text documents
documentSelector: [ documentSelector: [
@ -96,17 +97,28 @@ export async function activate(context: ExtensionContext) {
"typescriptreact", "typescriptreact",
"javascriptreact", "javascriptreact",
].map(lang => ({ language: lang, scheme: "file" })), ].map(lang => ({ language: lang, scheme: "file" })),
synchronize: { synchronize: {
// Notify the server about file changes to '.clientrc files contained in the workspace // Notify the server about file changes to '.clientrc files contained in the workspace
fileEvents: workspace.createFileSystemWatcher("**/.clientrc"), fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
}, },
initializationOptions: {
settings: clientConfig
},
outputChannel, outputChannel,
traceOutputChannel, traceOutputChannel,
}; };
// Create the language client and start the client. // Create the language client and start the client.
client = new LanguageClient(languageClientId, languageClientName, serverOptions, clientOptions); client = new LanguageClient(languageClientId, languageClientName, serverOptions, clientOptions);
workspace.onDidChangeConfiguration((e) => {
let settings: any = {};
if (e.affectsConfiguration("oxc-client.run")) {
settings.run = workspace.getConfiguration('oxc-client').get("run")
}
client.sendNotification("workspace/didChangeConfiguration", {
settings
})
})
client.start(); client.start();
} }

View file

@ -57,6 +57,16 @@
"type": "object", "type": "object",
"title": "oxc", "title": "oxc",
"properties": { "properties": {
"oxc-client.run": {
"scope": "resource",
"type": "string",
"enum": [
"onSave",
"onType"
],
"default": "onType",
"description": "Run the linter on save (onSave) or on type (onType)"
},
"oxc-client.trace.server": { "oxc-client.trace.server": {
"type": "string", "type": "string",
"scope": "window", "scope": "window",

View file

@ -37,3 +37,5 @@ ropey = { workspace = true }
tokio = { workspace = true, features = ["full"] } tokio = { workspace = true, features = ["full"] }
tower-lsp = { workspace = true, features = ["proposed"] } tower-lsp = { workspace = true, features = ["proposed"] }
log = "0.4.20" log = "0.4.20"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }

View file

@ -4,21 +4,24 @@ mod options;
mod walk; mod walk;
use crate::linter::{DiagnosticReport, ServerLinter}; use crate::linter::{DiagnosticReport, ServerLinter};
use log::debug; use log::{debug, error};
use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::path::PathBuf; use std::path::PathBuf;
use dashmap::DashMap; use dashmap::DashMap;
use futures::future::join_all; use futures::future::join_all;
use tokio::sync::{OnceCell, SetError}; use tokio::sync::{Mutex, OnceCell, SetError};
use tower_lsp::jsonrpc::{Error, ErrorCode, Result}; use tower_lsp::jsonrpc::{Error, ErrorCode, Result};
use tower_lsp::lsp_types::{ use tower_lsp::lsp_types::{
CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams, CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams,
CodeActionProviderCapability, CodeActionResponse, Diagnostic, DidChangeTextDocumentParams, CodeActionProviderCapability, CodeActionResponse, Diagnostic, DidChangeConfigurationParams,
DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializeResult, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
InitializedParams, MessageType, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, InitializeParams, InitializeResult, InitializedParams, MessageType, OneOf, Registration,
TextDocumentSyncKind, TextEdit, Url, WorkDoneProgressOptions, WorkspaceEdit, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit,
Url, WorkDoneProgressOptions, WorkspaceEdit, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
}; };
use tower_lsp::{Client, LanguageServer, LspService, Server}; use tower_lsp::{Client, LanguageServer, LspService, Server};
@ -28,13 +31,33 @@ struct Backend {
root_uri: OnceCell<Option<Url>>, root_uri: OnceCell<Option<Url>>,
server_linter: ServerLinter, server_linter: ServerLinter,
diagnostics_report_map: DashMap<String, Vec<DiagnosticReport>>, diagnostics_report_map: DashMap<String, Vec<DiagnosticReport>>,
options: Mutex<Options>,
}
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, PartialOrd, Clone, Copy)]
#[serde(rename_all = "camelCase")]
enum Run {
OnSave,
#[default]
OnType,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
struct Options {
run: Run,
} }
#[tower_lsp::async_trait] #[tower_lsp::async_trait]
impl LanguageServer for Backend { impl LanguageServer for Backend {
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> { async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
self.init(params.root_uri)?; self.init(params.root_uri)?;
let options = params.initialization_options.and_then(|mut value| {
let settings = value.get_mut("settings")?.take();
serde_json::from_value::<Options>(settings).ok()
});
if let Some(value) = options {
debug!("initialize: {:?}", value);
*self.options.lock().await = value;
}
Ok(InitializeResult { Ok(InitializeResult {
server_info: Some(ServerInfo { name: "oxc".into(), version: None }), server_info: Some(ServerInfo { name: "oxc".into(), version: None }),
offset_encoding: None, offset_encoding: None,
@ -42,6 +65,13 @@ impl LanguageServer for Backend {
text_document_sync: Some(TextDocumentSyncCapability::Kind( text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::FULL, TextDocumentSyncKind::FULL,
)), )),
workspace: Some(WorkspaceServerCapabilities {
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
supported: Some(true),
change_notifications: Some(OneOf::Left(true)),
}),
file_operations: None,
}),
code_action_provider: Some(CodeActionProviderCapability::Options( code_action_provider: Some(CodeActionProviderCapability::Options(
CodeActionOptions { CodeActionOptions {
code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]), code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
@ -56,7 +86,18 @@ impl LanguageServer for Backend {
}) })
} }
async fn initialized(&self, _: InitializedParams) { async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
let changed_options = match serde_json::from_value::<Options>(params.settings) {
Ok(option) => option,
Err(err) => {
error!("error parsing settings: {:?}", err);
return;
}
};
*self.options.lock().await = changed_options;
}
async fn initialized(&self, params: InitializedParams) {
debug!("oxc initialized."); debug!("oxc initialized.");
if let Some(Some(root_uri)) = self.root_uri.get() { if let Some(Some(root_uri)) = self.root_uri.get() {
@ -79,12 +120,21 @@ impl LanguageServer for Backend {
async fn did_save(&self, params: DidSaveTextDocumentParams) { async fn did_save(&self, params: DidSaveTextDocumentParams) {
debug!("oxc server did save"); debug!("oxc server did save");
// drop as fast as possible
let options = { self.options.lock().await.run };
if options < Run::OnSave {
return;
}
self.handle_file_update(params.text_document.uri, None).await; self.handle_file_update(params.text_document.uri, None).await;
} }
/// When the document changed, it may not be written to disk, so we should /// When the document changed, it may not be written to disk, so we should
/// get the file context from the language client /// get the file context from the language client
async fn did_change(&self, params: DidChangeTextDocumentParams) { async fn did_change(&self, params: DidChangeTextDocumentParams) {
let options = { self.options.lock().await.run };
if options < Run::OnType {
return;
}
let content = params.content_changes.first().map(|c| c.text.clone()); let content = params.content_changes.first().map(|c| c.text.clone());
self.handle_file_update(params.text_document.uri, content).await; self.handle_file_update(params.text_document.uri, content).await;
} }
@ -192,6 +242,7 @@ async fn main() {
root_uri: OnceCell::new(), root_uri: OnceCell::new(),
server_linter, server_linter,
diagnostics_report_map, diagnostics_report_map,
options: tokio::sync::Mutex::new(Options::default()),
}) })
.finish(); .finish();