diff --git a/src/editor/plugins/lsp/plugin.ts b/src/editor/plugins/lsp/plugin.ts index d53baf52e..09952786d 100644 --- a/src/editor/plugins/lsp/plugin.ts +++ b/src/editor/plugins/lsp/plugin.ts @@ -167,6 +167,7 @@ export class LanguageServerPlugin implements PluginValue { if (pos === null) return null const dom = document.createElement('div') dom.classList.add('documentation') + dom.style.zIndex = '99999999' if (this.allowHTMLContent) dom.innerHTML = formatContents(contents) else dom.textContent = formatContents(contents) return { pos, end, create: (view) => ({ dom }), above: true } diff --git a/src/wasm-lib/kcl/src/ast/types.rs b/src/wasm-lib/kcl/src/ast/types.rs index 96858b060..29fb96a85 100644 --- a/src/wasm-lib/kcl/src/ast/types.rs +++ b/src/wasm-lib/kcl/src/ast/types.rs @@ -36,6 +36,28 @@ pub struct Program { } impl Program { + pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option { + // 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 { let indentation = options.get_indentation(indentation_level); let result = self @@ -814,6 +836,18 @@ pub struct NonCodeNode { pub value: NonCodeValue, } +impl From 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 { pub fn contains(&self, pos: usize) -> bool { self.start <= pos && pos <= self.end @@ -2987,6 +3021,10 @@ pub enum Hover { parameter_index: u32, range: LspRange, }, + Comment { + value: String, + range: LspRange, + }, } /// Format options. diff --git a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs index 0effa67cc..5206a6116 100644 --- a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs +++ b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs @@ -795,11 +795,7 @@ impl LanguageServer for Backend { return Ok(None); }; - let Some(value) = ast.get_value_for_position(pos) else { - return Ok(None); - }; - - let Some(hover) = value.get_hover_value_for_position(pos, current_code) else { + let Some(hover) = ast.get_hover_value_for_position(pos, current_code) else { return Ok(None); }; @@ -836,6 +832,13 @@ impl LanguageServer for Backend { })) } 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())) } + crate::ast::types::Hover::Comment { value: _, range: _ } => { + return Ok(None); + } } } diff --git a/src/wasm-lib/kcl/src/lsp/tests.rs b/src/wasm-lib/kcl/src/lsp/tests.rs index 28d6d7480..7819443f1 100644 --- a/src/wasm-lib/kcl/src/lsp/tests.rs +++ b/src/wasm-lib/kcl/src/lsp/tests.rs @@ -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")] async fn test_kcl_lsp_signature_help() { let server = kcl_lsp_server(false).await.unwrap();