mirror of
https://github.com/Strum355/mcshader-lsp.git
synced 2025-09-10 18:46:21 +00:00
support document symbols request
This commit is contained in:
parent
f66f56603a
commit
1529460a5c
5 changed files with 420 additions and 262 deletions
427
server/Cargo.lock
generated
427
server/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -134,7 +134,7 @@ pub mod error {
|
||||||
impl From<CycleError> for Diagnostic {
|
impl From<CycleError> for Diagnostic {
|
||||||
fn from(e: CycleError) -> Diagnostic {
|
fn from(e: CycleError) -> Diagnostic {
|
||||||
Diagnostic {
|
Diagnostic {
|
||||||
severity: Some(DiagnosticSeverity::Error),
|
severity: Some(DiagnosticSeverity::ERROR),
|
||||||
range: Range::new(Position::new(0, 0), Position::new(0, 500)),
|
range: Range::new(Position::new(0, 0), Position::new(0, 500)),
|
||||||
source: Some(consts::SOURCE.into()),
|
source: Some(consts::SOURCE.into()),
|
||||||
message: e.into(),
|
message: e.into(),
|
||||||
|
|
|
@ -73,11 +73,11 @@ impl<'a, T: opengl::ShaderValidator + ?Sized> DiagnosticsParser<'a, T> {
|
||||||
|
|
||||||
let severity = match diagnostic_capture.name("severity") {
|
let severity = match diagnostic_capture.name("severity") {
|
||||||
Some(c) => match c.as_str().to_lowercase().as_str() {
|
Some(c) => match c.as_str().to_lowercase().as_str() {
|
||||||
"error" => DiagnosticSeverity::Error,
|
"error" => DiagnosticSeverity::ERROR,
|
||||||
"warning" => DiagnosticSeverity::Warning,
|
"warning" => DiagnosticSeverity::WARNING,
|
||||||
_ => DiagnosticSeverity::Information,
|
_ => DiagnosticSeverity::INFORMATION,
|
||||||
},
|
},
|
||||||
_ => DiagnosticSeverity::Information,
|
_ => DiagnosticSeverity::INFORMATION,
|
||||||
};
|
};
|
||||||
|
|
||||||
let origin = match diagnostic_capture.name("filepath") {
|
let origin = match diagnostic_capture.name("filepath") {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![feature(once_cell)]
|
#![feature(once_cell)]
|
||||||
|
#![feature(option_get_or_insert_default)]
|
||||||
|
|
||||||
use merge_views::FilialTuple;
|
use merge_views::FilialTuple;
|
||||||
use rust_lsp::jsonrpc::{method_types::*, *};
|
use rust_lsp::jsonrpc::{method_types::*, *};
|
||||||
|
@ -517,6 +518,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
|
||||||
let capabilities = ServerCapabilities {
|
let capabilities = ServerCapabilities {
|
||||||
definition_provider: Some(OneOf::Left(true)),
|
definition_provider: Some(OneOf::Left(true)),
|
||||||
references_provider: Some(OneOf::Left(true)),
|
references_provider: Some(OneOf::Left(true)),
|
||||||
|
document_symbol_provider: Some(OneOf::Left(true)),
|
||||||
document_link_provider: Some(DocumentLinkOptions {
|
document_link_provider: Some(DocumentLinkOptions {
|
||||||
resolve_provider: None,
|
resolve_provider: None,
|
||||||
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
|
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
|
||||||
|
@ -529,7 +531,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
|
||||||
open_close: Some(true),
|
open_close: Some(true),
|
||||||
will_save: None,
|
will_save: None,
|
||||||
will_save_wait_until: None,
|
will_save_wait_until: None,
|
||||||
change: Some(TextDocumentSyncKind::Full),
|
change: Some(TextDocumentSyncKind::FULL),
|
||||||
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { include_text: Some(true) })),
|
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { include_text: Some(true) })),
|
||||||
})),
|
})),
|
||||||
..ServerCapabilities::default()
|
..ServerCapabilities::default()
|
||||||
|
@ -661,7 +663,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
|
||||||
.send_notification(
|
.send_notification(
|
||||||
ShowMessage::METHOD,
|
ShowMessage::METHOD,
|
||||||
ShowMessageParams {
|
ShowMessageParams {
|
||||||
typ: MessageType::Info,
|
typ: MessageType::INFO,
|
||||||
message: format!("Command {} executed successfully.", params.command),
|
message: format!("Command {} executed successfully.", params.command),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -674,7 +676,7 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
|
||||||
.send_notification(
|
.send_notification(
|
||||||
ShowMessage::METHOD,
|
ShowMessage::METHOD,
|
||||||
ShowMessageParams {
|
ShowMessageParams {
|
||||||
typ: MessageType::Error,
|
typ: MessageType::ERROR,
|
||||||
message: format!("Failed to execute `{}`. Reason: {}", params.command, err),
|
message: format!("Failed to execute `{}`. Reason: {}", params.command, err),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -751,11 +753,38 @@ impl LanguageServerHandling for MinecraftShaderLanguageServer {
|
||||||
completable.complete(Err(Self::error_not_available(())));
|
completable.complete(Err(Self::error_not_available(())));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn document_symbols(&mut self, _: DocumentSymbolParams, completable: LSCompletable<Vec<SymbolInformation>>) {
|
fn document_symbols(&mut self, params: DocumentSymbolParams, completable: LSCompletable<DocumentSymbolResponse>) {
|
||||||
completable.complete(Err(Self::error_not_available(())));
|
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(())));
|
completable.complete(Err(Self::error_not_available(())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 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 slog_scope::{debug, info, trace};
|
||||||
use tree_sitter::{Node, Parser, Point, Query, QueryCursor, Tree};
|
use tree_sitter::{Node, Parser, Point, Query, QueryCursor, Tree};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::linemap::LineMap;
|
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 {
|
macro_rules! find_function_def_str {
|
||||||
() => {
|
() => {
|
||||||
r#"
|
r#"
|
||||||
|
@ -35,7 +82,6 @@ macro_rules! find_function_refs_str {
|
||||||
macro_rules! find_variable_def_str {
|
macro_rules! find_variable_def_str {
|
||||||
() => {
|
() => {
|
||||||
r#"
|
r#"
|
||||||
(
|
|
||||||
[
|
[
|
||||||
(init_declarator
|
(init_declarator
|
||||||
(identifier) @variable)
|
(identifier) @variable)
|
||||||
|
@ -48,11 +94,46 @@ macro_rules! find_variable_def_str {
|
||||||
|
|
||||||
(#match? @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> {
|
pub struct ParserContext<'a> {
|
||||||
source: String,
|
source: String,
|
||||||
tree: Tree,
|
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>>> {
|
pub fn find_definitions(&self, path: &Path, point: Position) -> Result<Option<Vec<Location>>> {
|
||||||
let current_node = match self.find_node_at_point(point) {
|
let current_node = match self.find_node_at_point(point) {
|
||||||
Some(node) => node,
|
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())?);
|
let query_str = format!(find_function_def_str!(), current_node.utf8_text(self.source.as_bytes())?);
|
||||||
self.simple_global_search(path, &query_str)?
|
self.simple_global_search(path, &query_str)?
|
||||||
}
|
}
|
||||||
("identifier", "argument_list") |
|
("identifier", "argument_list")
|
||||||
("identifier", "field_expression") |
|
| ("identifier", "field_expression")
|
||||||
("identifier", "binary_expression") |
|
| ("identifier", "binary_expression")
|
||||||
("identifier", "assignment_expression") => {
|
| ("identifier", "assignment_expression") => self.tree_climbing_search(path, current_node)?,
|
||||||
self.tree_climbing_search(path, current_node)?
|
|
||||||
}
|
|
||||||
_ => return Ok(None),
|
_ => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue