Skip to content

protols: publish to crates.io #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 22, 2024
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
21 changes: 11 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
[package]
name = "protols"
description = "Language server for proto3 files"
version = "0.1.0"
edition = "2021"
license = "MIT"
homepage = "https://github.com/coder3101/protols"
repository = "https://github.com/coder3101/protols"
readme = "README.md"
keywords = ["lsp", "proto3"]

[dependencies]
async-lsp = { version = "0.2.0", features = ["tokio"] }
Expand All @@ -11,6 +17,6 @@ tokio-util = { version = "0.7.11", features = ["compat"] }
tower = "0.4.13"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
tree-sitter-proto = { git = "https://github.com/coder3101/tree-sitter-proto", branch = "main" }
tree-sitter = "0.22.6"
tracing-appender = "0.2.3"
protols-tree-sitter-proto = "0.1.0"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# protols
A Language Server for **proto3** files. It only uses tree-sitter parser for all operations and always runs in **single file mode**.
A Language Server for **proto3** files. It uses tree-sitter parser for all operations and always runs in **single file mode**.

## Features
- [x] Hover
Expand All @@ -9,7 +9,7 @@ A Language Server for **proto3** files. It only uses tree-sitter parser for all

## Installation and testing

Clone the repository and run `cargo install --path .` to install locally in your `~/.cargo/bin` and the below to your `init.lua` until we start shipping this via Mason.
Clone the repository and run `cargo install protols` to install locally in your `~/.cargo/bin` and the below to your `init.lua` until we start shipping this via Mason.

