Skip to content

Commit

Permalink
feat: adds lsp document symbol provider
Browse files Browse the repository at this point in the history
  • Loading branch information
baszalmstra committed Jan 3, 2021
1 parent 30bfe19 commit 5de87e5
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 11 deletions.
9 changes: 8 additions & 1 deletion crates/mun_language_server/src/analysis.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::cancelation::Canceled;
use crate::change::AnalysisChange;
use crate::db::AnalysisDatabase;
use crate::diagnostics;
use crate::diagnostics::Diagnostic;
use crate::file_structure::StructureNode;
use crate::{diagnostics, file_structure};
use hir::line_index::LineIndex;
use hir::AstDatabase;
use hir::SourceDatabase;
use salsa::{ParallelDatabase, Snapshot};
use std::sync::Arc;
Expand Down Expand Up @@ -71,6 +73,11 @@ impl AnalysisSnapshot {
self.with_db(|db| db.line_index(file_id))
}

/// Returns a tree structure of the symbols of a file.
pub fn file_structure(&self, file_id: hir::FileId) -> Cancelable<Vec<StructureNode>> {
self.with_db(|db| file_structure::file_structure(&db.parse(file_id).tree()))
}

/// Performs an operation on that may be Canceled.
fn with_db<F: FnOnce(&AnalysisDatabase) -> T + std::panic::UnwindSafe, T>(
&self,
Expand Down
7 changes: 7 additions & 0 deletions crates/mun_language_server/src/cancelation.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::error::Error;

/// An error signifying a cancelled operation.
pub struct Canceled {
// This is here so that you cannot construct a Canceled
Expand Down Expand Up @@ -29,3 +31,8 @@ impl std::fmt::Debug for Canceled {
}

impl std::error::Error for Canceled {}

/// Returns true if the specified error refers to a cancellation event.
pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
e.downcast_ref::<Canceled>().is_some()
}
1 change: 1 addition & 0 deletions crates/mun_language_server/src/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use lsp_types::{
pub fn server_capabilities(_client_caps: &ClientCapabilities) -> ServerCapabilities {
ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::Full)),
document_symbol_provider: Some(true),
..Default::default()
}
}
10 changes: 10 additions & 0 deletions crates/mun_language_server/src/conversion.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::SymbolKind;
use lsp_types::Url;
use mun_syntax::{TextRange, TextUnit};
use std::path::{Component, Path, Prefix};
Expand Down Expand Up @@ -63,3 +64,12 @@ pub fn convert_unit(
character: line_col.col.into(),
}
}

/// Converts a symbol kind from this crate to one for the LSP protocol.
pub(crate) fn convert_symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
match symbol_kind {
SymbolKind::Function => lsp_types::SymbolKind::Function,
SymbolKind::Struct => lsp_types::SymbolKind::Struct,
SymbolKind::TypeAlias => lsp_types::SymbolKind::TypeParameter,
}
}
132 changes: 132 additions & 0 deletions crates/mun_language_server/src/file_structure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use crate::SymbolKind;
use mun_syntax::{
ast::{self, NameOwner},
match_ast, AstNode, SourceFile, SyntaxNode, TextRange, WalkEvent,
};

/// A description of a symbol in a source file.
#[derive(Debug, Clone)]
pub struct StructureNode {
/// An optional parent of this symbol. Refers to the index of the symbol in the collection that
/// this instance resides in.
pub parent: Option<usize>,

/// The text label
pub label: String,

/// The range to navigate to if selected
pub navigation_range: TextRange,

/// The entire range of the node in the file
pub node_range: TextRange,

/// The type of symbol
pub kind: SymbolKind,

/// Optional detailed information
pub detail: Option<String>,
}

/// Provides a tree of symbols defined in a `SourceFile`.
pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
let mut result = Vec::new();
let mut stack = Vec::new();

for event in file.syntax().preorder() {
match event {
WalkEvent::Enter(node) => {
if let Some(mut symbol) = try_convert_to_structure_node(&node) {
symbol.parent = stack.last().copied();
stack.push(result.len());
result.push(symbol);
}
}
WalkEvent::Leave(node) => {
if try_convert_to_structure_node(&node).is_some() {
stack.pop().unwrap();
}
}
}
}

result
}

/// Tries to convert an ast node to something that would reside in the hierarchical file structure.
fn try_convert_to_structure_node(node: &SyntaxNode) -> Option<StructureNode> {
/// Create a `StructureNode` from a declaration
fn decl<N: NameOwner>(node: N, kind: SymbolKind) -> Option<StructureNode> {
decl_with_detail(&node, None, kind)
}

/// Create a `StructureNode` from a declaration with extra text detail
fn decl_with_detail<N: NameOwner>(
node: &N,
detail: Option<String>,
kind: SymbolKind,
) -> Option<StructureNode> {
let name = node.name()?;

Some(StructureNode {
parent: None,
label: name.text().to_string(),
navigation_range: name.syntax().text_range(),
node_range: node.syntax().text_range(),
kind,
detail,
})
}

/// Given an SyntaxNode get the text without any whitespaces
fn collapse_whitespaces(node: &SyntaxNode, output: &mut String) {
let mut can_insert_ws = false;
node.text().for_each_chunk(|chunk| {
for line in chunk.lines() {
let line = line.trim();
if line.is_empty() {
if can_insert_ws {
output.push(' ');
can_insert_ws = false;
}
} else {
output.push_str(line);
can_insert_ws = true;
}
}
})
}

/// Given a SyntaxNode construct an StructureNode by referring to the type of a node.
fn decl_with_type_ref<N: NameOwner>(
node: &N,
type_ref: Option<ast::TypeRef>,
kind: SymbolKind,
) -> Option<StructureNode> {
let detail = type_ref.map(|type_ref| {
let mut detail = String::new();
collapse_whitespaces(type_ref.syntax(), &mut detail);
detail
});
decl_with_detail(node, detail, kind)
}

match_ast! {
match node {
ast::FunctionDef(it) => {
let mut detail = String::from("fn");
if let Some(param_list) = it.param_list() {
collapse_whitespaces(param_list.syntax(), &mut detail);
}
if let Some(ret_type) = it.ret_type() {
detail.push(' ');
collapse_whitespaces(ret_type.syntax(), &mut detail);
}

decl_with_detail(&it, Some(detail), SymbolKind::Function)
},
ast::StructDef(it) => decl(it, SymbolKind::Struct),
ast::TypeAliasDef(it) => decl_with_type_ref(&it, it.type_ref(), SymbolKind::TypeAlias),
_ => None
}
}
}
20 changes: 20 additions & 0 deletions crates/mun_language_server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ mod config;
mod conversion;
mod db;
mod diagnostics;
mod file_structure;
mod main_loop;
pub mod protocol;
mod symbol_kind;

pub use config::Config;
pub use main_loop::main_loop;

use crate::config::FilesWatcher;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::fmt;
use symbol_kind::SymbolKind;

pub type Result<T> = anyhow::Result<T>;

Expand Down Expand Up @@ -110,3 +114,19 @@ pub async fn run_server_async() -> Result<()> {
pub fn run_server() -> Result<()> {
async_std::task::block_on(run_server_async())
}

#[derive(Debug)]
struct LspError {
code: i32,
message: String,
}

impl fmt::Display for LspError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"language Server request failed with {}. ({})",
self.code, self.message
)
}
}
Loading

0 comments on commit 5de87e5

Please sign in to comment.