mirror of
https://github.com/Strum355/mcshader-lsp.git
synced 2025-08-31 05:47:22 +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 {
|
||||
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(),
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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(())));
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue