Skip to content

Commit

Permalink
protols: publish to crates.io (#6)
Browse files Browse the repository at this point in the history
Closes #5

---------

Co-authored-by: coder3101 <[email protected]>
  • Loading branch information
asharkhan3101 and coder3101 authored Jun 22, 2024
1 parent 59dc1ab commit 2ebbb0f
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 25 deletions.
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);
}
}

0 comments on commit 2ebbb0f

Please sign in to comment.