diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md
index 4a6e697a..4b737893 100644
--- a/book/src/generated/typable-cmd.md
+++ b/book/src/generated/typable-cmd.md
@@ -55,6 +55,7 @@
 | `:lsp-restart` | Restarts the language servers used by the current doc |
 | `:lsp-stop` | Stops the language servers that are used by the current doc |
 | `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
+| `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. |
 | `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
 | `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
 | `:debug-eval` | Evaluate expression in current debug context. |
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index ef539180..4237b6f6 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -1527,6 +1527,84 @@ fn tree_sitter_scopes(
     Ok(())
 }
 
+fn tree_sitter_highlight_name(
+    cx: &mut compositor::Context,
+    _args: &[Cow<str>],
+    event: PromptEvent,
+) -> anyhow::Result<()> {
+    fn find_highlight_at_cursor(
+        cx: &mut compositor::Context<'_>,
+    ) -> Option<helix_core::syntax::Highlight> {
+        use helix_core::syntax::HighlightEvent;
+
+        let (view, doc) = current!(cx.editor);
+        let syntax = doc.syntax()?;
+        let text = doc.text().slice(..);
+        let cursor = doc.selection(view.id).primary().cursor(text);
+        let byte = text.char_to_byte(cursor);
+        let node = syntax
+            .tree()
+            .root_node()
+            .descendant_for_byte_range(byte, byte)?;
+        // Query the same range as the one used in syntax highlighting.
+        let range = {
+            // Calculate viewport byte ranges:
+            let row = text.char_to_line(view.offset.anchor.min(text.len_chars()));
+            // Saturating subs to make it inclusive zero indexing.
+            let last_line = text.len_lines().saturating_sub(1);
+            let height = view.inner_area(doc).height;
+            let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
+            let start = text.line_to_byte(row.min(last_line));
+            let end = text.line_to_byte(last_visible_line + 1);
+
+            start..end
+        };
+
+        let mut highlight = None;
+
+        for event in syntax.highlight_iter(text, Some(range), None) {
+            match event.unwrap() {
+                HighlightEvent::Source { start, end }
+                    if start == node.start_byte() && end == node.end_byte() =>
+                {
+                    return highlight;
+                }
+                HighlightEvent::HighlightStart(hl) => {
+                    highlight = Some(hl);
+                }
+                _ => (),
+            }
+        }
+
+        None
+    }
+
+    if event != PromptEvent::Validate {
+        return Ok(());
+    }
+
+    let Some(highlight) = find_highlight_at_cursor(cx) else {
+        return Ok(());
+    };
+
+    let content = cx.editor.theme.scope(highlight.0).to_string();
+
+    let callback = async move {
+        let call: job::Callback = Callback::EditorCompositor(Box::new(
+            move |editor: &mut Editor, compositor: &mut Compositor| {
+                let content = ui::Markdown::new(content, editor.syn_loader.clone());
+                let popup = Popup::new("hover", content).auto_close(true);
+                compositor.replace_or_push("hover", popup);
+            },
+        ));
+        Ok(call)
+    };
+
+    cx.jobs.callback(callback);
+
+    Ok(())
+}
+
 fn vsplit(
     cx: &mut compositor::Context,
     args: &[Cow<str>],
@@ -2703,6 +2781,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
         fun: tree_sitter_scopes,
         signature: CommandSignature::none(),
     },
+    TypableCommand {
+        name: "tree-sitter-highlight-name",
+        aliases: &[],
+        doc: "Display name of tree-sitter highlight scope under the cursor.",
+        fun: tree_sitter_highlight_name,
+        signature: CommandSignature::none(),
+    },
     TypableCommand {
         name: "debug-start",
         aliases: &["dbg"],
diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs
index a288ae9a..4acc5664 100644
--- a/helix-view/src/theme.rs
+++ b/helix-view/src/theme.rs
@@ -297,6 +297,11 @@ impl Theme {
         self.highlights[index]
     }
 
+    #[inline]
+    pub fn scope(&self, index: usize) -> &str {
+        &self.scopes[index]
+    }
+
     pub fn name(&self) -> &str {
         &self.name
     }