diff --git a/kclvm/sema/src/resolver/config.rs b/kclvm/sema/src/resolver/config.rs index 902eb4a47..bfabe7b10 100644 --- a/kclvm/sema/src/resolver/config.rs +++ b/kclvm/sema/src/resolver/config.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use super::{ node::TypeRef, - scope::{ScopeKind, ScopeObject, ScopeObjectKind}, + scope::{Attr::ConfigAttr, ScopeKind, ScopeObject, ScopeObjectKind}, Resolver, }; use crate::ty::sup; @@ -42,7 +42,7 @@ impl<'ctx> Resolver<'ctx> { start, end, ty, - kind: ScopeObjectKind::Attribute, + kind: ScopeObjectKind::Attribute(ConfigAttr), used: false, doc: None, } @@ -420,7 +420,7 @@ impl<'ctx> Resolver<'ctx> { start: key.get_pos(), end: key.get_end_pos(), ty: val_ty.clone(), - kind: ScopeObjectKind::Attribute, + kind: ScopeObjectKind::Attribute(ConfigAttr), used: false, doc: None, }, @@ -454,7 +454,7 @@ impl<'ctx> Resolver<'ctx> { start: key.get_pos(), end: key.get_end_pos(), ty: val_ty.clone(), - kind: ScopeObjectKind::Attribute, + kind: ScopeObjectKind::Attribute(ConfigAttr), used: false, doc: None, }, diff --git a/kclvm/sema/src/resolver/doc.rs b/kclvm/sema/src/resolver/doc.rs index c73deb2f9..9586b8406 100644 --- a/kclvm/sema/src/resolver/doc.rs +++ b/kclvm/sema/src/resolver/doc.rs @@ -274,7 +274,7 @@ pub(crate) fn parse_doc_string(ori: &String) -> Doc { } /// The Doc struct contains a summary of schema and all the attributes described in the the docstring. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Clone)] pub(crate) struct Doc { pub summary: String, pub attrs: Vec, @@ -287,7 +287,7 @@ impl Doc { } /// The Attribute struct contains the attribute name and the corresponding description. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Clone)] pub(crate) struct Attribute { pub name: String, pub desc: Vec, diff --git a/kclvm/sema/src/resolver/node.rs b/kclvm/sema/src/resolver/node.rs index e0657fe21..ac9f506a3 100644 --- a/kclvm/sema/src/resolver/node.rs +++ b/kclvm/sema/src/resolver/node.rs @@ -11,7 +11,7 @@ use crate::ty::{ }; use super::format::VALID_FORMAT_SPEC_SET; -use super::scope::{ScopeKind, ScopeObject, ScopeObjectKind}; +use super::scope::{Attr::SchemaAttr, SchemaAttrObj, ScopeKind, ScopeObject, ScopeObjectKind}; use super::ty::ty_str_replace_pkgpath; use super::Resolver; @@ -367,7 +367,9 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { start, end, ty: expected_ty.clone(), - kind: ScopeObjectKind::Attribute, + kind: ScopeObjectKind::Attribute(SchemaAttr(SchemaAttrObj { + option: schema_attr.is_optional, + })), used: false, doc: doc_str, }, diff --git a/kclvm/sema/src/resolver/scope.rs b/kclvm/sema/src/resolver/scope.rs index dd72b5c5b..8d951db87 100644 --- a/kclvm/sema/src/resolver/scope.rs +++ b/kclvm/sema/src/resolver/scope.rs @@ -51,13 +51,24 @@ impl ContainsPos for ScopeObject { #[derive(PartialEq, Clone, Debug)] pub enum ScopeObjectKind { Variable, - Attribute, + Attribute(Attr), Definition, Parameter, TypeAlias, Module(Module), } +#[derive(PartialEq, Clone, Debug)] +pub enum Attr { + ConfigAttr, + SchemaAttr(SchemaAttrObj), +} + +#[derive(PartialEq, Clone, Debug)] +pub struct SchemaAttrObj { + pub option: bool, +} + /// A scope object of module type represents an import stmt on an AST and /// is used to record information on the AST #[derive(PartialEq, Clone, Debug)] diff --git a/kclvm/tools/src/LSP/src/document_symbol.rs b/kclvm/tools/src/LSP/src/document_symbol.rs index 3f3cd8015..986952f79 100644 --- a/kclvm/tools/src/LSP/src/document_symbol.rs +++ b/kclvm/tools/src/LSP/src/document_symbol.rs @@ -96,7 +96,7 @@ fn scope_obj_to_document_symbol(obj: ScopeObject) -> DocumentSymbol { fn scope_obj_kind_to_document_symbol_kind(kind: ScopeObjectKind) -> SymbolKind { match kind { ScopeObjectKind::Variable => SymbolKind::VARIABLE, - ScopeObjectKind::Attribute => SymbolKind::PROPERTY, + ScopeObjectKind::Attribute(_) => SymbolKind::PROPERTY, ScopeObjectKind::Definition => SymbolKind::STRUCT, ScopeObjectKind::Parameter => SymbolKind::VARIABLE, ScopeObjectKind::TypeAlias => SymbolKind::TYPE_PARAMETER, diff --git a/kclvm/tools/src/LSP/src/hover.rs b/kclvm/tools/src/LSP/src/hover.rs index a0c62db72..0c1497d66 100644 --- a/kclvm/tools/src/LSP/src/hover.rs +++ b/kclvm/tools/src/LSP/src/hover.rs @@ -20,14 +20,75 @@ pub(crate) fn hover( if let crate::goto_def::Definition::Object(obj) = def { match obj.kind { ScopeObjectKind::Definition => { - docs.insert(obj.ty.ty_str()); - let doc = obj.ty.into_schema_type().doc.clone(); - if !doc.is_empty() { - docs.insert(doc); + // Schema Definition hover + // ``` + // pkg + // schema Foo(Base) + // ----------------- + // doc + // ----------------- + // Attributes: + // attr1: type + // attr2? type + // ``` + let schema_ty = obj.ty.into_schema_type(); + let base: String = if let Some(base) = schema_ty.base { + base.name + } else { + "".to_string() + }; + docs.insert(format!( + "{}\n\nschema {}{}", + schema_ty.pkgpath, schema_ty.name, base + )); + if !schema_ty.doc.is_empty() { + docs.insert(schema_ty.doc.clone()); + } + let mut attrs = vec!["Attributes:".to_string()]; + for (name, attr) in schema_ty.attrs { + attrs.push(format!( + "{}{}:{}", + name, + if attr.is_optional { "?" } else { "" }, + format!(" {}", attr.ty.ty_str()), + )); } + docs.insert(attrs.join("\n\n")); } + ScopeObjectKind::Attribute(attr) => match attr { + // Schema Attribute Definition hover + // ``` + // attr1?: type + // ------------ + // doc + // ``` + kclvm_sema::resolver::scope::Attr::SchemaAttr(schema_attr) => { + docs.insert(format!( + "{}{}: {}", + obj.name, + if schema_attr.option { "?" } else { "" }, + obj.ty.ty_str() + )); + if let Some(doc) = obj.doc { + docs.insert(doc); + } + } + kclvm_sema::resolver::scope::Attr::ConfigAttr => { + docs.insert(format!("{}: {}", obj.name, obj.ty.ty_str())); + if let Some(doc) = obj.doc { + docs.insert(doc); + } + } + }, _ => { - docs.insert(obj.ty.ty_str()); + // Variable + // ``` + // name: type + //``` + docs.insert(format!("{}: {}", obj.name, obj.ty.ty_str())); + if let Some(doc) = obj.doc { + docs.insert(doc); + } } } } diff --git a/kclvm/tools/src/LSP/src/test_data/hover_test/hover.k b/kclvm/tools/src/LSP/src/test_data/hover_test/hover.k new file mode 100644 index 000000000..6f6976278 --- /dev/null +++ b/kclvm/tools/src/LSP/src/test_data/hover_test/hover.k @@ -0,0 +1,18 @@ +schema Person: + """ + hover doc test + + Attributes + ---------- + name : str, default is False, required + name doc test + age : int, default is False, optional + age doc test + """ + name: str + age?: int + + p = Person{ + name: "Alice" + age: 1 + } \ No newline at end of file diff --git a/kclvm/tools/src/LSP/src/tests.rs b/kclvm/tools/src/LSP/src/tests.rs index e34ee20f0..f1c2039e6 100644 --- a/kclvm/tools/src/LSP/src/tests.rs +++ b/kclvm/tools/src/LSP/src/tests.rs @@ -796,11 +796,17 @@ fn schema_doc_hover_test() { match got.contents { lsp_types::HoverContents::Array(vec) => { if let MarkedString::String(s) = vec[0].clone() { - assert_eq!(s, "Person"); + assert_eq!(s, "pkg\n\nschema Person"); } 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\n__settings__?: {str:any}\n\nname: str\n\nage: int" + ); + } } _ => unreachable!("test error"), } @@ -813,7 +819,80 @@ fn schema_doc_hover_test() { match got.contents { lsp_types::HoverContents::Scalar(marked_string) => { if let MarkedString::String(s) = marked_string { - assert_eq!(s, "str"); + assert_eq!(s, "name: str"); + } + } + _ => unreachable!("test error"), + } +} + +#[test] +fn schema_doc_hover_test1() { + let (file, program, prog_scope, _) = compile_test_file("src/test_data/hover_test/hover.k"); + + let pos = KCLPos { + filename: file.clone(), + line: 15, + column: Some(11), + }; + let got = hover(&program, &pos, &prog_scope).unwrap(); + + match got.contents { + lsp_types::HoverContents::Array(vec) => { + if let MarkedString::String(s) = vec[0].clone() { + assert_eq!(s, "__main__\n\nschema Person"); + } + 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\n__settings__?: {str:any}\n\nname: str\n\nage?: int" + ); + } + } + _ => unreachable!("test error"), + } +} + +#[test] +fn schema_attr_hover_test() { + let (file, program, prog_scope, _) = compile_test_file("src/test_data/hover_test/hover.k"); + + let pos = KCLPos { + filename: file.clone(), + line: 16, + column: Some(11), + }; + let got = hover(&program, &pos, &prog_scope).unwrap(); + + match got.contents { + lsp_types::HoverContents::Array(vec) => { + if let MarkedString::String(s) = vec[0].clone() { + assert_eq!(s, "name: str"); + } + if let MarkedString::String(s) = vec[1].clone() { + assert_eq!(s, "name doc test"); + } + } + _ => unreachable!("test error"), + } + + let pos = KCLPos { + filename: file.clone(), + line: 17, + column: Some(11), + }; + let got = hover(&program, &pos, &prog_scope).unwrap(); + + match got.contents { + lsp_types::HoverContents::Array(vec) => { + if let MarkedString::String(s) = vec[0].clone() { + assert_eq!(s, "age?: int"); + } + if let MarkedString::String(s) = vec[1].clone() { + assert_eq!(s, "age doc test"); } } _ => unreachable!("test error"),