Skip to content

Commit

Permalink
Feat(LSP): Document Symbol Provider. Select variable and schema defin…
Browse files Browse the repository at this point in the history
…ition and shown with document symbol(Outline in vscode)
  • Loading branch information
He1pa committed Apr 10, 2023
1 parent fd1c8c9 commit 5e5a58b
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 19 deletions.
2 changes: 1 addition & 1 deletion kclvm/sema/src/resolver/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> {
start,
end,
ty: expected_ty.clone(),
kind: ScopeObjectKind::Variable,
kind: ScopeObjectKind::Attribute,
used: false,
},
);
Expand Down
2 changes: 1 addition & 1 deletion kclvm/tools/src/LSP/src/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,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(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)),
completion_provider: Some(CompletionOptions {
resolve_provider: None,
trigger_characters: Some(vec![String::from(".")]),
Expand Down
103 changes: 103 additions & 0 deletions kclvm/tools/src/LSP/src/document_symbol.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::path::Path;

use kclvm_ast::ast::Program;
use kclvm_ast::MAIN_PKG;
use kclvm_sema::resolver::scope::ProgramScope;
use kclvm_sema::resolver::scope::Scope;
use kclvm_sema::resolver::scope::ScopeKind;
use kclvm_sema::resolver::scope::ScopeObject;
use kclvm_sema::resolver::scope::ScopeObjectKind;
use lsp_types::Range;
use lsp_types::{DocumentSymbol, DocumentSymbolResponse, SymbolKind};

use crate::to_lsp::lsp_pos;

pub(crate) fn document_symbol(
file: &str,
_program: &Program,
prog_scope: &ProgramScope,
) -> Option<lsp_types::DocumentSymbolResponse> {
let mut documentsymbols: Vec<DocumentSymbol> = vec![];
let scope = prog_scope.scope_map.get(MAIN_PKG).unwrap().borrow();
// Get variable in scope
for obj in scope.elems.values().filter(|obj| {
if let Ok(canonicalized_path) = Path::new(&obj.borrow().start.filename).canonicalize() {
canonicalized_path.eq(Path::new(file))
} else {
false
}
}) {
documentsymbols.push(scope_obj_to_document_symbol(obj.borrow().clone()));
}
// Get schema definition in scope
for child in scope.children.iter().filter(|child| {
if let Ok(canonicalized_path) = Path::new(&child.borrow().start.filename).canonicalize() {
canonicalized_path.eq(Path::new(file))
} else {
false
}
}) {
if let Some(symbol) = schema_scope_to_document_symbol(child.borrow().clone()) {
documentsymbols.push(symbol)
}
}
Some(DocumentSymbolResponse::Nested(documentsymbols))
}

#[allow(deprecated)]
fn schema_scope_to_document_symbol(scope: Scope) -> Option<DocumentSymbol> {
if let ScopeKind::Schema(schema_name) = &scope.kind {
let range = Range {
start: lsp_pos(&scope.start),
end: lsp_pos(&scope.end),
};
Some(DocumentSymbol {
name: schema_name.clone(),
kind: SymbolKind::STRUCT,
range,
selection_range: range,
children: Some(
scope
.elems
.iter()
.map(|(_, obj)| scope_obj_to_document_symbol(obj.borrow().clone()))
.collect(),
),
detail: None,
tags: None,
deprecated: None,
})
} else {
None
}
}

#[allow(deprecated)]
fn scope_obj_to_document_symbol(obj: ScopeObject) -> DocumentSymbol {
let kind = scope_obj_kind_to_document_symbol_kind(obj.kind);
let range = Range {
start: lsp_pos(&obj.start),
end: lsp_pos(&obj.end),
};
DocumentSymbol {
name: obj.name.clone(),
kind,
range,
selection_range: range,
detail: None,
tags: None,
children: None,
deprecated: None,
}
}

fn scope_obj_kind_to_document_symbol_kind(kind: ScopeObjectKind) -> SymbolKind {
match kind {
ScopeObjectKind::Variable => SymbolKind::VARIABLE,
ScopeObjectKind::Attribute => SymbolKind::PROPERTY,
ScopeObjectKind::Definition => SymbolKind::STRUCT,
ScopeObjectKind::Parameter => SymbolKind::VARIABLE,
ScopeObjectKind::TypeAlias => SymbolKind::TYPE_PARAMETER,
ScopeObjectKind::Module => SymbolKind::MODULE,
}
}
1 change: 1 addition & 0 deletions kclvm/tools/src/LSP/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod state;
mod to_lsp;
mod util;

mod document_symbol;
mod goto_def;
mod hover;
mod request;
1 change: 1 addition & 0 deletions kclvm/tools/src/LSP/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod completion;
mod config;
mod db;
mod dispatcher;
mod document_symbol;
mod from_lsp;
mod goto_def;
mod hover;
Expand Down
33 changes: 27 additions & 6 deletions kclvm/tools/src/LSP/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crossbeam_channel::Sender;
use crate::{
completion::completion,
dispatcher::RequestDispatcher,
document_symbol::document_symbol,
from_lsp::kcl_pos,
goto_def::goto_definition,
hover,
Expand Down Expand Up @@ -42,6 +43,7 @@ impl LanguageServerState {
.on::<lsp_types::request::GotoDefinition>(handle_goto_definition)?
.on::<lsp_types::request::Completion>(handle_completion)?
.on::<lsp_types::request::HoverRequest>(handle_hover)?
.on::<lsp_types::request::DocumentSymbolRequest>(handle_document_symbol)?
.finish();

Ok(())
Expand All @@ -65,8 +67,7 @@ pub(crate) fn handle_goto_definition(
file: file.to_string(),
},
Some(snapshot.vfs),
)
.unwrap();
)?;
let kcl_pos = kcl_pos(file, params.text_document_position_params.position);
let res = goto_definition(&program, &kcl_pos, &prog_scope);
if res.is_none() {
Expand All @@ -88,8 +89,7 @@ pub(crate) fn handle_completion(
file: file.to_string(),
},
Some(snapshot.vfs),
)
.unwrap();
)?;
let kcl_pos = kcl_pos(file, params.text_document_position.position);
log_message(
format!(
Expand Down Expand Up @@ -129,8 +129,7 @@ pub(crate) fn handle_hover(
file: file.to_string(),
},
Some(snapshot.vfs),
)
.unwrap();
)?;
let kcl_pos = kcl_pos(file, params.text_document_position_params.position);
log_message(
format!(
Expand All @@ -143,3 +142,25 @@ pub(crate) fn handle_hover(
let res = hover::hover(&program, &kcl_pos, &prog_scope);
Ok(res)
}

/// Called when a `GotoDefinition` request was received.
pub(crate) fn handle_document_symbol(
snapshot: LanguageServerSnapshot,
params: lsp_types::DocumentSymbolParams,
sender: Sender<Task>,
) -> anyhow::Result<Option<lsp_types::DocumentSymbolResponse>> {
let file = params.text_document.uri.path();

let (program, prog_scope, _) = parse_param_and_compile(
Param {
file: file.to_string(),
},
Some(snapshot.vfs),
)?;

let res = document_symbol(file, &program, &prog_scope);
if res.is_none() {
log_message("Document symbol not found".to_string(), &sender)?;
}
Ok(res)
}
6 changes: 6 additions & 0 deletions kclvm/tools/src/LSP/src/test_data/document_symbol.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
schema Person4:
name: str

p: Person4 = Person4 {
name: "alice"
}
72 changes: 61 additions & 11 deletions kclvm/tools/src/LSP/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ use kclvm_sema::builtin::MATH_FUNCTION_NAMES;
use kclvm_sema::builtin::STRING_MEMBER_FUNCTIONS;
use kclvm_sema::resolver::scope::ProgramScope;
use lsp_types::CompletionResponse;
use lsp_types::DocumentSymbol;
use lsp_types::DocumentSymbolResponse;
use lsp_types::MarkedString;
use lsp_types::SymbolKind;
use lsp_types::{Position, Range, TextDocumentContentChangeEvent};

use crate::document_symbol::document_symbol;
use crate::hover::hover;
use crate::{
completion::{completion, into_completion_items},
Expand All @@ -34,18 +38,8 @@ fn compile_test_file(testfile: &str) -> (String, Program, ProgramScope, IndexSet

#[test]
fn diagnostics_test() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut test_file = path;
test_file.push("src/test_data/diagnostics.k");
let file = test_file.to_str().unwrap();
let (_, _, _, diags) = compile_test_file("src/test_data/diagnostics.k");

let (_, _, diags) = parse_param_and_compile(
Param {
file: file.to_string(),
},
None,
)
.unwrap();
assert_eq!(diags.len(), 2);
assert_eq!(diags[0].code, Some(DiagnosticId::Error(InvalidSyntax)));
assert_eq!(diags[1].code, Some(DiagnosticId::Error(TypeError)));
Expand Down Expand Up @@ -624,3 +618,59 @@ fn schema_doc_hover_test() {
_ => unreachable!("test error"),
}
}

#[allow(deprecated)]
fn build_document_symbol(
name: &str,
kind: SymbolKind,
range: ((u32, u32), (u32, u32)),
child: Option<Vec<DocumentSymbol>>,
) -> DocumentSymbol {
let range: Range = Range {
start: Position {
line: range.0 .0,
character: range.0 .1,
},
end: Position {
line: range.1 .0,
character: range.1 .1,
},
};
DocumentSymbol {
name: name.to_string(),
detail: None,
kind,
tags: None,
deprecated: None,
range,
selection_range: range,
children: child,
}
}

#[test]
fn document_symbol_test() {
let (file, program, prog_scope, _) = compile_test_file("src/test_data/document_symbol.k");

let res = document_symbol(file.as_str(), &program, &prog_scope).unwrap();
let mut expect = vec![];
expect.push(build_document_symbol(
"p",
SymbolKind::VARIABLE,
((3, 0), (3, 1)),
None,
));
expect.push(build_document_symbol(
"Person4",
SymbolKind::STRUCT,
((0, 7), (1, 13)),
Some(vec![build_document_symbol(
"name",
SymbolKind::PROPERTY,
((1, 4), (1, 8)),
None,
)]),
));
let expect = DocumentSymbolResponse::Nested(expect);
assert_eq!(res, expect)
}
1 change: 1 addition & 0 deletions kclvm/tools/src/LSP/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub(crate) fn parse_param_and_compile(
let (files, opt) = lookup_compile_unit(&param.file, true);
let files: Vec<&str> = files.iter().map(|s| s.as_str()).collect();
let mut opt = opt.unwrap_or_default();
opt.load_plugins = true;

// update opt.k_code_list
if let Some(vfs) = vfs {
Expand Down

0 comments on commit 5e5a58b

Please sign in to comment.