Skip to content

Commit

Permalink
Syntax highlight in hover (#1336)
Browse files Browse the repository at this point in the history
* LSP Hover shows highlight(Incomplete, not classified)

Signed-off-by: Wck-iipi <[email protected]>

* Hover differentiates between LanguageString and String

Signed-off-by: Wck-iipi <[email protected]>

* Updated tests for attributes and schema

Signed-off-by: Wck-iipi <[email protected]>

* Changed "String" and "LanguageString" to enum types

Signed-off-by: Wck-iipi <[email protected]>

* Added new formatting for hover

Signed-off-by: Wck-iipi <[email protected]>

* Added hover for all types

Signed-off-by: Wck-iipi <[email protected]>

* Fixed tests

Signed-off-by: Wck-iipi <[email protected]>

* Fixed CI

Signed-off-by: Wck-iipi <[email protected]>

* Fixed CI

Signed-off-by: Wck-iipi <[email protected]>

* Put docs in bottom and fixed schema indent and tested it.

Signed-off-by: Wck-iipi <[email protected]>

* Added 4 spaces instead of tabs

Signed-off-by: Wck-iipi <[email protected]>

* Fixed CI(config_hover_test_main)

Signed-off-by: Wck-iipi <[email protected]>

* Fixed dict_key_in_schema test

Signed-off-by: Wck-iipi <[email protected]>

---------

Signed-off-by: Wck-iipi <[email protected]>
  • Loading branch information
Wck-iipi authored May 21, 2024
1 parent f370c2a commit 6bf352c
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 71 deletions.
31 changes: 31 additions & 0 deletions kclvm/sema/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,37 @@ impl SchemaType {

format!("{}\n\nschema {}{}", self.pkgpath, self.name, params_str)
}

pub fn schema_ty_signature_no_pkg(&self) -> String {
let base: String = if let Some(base) = &self.base {
format!("({})", base.name)
} else {
"".to_string()
};
let params: String = if self.func.params.is_empty() {
"".to_string()
} else {
format!(
"[{}]",
self.func
.params
.iter()
.map(|p| format!("{}: {}", p.name.clone(), p.ty.ty_str()))
.collect::<Vec<String>>()
.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!("schema {}{}", self.name, params_str)
}
}

#[derive(Debug, Clone, PartialEq)]
Expand Down
168 changes: 117 additions & 51 deletions kclvm/tools/src/LSP/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@ 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)
enum MarkedStringType {
String,
LanguageString,
}

pub(crate) fn hover(
_program: &Program,
kcl_pos: &KCLPos,
gs: &GlobalState,
) -> Option<lsp_types::Hover> {
let mut docs: Vec<String> = vec![];
let mut docs: Vec<(String, MarkedStringType)> = vec![];
let def = find_def_with_gs(kcl_pos, gs, true);
match def {
Some(def_ref) => match gs.get_symbols().get_symbol(def_ref) {
Expand All @@ -28,24 +34,24 @@ pub(crate) fn hover(
// ```
// pkg
// schema Foo(Base)[param: type]
// attr1: type
// attr2? type
// ```
// -----------------
// doc
// -----------------
// Attributes:
// attr1: type
// attr2? type
// ```
let schema_ty = ty.into_schema_type();
docs.push(schema_ty.schema_ty_signature_str());
if !schema_ty.doc.is_empty() {
docs.push(schema_ty.doc.clone());
}

let schema_ty_signature_no_pkg = schema_ty.schema_ty_signature_no_pkg();

docs.push((schema_ty.pkgpath, MarkedStringType::String));

// The attr of schema_ty does not contain the attrs from inherited base schema.
// 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![schema_ty_signature_no_pkg];
for schema_attr in schema_attrs {
if let kclvm_sema::core::symbol::SymbolKind::Attribute =
schema_attr.get_kind()
Expand All @@ -59,25 +65,31 @@ pub(crate) fn hover(
None => ANY_TYPE_STR.to_string(),
};
attrs.push(format!(
"{}{}: {}",
" {}{}: {}",
name,
if attr_symbol.is_optional() { "?" } else { "" },
attr_ty_str,
));
}
}
docs.push(attrs.join("\n\n"));
docs.push((attrs.join("\n"), MarkedStringType::LanguageString));
if !schema_ty.doc.is_empty() {
docs.push((schema_ty.doc.clone(), MarkedStringType::String));
}
}
_ => {}
},
kclvm_sema::core::symbol::SymbolKind::Attribute => {
let sema_info = obj.get_sema_info();
match &sema_info.ty {
Some(ty) => {
docs.push(format!("{}: {}", &obj.get_name(), ty.ty_str()));
docs.push((
format!("{}: {}", &obj.get_name(), ty.ty_str()),
MarkedStringType::LanguageString,
));
if let Some(doc) = &sema_info.doc {
if !doc.is_empty() {
docs.push(doc.clone());
docs.push((doc.clone(), MarkedStringType::String));
}
}
}
Expand All @@ -87,10 +99,16 @@ pub(crate) fn hover(
kclvm_sema::core::symbol::SymbolKind::Value => match &obj.get_sema_info().ty {
Some(ty) => match &ty.kind {
kclvm_sema::ty::TypeKind::Function(func_ty) => {
docs.extend(build_func_hover_content(func_ty, obj.get_name().clone()));
docs.append(&mut build_func_hover_content(
func_ty.clone(),
obj.get_name().clone(),
));
}
_ => {
docs.push(format!("{}: {}", &obj.get_name(), ty.ty_str()));
docs.push((
format!("{}: {}", &obj.get_name(), ty.ty_str()),
MarkedStringType::LanguageString,
));
}
},
_ => {}
Expand All @@ -100,10 +118,12 @@ pub(crate) fn hover(
kclvm_sema::core::symbol::SymbolKind::Decorator => {
match BUILTIN_DECORATORS.get(&obj.get_name()) {
Some(ty) => {
docs.extend(build_func_hover_content(
&ty.into_func_type(),
let mut hover_content = build_func_hover_content(
ty.into_func_type(),
obj.get_name().clone(),
));
);

docs.append(&mut hover_content);
}
None => todo!(),
}
Expand All @@ -113,7 +133,10 @@ pub(crate) fn hover(
Some(ty) => ty.ty_str(),
None => "".to_string(),
};
docs.push(format!("{}: {}", &obj.get_name(), ty_str));
docs.push((
format!("{}: {}", &obj.get_name(), ty_str),
MarkedStringType::LanguageString,
));
}
},
None => {}
Expand All @@ -123,19 +146,31 @@ pub(crate) fn hover(
docs_to_hover(docs)
}

fn convert_doc_to_marked_string(doc: &(String, MarkedStringType)) -> MarkedString {
match doc.1 {
MarkedStringType::String => MarkedString::String(doc.0.clone()),
MarkedStringType::LanguageString => {
MarkedString::LanguageString(lsp_types::LanguageString {
language: "kcl".to_string(),
value: doc.0.clone(),
})
}
}
}

// Convert docs to Hover. This function will convert to
// None, Scalar or Array according to the number of positions
fn docs_to_hover(docs: Vec<String>) -> Option<lsp_types::Hover> {
fn docs_to_hover(docs: Vec<(String, MarkedStringType)>) -> Option<lsp_types::Hover> {
match docs.len() {
0 => None,
1 => Some(Hover {
contents: HoverContents::Scalar(MarkedString::String(docs[0].clone())),
contents: HoverContents::Scalar(convert_doc_to_marked_string(&docs[0])),
range: None,
}),
_ => Some(Hover {
contents: HoverContents::Array(
docs.iter()
.map(|doc| MarkedString::String(doc.clone()))
.map(|doc| convert_doc_to_marked_string(doc))
.collect(),
),
range: None,
Expand All @@ -151,11 +186,14 @@ fn docs_to_hover(docs: Vec<String>) -> Option<lsp_types::Hover> {
// -----------------
// doc
// ```
fn build_func_hover_content(func_ty: &FunctionType, name: String) -> Vec<String> {
let mut docs = vec![];
fn build_func_hover_content(
func_ty: FunctionType,
name: String,
) -> Vec<(String, MarkedStringType)> {
let mut docs: Vec<(String, MarkedStringType)> = vec![];
if let Some(ty) = &func_ty.self_ty {
let self_ty = format!("{}\n\n", ty.ty_str());
docs.push(self_ty);
docs.push((self_ty, MarkedStringType::String));
}

let mut sig = format!("fn {}(", name);
Expand All @@ -172,17 +210,21 @@ fn build_func_hover_content(func_ty: &FunctionType, name: String) -> Vec<String>
sig.push(')');
}
sig.push_str(&format!(" -> {}", func_ty.return_ty.ty_str()));
docs.push(sig);
docs.push((sig, MarkedStringType::LanguageString));

if !func_ty.doc.is_empty() {
docs.push(func_ty.doc.clone().replace('\n', "\n\n"));
docs.push((
func_ty.doc.clone().replace('\n', "\n\n"),
MarkedStringType::String,
));
}
docs
}

#[cfg(test)]
mod tests {
use crate::hover::docs_to_hover;
use crate::hover::MarkedStringType;
use std::path::PathBuf;

use kclvm_error::Position as KCLPos;
Expand Down Expand Up @@ -213,13 +255,20 @@ 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");
}
if let MarkedString::String(s) = vec[1].clone() {
assert_eq!(s, "hover doc test");
if let MarkedString::LanguageString(s) = vec[1].clone() {
assert_eq!(
s.value,
"schema Person\n name: str\n age: int".to_string()
);
} else {
unreachable!("Wrong type");
}
if let MarkedString::String(s) = vec[2].clone() {
assert_eq!(s, "Attributes:\n\nname: str\n\nage: int");
assert_eq!(s, "hover doc test");
} else {
unreachable!("Wrong type");
}
}
_ => unreachable!("test error"),
Expand All @@ -232,8 +281,8 @@ mod tests {
let got = hover(&program, &pos, &gs).unwrap();
match got.contents {
lsp_types::HoverContents::Scalar(marked_string) => {
if let MarkedString::String(s) = marked_string {
assert_eq!(s, "name: str");
if let MarkedString::LanguageString(s) = marked_string {
assert_eq!(s.value, "name: str");
}
}
_ => unreachable!("test error"),
Expand All @@ -245,13 +294,22 @@ mod tests {
fn test_docs_to_hover_multiple_docs() {
// Given multiple documentation strings
let docs = vec![
"Documentation string 1".to_string(),
"Documentation string 2".to_string(),
"Documentation string 3".to_string(),
(
"Documentation string 1".to_string(),
MarkedStringType::String,
),
(
"Documentation string 2".to_string(),
MarkedStringType::String,
),
(
"Documentation string 3".to_string(),
MarkedStringType::String,
),
];

// When converting to hover content
let hover = docs_to_hover(docs.clone());
let hover = docs_to_hover(docs);

// Then the result should be a Hover object with an Array of MarkedString::String
assert!(hover.is_some());
Expand Down Expand Up @@ -291,13 +349,17 @@ 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__");
}
if let MarkedString::String(s) = vec[1].clone() {
assert_eq!(s, "hover doc test");
if let MarkedString::LanguageString(s) = vec[1].clone() {
assert_eq!(s.value, "schema Person\n name: str\n age?: int");
} else {
unreachable!("Wrong type");
}
if let MarkedString::String(s) = vec[2].clone() {
assert_eq!(s, "Attributes:\n\nname: str\n\nage?: int");
assert_eq!(s, "hover doc test");
} else {
unreachable!("Wrong type");
}
}
_ => unreachable!("test error"),
Expand Down Expand Up @@ -526,10 +588,10 @@ 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");
}
if let MarkedString::String(s) = vec[1].clone() {
assert_eq!(s, "Attributes:\n\nn: int\n\nvalue: int");
assert_eq!(s, "schema Fib\n\n n: int\n\n value: int");
}
}
_ => unreachable!("test error"),
Expand Down Expand Up @@ -586,11 +648,11 @@ mod tests {
column: Some(1),
};
let got = hover(&program, &pos, &gs).unwrap();
let expect_content = vec![MarkedString::String(
"fn deprecated(version: str, reason: str, strict: bool) -> any".to_string(),
), MarkedString::String(
"This decorator is used to get the deprecation message according to the wrapped key-value pair.".to_string(),
)];
let expect_content = vec![MarkedString::LanguageString(lsp_types::LanguageString {
language: "kcl".to_string(),
value: "fn deprecated(version: str, reason: str, strict: bool) -> any".to_string(),
}),
MarkedString::String("This decorator is used to get the deprecation message according to the wrapped key-value pair.".to_string())];
match got.contents {
lsp_types::HoverContents::Array(vec) => {
assert_eq!(vec, expect_content)
Expand Down Expand Up @@ -623,9 +685,13 @@ 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()),
let expect_content: Vec<MarkedString> = vec![
MarkedString::String("__main__".to_string()),
MarkedString::LanguageString(lsp_types::LanguageString {
language: "kcl".to_string(),
value: "schema Data1\\[m: {str:str}](Data)\n name: str\n age: int"
.to_string(),
}),
];

match got.contents {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
source: tools/src/LSP/src/hover.rs
expression: "format!(\"{:?}\", got)"
---
Hover { contents: Scalar(String("name: int")), range: None }
Hover { contents: Scalar(LanguageString(LanguageString { language: "kcl", value: "name: int" })), range: None }
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
source: tools/src/LSP/src/hover.rs
expression: "format!(\"{:?}\", got)"
---
Hover { contents: Scalar(String("name: int")), range: None }
Hover { contents: Scalar(LanguageString(LanguageString { language: "kcl", value: "name: int" })), range: None }
Loading

0 comments on commit 6bf352c

Please sign in to comment.