Skip to content

Commit a0f6e51

Browse files
authored
feat: Read import path and produce import diagnostics (#49)
1 parent f5cda97 commit a0f6e51

16 files changed

+257
-170
lines changed

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
[![Crates.io](https://img.shields.io/crates/v/protols.svg)](https://crates.io/crates/protols)
44
[![Build and Test](https://github.com/coder3101/protols/actions/workflows/ci.yml/badge.svg)](https://github.com/coder3101/protols/actions/workflows/ci.yml)
55

6+
**WARNING** : Master branch is undergoing a massive refactoring, please use last relesed tag instead.
7+
68
**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.
79

810
![Protols Demo](./assets/protols.mov)
@@ -71,7 +73,7 @@ If you're using Visual Studio Code, you can install the [Protobuf Language Suppo
7173

7274
## ⚙️ Configuration
7375

74-
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.
76+
Protols is configured using a `protols.toml` file, which you can place in any directory.
7577

7678
### Sample `protols.toml`
7779

@@ -108,10 +110,6 @@ The `[formatter]` section allows configuration for code formatting.
108110

109111
- `clang_format_path`: Specify the path to the `clang-format` binary.
110112

111-
### Multiple Configuration Files
112-
113-
You can place multiple `protols.toml` files across different directories. **Protols** will use the closest configuration file by searching up the directory tree.
114-
115113
---
116114

117115
## 🛠️ Usage

protols.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[config]
2+
include_paths = ["sample", "src/workspace/input"]

src/config/mod.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ fn default_clang_format_path() -> String {
66
"clang-format".to_string()
77
}
88

9-
#[derive(Serialize, Deserialize, Debug, Clone)]
9+
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
1010
#[serde(default)]
1111
pub struct ProtolsConfig {
1212
pub config: Config,
@@ -34,15 +34,6 @@ pub struct ExperimentalConfig {
3434
pub use_protoc_diagnostics: bool,
3535
}
3636

37-
impl Default for ProtolsConfig {
38-
fn default() -> Self {
39-
Self {
40-
config: Config::default(),
41-
formatter: FormatterConfig::default(),
42-
}
43-
}
44-
}
45-
4637
impl Default for FormatterConfig {
4738
fn default() -> Self {
4839
Self {

src/config/workspace.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
collections::{HashMap, HashSet},
3-
path::Path,
3+
path::{Path, PathBuf},
44
};
55

66
use async_lsp::lsp_types::{Url, WorkspaceFolder};
@@ -61,6 +61,21 @@ impl WorkspaceProtoConfigs {
6161
.iter()
6262
.find(|&k| upath.starts_with(k.to_file_path().unwrap()))
6363
}
64+
65+
pub fn get_include_paths(&self, uri: &Url) -> Option<Vec<PathBuf>> {
66+
let c = self.get_config_for_uri(uri)?;
67+
let w = self.get_workspace_for_uri(uri)?.to_file_path().ok()?;
68+
let mut ipath: Vec<PathBuf> = c
69+
.config
70+
.include_paths
71+
.iter()
72+
.map(PathBuf::from)
73+
.map(|p| if p.is_relative() { w.join(p) } else { p })
74+
.collect();
75+
76+
ipath.push(w.to_path_buf());
77+
Some(ipath)
78+
}
6479
}
6580

6681
#[cfg(test)]

src/lsp.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
use std::ops::ControlFlow;
2-
use std::sync::mpsc;
3-
use std::thread;
42
use std::{collections::HashMap, fs::read_to_string};
53
use tracing::{error, info};
64

@@ -12,11 +10,12 @@ use async_lsp::lsp_types::{
1210
DocumentSymbolParams, DocumentSymbolResponse, FileOperationFilter, FileOperationPattern,
1311
FileOperationPatternKind, FileOperationRegistrationOptions, GotoDefinitionParams,
1412
GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability,
15-
InitializeParams, InitializeResult, Location, OneOf, PrepareRenameResponse, ProgressParams,
16-
ReferenceParams, RenameFilesParams, RenameOptions, RenameParams, ServerCapabilities,
17-
ServerInfo, TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind,
18-
TextEdit, Url, WorkspaceEdit, WorkspaceFileOperationsServerCapabilities,
19-
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
13+
InitializeParams, InitializeResult, Location, OneOf, PrepareRenameResponse,
14+
ReferenceParams, RenameFilesParams, RenameOptions, RenameParams,
15+
ServerCapabilities, ServerInfo, TextDocumentPositionParams, TextDocumentSyncCapability,
16+
TextDocumentSyncKind, TextEdit, Url, WorkspaceEdit,
17+
WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
18+
WorkspaceServerCapabilities,
2019
};
2120
use async_lsp::{LanguageClient, LanguageServer, ResponseError};
2221
use futures::future::BoxFuture;
@@ -377,15 +376,19 @@ impl LanguageServer for ProtoLanguageServer {
377376
let uri = params.text_document.uri;
378377
let content = params.text_document.text;
379378

380-
let Some(diagnostics) = self.state.upsert_file(&uri, content) else {
379+
let Some(ipath) = self.configs.get_include_paths(&uri) else {
381380
return ControlFlow::Continue(());
382381
};
383382

384-
let Some(ws) = self.configs.get_config_for_uri(&uri) else {
383+
let Some(diagnostics) = self.state.upsert_file(&uri, content.clone(), &ipath) else {
385384
return ControlFlow::Continue(());
386385
};
387386

388-
if !ws.config.disable_parse_diagnostics {
387+
let Some(pconf) = self.configs.get_config_for_uri(&uri) else {
388+
return ControlFlow::Continue(());
389+
};
390+
391+
if !pconf.config.disable_parse_diagnostics {
389392
if let Err(e) = self.client.publish_diagnostics(diagnostics) {
390393
error!(error=%e, "failed to publish diagnostics")
391394
}
@@ -397,7 +400,11 @@ impl LanguageServer for ProtoLanguageServer {
397400
let uri = params.text_document.uri;
398401
let content = params.content_changes[0].text.clone();
399402

400-
let Some(diagnostics) = self.state.upsert_file(&uri, content) else {
403+
let Some(ipath) = self.configs.get_include_paths(&uri) else {
404+
return ControlFlow::Continue(());
405+
};
406+
407+
let Some(diagnostics) = self.state.upsert_file(&uri, content, &ipath) else {
401408
return ControlFlow::Continue(());
402409
};
403410

@@ -419,7 +426,7 @@ impl LanguageServer for ProtoLanguageServer {
419426
if let Ok(uri) = Url::from_file_path(&file.uri) {
420427
// Safety: The uri is always a file type
421428
let content = read_to_string(uri.to_file_path().unwrap()).unwrap_or_default();
422-
self.state.upsert_content(&uri, content);
429+
self.state.upsert_content(&uri, content, &[]);
423430
} else {
424431
error!(uri=%file.uri, "failed parse uri");
425432
}

src/parser/diagnostics.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
use async_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams, Range};
1+
use async_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Range};
22

33
use crate::{nodekind::NodeKind, utils::ts_to_lsp_position};
44

55
use super::ParsedTree;
66

77
impl ParsedTree {
8-
pub fn collect_parse_errors(&self) -> PublishDiagnosticsParams {
9-
let diagnostics = self
10-
.find_all_nodes(NodeKind::is_error)
8+
pub fn collect_parse_diagnostics(&self) -> Vec<Diagnostic> {
9+
self.find_all_nodes(NodeKind::is_error)
1110
.into_iter()
1211
.map(|n| Diagnostic {
1312
range: Range {
@@ -19,12 +18,24 @@ impl ParsedTree {
1918
message: "Syntax error".to_string(),
2019
..Default::default()
2120
})
22-
.collect();
23-
PublishDiagnosticsParams {
24-
uri: self.uri.clone(),
25-
diagnostics,
26-
version: None,
27-
}
21+
.collect()
22+
}
23+
24+
pub fn collect_import_diagnostics(
25+
&self,
26+
content: &[u8],
27+
import: Vec<String>,
28+
) -> Vec<Diagnostic> {
29+
self.get_import_path_range(content, import)
30+
.into_iter()
31+
.map(|r| Diagnostic {
32+
range: r,
33+
severity: Some(DiagnosticSeverity::ERROR),
34+
source: Some(String::from("protols")),
35+
message: "failed to find proto file".to_string(),
36+
..Default::default()
37+
})
38+
.collect()
2839
}
2940
}
3041

@@ -42,12 +53,12 @@ mod test {
4253

4354
let parsed = ProtoParser::new().parse(url.clone(), contents);
4455
assert!(parsed.is_some());
45-
assert_yaml_snapshot!(parsed.unwrap().collect_parse_errors());
56+
assert_yaml_snapshot!(parsed.unwrap().collect_parse_diagnostics());
4657

4758
let contents = include_str!("input/test_collect_parse_error2.proto");
4859

4960
let parsed = ProtoParser::new().parse(url.clone(), contents);
5061
assert!(parsed.is_some());
51-
assert_yaml_snapshot!(parsed.unwrap().collect_parse_errors());
62+
assert_yaml_snapshot!(parsed.unwrap().collect_parse_diagnostics());
5263
}
5364
}

src/parser/docsymbol.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ impl DocumentSymbolTreeBuilder {
1919
}
2020

2121
pub(super) fn maybe_pop(&mut self, node: usize) {
22-
let should_pop = self.stack.last().map_or(false, |(n, _)| *n == node);
22+
let should_pop = self.stack.last().is_some_and(|(n, _)| *n == node);
2323
if should_pop {
2424
let (_, explored) = self.stack.pop().unwrap();
2525
if let Some((_, parent)) = self.stack.last_mut() {
Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
---
22
source: src/parser/diagnostics.rs
3-
expression: parsed.unwrap().collect_parse_errors()
3+
expression: parsed.unwrap().collect_parse_diagnostics()
4+
snapshot_kind: text
45
---
5-
uri: "file://foo/bar.proto"
6-
diagnostics:
7-
- range:
8-
start:
9-
line: 6
10-
character: 8
11-
end:
12-
line: 6
13-
character: 19
14-
severity: 1
15-
source: protols
16-
message: Syntax error
6+
- range:
7+
start:
8+
line: 6
9+
character: 8
10+
end:
11+
line: 6
12+
character: 19
13+
severity: 1
14+
source: protols
15+
message: Syntax error
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
source: src/parser/diagnostics.rs
3-
expression: parsed.unwrap().collect_parse_errors()
3+
expression: parsed.unwrap().collect_parse_diagnostics()
4+
snapshot_kind: text
45
---
5-
uri: "file://foo/bar.proto"
6-
diagnostics: []
6+
[]

0 commit comments

Comments
 (0)