support document symbols request

This commit is contained in:
Noah Santschi-Cooney 2022-04-16 00:21:10 +01:00
parent f66f56603a
commit 1529460a5c
No known key found for this signature in database
GPG key ID: 3B22282472C8AE48
5 changed files with 420 additions and 262 deletions

427
server/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -134,7 +134,7 @@ pub mod error {
impl From<CycleError> for Diagnostic {
fn from(e: CycleError) -> Diagnostic {
Diagnostic {
severity: Some(DiagnosticSeverity::Error),
severity: Some(DiagnosticSeverity::ERROR),
range: Range::new(Position::new(0, 0), Position::new(0, 500)),
source: Some(consts::SOURCE.into()),
message: e.into(),

View file

@ -73,11 +73,11 @@ impl<'a, T: opengl::ShaderValidator + ?Sized> DiagnosticsParser<'a, T> {
let severity = match diagnostic_capture.name("severity") {
Some(c) => match c.as_str().to_lowercase().as_str() {
"error" => DiagnosticSeverity::Error,
"warning" => DiagnosticSeverity::Warning,
_ => DiagnosticSeverity::Information,
"error" => DiagnosticSeverity::ERROR,
"warning" => DiagnosticSeverity::WARNING,
_ => DiagnosticSeverity::INFORMATION,
},
_ => DiagnosticSeverity::Information,
_ => DiagnosticSeverity::INFORMATION,
};
let origin = match diagnostic_capture.name("filepath") {

View file

@ -1,4 +1,5 @@
#![feature(once_cell)]
#![feature(option_get_or_insert_default)]
use merge_views::FilialTuple;
use rust_lsp::jsonrpc::{method_types::*, *};
@ -517,6 +518,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
let capabilities = ServerCapabilities {
definition_provider: Some(OneOf::Left(true)),
references_provider: Some(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)),
document_link_provider: Some(DocumentLinkOptions {
resolve_provider: None,
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
@ -529,7 +531,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
open_close: Some(true),
will_save: None,
will_save_wait_until: None,
change: Some(TextDocumentSyncKind::Full),
change: Some(TextDocumentSyncKind::FULL),
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { include_text: Some(true) })),
})),
..ServerCapabilities::default()
@ -661,7 +663,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
.send_notification(
ShowMessage::METHOD,
ShowMessageParams {
typ: MessageType::Info,
typ: MessageType::INFO,
message: format!("Command {} executed successfully.", params.command),
},
)
@ -674,7 +676,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
.send_notification(
ShowMessage::METHOD,
ShowMessageParams {
typ: MessageType::Error,
typ: MessageType::ERROR,
message: format!("Failed to execute `{}`. Reason: {}", params.command, err),
},
)
@ -751,11 +753,38 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
completable.complete(Err(Self::error_not_available(())));
}
fn document_symbols(&mut self, _: DocumentSymbolParams, completable: LSCompletable<Vec<SymbolInformation>>) {
completable.complete(Err(Self::error_not_available(())));
fn document_symbols(&mut self, params: DocumentSymbolParams, completable: LSCompletable<DocumentSymbolResponse>) {
logging::slog_with_trace_id(|| {
let path = PathBuf::from_url(params.text_document.uri);
if !path.starts_with(&self.root) {
return;
}
let parser = &mut self.tree_sitter.borrow_mut();
let parser_ctx = match navigation::ParserContext::new(parser, &path) {
Ok(ctx) => ctx,
Err(e) => {
return completable.complete(Err(MethodError {
code: 42069,
message: format!("error building parser context: error={}, path={:?}", e, path),
data: (),
}))
}
};
match parser_ctx.list_symbols(&path) {
Ok(symbols) => completable.complete(Ok(DocumentSymbolResponse::from(symbols.unwrap_or_default()))),
Err(e) => {
return completable.complete(Err(MethodError {
code: 42069,
message: format!("error finding definitions: error={}, path={:?}", e, path),
data: (),
}))
}
}
});
}
fn workspace_symbols(&mut self, _: WorkspaceSymbolParams, completable: LSCompletable<Vec<SymbolInformation>>) {
fn workspace_symbols(&mut self, _: WorkspaceSymbolParams, completable: LSCompletable<DocumentSymbolResponse>) {
completable.complete(Err(Self::error_not_available(())));
}

View file

@ -1,13 +1,60 @@
use std::{fs::read_to_string, path::Path};
use std::{collections::HashMap, fs::read_to_string, path::Path, vec};
use anyhow::Result;
use rust_lsp::lsp_types::{Location, Position, Range};
use rust_lsp::lsp_types::{DocumentSymbol, Location, Position, Range, SymbolKind};
use slog_scope::{debug, info, trace};
use tree_sitter::{Node, Parser, Point, Query, QueryCursor, Tree};
use url::Url;
use crate::linemap::LineMap;
#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)]
struct SymbolName(String);
impl SymbolName {
// construct a new SymbolName from a node and its node ID for overload disambiguating.
fn new(node: &Node, source: &str, node_id: usize) -> Self {
let mut fqname = vec![format!("{}[{}]", node.utf8_text(source.as_bytes()).unwrap(), node_id)];
// first node will always have a parent
let mut prev = *node;
let mut node = node.parent().unwrap();
loop {
match (node.kind(), prev.kind()) {
("function_definition", "compound_statement") => {
let func_ident = node.child_by_field_name("declarator").unwrap().child(0).unwrap();
fqname.push(format!("{}[{}]", func_ident.utf8_text(source.as_bytes()).unwrap(), func_ident.id()));
}
("struct_specifier", "field_declaration_list") => {
let struct_ident = node.child_by_field_name("name").unwrap();
fqname.push(format!("{}[{}]", struct_ident.utf8_text(source.as_bytes()).unwrap(), struct_ident.id()));
}
_ => (),
}
prev = node;
node = match node.parent() {
Some(n) => n,
None => break,
};
}
fqname.reverse();
SymbolName(fqname.join("/"))
}
fn parent(&self) -> Option<Self> {
self.0.rsplit_once('/').map(|(left, _)| SymbolName(left.to_string()))
}
}
impl slog::Value for SymbolName {
fn serialize(&self, record: &slog::Record, key: slog::Key, serializer: &mut dyn slog::Serializer) -> slog::Result {
self.0.serialize(record, key, serializer)
}
}
macro_rules! find_function_def_str {
() => {
r#"
@ -35,24 +82,58 @@ macro_rules! find_function_refs_str {
macro_rules! find_variable_def_str {
() => {
r#"
(
[
(init_declarator
(identifier) @variable)
(parameter_declaration
(identifier) @variable)
(declaration
(identifier) @variable)
[
(init_declarator
(identifier) @variable)
(#match? @variable "^{}$")
]
)
(parameter_declaration
(identifier) @variable)
(declaration
(identifier) @variable)
(#match? @variable "^{}$")
]
"#
};
}
const LIST_SYMBOLS_STR: &str = r#"
; global consts
(declaration
(type_qualifier) @const_qualifier
(init_declarator
(identifier) @const_ident))
(#match? @const_qualifier "^const")
; global uniforms, varyings, struct variables etc
(translation_unit
(declaration
(identifier) @ident))
; #defines
(preproc_def
(identifier) @define_ident)
; function definitions
(function_declarator
(identifier) @func_ident)
; struct definitions
(struct_specifier
(type_identifier) @struct_ident)
; struct fields
(struct_specifier
(field_declaration_list
(field_declaration
[
(field_identifier) @field_ident
(array_declarator
(field_identifier) @field_ident)
])) @field_list)
"#;
pub struct ParserContext<'a> {
source: String,
tree: Tree,
@ -76,6 +157,89 @@ impl<'a> ParserContext<'a> {
})
}
pub fn list_symbols(&self, _path: &Path) -> Result<Option<Vec<DocumentSymbol>>> {
let query = Query::new(tree_sitter_glsl::language(), LIST_SYMBOLS_STR)?;
let mut query_cursor = QueryCursor::new();
let mut parent_child_vec: Vec<(Option<SymbolName>, DocumentSymbol)> = vec![];
let mut fqname_to_index: HashMap<SymbolName, usize> = HashMap::new();
for (m, _) in query_cursor.captures(&query, self.root_node(), self.source.as_bytes()) {
if m.captures.is_empty() {
continue;
}
let mut capture_iter = m.captures.iter();
let capture = capture_iter.next().unwrap();
let capture_name = query.capture_names()[capture.index as usize].as_str();
trace!("next capture name"; "name" => capture_name, "capture" => format!("{:?}", capture));
let (kind, node) = match capture_name {
"const_qualifier" => (SymbolKind::CONSTANT, capture_iter.next().unwrap().node),
"ident" => (SymbolKind::VARIABLE, capture.node),
"func_ident" => (SymbolKind::FUNCTION, capture.node),
"define_ident" => (SymbolKind::STRING, capture.node),
"struct_ident" => (SymbolKind::STRUCT, capture.node),
"field_list" => (SymbolKind::FIELD, capture_iter.next().unwrap().node),
_ => (SymbolKind::NULL, capture.node),
};
let range = Range {
start: Position {
line: node.start_position().row as u32,
character: node.start_position().column as u32,
},
end: Position {
line: node.end_position().row as u32,
character: node.end_position().column as u32,
},
};
let name = node.utf8_text(self.source.as_bytes()).unwrap().to_string();
let fqname = SymbolName::new(&node, self.source.as_str(), node.id());
debug!("found symbol"; "node_name" => &name, "kind" => format!("{:?}", kind), "fqname" => &fqname);
let child_symbol = DocumentSymbol {
name,
detail: None,
kind,
tags: None,
deprecated: None,
range,
selection_range: range,
children: None,
};
parent_child_vec.push((fqname.parent(), child_symbol));
trace!("inserting fqname"; "fqname" => &fqname, "index" => parent_child_vec.len() - 1);
fqname_to_index.insert(fqname, parent_child_vec.len() - 1);
}
// let mut symbols = vec![];
for i in 1..parent_child_vec.len() {
let (left, right) = parent_child_vec.split_at_mut(i);
let parent = &right[0].0;
let child = &right[0].1;
if let Some(parent) = parent {
trace!("finding parent"; "parent_symbol_name" => &parent, "child" => format!("{:?}", child), "split_point" => i, "left_len" => left.len(), "right_len" => right.len());
let parent_index = fqname_to_index.get(parent).unwrap();
let parent_sym = &mut left[*parent_index];
parent_sym.1.children.get_or_insert_default().push(right[0].1.clone())
}
}
let symbols = parent_child_vec
.iter()
.filter(|tuple| tuple.0.is_none())
.map(|tuple| tuple.1.clone())
.collect();
Ok(Some(symbols))
}
pub fn find_definitions(&self, path: &Path, point: Position) -> Result<Option<Vec<Location>>> {
let current_node = match self.find_node_at_point(point) {
Some(node) => node,
@ -94,12 +258,10 @@ impl<'a> ParserContext<'a> {
let query_str = format!(find_function_def_str!(), current_node.utf8_text(self.source.as_bytes())?);
self.simple_global_search(path, &query_str)?
}
("identifier", "argument_list") |
("identifier", "field_expression") |
("identifier", "binary_expression") |
("identifier", "assignment_expression") => {
self.tree_climbing_search(path, current_node)?
}
("identifier", "argument_list")
| ("identifier", "field_expression")
| ("identifier", "binary_expression")
| ("identifier", "assignment_expression") => self.tree_climbing_search(path, current_node)?,
_ => return Ok(None),
};