From 397f25af90ce121012cd0ca01b74e25ce23057d3 Mon Sep 17 00:00:00 2001 From: coder3101 Date: Sun, 20 Oct 2024 21:08:07 +0530 Subject: [PATCH 1/4] feat: add protols.toml to configure protols in a workspace --- Cargo.lock | 14 +- Cargo.toml | 1 + src/config/input/protols-valid.toml | 9 ++ src/config/mod.rs | 52 +++++++ ..._workspace__test__get_for_workspace-2.snap | 12 ++ ...g__workspace__test__get_for_workspace.snap | 14 ++ ...s__config__workspace__test__workspace.snap | 14 ++ src/config/workspace.rs | 141 ++++++++++++++++++ src/formatter/clang.rs | 24 ++- src/lsp.rs | 63 +++++--- src/main.rs | 1 + src/server.rs | 6 +- src/state.rs | 15 +- src/workspace/definition.rs | 10 +- src/workspace/hover.rs | 10 +- src/workspace/rename.rs | 8 +- 16 files changed, 324 insertions(+), 70 deletions(-) create mode 100644 src/config/input/protols-valid.toml create mode 100644 src/config/mod.rs create mode 100644 src/config/snapshots/protols__config__workspace__test__get_for_workspace-2.snap create mode 100644 src/config/snapshots/protols__config__workspace__test__get_for_workspace.snap create mode 100644 src/config/snapshots/protols__config__workspace__test__workspace.snap create mode 100644 src/config/workspace.rs diff --git a/Cargo.lock b/Cargo.lock index 5a84072..19f7fe7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,15 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -509,9 +518,10 @@ dependencies = [ [[package]] name = "protols" -version = "0.6.0" +version = "0.6.2" dependencies = [ "async-lsp", + "basic-toml", "futures", "hard-xml", "insta", @@ -1061,7 +1071,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cc113ef..3819992 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ walkdir = "2.5.0" hard-xml = "1.36.0" tempfile = "3.12.0" serde = { version = "1.0.209", features = ["derive"] } +basic-toml = "0.1.9" [dev-dependencies] insta = { version = "1.39.0", features = ["yaml"] } diff --git a/src/config/input/protols-valid.toml b/src/config/input/protols-valid.toml new file mode 100644 index 0000000..e430f14 --- /dev/null +++ b/src/config/input/protols-valid.toml @@ -0,0 +1,9 @@ +[config] +include_paths = ["foobar", "bazbaaz"] +disable_parse_diagnostics = true + +[config.experimental] +use_protoc_diagnostics = true + +[formatter] +clang_format_path = "/usr/bin/clang-format" diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..e770af8 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,52 @@ +use serde::{Deserialize, Serialize}; + +pub mod workspace; + +fn default_clang_format_path() -> String { + "clang-format".to_string() +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(default)] +pub struct ProtolsConfig { + pub config: Config, + pub formatter: FormatterConfig, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct FormatterConfig { + #[serde(default = "default_clang_format_path")] + pub clang_format_path: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[serde(default)] +pub struct Config { + pub include_paths: Vec, + pub single_file_mode: bool, + pub disable_parse_diagnostics: bool, + pub experimental: ExperimentalConfig, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[serde(default)] +pub struct ExperimentalConfig { + pub use_protoc_diagnostics: bool, +} + +impl Default for ProtolsConfig { + fn default() -> Self { + Self { + config: Config::default(), + formatter: FormatterConfig::default(), + } + } +} + +impl Default for FormatterConfig { + fn default() -> Self { + Self { + clang_format_path: default_clang_format_path(), + } + } +} diff --git a/src/config/snapshots/protols__config__workspace__test__get_for_workspace-2.snap b/src/config/snapshots/protols__config__workspace__test__get_for_workspace-2.snap new file mode 100644 index 0000000..a45e4b9 --- /dev/null +++ b/src/config/snapshots/protols__config__workspace__test__get_for_workspace-2.snap @@ -0,0 +1,12 @@ +--- +source: src/config/workspace.rs +expression: ws.get_config_for_uri(&inworkspace2).unwrap() +--- +config: + include_paths: [] + single_file_mode: false + disable_parse_diagnostics: false + experimental: + use_protoc_diagnostics: false +formatter: + clang_format_path: clang-format diff --git a/src/config/snapshots/protols__config__workspace__test__get_for_workspace.snap b/src/config/snapshots/protols__config__workspace__test__get_for_workspace.snap new file mode 100644 index 0000000..a922784 --- /dev/null +++ b/src/config/snapshots/protols__config__workspace__test__get_for_workspace.snap @@ -0,0 +1,14 @@ +--- +source: src/config/workspace.rs +expression: ws.get_config_for_uri(&inworkspace).unwrap() +--- +config: + include_paths: + - foobar + - bazbaaz + single_file_mode: false + disable_parse_diagnostics: true + experimental: + use_protoc_diagnostics: true +formatter: + clang_format_path: /usr/bin/clang-format diff --git a/src/config/snapshots/protols__config__workspace__test__workspace.snap b/src/config/snapshots/protols__config__workspace__test__workspace.snap new file mode 100644 index 0000000..1438bb3 --- /dev/null +++ b/src/config/snapshots/protols__config__workspace__test__workspace.snap @@ -0,0 +1,14 @@ +--- +source: src/config/workspace.rs +expression: ws.get_config_for_uri(&inworkspace) +--- +config: + include_paths: + - foobar + - bazbaaz + single_file_mode: false + disable_parse_diagnostics: true + experimental: + use_protoc_diagnostics: true +formatter: + clang_format_path: /usr/bin/clang-format diff --git a/src/config/workspace.rs b/src/config/workspace.rs new file mode 100644 index 0000000..d88193b --- /dev/null +++ b/src/config/workspace.rs @@ -0,0 +1,141 @@ +use std::{ + collections::{HashMap, HashSet}, + path::Path, +}; + +use async_lsp::lsp_types::{Url, WorkspaceFolder}; + +use crate::formatter::clang::ClangFormatter; + +use super::ProtolsConfig; + +const CONFIG_FILE_NAME: &str = "protols.toml"; + +pub struct WorkspaceProtoConfigs { + workspaces: HashSet, + configs: HashMap, + formatters: HashMap, +} + +impl WorkspaceProtoConfigs { + pub fn new() -> Self { + Self { + workspaces: Default::default(), + formatters: Default::default(), + configs: Default::default(), + } + } + + pub fn add_workspace(&mut self, w: &WorkspaceFolder) { + let Ok(wpath) = w.uri.to_file_path() else { + return; + }; + + let p = Path::new(&wpath).join(CONFIG_FILE_NAME); + let content = std::fs::read_to_string(p).unwrap_or_default(); + + let wr: ProtolsConfig = basic_toml::from_str(&content).unwrap_or_default(); + let fmt = ClangFormatter::new( + &wr.formatter.clang_format_path, + wpath.to_str().expect("non-utf8 path"), + ); + + self.workspaces.insert(w.uri.clone()); + self.configs.insert(w.uri.clone(), wr); + self.formatters.insert(w.uri.clone(), fmt); + } + + pub fn get_config_for_uri(&self, u: &Url) -> Option<&ProtolsConfig> { + self.get_workspace_for_uri(u) + .and_then(|w| self.configs.get(w)) + } + + pub fn get_formatter_for_uri(&self, u: &Url) -> Option<&ClangFormatter> { + self.get_workspace_for_uri(u) + .and_then(|w| self.formatters.get(w)) + } + + pub fn get_workspace_for_uri(&self, u: &Url) -> Option<&Url> { + let upath = u.to_file_path().ok()?; + self.workspaces + .iter() + .find(|&k| upath.starts_with(k.to_file_path().unwrap())) + } +} + +#[cfg(test)] +mod test { + use async_lsp::lsp_types::{Url, WorkspaceFolder}; + use insta::assert_yaml_snapshot; + use tempfile::tempdir; + + use super::WorkspaceProtoConfigs; + + #[test] + fn test_get_for_workspace() { + let tmpdir = tempdir().expect("failed to create temp directory"); + let tmpdir2 = tempdir().expect("failed to create temp2 directory"); + let f = tmpdir.path().join("protols.toml"); + std::fs::write(f, include_str!("input/protols-valid.toml")).unwrap(); + + let mut ws = WorkspaceProtoConfigs::new(); + ws.add_workspace(&WorkspaceFolder { + uri: Url::from_directory_path(tmpdir.path()).unwrap(), + name: "Test".to_string(), + }); + ws.add_workspace(&WorkspaceFolder { + uri: Url::from_directory_path(tmpdir2.path()).unwrap(), + name: "Test2".to_string(), + }); + + let inworkspace = Url::from_file_path(tmpdir.path().join("foobar.proto")).unwrap(); + let outworkspace = + Url::from_file_path(tempdir().unwrap().path().join("out.proto")).unwrap(); + let inworkspace2 = Url::from_file_path(tmpdir2.path().join("foobar.proto")).unwrap(); + + assert!(ws.get_config_for_uri(&inworkspace).is_some()); + assert!(ws.get_config_for_uri(&inworkspace2).is_some()); + assert!(ws.get_config_for_uri(&outworkspace).is_none()); + + assert!(ws.get_workspace_for_uri(&inworkspace).is_some()); + assert!(ws.get_workspace_for_uri(&inworkspace2).is_some()); + assert!(ws.get_workspace_for_uri(&outworkspace).is_none()); + + assert_yaml_snapshot!(ws.get_config_for_uri(&inworkspace).unwrap()); + assert_yaml_snapshot!(ws.get_config_for_uri(&inworkspace2).unwrap()); + } + + #[test] + fn test_get_formatter_for_uri() { + let tmpdir = tempdir().expect("failed to create temp directory"); + let tmpdir2 = tempdir().expect("failed to create temp2 directory"); + let f = tmpdir.path().join("protols.toml"); + std::fs::write(f, include_str!("input/protols-valid.toml")).unwrap(); + + let mut ws = WorkspaceProtoConfigs::new(); + ws.add_workspace(&WorkspaceFolder { + uri: Url::from_directory_path(tmpdir.path()).unwrap(), + name: "Test".to_string(), + }); + + ws.add_workspace(&WorkspaceFolder { + uri: Url::from_directory_path(tmpdir2.path()).unwrap(), + name: "Test2".to_string(), + }); + + let inworkspace = Url::from_file_path(tmpdir.path().join("foobar.proto")).unwrap(); + let outworkspace = + Url::from_file_path(tempdir().unwrap().path().join("out.proto")).unwrap(); + let inworkspace2 = Url::from_file_path(tmpdir2.path().join("foobar.proto")).unwrap(); + + assert!(ws.get_formatter_for_uri(&outworkspace).is_none()); + assert_eq!( + ws.get_formatter_for_uri(&inworkspace).unwrap().path, + "/usr/bin/clang-format" + ); + assert_eq!( + ws.get_formatter_for_uri(&inworkspace2).unwrap().path, + "clang-format" + ); + } +} diff --git a/src/formatter/clang.rs b/src/formatter/clang.rs index d2e72d5..23e7c74 100644 --- a/src/formatter/clang.rs +++ b/src/formatter/clang.rs @@ -1,7 +1,6 @@ #![allow(clippy::needless_late_init)] use std::{ borrow::Cow, - error::Error, fs::File, io::Write, path::{Path, PathBuf}, @@ -16,8 +15,8 @@ use tempfile::{tempdir, TempDir}; use super::ProtoFormatter; pub struct ClangFormatter { - path: String, - working_dir: Option, + pub path: String, + working_dir: String, temp_dir: TempDir, } @@ -67,15 +66,12 @@ impl<'a> Replacement<'a> { } impl ClangFormatter { - pub fn new(path: &str, workdir: Option<&str>) -> Result> { - let mut c = Command::new(path); - c.arg("--version").status()?; - - Ok(Self { - temp_dir: tempdir()?, - path: path.to_owned(), - working_dir: workdir.map(ToOwned::to_owned), - }) + pub fn new(cmd: &str, wdir: &str) -> Self { + Self { + temp_dir: tempdir().expect("faile to creat temp dir"), + path: cmd.to_owned(), + working_dir: wdir.to_owned() + } } fn get_temp_file_path(&self, content: &str) -> Option { @@ -87,9 +83,7 @@ impl ClangFormatter { fn get_command(&self, u: &Path) -> Command { let mut c = Command::new(self.path.as_str()); - if let Some(wd) = self.working_dir.as_ref() { - c.current_dir(wd.as_str()); - } + c.current_dir(self.working_dir.as_str()); c.args([u.to_str().unwrap(), "--output-replacements-xml"]); c } diff --git a/src/lsp.rs b/src/lsp.rs index 4881747..a8c45b5 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -1,17 +1,26 @@ -use std::collections::HashMap; -use std::fs::read_to_string; use std::ops::ControlFlow; use std::sync::mpsc; use std::thread; +use std::{collections::HashMap, fs::read_to_string}; use tracing::{error, info}; use async_lsp::lsp_types::{ - CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, CreateFilesParams, DeleteFilesParams, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentFormattingParams, DocumentRangeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, FileOperationFilter, FileOperationPattern, FileOperationPatternKind, FileOperationRegistrationOptions, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult, OneOf, PrepareRenameResponse, ProgressParams, RenameFilesParams, RenameOptions, RenameParams, ServerCapabilities, ServerInfo, TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url, WorkspaceEdit, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities + CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, + CreateFilesParams, DeleteFilesParams, DidChangeConfigurationParams, + DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, + DidSaveTextDocumentParams, DocumentFormattingParams, DocumentRangeFormattingParams, + DocumentSymbolParams, DocumentSymbolResponse, FileOperationFilter, FileOperationPattern, + FileOperationPatternKind, FileOperationRegistrationOptions, GotoDefinitionParams, + GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability, + InitializeParams, InitializeResult, OneOf, PrepareRenameResponse, ProgressParams, + RenameFilesParams, RenameOptions, RenameParams, ServerCapabilities, ServerInfo, + TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url, + WorkspaceEdit, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, + WorkspaceServerCapabilities, }; use async_lsp::{LanguageClient, LanguageServer, ResponseError}; use futures::future::BoxFuture; -use crate::formatter::clang::ClangFormatter; use crate::formatter::ProtoFormatter; use crate::server::ProtoLanguageServer; @@ -66,19 +75,12 @@ impl LanguageServer for ProtoLanguageServer { }; let mut workspace_capabilities = None; - let mut formatter_provider = None; - let mut formatter_range_provider = None; + if let Some(folders) = params.workspace_folders { - if let Ok(f) = ClangFormatter::new("clang-format", folders.first().map(|f| f.uri.path())) - { - self.state.add_formatter(f); - formatter_provider = Some(OneOf::Left(true)); - formatter_range_provider = Some(OneOf::Left(true)); - info!("Setting formatting server capability"); - } for workspace in folders { info!("Workspace folder: {workspace:?}"); - self.state.add_workspace_folder_async(workspace, tx.clone()) + self.configs.add_workspace(&workspace); + self.state.add_workspace_folder_async(workspace, tx.clone()); } workspace_capabilities = Some(WorkspaceServerCapabilities { workspace_folders: Some(WorkspaceFoldersServerCapabilities { @@ -122,8 +124,8 @@ impl LanguageServer for ProtoLanguageServer { document_symbol_provider: Some(OneOf::Left(true)), completion_provider: Some(CompletionOptions::default()), rename_provider: Some(rename_provider), - document_formatting_provider: formatter_provider, - document_range_formatting_provider: formatter_range_provider, + document_formatting_provider: Some(OneOf::Left(true)), + document_range_formatting_provider: Some(OneOf::Left(true)), ..ServerCapabilities::default() }, @@ -330,8 +332,8 @@ impl LanguageServer for ProtoLanguageServer { let content = self.state.get_content(&uri); let response = self - .state - .get_formatter() + .configs + .get_formatter_for_uri(&uri) .and_then(|f| f.format_document(content.as_str())); Box::pin(async move { Ok(response) }) @@ -345,8 +347,8 @@ impl LanguageServer for ProtoLanguageServer { let content = self.state.get_content(&uri); let response = self - .state - .get_formatter() + .configs + .get_formatter_for_uri(&uri) .and_then(|f| f.format_document_range(¶ms.range, content.as_str())); Box::pin(async move { Ok(response) }) @@ -364,7 +366,15 @@ impl LanguageServer for ProtoLanguageServer { let uri = params.text_document.uri; let content = params.text_document.text; - if let Some(diagnostics) = self.state.upsert_file(&uri, content) { + let Some(diagnostics) = self.state.upsert_file(&uri, content) else { + return ControlFlow::Continue(()); + }; + + let Some(ws) = self.configs.get_config_for_uri(&uri) else { + return ControlFlow::Continue(()); + }; + + if !ws.config.disable_parse_diagnostics { if let Err(e) = self.client.publish_diagnostics(diagnostics) { error!(error=%e, "failed to publish diagnostics") } @@ -376,11 +386,20 @@ impl LanguageServer for ProtoLanguageServer { let uri = params.text_document.uri; let content = params.content_changes[0].text.clone(); - if let Some(diagnostics) = self.state.upsert_file(&uri, content) { + let Some(diagnostics) = self.state.upsert_file(&uri, content) else { + return ControlFlow::Continue(()); + }; + + let Some(ws) = self.configs.get_config_for_uri(&uri) else { + return ControlFlow::Continue(()); + }; + + if !ws.config.disable_parse_diagnostics { if let Err(e) = self.client.publish_diagnostics(diagnostics) { error!(error=%e, "failed to publish diagnostics") } } + ControlFlow::Continue(()) } diff --git a/src/main.rs b/src/main.rs index b77c643..e0eaa81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ mod state; mod utils; mod workspace; mod formatter; +mod config; #[tokio::main(flavor = "current_thread")] async fn main() { diff --git a/src/server.rs b/src/server.rs index 37d022e..a1aa956 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,13 +1,14 @@ use async_lsp::{router::Router, ClientSocket}; use std::ops::ControlFlow; -use crate::{formatter::clang::ClangFormatter, state::ProtoLanguageState}; +use crate::{config::workspace::WorkspaceProtoConfigs, state::ProtoLanguageState}; pub struct TickEvent; pub struct ProtoLanguageServer { pub client: ClientSocket, pub counter: i32, - pub state: ProtoLanguageState, + pub state: ProtoLanguageState, + pub configs: WorkspaceProtoConfigs, } impl ProtoLanguageServer { @@ -16,6 +17,7 @@ impl ProtoLanguageServer { client, counter: 0, state: ProtoLanguageState::new(), + configs: WorkspaceProtoConfigs::new(), }); router.event(Self::on_tick); router diff --git a/src/state.rs b/src/state.rs index 901d8c4..fc20510 100644 --- a/src/state.rs +++ b/src/state.rs @@ -15,25 +15,22 @@ use tree_sitter::Node; use walkdir::WalkDir; use crate::{ - formatter::ProtoFormatter, nodekind::NodeKind, parser::{ParsedTree, ProtoParser}, }; -pub struct ProtoLanguageState { +pub struct ProtoLanguageState { documents: Arc>>, trees: Arc>>, - formatter: Option, parser: Arc>, } -impl ProtoLanguageState { +impl ProtoLanguageState { pub fn new() -> Self { ProtoLanguageState { documents: Default::default(), trees: Default::default(), parser: Arc::new(Mutex::new(ProtoParser::new())), - formatter: Default::default(), } } @@ -97,14 +94,6 @@ impl ProtoLanguageState { Self::upsert_content_impl(parser, uri, content, docs, tree) } - pub fn add_formatter(&mut self, formatter: F) { - self.formatter.replace(formatter); - } - - pub fn get_formatter(&self) -> Option<&F> { - self.formatter.as_ref() - } - pub fn add_workspace_folder_async( &mut self, workspace: WorkspaceFolder, diff --git a/src/workspace/definition.rs b/src/workspace/definition.rs index 7d425a0..2e723d4 100644 --- a/src/workspace/definition.rs +++ b/src/workspace/definition.rs @@ -1,10 +1,8 @@ use async_lsp::lsp_types::Location; -use crate::{ - formatter::ProtoFormatter, state::ProtoLanguageState, utils::split_identifier_package, -}; +use crate::{state::ProtoLanguageState, utils::split_identifier_package}; -impl ProtoLanguageState { +impl ProtoLanguageState { pub fn definition(&self, curr_package: &str, identifier: &str) -> Vec { let (mut package, identifier) = split_identifier_package(identifier); if package.is_empty() { @@ -23,7 +21,7 @@ impl ProtoLanguageState { mod test { use insta::assert_yaml_snapshot; - use crate::{formatter::clang::ClangFormatter, state::ProtoLanguageState}; + use crate::state::ProtoLanguageState; #[test] fn workspace_test_definition() { @@ -35,7 +33,7 @@ mod test { let b = include_str!("input/b.proto"); let c = include_str!("input/c.proto"); - let mut state: ProtoLanguageState = ProtoLanguageState::new(); + let mut state: ProtoLanguageState = ProtoLanguageState::new(); state.upsert_file(&a_uri, a.to_owned()); state.upsert_file(&b_uri, b.to_owned()); state.upsert_file(&c_uri, c.to_owned()); diff --git a/src/workspace/hover.rs b/src/workspace/hover.rs index 70bb3e6..bd0913d 100644 --- a/src/workspace/hover.rs +++ b/src/workspace/hover.rs @@ -1,10 +1,8 @@ use async_lsp::lsp_types::MarkedString; -use crate::{ - formatter::ProtoFormatter, state::ProtoLanguageState, utils::split_identifier_package, -}; +use crate::{state::ProtoLanguageState, utils::split_identifier_package}; -impl ProtoLanguageState { +impl ProtoLanguageState { pub fn hover(&self, curr_package: &str, identifier: &str) -> Vec { let (mut package, identifier) = split_identifier_package(identifier); if package.is_empty() { @@ -24,7 +22,7 @@ impl ProtoLanguageState { mod test { use insta::assert_yaml_snapshot; - use crate::{formatter::clang::ClangFormatter, state::ProtoLanguageState}; + use crate::state::ProtoLanguageState; #[test] fn workspace_test_hover() { @@ -36,7 +34,7 @@ mod test { let b = include_str!("input/b.proto"); let c = include_str!("input/c.proto"); - let mut state: ProtoLanguageState = ProtoLanguageState::new(); + let mut state: ProtoLanguageState = ProtoLanguageState::new(); state.upsert_file(&a_uri, a.to_owned()); state.upsert_file(&b_uri, b.to_owned()); state.upsert_file(&c_uri, c.to_owned()); diff --git a/src/workspace/rename.rs b/src/workspace/rename.rs index ea450d6..65700bf 100644 --- a/src/workspace/rename.rs +++ b/src/workspace/rename.rs @@ -1,11 +1,11 @@ -use crate::{formatter::ProtoFormatter, utils::split_identifier_package}; +use crate::utils::split_identifier_package; use std::collections::HashMap; use async_lsp::lsp_types::{TextEdit, Url}; use crate::state::ProtoLanguageState; -impl ProtoLanguageState { +impl ProtoLanguageState { pub fn rename_fields( &self, current_package: &str, @@ -37,7 +37,7 @@ impl ProtoLanguageState { mod test { use insta::assert_yaml_snapshot; - use crate::{formatter::clang::ClangFormatter, state::ProtoLanguageState}; + use crate::state::ProtoLanguageState; #[test] fn test_rename() { @@ -49,7 +49,7 @@ mod test { let b = include_str!("input/b.proto"); let c = include_str!("input/c.proto"); - let mut state: ProtoLanguageState = ProtoLanguageState::new(); + let mut state: ProtoLanguageState = ProtoLanguageState::new(); state.upsert_file(&a_uri, a.to_owned()); state.upsert_file(&b_uri, b.to_owned()); state.upsert_file(&c_uri, c.to_owned()); From 6885a086b133fd976a53a56f7ba89d91f901f7b4 Mon Sep 17 00:00:00 2001 From: coder3101 Date: Sat, 11 Jan 2025 15:59:20 +0530 Subject: [PATCH 2/4] format code --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/formatter/clang.rs | 14 +++++++++++--- src/formatter/mod.rs | 7 ++++++- src/main.rs | 2 +- src/workspace/rename.rs | 2 +- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33bd69f..bd233df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,7 +512,7 @@ dependencies = [ [[package]] name = "protols" -version = "0.9.0" +version = "0.10.0" dependencies = [ "async-lsp", "basic-toml", diff --git a/Cargo.toml b/Cargo.toml index 92cb105..44d97e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "protols" description = "Language server for proto3 files" -version = "0.9.0" +version = "0.10.0" edition = "2021" license = "MIT" homepage = "https://github.com/coder3101/protols" diff --git a/src/formatter/clang.rs b/src/formatter/clang.rs index 6a109ee..e3fdb4f 100644 --- a/src/formatter/clang.rs +++ b/src/formatter/clang.rs @@ -70,7 +70,7 @@ impl ClangFormatter { Self { temp_dir: tempdir().expect("faile to creat temp dir"), path: cmd.to_owned(), - working_dir: wdir.to_owned() + working_dir: wdir.to_owned(), } } @@ -85,7 +85,10 @@ impl ClangFormatter { let mut c = Command::new(self.path.as_str()); c.current_dir(self.working_dir.as_str()); c.stdin(File::open(u).ok()?); - c.args(["--output-replacements-xml", format!("--assume-filename={f}").as_str()]); + c.args([ + "--output-replacements-xml", + format!("--assume-filename={f}").as_str(), + ]); Some(c) } @@ -116,7 +119,12 @@ impl ProtoFormatter for ClangFormatter { self.output_to_textedit(&String::from_utf8_lossy(&output.stdout), content) } - fn format_document_range(&self, r: &Range, filename: &str, content: &str) -> Option> { + fn format_document_range( + &self, + r: &Range, + filename: &str, + content: &str, + ) -> Option> { let p = self.get_temp_file_path(content)?; let start = r.start.line + 1; let end = r.end.line + 1; diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index d5e1993..5cb3af5 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -4,5 +4,10 @@ pub mod clang; pub trait ProtoFormatter: Sized { fn format_document(&self, filename: &str, content: &str) -> Option>; - fn format_document_range(&self, r: &Range, filename: &str, content: &str) -> Option>; + fn format_document_range( + &self, + r: &Range, + filename: &str, + content: &str, + ) -> Option>; } diff --git a/src/main.rs b/src/main.rs index 9fffab0..8a45be5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use server::{ProtoLanguageServer, TickEvent}; use tower::ServiceBuilder; use tracing::Level; +mod config; mod formatter; mod lsp; mod nodekind; @@ -17,7 +18,6 @@ mod server; mod state; mod utils; mod workspace; -mod config; #[tokio::main(flavor = "current_thread")] async fn main() { diff --git a/src/workspace/rename.rs b/src/workspace/rename.rs index fb02cdd..46bd91d 100644 --- a/src/workspace/rename.rs +++ b/src/workspace/rename.rs @@ -99,7 +99,7 @@ mod test { let b = include_str!("input/b.proto"); let c = include_str!("input/c.proto"); - let mut state: ProtoLanguageState = ProtoLanguageState::new(); + let mut state: ProtoLanguageState = ProtoLanguageState::new(); state.upsert_file(&a_uri, a.to_owned()); state.upsert_file(&b_uri, b.to_owned()); state.upsert_file(&c_uri, c.to_owned()); From 565a51e9ba321ef1b2eec88c287b82eec49d7e0d Mon Sep 17 00:00:00 2001 From: coder3101 Date: Sat, 11 Jan 2025 16:19:57 +0530 Subject: [PATCH 3/4] update readme --- README.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fdf348..51a3746 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,18 @@ - โœ… Rename Symbols - โœ… Find references -## ๐Ÿš€ Getting Started +## Table of Contents + +- [Installation](#installation) +- [Configuration](#configuration) + - [Basic Configuration](#basic-configuration) + - [Experimental Configuration](#experimental-configuration) + - [Formatter Configuration](#formatter-configuration) +- [Usage](#usage) +- [Contributing](#contributing) +- [License](#license) + +--- ### Installation @@ -41,6 +52,57 @@ require'lspconfig'.protols.setup{} You can use the [Protobuf Language Support](https://marketplace.visualstudio.com/items?itemName=ianandhum.protobuf-support) extension, which leverages this LSP under the hood. > **Note:** This extension is [open source](https://github.com/ianandhum/vscode-protobuf-support) but is not maintained by us. +You can install **protols** via your preferred method, such as downloading the binary or building from source. Hereโ€™s how to get started with the simplest method: + + +--- + +## Configuration + +Protols is configured using a `protols.toml` file. This configuration file can be placed in any directory, and **protols** will look for the closest file by recursively searching through parent directories. + +Hereโ€™s a sample configuration: + +### Sample `protols.toml` + +```toml +[config] # Base configuration; these are considered stable and should not change +include_paths = ["foobar", "bazbaaz"] # Include paths to look for protofiles during parsing +disable_parse_diagnostics = true # Disable diagnostics for parsing + +[config.experimental] # Experimental configuration; this should be considered unsafe and not fully tested +use_protoc_diagnostics = true # Use diagnostics from protoc + +[formatter] # Formatter specific configuration +clang_format_path = "/usr/bin/clang-format" # clang-format binary to execute in formatting +``` + +### Configuration Sections + +#### Basic Configuration + +The `[config]` section contains stable settings that should generally remain unchanged. + +- `include_paths`: List the directories where your `.proto` files are located. +- `disable_parse_diagnostics`: Set to `true` to disable parsing diagnostics. + +#### Experimental Configuration + +The `[config.experimental]` section contains settings that are still under development or are not fully tested. + +- `use_protoc_diagnostics`: If set to `true`, this will enable diagnostics from the `protoc` compiler. + +#### Formatter Configuration + +The `[formatter]` section configures how the formatting is done. + +- `clang_format_path`: Path to the `clang-format` binary used for formatting `.proto` files. + +### Multiple Configuration Files + +You can use multiple `protols.toml` files across different directories, and **protols** will use the closest configuration file found by traversing the parent directories. + +--- ## ๐Ÿ› ๏ธ Usage @@ -78,4 +140,30 @@ Allows user defined types like messages and enums can be checked for references. --- -Protols is designed to supercharge your workflow with **proto** files. We welcome contributions and feedback from the community! Feel free to check out the [repository](https://github.com/coder3101/protols) and join in on improving this tool! ๐ŸŽ‰ +## Contributing + +We welcome contributions from the community! If youโ€™d like to help improve **protols**, hereโ€™s how you can get started: + +1. Fork the repository and clone it to your machine. +2. Make your changes in a new branch. +3. Run the tests to ensure everything works properly. +4. Open a pull request with a description of the changes. + +### Setting Up Locally + +1. Clone the repository: + ```bash + git clone https://github.com/coder3101/protols.git + cd protols + ``` + +2. Build and test your changes: + ```bash + cargo build + cargo test + ``` +--- + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. From e7a98f30192c7d46a7809d5300a8ee98643ea713 Mon Sep 17 00:00:00 2001 From: coder3101 Date: Sat, 11 Jan 2025 16:24:35 +0530 Subject: [PATCH 4/4] update readme --- README.md | 113 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 51a3746..06e6fca 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,77 @@ # Protols - Protobuf Language Server -[![Crates.io](https://img.shields.io/crates/v/protols.svg)](https://crates.io/crates/protols) +[![Crates.io](https://img.shields.io/crates/v/protols.svg)](https://crates.io/crates/protols) [![Build and Test](https://github.com/coder3101/protols/actions/workflows/ci.yml/badge.svg)](https://github.com/coder3101/protols/actions/workflows/ci.yml) -**Protols** is an open-source Language Server Protocol (LSP) for **proto** files, powered by the robust and efficient [tree-sitter](https://tree-sitter.github.io/tree-sitter/) parser. With Protols, you get powerful code assistance for protobuf files, including auto-completion, syntax diagnostics, and more. +**Protols** is an open-source, feature-rich [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/) for **Protocol Buffers (proto)** files. Powered by the efficient [tree-sitter](https://tree-sitter.github.io/tree-sitter/) parser, Protols offers intelligent code assistance for protobuf development, including features like auto-completion, diagnostics, formatting, and more. -![](./assets/protols.mov) +![Protols Demo](./assets/protols.mov) ## โœจ Features -- โœ… Code Completion -- โœ… Diagnostics -- โœ… Document Symbols -- โœ… Code Formatting -- โœ… Go to Definition -- โœ… Hover Information -- โœ… Rename Symbols -- โœ… Find references +- โœ… **Code Completion**: Auto-complete messages, enums, and keywords in your `.proto` files. +- โœ… **Diagnostics**: Syntax errors detected with the tree-sitter parser. +- โœ… **Document Symbols**: Navigate and view all symbols, including messages and enums. +- โœ… **Code Formatting**: Format `.proto` files using `clang-format` for a consistent style. +- โœ… **Go to Definition**: Jump to the definition of symbols like messages or enums. +- โœ… **Hover Information**: Get detailed information and documentation on hover. +- โœ… **Rename Symbols**: Rename protobuf symbols and propagate changes across the codebase. +- โœ… **Find References**: Find where messages, enums, and fields are used throughout the codebase. + +--- ## Table of Contents - [Installation](#installation) + - [For Neovim](#for-neovim) + - [For Visual Studio Code](#for-visual-studio-code) - [Configuration](#configuration) - [Basic Configuration](#basic-configuration) - [Experimental Configuration](#experimental-configuration) - [Formatter Configuration](#formatter-configuration) + - [Multiple Configuration Files](#multiple-configuration-files) - [Usage](#usage) + - [Code Completion](#code-completion) + - [Diagnostics](#diagnostics) + - [Code Formatting](#code-formatting) + - [Document Symbols](#document-symbols) + - [Go to Definition](#go-to-definition) + - [Hover Information](#hover-information) + - [Rename Symbols](#rename-symbols) + - [Find References](#find-references) - [Contributing](#contributing) + - [Setting Up Locally](#setting-up-locally) - [License](#license) --- -### Installation +## ๐Ÿš€ Installation -#### For Neovim +### For Neovim -You can install [protols with mason.nvim](https://github.com/mason-org/mason-registry/blob/main/packages/protols/package.yaml) or directly from crates.io with: +You can install **Protols** via [mason.nvim](https://github.com/mason-org/mason-registry/blob/main/packages/protols/package.yaml), or install it directly from [crates.io](https://crates.io/crates/protols): ```bash cargo install protols ``` -Then, configure it with [`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#protols): +Then, configure it in your `init.lua` using [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig): ```lua require'lspconfig'.protols.setup{} ``` -#### For Visual Studio Code - -You can use the [Protobuf Language Support](https://marketplace.visualstudio.com/items?itemName=ianandhum.protobuf-support) extension, which leverages this LSP under the hood. +### For Visual Studio Code -> **Note:** This extension is [open source](https://github.com/ianandhum/vscode-protobuf-support) but is not maintained by us. -You can install **protols** via your preferred method, such as downloading the binary or building from source. Hereโ€™s how to get started with the simplest method: +If you're using Visual Studio Code, you can install the [Protobuf Language Support](https://marketplace.visualstudio.com/items?itemName=ianandhum.protobuf-support) extension, which uses this LSP under the hood. +> **Note**: This extension is [open source](https://github.com/ianandhum/vscode-protobuf-support), but is not officially maintained by us. --- -## Configuration +## โš™๏ธ Configuration -Protols is configured using a `protols.toml` file. This configuration file can be placed in any directory, and **protols** will look for the closest file by recursively searching through parent directories. - -Hereโ€™s a sample configuration: +Protols is configured using a `protols.toml` file, which you can place in any directory. **Protols** will search for the closest configuration file by recursively traversing the parent directories. ### Sample `protols.toml` @@ -83,87 +93,94 @@ clang_format_path = "/usr/bin/clang-format" # clang-format binary to execute in The `[config]` section contains stable settings that should generally remain unchanged. -- `include_paths`: List the directories where your `.proto` files are located. -- `disable_parse_diagnostics`: Set to `true` to disable parsing diagnostics. +- `include_paths`: Directories to search for `.proto` files. +- `disable_parse_diagnostics`: Set to `true` to disable diagnostics during parsing. #### Experimental Configuration -The `[config.experimental]` section contains settings that are still under development or are not fully tested. +The `[config.experimental]` section contains settings that are in development or not fully tested. -- `use_protoc_diagnostics`: If set to `true`, this will enable diagnostics from the `protoc` compiler. +- `use_protoc_diagnostics`: Enable diagnostics from the `protoc` compiler when set to `true`. #### Formatter Configuration -The `[formatter]` section configures how the formatting is done. +The `[formatter]` section allows configuration for code formatting. -- `clang_format_path`: Path to the `clang-format` binary used for formatting `.proto` files. +- `clang_format_path`: Specify the path to the `clang-format` binary. ### Multiple Configuration Files -You can use multiple `protols.toml` files across different directories, and **protols** will use the closest configuration file found by traversing the parent directories. +You can place multiple `protols.toml` files across different directories. **Protols** will use the closest configuration file by searching up the directory tree. --- ## ๐Ÿ› ๏ธ Usage +Protols offers a rich set of features to enhance your `.proto` file editing experience. + ### Code Completion -Protols provides intelligent autocompletion for messages, enums, and proto3 keywords within the current package. +**Protols** offers intelligent autocompletion for messages, enums, and proto3 keywords within the current package. Simply start typing, and Protols will suggest valid completions. ### Diagnostics -Diagnostics are powered by the tree-sitter parser, which catches syntax errors but does not utilize `protoc` for more advanced error reporting. +Syntax errors are caught by the tree-sitter parser, which highlights issues directly in your editor. For more advanced error reporting, the LSP can be configured to use `protoc` diagnostics. ### Code Formatting -Formatting is enabled if [clang-format](https://clang.llvm.org/docs/ClangFormat.html) is available. You can control the [formatting style](https://clang.llvm.org/docs/ClangFormatStyleOptions.html) by placing a `.clang-format` file in the root of your workspace. Both document and range formatting are supported. +Format your `.proto` files using `clang-format`. To customize the formatting style, add a `.clang-format` file to the root of your project. Both document and range formatting are supported. ### Document Symbols -Provides symbols for the entire document, including nested symbols, messages, and enums. +Protols provides a list of symbols in the current document, including nested symbols such as messages and enums. This allows for easy navigation and reference. ### Go to Definition -Jump to the definition of any custom symbol, even across package boundaries. +Jump directly to the definition of any custom symbol, including those in other files or packages. This feature works across package boundaries. ### Hover Information -Displays comments and documentation for protobuf symbols on hover. Works seamlessly across package boundaries. +Hover over any symbol to get detailed documentation and comments associated with it. This works seamlessly across different packages and namespaces. ### Rename Symbols -Allows renaming of symbols like messages and enums, along with all their usages across packages. Currently, renaming fields within symbols is not supported directly. +Rename symbols like messages or enums, and Propagate the changes throughout the codebase. Currently, field renaming within symbols is not supported. ### Find References -Allows user defined types like messages and enums can be checked for references. Nested fields are completely supported. +Find all references to user-defined types like messages or enums. Nested fields are fully supported, making it easier to track symbol usage across your project. --- -## Contributing +## ๐Ÿค Contributing -We welcome contributions from the community! If youโ€™d like to help improve **protols**, hereโ€™s how you can get started: +We welcome contributions from developers of all experience levels! To get started: -1. Fork the repository and clone it to your machine. -2. Make your changes in a new branch. -3. Run the tests to ensure everything works properly. -4. Open a pull request with a description of the changes. +1. **Fork** the repository and clone it to your local machine. +2. Create a **new branch** for your feature or fix. +3. Run the tests to ensure everything works as expected. +4. **Open a pull request** with a detailed description of your changes. ### Setting Up Locally 1. Clone the repository: + ```bash git clone https://github.com/coder3101/protols.git cd protols ``` -2. Build and test your changes: +2. Build and test the project: + ```bash cargo build cargo test ``` + --- -## License +## ๐Ÿ“„ License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. + +---