```lua
local client = vim.lsp.start_client({
Expand Down
2 changes: 1 addition & 1 deletion src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl LanguageServer for ServerState {
let (cname, version) = params
.client_info
.as_ref()
.map(|c| (c.name.as_str(), c.version.as_ref().map(|x| x.as_str())))
.map(|c| (c.name.as_str(), c.version.as_deref()))
.unwrap_or(("<unknown>", None));

let cversion = version.unwrap_or("<unknown>");
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use async_lsp::server::LifecycleLayer;
use async_lsp::tracing::TracingLayer;
use server::{ServerState, TickEvent};
use tower::ServiceBuilder;
use tracing::{info, Level};
use tracing::Level;

mod lsp;
mod parser;
Expand Down
207 changes: 197 additions & 10 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub struct ParsedTree {
impl ProtoParser {
pub fn new() -> Self {
let mut parser = tree_sitter::Parser::new();
if let Err(e) = parser.set_language(&tree_sitter_proto::language()) {
if let Err(e) = parser.set_language(&protols_tree_sitter_proto::language()) {
panic!("failed to set ts language parser {:?}", e);
}
Self { parser }
Expand All @@ -33,7 +33,6 @@ impl ProtoParser {

impl ParsedTree {
fn walk_and_collect_kinds<'a>(
&self,
cursor: &mut TreeCursor<'a>,
kinds: &[&str],
) -> Vec<Node<'a>> {
Expand All @@ -47,7 +46,7 @@ impl ParsedTree {
}

if cursor.goto_first_child() {
v.extend(self.walk_and_collect_kinds(cursor, kinds));
v.extend(Self::walk_and_collect_kinds(cursor, kinds));
cursor.goto_parent();
}

Expand All @@ -59,14 +58,14 @@ impl ParsedTree {
v
}

fn advance_cursor_to<'a>(&self, cursor: &mut TreeCursor<'a>, nid: usize) -> bool {
fn advance_cursor_to(cursor: &mut TreeCursor<'_>, nid: usize) -> bool {
loop {
let node = cursor.node();
if node.id() == nid {
return true;
}
if cursor.goto_first_child() {
if self.advance_cursor_to(cursor, nid) {
if Self::advance_cursor_to(cursor, nid) {
return true;
}
cursor.goto_parent();
Expand All @@ -83,7 +82,7 @@ impl ParsedTree {

info!("Looking for node with id: {nid}");

self.advance_cursor_to(&mut cursor, nid);
Self::advance_cursor_to(&mut cursor, nid);
if !cursor.goto_parent() {
return None;
}
Expand All @@ -108,12 +107,12 @@ impl ParsedTree {
break;
}
}
return if comments.len() != 0 {
if !comments.is_empty() {
comments.reverse();
Some(comments.join("\n"))
} else {
None
};
}
}
}

Expand All @@ -132,7 +131,7 @@ impl ParsedTree {

pub fn find_childrens_by_kinds(&self, kinds: &[&str]) -> Vec<Node> {
let mut cursor = self.tree.root_node().walk();
self.walk_and_collect_kinds(&mut cursor, kinds)
Self::walk_and_collect_kinds(&mut cursor, kinds)
}

pub fn definition(
Expand Down Expand Up @@ -170,7 +169,7 @@ impl ParsedTree {
.into_iter()
.filter(|n| n.utf8_text(content.as_ref()).expect("utf-8 parse error") == text)
.filter_map(|n| self.find_preceeding_comments(n.id(), content.as_ref()))
.map(|s| MarkedString::String(s))
.map(MarkedString::String)
.collect(),
None => vec![],
}
Expand Down Expand Up @@ -198,3 +197,191 @@ impl ParsedTree {
}
}
}

#[cfg(test)]
mod test {
use async_lsp::lsp_types::{DiagnosticSeverity, MarkedString, Position, Range, Url};

use super::ProtoParser;

#[test]
fn test_find_children_by_kind() {
let contents = r#"syntax = "proto3";

package com.book;

message Book {

message Author {
string name = 1;
string country = 2;
};
// This is a multi line comment on the field name
// Of a message called Book
int64 isbn = 1;
string title = 2;
Author author = 3;
}
"#;
let parsed = ProtoParser::new().parse(contents);
assert!(parsed.is_some());
let tree = parsed.unwrap();
let nodes = tree.find_childrens_by_kinds(&["message_name"]);

assert_eq!(nodes.len(), 2);

let names: Vec<_> = nodes
.into_iter()
.map(|n| n.utf8_text(contents.as_ref()).unwrap())
.collect();
assert_eq!(names[0], "Book");
assert_eq!(names[1], "Author");
}

#[test]
fn test_collect_parse_error() {
let url = "file://foo/bar.proto";
let contents = r#"syntax = "proto3";

package com.book;

message Book {
message Author {
string name;
string country = 2;
};
}
"#;
let parsed = ProtoParser::new().parse(contents);
assert!(parsed.is_some());
let tree = parsed.unwrap();
let diagnostics = tree.collect_parse_errors(&url.parse().unwrap());

assert_eq!(diagnostics.uri, Url::parse(url).unwrap());
assert_eq!(diagnostics.diagnostics.len(), 1);

let error = &diagnostics.diagnostics[0];
assert_eq!(error.severity, Some(DiagnosticSeverity::ERROR));
assert_eq!(error.source, Some("protols".to_owned()));
assert_eq!(error.message, "Syntax error");
assert_eq!(
error.range,
Range {
start: Position {
line: 6,
character: 8
},
end: Position {
line: 6,
character: 19
}
}
);
}

#[test]
fn test_hover() {
let posbook = Position {
line: 5,
character: 9,
};
let posinvalid = Position {
line: 0,
character: 1,
};
let posauthor = Position {
line: 11,
character: 14,
};
let contents = r#"syntax = "proto3";

package com.book;

// A Book is book
message Book {

// This is represents author
// A author is a someone who writes books
//
// Author has a name and a country where they were born
message Author {
string name = 1;
string country = 2;
};
}
"#;
let parsed = ProtoParser::new().parse(contents);
assert!(parsed.is_some());
let tree = parsed.unwrap();
let res = tree.hover(&posbook, contents);

assert_eq!(res.len(), 1);
assert_eq!(res[0], MarkedString::String("A Book is book".to_owned()));

let res = tree.hover(&posinvalid, contents);
assert_eq!(res.len(), 0);

let res = tree.hover(&posauthor, contents);
assert_eq!(res.len(), 1);
assert_eq!(
res[0],
MarkedString::String(
r#"This is represents author
A author is a someone who writes books

Author has a name and a country where they were born"#
.to_owned()
)
);
}

#[test]
fn test_goto_definition() {
let url = "file://foo/bar.proto";
let posinvalid = Position {
line: 0,
character: 1,
};
let posauthor = Position {
line: 10,
character: 5,
};
let contents = r#"syntax = "proto3";

package com.book;

message Book {
message Author {
string name = 1;
string country = 2;
};

Author author = 1;
string isbn = 2;
}
"#;
let parsed = ProtoParser::new().parse(contents);
assert!(parsed.is_some());
let tree = parsed.unwrap();
let res = tree.definition(&posauthor, &url.parse().unwrap(), contents);

assert_eq!(res.len(), 1);
assert_eq!(res[0].uri, Url::parse(url).unwrap());
assert_eq!(
res[0].range,
Range {
start: Position {
line: 5,
character: 12
},
end: Position {
line: 5,
character: 18
},
}
);

let res = tree.definition(&posinvalid, &url.parse().unwrap(), contents);
assert_eq!(res.len(), 0);
}
}