shebang hover (#2290)

* add a test

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* plugoin

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2024-05-02 16:31:33 -07:00
committed by GitHub
parent 3950de0a4d
commit 723cf4f746
4 changed files with 97 additions and 5 deletions

View File

@ -167,6 +167,7 @@ export class LanguageServerPlugin implements PluginValue {
if (pos === null) return null if (pos === null) return null
const dom = document.createElement('div') const dom = document.createElement('div')
dom.classList.add('documentation') dom.classList.add('documentation')
dom.style.zIndex = '99999999'
if (this.allowHTMLContent) dom.innerHTML = formatContents(contents) if (this.allowHTMLContent) dom.innerHTML = formatContents(contents)
else dom.textContent = formatContents(contents) else dom.textContent = formatContents(contents)
return { pos, end, create: (view) => ({ dom }), above: true } return { pos, end, create: (view) => ({ dom }), above: true }

View File

@ -36,6 +36,28 @@ pub struct Program {
} }
impl Program { impl Program {
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
// Check if we are in the non code meta.
if let Some(meta) = self.get_non_code_meta_for_position(pos) {
for node in &meta.start {
if node.contains(pos) {
// We only care about the shebang.
if let NonCodeValue::Shebang { value: _ } = &node.value {
let source_range: SourceRange = node.into();
return Some(Hover::Comment {
value: r#"The `#!` at the start of a script, known as a shebang, specifies the path to the interpreter that should execute the script. This line is not necessary for your `kcl` to run in the modeling-app. You can safely delete it. If you wish to learn more about what you _can_ do with a shebang, read this doc: [zoo.dev/docs/faq/shebang](https://zoo.dev/docs/faq/shebang)."#.to_string(),
range: source_range.to_lsp_range(code),
});
}
}
}
}
let value = self.get_value_for_position(pos)?;
value.get_hover_value_for_position(pos, code)
}
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
let indentation = options.get_indentation(indentation_level); let indentation = options.get_indentation(indentation_level);
let result = self let result = self
@ -814,6 +836,18 @@ pub struct NonCodeNode {
pub value: NonCodeValue, pub value: NonCodeValue,
} }
impl From<NonCodeNode> for SourceRange {
fn from(value: NonCodeNode) -> Self {
Self([value.start, value.end])
}
}
impl From<&NonCodeNode> for SourceRange {
fn from(value: &NonCodeNode) -> Self {
Self([value.start, value.end])
}
}
impl NonCodeNode { impl NonCodeNode {
pub fn contains(&self, pos: usize) -> bool { pub fn contains(&self, pos: usize) -> bool {
self.start <= pos && pos <= self.end self.start <= pos && pos <= self.end
@ -2987,6 +3021,10 @@ pub enum Hover {
parameter_index: u32, parameter_index: u32,
range: LspRange, range: LspRange,
}, },
Comment {
value: String,
range: LspRange,
},
} }
/// Format options. /// Format options.

View File

@ -795,11 +795,7 @@ impl LanguageServer for Backend {
return Ok(None); return Ok(None);
}; };
let Some(value) = ast.get_value_for_position(pos) else { let Some(hover) = ast.get_hover_value_for_position(pos, current_code) else {
return Ok(None);
};
let Some(hover) = value.get_hover_value_for_position(pos, current_code) else {
return Ok(None); return Ok(None);
}; };
@ -836,6 +832,13 @@ impl LanguageServer for Backend {
})) }))
} }
crate::ast::types::Hover::Signature { .. } => Ok(None), crate::ast::types::Hover::Signature { .. } => Ok(None),
crate::ast::types::Hover::Comment { value, range } => Ok(Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value,
}),
range: Some(range),
})),
} }
} }
@ -944,6 +947,9 @@ impl LanguageServer for Backend {
Ok(Some(signature.clone())) Ok(Some(signature.clone()))
} }
crate::ast::types::Hover::Comment { value: _, range: _ } => {
return Ok(None);
}
} }
} }

View File

@ -884,6 +884,53 @@ async fn test_kcl_lsp_on_hover() {
} }
} }
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_on_hover_shebang() {
let server = kcl_lsp_server(false).await.unwrap();
// Send open file.
server
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
text_document: tower_lsp::lsp_types::TextDocumentItem {
uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(),
version: 1,
text: r#"#!/usr/bin/env zoo kcl view
startSketchOn()"#
.to_string(),
},
})
.await;
server.wait_on_handle().await;
// Send hover request.
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 0, character: 2 },
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
// Check the hover.
if let Some(hover) = hover {
assert_eq!(
hover.contents,
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent {
kind: tower_lsp::lsp_types::MarkupKind::Markdown,
value: "The `#!` at the start of a script, known as a shebang, specifies the path to the interpreter that should execute the script. This line is not necessary for your `kcl` to run in the modeling-app. You can safely delete it. If you wish to learn more about what you _can_ do with a shebang, read this doc: [zoo.dev/docs/faq/shebang](https://zoo.dev/docs/faq/shebang).".to_string()
})
);
} else {
panic!("Expected hover");
}
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_signature_help() { async fn test_kcl_lsp_signature_help() {
let server = kcl_lsp_server(false).await.unwrap(); let server = kcl_lsp_server(false).await.unwrap();