Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: lsp hover keyword highlight #1331

Merged
merged 11 commits into from
May 22, 2024
15 changes: 4 additions & 11 deletions kclvm/sema/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ impl SchemaType {
}
}

pub fn schema_ty_signature_str(&self) -> String {
pub fn schema_ty_signature_str(&self) -> (String, String) {
let base: String = if let Some(base) = &self.base {
format!("({})", base.name)
} else {
Expand All @@ -312,17 +312,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 rest_sign = format!("schema {}{}{}:", self.name, params, base);

(self.pkgpath.clone(), rest_sign)
}

pub fn schema_ty_signature_no_pkg(&self) -> String {
Expand Down
8 changes: 5 additions & 3 deletions kclvm/tools/src/LSP/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,8 @@ fn schema_ty_to_value_complete_item(schema_ty: &SchemaType) -> KCLCompletionItem
);
let detail = {
let mut details = vec![];
details.push(schema_ty.schema_ty_signature_str());
let (pkgpath, rest_sign) = schema_ty.schema_ty_signature_str();
Peefy marked this conversation as resolved.
Show resolved Hide resolved
details.push(format!("{}\n\n{}", pkgpath, rest_sign));
details.push("Attributes:".to_string());
for (name, attr) in &schema_ty.attrs {
details.push(format!(
Expand Down Expand Up @@ -543,7 +544,8 @@ fn schema_ty_to_value_complete_item(schema_ty: &SchemaType) -> KCLCompletionItem
fn schema_ty_to_type_complete_item(schema_ty: &SchemaType) -> KCLCompletionItem {
let detail = {
let mut details = vec![];
details.push(schema_ty.schema_ty_signature_str());
let (pkgpath, rest_sign) = schema_ty.schema_ty_signature_str();
details.push(format!("{}\n\n{}", pkgpath, rest_sign));
details.push("Attributes:".to_string());
for (name, attr) in &schema_ty.attrs {
details.push(format!(
Expand Down Expand Up @@ -1259,7 +1261,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\nschema Person[b: int](Base):\nAttributes:\nc: int"
.to_string()
),
documentation: Some(lsp_types::Documentation::String("".to_string())),
Expand Down
145 changes: 78 additions & 67 deletions kclvm/tools/src/LSP/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ 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)

enum MarkedStringType {
String,
LanguageString,
}

/// Returns a short text describing element at position.
/// Specifically, the doc for schema and schema attr(todo)
pub(crate) fn hover(
_program: &Program,
kcl_pos: &KCLPos,
gs: &GlobalState,
) -> Option<lsp_types::Hover> {
let mut docs: Vec<(String, MarkedStringType)> = vec![];
let mut pkg_path = String::new();
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 @@ -33,25 +33,22 @@ pub(crate) fn hover(
// Schema Definition hover
// ```
// pkg
// schema Foo(Base)[param: type]
// schema Foo(Base)[param: type]:
// -----------------
// attr1: type
// attr2? type
// ```
// -----------------
// doc
// -----------------
// ```
let schema_ty = ty.into_schema_type();

let schema_ty_signature_no_pkg = schema_ty.schema_ty_signature_no_pkg();

docs.push((schema_ty.pkgpath, MarkedStringType::String));
let (pkgpath, rest_sign) = schema_ty.schema_ty_signature_str();
pkg_path = pkgpath;

// 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![schema_ty_signature_no_pkg];
let mut attrs: Vec<String> = vec![];
for schema_attr in schema_attrs {
if let kclvm_sema::core::symbol::SymbolKind::Attribute =
schema_attr.get_kind()
Expand All @@ -72,7 +69,10 @@ pub(crate) fn hover(
));
}
}
docs.push((attrs.join("\n"), MarkedStringType::LanguageString));

let merged_doc = format!("{}\n{}", rest_sign.clone(), attrs.join("\n"));
docs.push((merged_doc, MarkedStringType::LanguageString));

if !schema_ty.doc.is_empty() {
docs.push((schema_ty.doc.clone(), MarkedStringType::String));
}
Expand All @@ -89,7 +89,7 @@ pub(crate) fn hover(
));
if let Some(doc) = &sema_info.doc {
if !doc.is_empty() {
docs.push((doc.clone(), MarkedStringType::String));
docs.push((doc.clone(), MarkedStringType::LanguageString));
}
}
}
Expand Down Expand Up @@ -143,15 +143,16 @@ pub(crate) fn hover(
},
None => {}
}
docs_to_hover(docs)
docs_to_hover(docs, pkg_path)
}

// Convert doc to Marked String. This function will convert docs to Markedstrings
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(),
language: "KCL".to_owned(),
value: doc.0.clone(),
})
}
Expand All @@ -160,19 +161,28 @@ fn convert_doc_to_marked_string(doc: &(String, MarkedStringType)) -> MarkedStrin

// 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, MarkedStringType)>) -> Option<lsp_types::Hover> {
match docs.len() {
fn docs_to_hover(
docs: Vec<(String, MarkedStringType)>,
pkg_path: String,
Peefy marked this conversation as resolved.
Show resolved Hide resolved
) -> Option<lsp_types::Hover> {
let mut all_docs: Vec<MarkedString> = Vec::new();

if !pkg_path.is_empty() {
all_docs.push(MarkedString::String(pkg_path.clone()));
}

for doc in docs {
all_docs.push(convert_doc_to_marked_string(&doc));
}

match all_docs.len() {
0 => None,
1 => Some(Hover {
contents: HoverContents::Scalar(convert_doc_to_marked_string(&docs[0])),
contents: HoverContents::Scalar(all_docs.remove(0)),
range: None,
}),
_ => Some(Hover {
contents: HoverContents::Array(
docs.iter()
.map(|doc| convert_doc_to_marked_string(doc))
.collect(),
),
contents: HoverContents::Array(all_docs),
range: None,
}),
}
Expand Down Expand Up @@ -228,7 +238,7 @@ mod tests {
use std::path::PathBuf;

use kclvm_error::Position as KCLPos;
use lsp_types::MarkedString;
use lsp_types::{LanguageString, MarkedString};
use proc_macro_crate::bench_test;

use crate::tests::compile_test_file;
Expand Down Expand Up @@ -257,18 +267,11 @@ mod tests {
if let MarkedString::String(s) = vec[0].clone() {
assert_eq!(s, "pkg");
}
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[1].clone() {
Peefy marked this conversation as resolved.
Show resolved Hide resolved
assert_eq!(s, "schema Person:\n name: str\n age: int");
}
if let MarkedString::String(s) = vec[2].clone() {
assert_eq!(s, "hover doc test");
} else {
unreachable!("Wrong type");
}
}
_ => unreachable!("test error"),
Expand All @@ -281,8 +284,8 @@ mod tests {
let got = hover(&program, &pos, &gs).unwrap();
match got.contents {
lsp_types::HoverContents::Scalar(marked_string) => {
if let MarkedString::LanguageString(s) = marked_string {
assert_eq!(s.value, "name: str");
if let MarkedString::String(s) = marked_string {
Peefy marked this conversation as resolved.
Show resolved Hide resolved
assert_eq!(s, "name: str");
}
}
_ => unreachable!("test error"),
Expand All @@ -309,7 +312,7 @@ mod tests {
];

// When converting to hover content
let hover = docs_to_hover(docs);
let hover = docs_to_hover(docs, "".to_string());

// Then the result should be a Hover object with an Array of MarkedString::String
assert!(hover.is_some());
Expand Down Expand Up @@ -351,15 +354,11 @@ mod tests {
if let MarkedString::String(s) = vec[0].clone() {
assert_eq!(s, "__main__");
}
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[1].clone() {
assert_eq!(s, "schema Person:\n\tname: str\n\tage?: int");
Peefy marked this conversation as resolved.
Show resolved Hide resolved
}
if let MarkedString::String(s) = vec[2].clone() {
assert_eq!(s, "hover doc test");
} else {
unreachable!("Wrong type");
}
}
_ => unreachable!("test error"),
Expand All @@ -381,10 +380,7 @@ mod tests {
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");
assert_eq!(s, "name: str\nname doc test");
}
}
_ => unreachable!("test error"),
Expand All @@ -400,10 +396,7 @@ mod tests {
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");
assert_eq!(s, "age: int\nage doc test");
}
}
_ => unreachable!("test error"),
Expand All @@ -425,10 +418,7 @@ mod tests {
match got.contents {
lsp_types::HoverContents::Array(vec) => {
if let MarkedString::String(s) = vec[0].clone() {
assert_eq!(s, "fn f(x: any) -> any");
}
if let MarkedString::String(s) = vec[1].clone() {
assert_eq!(s, "lambda documents");
assert_eq!(s, "fn f(x: any) -> any\nlambda documents");
}
}
_ => unreachable!("test error"),
Expand Down Expand Up @@ -591,7 +581,7 @@ mod tests {
assert_eq!(s, "fib");
}
if let MarkedString::String(s) = vec[1].clone() {
assert_eq!(s, "schema Fib\n\n n: int\n\n value: int");
assert_eq!(s, "schema Fib:\n\tn: int\n\tvalue: int");
Peefy marked this conversation as resolved.
Show resolved Hide resolved
}
}
_ => unreachable!("test error"),
Expand Down Expand Up @@ -648,11 +638,15 @@ mod tests {
column: Some(1),
};
let got = hover(&program, &pos, &gs).unwrap();
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(),
let expect_content = vec![
MarkedString::LanguageString(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())];
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 @@ -685,12 +679,11 @@ mod tests {
};
let got = hover(&program, &pos, &gs).unwrap();

let expect_content: Vec<MarkedString> = vec![
let expect_content = 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(),
MarkedString::LanguageString(LanguageString {
language: "KCL".to_string(),
value: "schema Data1[m: {str:str}](Data):\n name: str\n age: int".to_string(),
}),
];

Expand All @@ -713,22 +706,40 @@ mod tests {
column: Some(5),
};
let got = hover(&program, &pos, &gs).unwrap();
insta::assert_snapshot!(format!("{:?}", got));

match got.contents {
lsp_types::HoverContents::Scalar(marked_string) => {
if let MarkedString::String(s) = marked_string {
assert_eq!(s, "name: int");
}
}
_ => unreachable!("test error"),
}

let pos = KCLPos {
filename: file.clone(),
line: 9,
column: Some(5),
};
let got = hover(&program, &pos, &gs).unwrap();
insta::assert_snapshot!(format!("{:?}", got));
let expected =
lsp_types::HoverContents::Scalar(MarkedString::LanguageString(LanguageString {
language: "KCL".to_string(),
value: "name: int".to_string(),
}));
assert_eq!(got.contents, expected);

let pos = KCLPos {
filename: file.clone(),
line: 13,
column: Some(5),
};
let got = hover(&program, &pos, &gs).unwrap();
insta::assert_snapshot!(format!("{:?}", got));
let expected =
lsp_types::HoverContents::Scalar(MarkedString::LanguageString(LanguageString {
language: "KCL".to_string(),
value: "name: int".to_string(),
}));
assert_eq!(got.contents, expected);
}
}
Loading
Loading