From 31ee8c3c74be44e71cde659409465ed3c0070002 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Fri, 17 May 2024 19:40:42 +0530 Subject: [PATCH] feat: syntax highlighting for hover content Signed-off-by: shruti2522 fmt check Signed-off-by: shruti2522 kcl formatting Signed-off-by: shruti2522 fmt check Signed-off-by: shruti2522 updated test cases Signed-off-by: shruti2522 fmt check Signed-off-by: shruti2522 feat: syntax highlighting for hover content Signed-off-by: shruti2522 updated tests Signed-off-by: shruti2522 updated tests Signed-off-by: shruti2522 test check Signed-off-by: shruti2522 feat: syntax highlighting for hover content Signed-off-by: shruti2522 add markup Signed-off-by: shruti2522 fmt check Signed-off-by: shruti2522 resolved func formatting error Signed-off-by: shruti2522 updated test cases Signed-off-by: shruti2522 updated tests Signed-off-by: shruti2522 feat: syntax highlighting for hover content Signed-off-by: shruti2522 --- kclvm/sema/src/ty/mod.rs | 14 ++-- kclvm/tools/src/LSP/src/completion.rs | 2 +- kclvm/tools/src/LSP/src/hover.rs | 93 +++++++++++++++------------ kclvm/tools/src/LSP/src/tests.rs | 19 ++++-- kclvm/utils/src/lib.rs | 1 + kclvm/utils/src/markup.rs | 5 ++ 6 files changed, 74 insertions(+), 60 deletions(-) create mode 100644 kclvm/utils/src/markup.rs diff --git a/kclvm/sema/src/ty/mod.rs b/kclvm/sema/src/ty/mod.rs index ad47f5262..afb09d86a 100644 --- a/kclvm/sema/src/ty/mod.rs +++ b/kclvm/sema/src/ty/mod.rs @@ -20,6 +20,7 @@ pub use unify::*; pub use walker::walk_type; use super::resolver::doc::Example; +use kclvm_utils::markup::add_markup; #[cfg(test)] mod tests; @@ -312,17 +313,10 @@ impl SchemaType { .join(", ") ) }; - let params_str = if !params.is_empty() && !base.is_empty() { - format!("\\{}{}", params, base) - } else if !params.is_empty() { - format!("{}", params) - } else if !base.is_empty() { - format!("{}", base) - } else { - "".to_string() - }; - format!("{}\n\nschema {}{}", self.pkgpath, self.name, params_str) + let schema_str = add_markup(&format!("schema {}{}{}", self.name, params, base)); + + format!("{}\n\n{}", self.pkgpath, schema_str) } } diff --git a/kclvm/tools/src/LSP/src/completion.rs b/kclvm/tools/src/LSP/src/completion.rs index 540c589d5..4b9ea35d8 100644 --- a/kclvm/tools/src/LSP/src/completion.rs +++ b/kclvm/tools/src/LSP/src/completion.rs @@ -1259,7 +1259,7 @@ mod tests { label: "Person(b){}".to_string(), kind: Some(CompletionItemKind::CLASS), detail: Some( - "__main__\n\nschema Person\\[b: int](Base)\nAttributes:\nc: int" + "__main__\n\n```kcl\nschema Person[b: int](Base)\n```\nAttributes:\nc: int" .to_string() ), documentation: Some(lsp_types::Documentation::String("".to_string())), diff --git a/kclvm/tools/src/LSP/src/hover.rs b/kclvm/tools/src/LSP/src/hover.rs index 537b59b09..00ae5ee36 100644 --- a/kclvm/tools/src/LSP/src/hover.rs +++ b/kclvm/tools/src/LSP/src/hover.rs @@ -1,3 +1,4 @@ +use crate::goto_def::find_def_with_gs; use kclvm_ast::ast::Program; use kclvm_error::Position as KCLPos; use kclvm_sema::{ @@ -5,10 +6,9 @@ use kclvm_sema::{ core::global_state::GlobalState, ty::{FunctionType, ANY_TYPE_STR}, }; +use kclvm_utils::markup::add_markup; use lsp_types::{Hover, HoverContents, MarkedString}; -use crate::goto_def::find_def_with_gs; - /// Returns a short text describing element at position. /// Specifically, the doc for schema and schema attr(todo) pub(crate) fn hover( @@ -45,7 +45,7 @@ pub(crate) fn hover( // Use the api provided by GlobalState to get all attrs let module_info = gs.get_packages().get_module_info(&kcl_pos.filename); let schema_attrs = obj.get_all_attributes(gs.get_symbols(), module_info); - let mut attrs = vec!["Attributes:".to_string()]; + let mut attrs = vec!["**Attributes:**".to_string()]; for schema_attr in schema_attrs { if let kclvm_sema::core::symbol::SymbolKind::Attribute = schema_attr.get_kind() @@ -58,12 +58,13 @@ pub(crate) fn hover( Some(ty) => ty.ty_str(), None => ANY_TYPE_STR.to_string(), }; - attrs.push(format!( + let formatted_attr = format!( "{}{}: {}", name, if attr_symbol.is_optional() { "?" } else { "" }, attr_ty_str, - )); + ); + attrs.push(add_markup(&formatted_attr)); } } docs.push(attrs.join("\n\n")); @@ -74,7 +75,7 @@ pub(crate) fn hover( let sema_info = obj.get_sema_info(); match &sema_info.ty { Some(ty) => { - docs.push(format!("{}: {}", &obj.get_name(), ty.ty_str())); + docs.push(add_markup(&format!("{}: {}", &obj.get_name(), ty.ty_str()))); if let Some(doc) = &sema_info.doc { if !doc.is_empty() { docs.push(doc.clone()); @@ -90,7 +91,7 @@ pub(crate) fn hover( docs.extend(build_func_hover_content(func_ty, obj.get_name().clone())); } _ => { - docs.push(format!("{}: {}", &obj.get_name(), ty.ty_str())); + docs.push(add_markup(&format!("{}: {}", &obj.get_name(), ty.ty_str()))); } }, _ => {} @@ -113,7 +114,7 @@ pub(crate) fn hover( Some(ty) => ty.ty_str(), None => "".to_string(), }; - docs.push(format!("{}: {}", &obj.get_name(), ty_str)); + docs.push(add_markup(&format!("{}: {}", &obj.get_name(), ty_str))); } }, None => {} @@ -144,26 +145,20 @@ fn docs_to_hover(docs: Vec) -> Option { } // Build hover content for function call -// ``` -// pkg -// ----------------- -// function func_name(arg1: type, arg2: type, ..) -> type -// ----------------- -// doc -// ``` fn build_func_hover_content(func_ty: &FunctionType, name: String) -> Vec { let mut docs = vec![]; if let Some(ty) = &func_ty.self_ty { - let self_ty = format!("{}\n\n", ty.ty_str()); + let self_ty = add_markup(&format!("{}\n\n", ty.ty_str())); docs.push(self_ty); } - - let mut sig = format!("fn {}(", name); + let func_str = add_markup(&format!("fn {}", name)); + let mut sig = format!("{}(", func_str); if func_ty.params.is_empty() { sig.push(')'); } else { for (i, p) in func_ty.params.iter().enumerate() { - sig.push_str(&format!("{}: {}", p.name, p.ty.ty_str())); + let p_str = &format!("{}: {}", p.name, p.ty.ty_str()); + sig.push_str(&add_markup(&p_str)); if i != func_ty.params.len() - 1 { sig.push_str(", "); @@ -171,7 +166,8 @@ fn build_func_hover_content(func_ty: &FunctionType, name: String) -> Vec } sig.push(')'); } - sig.push_str(&format!(" -> {}", func_ty.return_ty.ty_str())); + let func_ty_str = &format!(" -> {}", func_ty.return_ty.ty_str()); + sig.push_str(&add_markup(&func_ty_str)); docs.push(sig); if !func_ty.doc.is_empty() { @@ -213,13 +209,16 @@ mod tests { match got.contents { lsp_types::HoverContents::Array(vec) => { if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "pkg\n\nschema Person"); + assert_eq!(s, "pkg\n\n```kcl\nschema Person\n```"); } if let MarkedString::String(s) = vec[1].clone() { assert_eq!(s, "hover doc test"); } if let MarkedString::String(s) = vec[2].clone() { - assert_eq!(s, "Attributes:\n\nname: str\n\nage: int"); + assert_eq!( + s, + "**Attributes:**\n\n```kcl\nname: str\n```\n\n```kcl\nage: int\n```" + ); } } _ => unreachable!("test error"), @@ -233,7 +232,7 @@ mod tests { match got.contents { lsp_types::HoverContents::Scalar(marked_string) => { if let MarkedString::String(s) = marked_string { - assert_eq!(s, "name: str"); + assert_eq!(s, "```kcl\nname: str\n```"); } } _ => unreachable!("test error"), @@ -291,13 +290,16 @@ mod tests { match got.contents { lsp_types::HoverContents::Array(vec) => { if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "__main__\n\nschema Person"); + assert_eq!(s, "__main__\n\n```kcl\nschema Person\n```"); } if let MarkedString::String(s) = vec[1].clone() { assert_eq!(s, "hover doc test"); } if let MarkedString::String(s) = vec[2].clone() { - assert_eq!(s, "Attributes:\n\nname: str\n\nage?: int"); + assert_eq!( + s, + "**Attributes:**\n\n```kcl\nname: str\n```\n\n```kcl\nage?: int\n```" + ); } } _ => unreachable!("test error"), @@ -319,7 +321,7 @@ mod tests { match got.contents { lsp_types::HoverContents::Array(vec) => { if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "name: str"); + assert_eq!(s, "```kcl\nname: str\n```"); } if let MarkedString::String(s) = vec[1].clone() { assert_eq!(s, "name doc test"); @@ -338,7 +340,7 @@ mod tests { match got.contents { lsp_types::HoverContents::Array(vec) => { if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "age: int"); + assert_eq!(s, "```kcl\nage: int\n```"); } if let MarkedString::String(s) = vec[1].clone() { assert_eq!(s, "age doc test"); @@ -364,7 +366,7 @@ mod tests { lsp_types::HoverContents::Array(vec) => { assert_eq!(vec.len(), 2); if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "fn encode(value: str, encoding: str) -> str"); + assert_eq!(s, "```kcl\nfn encode\n```(```kcl\nvalue: str\n```, ```kcl\nencoding: str\n```)```kcl\n -> str\n```"); } if let MarkedString::String(s) = vec[1].clone() { assert_eq!( @@ -387,10 +389,10 @@ mod tests { lsp_types::HoverContents::Array(vec) => { assert_eq!(vec.len(), 3); if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "str\n\n"); + assert_eq!(s, "```kcl\nstr\n\n\n```"); } if let MarkedString::String(s) = vec[1].clone() { - assert_eq!(s, "fn count(sub: str, start: int, end: int) -> int"); + assert_eq!(s, "```kcl\nfn count\n```(```kcl\nsub: str\n```, ```kcl\nstart: int\n```, ```kcl\nend: int\n```)```kcl\n -> int\n```"); } if let MarkedString::String(s) = vec[2].clone() { assert_eq!(s, "Return the number of non-overlapping occurrences of substring sub in the range [start, end]. Optional arguments start and end are interpreted as in slice notation."); @@ -410,7 +412,7 @@ mod tests { lsp_types::HoverContents::Array(vec) => { assert_eq!(vec.len(), 2); if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "fn print() -> NoneType"); + assert_eq!(s, "```kcl\nfn print\n```()```kcl\n -> NoneType\n```"); } if let MarkedString::String(s) = vec[1].clone() { assert_eq!(s, "Prints the values to a stream, or to the system stdout by default.\n\nOptional keyword arguments:\n\nsep: string inserted between values, default a space.\n\nend: string appended after the last value, default a newline."); @@ -433,7 +435,7 @@ mod tests { match got.contents { lsp_types::HoverContents::Scalar(marked_string) => { if let MarkedString::String(s) = marked_string { - assert_eq!(s, "value: int"); + assert_eq!(s, "```kcl\nvalue: int\n```"); } } _ => unreachable!("test error"), @@ -453,7 +455,7 @@ mod tests { match got.contents { lsp_types::HoverContents::Scalar(marked_string) => { if let MarkedString::String(s) = marked_string { - assert_eq!(s, "result: {str:str}"); + assert_eq!(s, "```kcl\nresult: {str:str}\n```"); } } _ => unreachable!("test error"), @@ -474,10 +476,10 @@ mod tests { lsp_types::HoverContents::Array(vec) => { assert_eq!(vec.len(), 3); if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "str\n\n"); + assert_eq!(s, "```kcl\nstr\n\n\n```"); } if let MarkedString::String(s) = vec[1].clone() { - assert_eq!(s, "fn capitalize() -> str"); + assert_eq!(s, "```kcl\nfn capitalize\n```()```kcl\n -> str\n```"); } if let MarkedString::String(s) = vec[2].clone() { assert_eq!(s, "Return a copy of the string with its first character capitalized and the rest lowercased."); @@ -501,10 +503,13 @@ mod tests { lsp_types::HoverContents::Array(vec) => { assert_eq!(vec.len(), 2); if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "fib\n\nschema Fib"); + assert_eq!(s, "fib\n\n```kcl\nschema Fib\n```"); } if let MarkedString::String(s) = vec[1].clone() { - assert_eq!(s, "Attributes:\n\nn: int\n\nvalue: int"); + assert_eq!( + s, + "**Attributes:**\n\n```kcl\nn: int\n```\n\n```kcl\nvalue: int\n```" + ); } } _ => unreachable!("test error"), @@ -524,7 +529,7 @@ mod tests { match got.contents { lsp_types::HoverContents::Scalar(marked_string) => { if let MarkedString::String(s) = marked_string { - assert_eq!(s, "stratege: str"); + assert_eq!(s, "```kcl\nstratege: str\n```"); } } _ => unreachable!("test error"), @@ -544,7 +549,7 @@ mod tests { match got.contents { lsp_types::HoverContents::Scalar(marked_string) => { if let MarkedString::String(s) = marked_string { - assert_eq!(s, "n1: int"); + assert_eq!(s, "```kcl\nn1: int\n```"); } } _ => unreachable!("test error"), @@ -562,7 +567,7 @@ mod tests { }; let got = hover(&program, &pos, &gs).unwrap(); let expect_content = vec![MarkedString::String( - "fn deprecated(version: str, reason: str, strict: bool) -> any".to_string(), + "```kcl\nfn deprecated\n```(```kcl\nversion: str\n```, ```kcl\nreason: str\n```, ```kcl\nstrict: bool\n```)```kcl\n -> any\n```".to_string(), ), MarkedString::String( "This decorator is used to get the deprecation message according to the wrapped key-value pair.".to_string(), )]; @@ -599,8 +604,12 @@ mod tests { let got = hover(&program, &pos, &gs).unwrap(); let expect_content = vec![ - MarkedString::String("__main__\n\nschema Data1\\[m: {str:str}](Data)".to_string()), - MarkedString::String("Attributes:\n\nname: str\n\nage: int".to_string()), + MarkedString::String( + "__main__\n\n```kcl\nschema Data1[m: {str:str}](Data)\n```".to_string(), + ), + MarkedString::String( + "**Attributes:**\n\n```kcl\nname: str\n```\n\n```kcl\nage: int\n```".to_string(), + ), ]; match got.contents { diff --git a/kclvm/tools/src/LSP/src/tests.rs b/kclvm/tools/src/LSP/src/tests.rs index 8e750d8bb..47a8868fb 100644 --- a/kclvm/tools/src/LSP/src/tests.rs +++ b/kclvm/tools/src/LSP/src/tests.rs @@ -1169,9 +1169,12 @@ fn hover_test() { res.result.unwrap(), to_json(Hover { contents: HoverContents::Array(vec![ - MarkedString::String("__main__\n\nschema Person".to_string()), + MarkedString::String("__main__\n\n```kcl\nschema Person\n```".to_string()), MarkedString::String("hover doc test".to_string()), - MarkedString::String("Attributes:\n\nname: str\n\nage?: int".to_string()), + MarkedString::String( + "```kcl\nAttributes:\n```\n\n```kcl\nname: str\n```\n\n```kcl\nage?: int\n```" + .to_string() + ), ]), range: None }) @@ -1225,7 +1228,9 @@ fn hover_assign_in_lambda_test() { assert_eq!( res.result.unwrap(), to_json(Hover { - contents: HoverContents::Scalar(MarkedString::String("images: [str]".to_string()),), + contents: HoverContents::Scalar(MarkedString::String( + "```kcl\nimages: [str]\n```".to_string() + ),), range: None }) .unwrap() @@ -1663,9 +1668,9 @@ fn konfig_hover_test_main() { let got = hover(&program, &pos, &gs).unwrap(); match got.contents { HoverContents::Array(arr) => { - let expect: Vec = ["base.pkg.kusion_models.kube.frontend\n\nschema Server", + let expect: Vec = ["base.pkg.kusion_models.kube.frontend\n\n```kcl\nschema Server\n```", "Server is abstaction of Deployment and StatefulSet.", - "Attributes:\n\nname?: str\n\nworkloadType: str(Deployment) | str(StatefulSet)\n\nrenderType?: str(Server) | str(KubeVelaApplication)\n\nreplicas: int\n\nimage: str\n\nschedulingStrategy: SchedulingStrategy\n\nmainContainer: Main\n\nsidecarContainers?: [Sidecar]\n\ninitContainers?: [Sidecar]\n\nuseBuiltInLabels?: bool\n\nlabels?: {str:str}\n\nannotations?: {str:str}\n\nuseBuiltInSelector?: bool\n\nselector?: {str:str}\n\npodMetadata?: ObjectMeta\n\nvolumes?: [Volume]\n\nneedNamespace?: bool\n\nenableMonitoring?: bool\n\nconfigMaps?: [ConfigMap]\n\nsecrets?: [Secret]\n\nservices?: [Service]\n\ningresses?: [Ingress]\n\nserviceAccount?: ServiceAccount\n\nstorage?: ObjectStorage\n\ndatabase?: DataBase"] + "```kcl\nAttributes:\n```\n\n```kcl\nname?: str\n```\n\n```kcl\nworkloadType: str(Deployment) | str(StatefulSet)\n```\n\n```kcl\nrenderType?: str(Server) | str(KubeVelaApplication)\n```\n\n```kcl\nreplicas: int\n```\n\n```kcl\nimage: str\n```\n\n```kcl\nschedulingStrategy: SchedulingStrategy\n```\n\n```kcl\nmainContainer: Main\n```\n\n```kcl\nsidecarContainers?: [Sidecar]\n```\n\n```kcl\ninitContainers?: [Sidecar]\n```\n\n```kcl\nuseBuiltInLabels?: bool\n```\n\n```kcl\nlabels?: {str:str}\n```\n\n```kcl\nannotations?: {str:str}\n```\n\n```kcl\nuseBuiltInSelector?: bool\n```\n\n```kcl\nselector?: {str:str}\n```\n\n```kcl\npodMetadata?: ObjectMeta\n```\n\n```kcl\nvolumes?: [Volume]\n```\n\n```kcl\nneedNamespace?: bool\n```\n\n```kcl\nenableMonitoring?: bool\n```\n\n```kcl\nconfigMaps?: [ConfigMap]\n```\n\n```kcl\nsecrets?: [Secret]\n```\n\n```kcl\nservices?: [Service]\n```\n\n```kcl\ningresses?: [Ingress]\n```\n\n```kcl\nserviceAccount?: ServiceAccount\n```\n\n```kcl\nstorage?: ObjectStorage\n```\n\n```kcl\ndatabase?: DataBase\n```"] .iter() .map(|s| MarkedString::String(s.to_string())) .collect(); @@ -1684,7 +1689,7 @@ fn konfig_hover_test_main() { match got.contents { HoverContents::Array(arr) => { let expect: Vec = [ - "schedulingStrategy: SchedulingStrategy", + "```kcl\nschedulingStrategy: SchedulingStrategy\n```", "SchedulingStrategy represents scheduling strategy.", ] .iter() @@ -1706,7 +1711,7 @@ fn konfig_hover_test_main() { HoverContents::Scalar(s) => { assert_eq!( s, - MarkedString::String("appConfiguration: Server".to_string()) + MarkedString::String("```kcl\nappConfiguration: Server\n```".to_string()) ); } _ => unreachable!("test error"), diff --git a/kclvm/utils/src/lib.rs b/kclvm/utils/src/lib.rs index 78d566ee1..3d1a43333 100644 --- a/kclvm/utils/src/lib.rs +++ b/kclvm/utils/src/lib.rs @@ -1,3 +1,4 @@ pub mod fslock; +pub mod markup; pub mod path; pub mod pkgpath; diff --git a/kclvm/utils/src/markup.rs b/kclvm/utils/src/markup.rs new file mode 100644 index 000000000..b2c3fb2af --- /dev/null +++ b/kclvm/utils/src/markup.rs @@ -0,0 +1,5 @@ +// KCL function for highlighting + +pub fn add_markup(text: &str) -> String { + format!("```kcl\n{}\n```", text) +}