diff --git a/kclvm/tools/src/LSP/Cargo.toml b/kclvm/tools/src/LSP/Cargo.toml index f396e7fed..ce73b928c 100644 --- a/kclvm/tools/src/LSP/Cargo.toml +++ b/kclvm/tools/src/LSP/Cargo.toml @@ -37,7 +37,7 @@ anyhow = { version = "1.0", default-features = false, features = ["std"] } crossbeam-channel = { version = "0.5.7", default-features = false } ra_ap_vfs = "0.0.149" ra_ap_vfs-notify = "0.0.149" -lsp-types = { version = "0.93.0", default-features = false } +lsp-types = { version = "0.93.0", features = ["proposed"]} threadpool = { version = "1.8.1", default-features = false } salsa = { version = "0.16.1", default-features = false } serde_json = { version = "1.0", default-features = false } diff --git a/kclvm/tools/src/LSP/src/capabilities.rs b/kclvm/tools/src/LSP/src/capabilities.rs index 6f0f0086c..17d83f413 100644 --- a/kclvm/tools/src/LSP/src/capabilities.rs +++ b/kclvm/tools/src/LSP/src/capabilities.rs @@ -38,6 +38,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None, }, + completion_item: None, }), hover_provider: Some(HoverProviderCapability::Simple(true)), definition_provider: Some(OneOf::Left(true)), @@ -62,6 +63,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti document_range_formatting_provider: Some(OneOf::Left(true)), references_provider: Some(OneOf::Left(true)), rename_provider: Some(OneOf::Left(true)), + inlay_hint_provider: Some(lsp_types::OneOf::Left(true)), ..Default::default() } } diff --git a/kclvm/tools/src/LSP/src/inlay_hints.rs b/kclvm/tools/src/LSP/src/inlay_hints.rs new file mode 100644 index 000000000..c2363756d --- /dev/null +++ b/kclvm/tools/src/LSP/src/inlay_hints.rs @@ -0,0 +1,96 @@ +use kclvm_ast::ast::{self, Program}; +use kclvm_ast::pos::GetPos; +use kclvm_error::Position as KCLPos; +use kclvm_sema::core::{global_state::GlobalState, symbol::KCLSymbol}; +use kclvm_sema::ty::TypeKind; +use lsp_types::{InlayHint, InlayHintLabelPart, Position as LspPosition, Range}; +use std::convert::TryInto; + +pub fn inlay_hints(file: &str, gs: &GlobalState, program: &Program) -> Option> { + let mut inlay_hints: Vec = vec![]; + let sema_db = gs.get_sema_db(); + if let Some(file_sema) = sema_db.get_file_sema(file) { + let symbols = file_sema.get_symbols(); + for symbol_ref in symbols { + if let Some(symbol) = gs.get_symbols().get_symbol(*symbol_ref) { + let (start, end) = symbol.get_range(); + if has_type_assignment(program, &start) { + if let Some(hint) = generate_inlay_hint(symbol, gs, &start, &end) { + inlay_hints.push(hint); + } + } + } + } + } + Some(inlay_hints) +} + +fn has_type_assignment(program: &Program, start: &KCLPos) -> bool { + if let Some(stmt_node) = program.pos_to_stmt(start) { + if let ast::Stmt::Assign(assign_stmt) = stmt_node.node { + if assign_stmt + .targets + .iter() + .any(|target| target.get_pos() == *start) + && assign_stmt.ty.is_none() + { + return true; + } + } + } + false +} + +fn generate_inlay_hint( + symbol: &KCLSymbol, + gs: &GlobalState, + start: &KCLPos, + end: &KCLPos, +) -> Option { + match get_hint_label(symbol, gs) { + Some(label_parts) => { + let range = Range { + start: LspPosition::new( + (start.line - 1).try_into().unwrap(), + start.column.unwrap_or(0).try_into().unwrap(), + ), + end: LspPosition::new( + (end.line - 1).try_into().unwrap(), + end.column.unwrap_or(0).try_into().unwrap(), + ), + }; + Some(InlayHint { + position: range.end, + label: lsp_types::InlayHintLabel::LabelParts(label_parts), + kind: None, + text_edits: None, + tooltip: None, + padding_left: Some(true), + padding_right: Some(true), + data: None, + }) + } + None => None, + } +} + +fn get_hint_label(symbol: &KCLSymbol, _gs: &GlobalState) -> Option> { + if let Some(ty) = &symbol.get_sema_info().ty { + let mut label_parts = Vec::new(); + + match &ty.kind { + TypeKind::Str | TypeKind::Bool | TypeKind::Int | TypeKind::Float | TypeKind::Any => { + label_parts.push(InlayHintLabelPart { + value: format!("[: {}]", ty.ty_str()), + ..Default::default() + }); + } + _ => { + return None; + } + } + Some(label_parts) + } else { + None + } +} diff --git a/kclvm/tools/src/LSP/src/lib.rs b/kclvm/tools/src/LSP/src/lib.rs index 01cac1fd1..1a43b9a68 100644 --- a/kclvm/tools/src/LSP/src/lib.rs +++ b/kclvm/tools/src/LSP/src/lib.rs @@ -9,6 +9,7 @@ mod formatting; mod from_lsp; mod goto_def; mod hover; +mod inlay_hints; mod main_loop; mod notification; mod quick_fix; diff --git a/kclvm/tools/src/LSP/src/main.rs b/kclvm/tools/src/LSP/src/main.rs index 4ff88e992..5c887f292 100644 --- a/kclvm/tools/src/LSP/src/main.rs +++ b/kclvm/tools/src/LSP/src/main.rs @@ -11,6 +11,7 @@ mod find_refs; mod from_lsp; mod goto_def; mod hover; +mod inlay_hints; mod main_loop; mod notification; mod quick_fix; @@ -73,6 +74,7 @@ fn run_server() -> anyhow::Result<()> { name: String::from("kcl-language-server"), version: None, }), + offset_encoding: None, }; let initialize_result = serde_json::to_value(initialize_result) diff --git a/kclvm/tools/src/LSP/src/request.rs b/kclvm/tools/src/LSP/src/request.rs index 48bfb3fb0..b4ad85a40 100644 --- a/kclvm/tools/src/LSP/src/request.rs +++ b/kclvm/tools/src/LSP/src/request.rs @@ -18,7 +18,9 @@ use crate::{ formatting::format, from_lsp::{self, file_path_from_url, kcl_pos}, goto_def::goto_def, - hover, quick_fix, + hover, + inlay_hints::inlay_hints, + quick_fix, semantic_token::semantic_tokens_full, state::{log_message, LanguageServerSnapshot, LanguageServerState, Task}, }; @@ -58,6 +60,7 @@ impl LanguageServerState { .on::(handle_range_formatting)? .on::(handle_rename)? .on::(handle_semantic_tokens_full)? + .on::(handle_inlay_hint)? .finish(); Ok(()) @@ -439,3 +442,25 @@ pub(crate) fn handle_rename( } } } + +pub(crate) fn handle_inlay_hint( + snapshot: LanguageServerSnapshot, + params: lsp_types::InlayHintParams, + _sender: Sender, +) -> anyhow::Result>> { + let file = file_path_from_url(¶ms.text_document.uri)?; + let path = from_lsp::abs_path(¶ms.text_document.uri)?; + let db = match snapshot.try_get_db(&path.clone().into()) { + Ok(option_db) => match option_db { + Some(db) => db, + None => return Err(anyhow!(LSPError::Retry)), + }, + Err(_) => return Ok(None), + }; + let res = inlay_hints(&file, &db.gs, &db.prog); + + if !snapshot.verify_request_version(db.version, &path)? { + return Err(anyhow!(LSPError::Retry)); + } + Ok(res) +}