Add vim bindings for hover

Allow scrolling in hover popover
This commit is contained in:
Keith Simmons 2022-06-07 11:47:17 -07:00
parent 67d9abc00f
commit a6c0ee472c
11 changed files with 1058 additions and 1006 deletions

1504
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -99,6 +99,7 @@
"context": "Editor && vim_operator == g",
"bindings": {
"g": "vim::StartOfDocument",
"h": "editor::Hover",
"escape": [
"vim::SwitchMode",
"Normal"

View File

@ -25,9 +25,9 @@ use gpui::{
geometry::vector::{vec2f, Vector2F},
impl_actions, impl_internal_actions,
platform::CursorStyle,
text_layout, AppContext, AsyncAppContext, Axis, ClipboardItem, Element, ElementBox, Entity,
ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
WeakViewHandle,
text_layout, AppContext, AsyncAppContext, Axis, ClipboardItem, Element, ElementBox,
ElementStateContext, Entity, ModelHandle, MutableAppContext, ReadModel, RenderContext, Task,
View, ViewContext, ViewHandle, WeakViewHandle,
};
pub use language::{char_kind, CharKind};
use language::{
@ -81,7 +81,7 @@ pub struct Scroll(pub Vector2F);
pub struct Select(pub SelectPhase);
#[derive(Clone)]
pub struct Hover {
pub struct HoverAt {
point: Option<DisplayPoint>,
}
@ -198,6 +198,7 @@ actions!(
ShowCompletions,
OpenExcerpts,
RestartLanguageServer,
Hover,
]
);
@ -214,7 +215,7 @@ impl_actions!(
]
);
impl_internal_actions!(editor, [Scroll, Select, Hover, GoToDefinitionAt]);
impl_internal_actions!(editor, [Scroll, Select, HoverAt, GoToDefinitionAt]);
enum DocumentHighlightRead {}
enum DocumentHighlightWrite {}
@ -302,6 +303,7 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::show_completions);
cx.add_action(Editor::toggle_code_actions);
cx.add_action(Editor::hover);
cx.add_action(Editor::hover_at);
cx.add_action(Editor::open_excerpts);
cx.add_action(Editor::restart_language_server);
cx.add_async_action(Editor::confirm_completion);
@ -888,48 +890,64 @@ impl CodeActionsMenu {
}
}
struct HoverPopover {
#[derive(Clone)]
pub(crate) struct HoverPopover {
pub project: ModelHandle<Project>,
pub hover_point: DisplayPoint,
pub range: Range<DisplayPoint>,
pub contents: Vec<HoverBlock>,
}
impl HoverPopover {
fn render(&self, style: EditorStyle, project: &Project) -> (DisplayPoint, ElementBox) {
let mut flex = Flex::new(Axis::Vertical);
flex.extend(self.contents.iter().map(|content| {
if let Some(language) = content
.language
.clone()
.and_then(|language| project.languages().get_language(&language))
{
let runs =
language.highlight_text(&content.text.as_str().into(), 0..content.text.len());
fn render<C: ElementStateContext + ReadModel>(
&self,
style: EditorStyle,
cx: &mut C,
) -> (DisplayPoint, ElementBox) {
let element = MouseEventHandler::new::<HoverPopover, _, _>(0, cx, |_, cx| {
let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock, _>(1, None, cx);
flex.extend(self.contents.iter().map(|content| {
let project = self.project.read(cx);
if let Some(language) = content
.language
.clone()
.and_then(|language| project.languages().get_language(&language))
{
let runs = language
.highlight_text(&content.text.as_str().into(), 0..content.text.len());
Text::new(content.text.clone(), style.text.clone())
.with_soft_wrap(true)
.with_highlights(
runs.iter()
.filter_map(|(range, id)| {
id.style(style.theme.syntax.as_ref())
.map(|style| (range.clone(), style))
})
.collect(),
)
.boxed()
} else {
Text::new(content.text.clone(), style.hover_popover.prose.clone())
.with_soft_wrap(true)
.contained()
.with_style(style.hover_popover.block_style)
.boxed()
}
}));
(
self.range.start,
Text::new(content.text.clone(), style.text.clone())
.with_soft_wrap(true)
.with_highlights(
runs.iter()
.filter_map(|(range, id)| {
id.style(style.theme.syntax.as_ref())
.map(|style| (range.clone(), style))
})
.collect(),
)
.boxed()
} else {
Text::new(content.text.clone(), style.hover_popover.prose.clone())
.with_soft_wrap(true)
.contained()
.with_style(style.hover_popover.block_style)
.boxed()
}
}));
flex.contained()
.with_style(style.hover_popover.container)
.boxed(),
)
.boxed()
})
.with_cursor_style(CursorStyle::Arrow)
.with_padding(Padding {
bottom: 5.,
top: 5.,
..Default::default()
})
.boxed();
(self.range.start, element)
}
}
@ -1489,7 +1507,7 @@ impl Editor {
}
}
self.hover_state.close();
self.hide_hover(cx);
if old_cursor_position.to_display_point(&display_map).row()
!= new_cursor_position.to_display_point(&display_map).row()
@ -1852,6 +1870,10 @@ impl Editor {
return;
}
if self.hide_hover(cx) {
return;
}
if self.hide_context_menu(cx).is_some() {
return;
}
@ -2480,49 +2502,65 @@ impl Editor {
}))
}
/// The hover action dispatches between `show_hover` or `hide_hover`
/// Bindable action which uses the most recent selection head to trigger a hover
fn hover(&mut self, _: &Hover, cx: &mut ViewContext<Self>) {
let head = self.selections.newest_display(cx).head();
self.show_hover(head, true, cx);
}
/// The internal hover action dispatches between `show_hover` or `hide_hover`
/// depending on whether a point to hover over is provided.
fn hover(&mut self, action: &Hover, cx: &mut ViewContext<Self>) {
fn hover_at(&mut self, action: &HoverAt, cx: &mut ViewContext<Self>) {
if let Some(point) = action.point {
self.show_hover(point, cx);
self.show_hover(point, false, cx);
} else {
self.hide_hover(cx);
}
}
/// Hides the type information popup ASAP.
/// Triggered by the `Hover` action when the cursor is not over a symbol.
fn hide_hover(&mut self, cx: &mut ViewContext<Self>) {
let task = cx.spawn_weak(|this, mut cx| {
async move {
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
// consistently keep track of state to make handoff smooth
let (_recent_hover, _in_grace) = this.hover_state.determine_state(false);
/// Hides the type information popup.
/// Triggered by the `Hover` action when the cursor is not over a symbol or when the
/// selecitons changed.
fn hide_hover(&mut self, cx: &mut ViewContext<Self>) -> bool {
// consistently keep track of state to make handoff smooth
self.hover_state.determine_state(false);
// only notify the context once
if this.hover_state.popover.is_some() {
this.hover_state.popover = None;
cx.notify();
}
});
}
Ok(())
}
.log_err()
});
let mut did_hide = false;
self.hover_task = Some(task);
// only notify the context once
if self.hover_state.popover.is_some() {
self.hover_state.popover = None;
did_hide = true;
cx.notify();
}
self.clear_background_highlights::<HoverState>(cx);
self.hover_task = None;
did_hide
}
/// Queries the LSP and shows type info and documentation
/// about the symbol the mouse is currently hovering over.
/// Triggered by the `Hover` action when the cursor may be over a symbol.
fn show_hover(&mut self, point: DisplayPoint, cx: &mut ViewContext<Self>) {
fn show_hover(
&mut self,
point: DisplayPoint,
ignore_timeout: bool,
cx: &mut ViewContext<Self>,
) {
if self.pending_rename.is_some() {
return;
}
if let Some(hover) = &self.hover_state.popover {
if hover.hover_point == point {
// Hover triggered from same location as last time. Don't show again.
return;
}
}
let snapshot = self.snapshot(cx);
let (buffer, buffer_position) = if let Some(output) = self
.buffer
@ -2534,15 +2572,6 @@ impl Editor {
return;
};
let buffer_snapshot = buffer.read(cx).snapshot();
if let Some(existing_popover) = &self.hover_state.popover {
if existing_popover.range.contains(&point) {
// Hover already contains value. No need to request a new one
return;
}
}
let project = if let Some(project) = self.project.clone() {
project
} else {
@ -2550,49 +2579,44 @@ impl Editor {
};
// query the LSP for hover info
let hover = project.update(cx, |project, cx| {
let hover_request = project.update(cx, |project, cx| {
project.hover(&buffer, buffer_position.clone(), cx)
});
let buffer_snapshot = buffer.read(cx).snapshot();
let task = cx.spawn_weak(|this, mut cx| {
async move {
let mut contents = None;
let hover = match hover.await {
Ok(hover) => hover,
Err(_) => None,
};
let mut symbol_range = point..point;
// determine the contents of the popover
if let Some(hover) = hover {
if hover.contents.is_empty() {
contents = None;
} else {
contents = Some(hover.contents);
if let Some(range) = hover.range {
let offset_range = range.to_offset(&buffer_snapshot);
if offset_range
.contains(&point.to_offset(&snapshot.display_snapshot, Bias::Left))
{
symbol_range = offset_range
.start
.to_display_point(&snapshot.display_snapshot)
..offset_range
.end
.to_display_point(&snapshot.display_snapshot);
} else {
contents = None;
}
}
// Construct new hover popover from hover request
let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| {
if hover_result.contents.is_empty() {
return None;
}
};
let hover_popover = contents.map(|contents| HoverPopover {
range: symbol_range,
contents,
let range = if let Some(range) = hover_result.range {
let offset_range = range.to_offset(&buffer_snapshot);
if !offset_range
.contains(&point.to_offset(&snapshot.display_snapshot, Bias::Left))
{
return None;
}
offset_range
.start
.to_display_point(&snapshot.display_snapshot)
..offset_range
.end
.to_display_point(&snapshot.display_snapshot)
} else {
point..point
};
Some(HoverPopover {
project: project.clone(),
hover_point: point,
range,
contents: hover_result.contents,
})
});
if let Some(this) = this.upgrade(&cx) {
@ -2612,7 +2636,32 @@ impl Editor {
// `smooth_handoff` and `in_grace` determine whether to switch right away.
// `recent_hover` will activate the handoff after the initial delay.
if (smooth_handoff || !recent_hover || in_grace) && visible {
// `ignore_timeout` is set when the user manually sent the hover action.
if (ignore_timeout || smooth_handoff || !recent_hover || in_grace)
&& visible
{
// Highlight the selected symbol using a background highlight
if let Some(display_range) =
hover_popover.as_ref().map(|popover| popover.range.clone())
{
let start = snapshot.display_snapshot.buffer_snapshot.anchor_after(
display_range
.start
.to_offset(&snapshot.display_snapshot, Bias::Right),
);
let end = snapshot.display_snapshot.buffer_snapshot.anchor_before(
display_range
.end
.to_offset(&snapshot.display_snapshot, Bias::Left),
);
this.highlight_background::<HoverState>(
vec![start..end],
|theme| theme.editor.hover_popover.highlight,
cx,
);
}
this.hover_state.popover = hover_popover;
cx.notify();
}
@ -2869,18 +2918,11 @@ impl Editor {
) -> Option<(DisplayPoint, ElementBox)> {
self.context_menu
.as_ref()
.map(|menu| menu.render(cursor_position, style))
.map(|menu| menu.render(cursor_position, style, cx))
}
pub fn render_hover_popover(
&self,
style: EditorStyle,
project: &Project,
) -> Option<(DisplayPoint, ElementBox)> {
self.hover_state
.popover
.as_ref()
.map(|hover| hover.render(style, project))
pub(crate) fn hover_popover(&self) -> Option<HoverPopover> {
self.hover_state.popover.clone()
}
fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext<Self>) {
@ -4963,7 +5005,6 @@ impl Editor {
// Position the selection in the rename editor so that it matches the current selection.
this.show_local_selections = false;
let rename_editor = cx.add_view(|cx| {
println!("Rename editor created.");
let mut editor = Editor::single_line(None, cx);
if let Some(old_highlight_id) = old_highlight_id {
editor.override_text_style =

View File

@ -5,7 +5,7 @@ use super::{
};
use crate::{
display_map::{DisplaySnapshot, TransformBlock},
EditorStyle, GoToDefinition, Hover,
EditorStyle, GoToDefinition, HoverAt,
};
use clock::ReplicaId;
use collections::{BTreeMap, HashMap};
@ -509,36 +509,31 @@ impl EditorElement {
}
if let Some((position, hover_popover)) = layout.hover.as_mut() {
if position.row() >= start_row {
if let Some(cursor_row_layout) = &layout
.line_layouts
.get((position.row() - start_row) as usize)
{
cx.scene.push_stacking_context(None);
cx.scene.push_stacking_context(None);
let size = hover_popover.size();
let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
let y = position.row() as f32 * layout.line_height - scroll_top - size.y();
let mut popover_origin = content_origin + vec2f(x, y);
// This is safe because we check on layout whether the required row is available
let hovered_row_layout = &layout.line_layouts[(position.row() - start_row) as usize];
let size = hover_popover.size();
let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;
let y = position.row() as f32 * layout.line_height - scroll_top - size.y();
let mut popover_origin = content_origin + vec2f(x, y);
if popover_origin.y() < 0.0 {
popover_origin.set_y(popover_origin.y() + layout.line_height + size.y());
}
let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
if x_out_of_bounds < 0.0 {
popover_origin.set_x(popover_origin.x() + x_out_of_bounds);
}
hover_popover.paint(
popover_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
cx,
);
cx.scene.pop_stacking_context();
}
if popover_origin.y() < 0.0 {
popover_origin.set_y(popover_origin.y() + layout.line_height + size.y());
}
let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
if x_out_of_bounds < 0.0 {
popover_origin.set_x(popover_origin.x() + x_out_of_bounds);
}
hover_popover.paint(
popover_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
cx,
);
cx.scene.pop_stacking_context();
}
cx.scene.pop_layer();
@ -1114,7 +1109,6 @@ impl Element for EditorElement {
let mut context_menu = None;
let mut code_actions_indicator = None;
let mut hover = None;
cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| {
let newest_selection_head = view
.selections
@ -1125,18 +1119,14 @@ impl Element for EditorElement {
let style = view.style(cx);
if (start_row..end_row).contains(&newest_selection_head.row()) {
if view.context_menu_visible() {
context_menu = view.render_context_menu(newest_selection_head, style.clone());
context_menu =
view.render_context_menu(newest_selection_head, style.clone(), cx);
}
code_actions_indicator = view
.render_code_actions_indicator(&style, cx)
.map(|indicator| (newest_selection_head.row(), indicator));
}
if let Some(project) = view.project.clone() {
let project = project.read(cx);
hover = view.render_hover_popover(style, project);
}
});
if let Some((_, context_menu)) = context_menu.as_mut() {
@ -1159,18 +1149,27 @@ impl Element for EditorElement {
);
}
if let Some((_, hover)) = hover.as_mut() {
hover.layout(
SizeConstraint {
min: Vector2F::zero(),
max: vec2f(
(120. * em_width).min(size.x()),
(size.y() - line_height) * 3. / 2.,
),
},
cx,
);
}
let hover = self.view(cx).hover_popover().and_then(|hover| {
let (point, mut rendered) = hover.render(style.clone(), cx);
if point.row() >= snapshot.scroll_position().y() as u32 {
if line_layouts.len() > (point.row() - start_row) as usize {
rendered.layout(
SizeConstraint {
min: Vector2F::zero(),
max: vec2f(
(120. * em_width).min(size.x()),
(size.y() - line_height) * 1. / 2.,
),
},
cx,
);
return Some((point, rendered));
}
}
None
});
let blocks = self.layout_blocks(
start_row..end_row,
@ -1270,6 +1269,12 @@ impl Element for EditorElement {
}
}
if let Some((_, hover)) = &mut layout.hover {
if hover.dispatch_event(event, cx) {
return true;
}
}
for (_, block) in &mut layout.blocks {
if block.dispatch_event(event, cx) {
return true;
@ -1317,7 +1322,7 @@ impl Element for EditorElement {
None
};
cx.dispatch_action(Hover { point });
cx.dispatch_action(HoverAt { point });
true
}
_ => false,

View File

@ -195,6 +195,14 @@ impl SelectionsCollection {
resolve(self.newest_anchor(), &self.buffer(cx))
}
pub fn newest_display(&self, cx: &mut MutableAppContext) -> Selection<DisplayPoint> {
let display_map = self.display_map(cx);
let selection = self
.newest_anchor()
.map(|point| point.to_display_point(&display_map));
selection
}
pub fn oldest_anchor(&self) -> &Selection<Anchor> {
self.disjoint
.iter()

View File

@ -319,6 +319,17 @@ impl Element for Flex {
}
}
}
if !handled {
if let &Event::MouseMoved { position, .. } = event {
// If this is a scrollable flex, and the mouse is over it, eat the scroll event to prevent
// propogating it to the element below.
if self.scroll_state.is_some() && bounds.contains_point(position) {
handled = true;
}
}
}
handled
}

View File

@ -160,7 +160,12 @@ impl Presenter {
if cx.window_is_active(self.window_id) {
if let Some(event) = self.last_mouse_moved_event.clone() {
self.dispatch_event(event, cx)
let mut invalidated_views = Vec::new();
self.handle_hover_events(&event, &mut invalidated_views, cx);
for view_id in invalidated_views {
cx.notify_view(self.window_id, view_id);
}
}
}
} else {
@ -222,8 +227,6 @@ impl Presenter {
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
let mut invalidated_views = Vec::new();
let mut hovered_regions = Vec::new();
let mut unhovered_regions = Vec::new();
let mut mouse_down_out_handlers = Vec::new();
let mut mouse_down_region = None;
let mut clicked_region = None;
@ -288,46 +291,8 @@ impl Presenter {
}
}
}
Event::MouseMoved {
position,
left_mouse_down,
} => {
Event::MouseMoved { .. } => {
self.last_mouse_moved_event = Some(event.clone());
if !left_mouse_down {
let mut style_to_assign = CursorStyle::Arrow;
for region in self.cursor_regions.iter().rev() {
if region.bounds.contains_point(position) {
style_to_assign = region.style;
break;
}
}
cx.platform().set_cursor_style(style_to_assign);
let mut hover_depth = None;
for (region, depth) in self.mouse_regions.iter().rev() {
if region.bounds.contains_point(position)
&& hover_depth.map_or(true, |hover_depth| hover_depth == *depth)
{
hover_depth = Some(*depth);
if let Some(region_id) = region.id() {
if !self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
hovered_regions.push((region.clone(), position));
self.hovered_region_ids.insert(region_id);
}
}
} else {
if let Some(region_id) = region.id() {
if self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
unhovered_regions.push((region.clone(), position));
self.hovered_region_ids.remove(&region_id);
}
}
}
}
}
}
Event::LeftMouseDragged { position } => {
if let Some((clicked_region, prev_drag_position)) = self
@ -348,25 +313,8 @@ impl Presenter {
_ => {}
}
let mut event_cx = self.build_event_context(cx);
let mut handled = false;
for (unhovered_region, position) in unhovered_regions {
handled = true;
if let Some(hover_callback) = unhovered_region.hover {
event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
hover_callback(position, false, event_cx);
})
}
}
for (hovered_region, position) in hovered_regions {
handled = true;
if let Some(hover_callback) = hovered_region.hover {
event_cx.with_current_view(hovered_region.view_id, |event_cx| {
hover_callback(position, true, event_cx);
})
}
}
let (mut handled, mut event_cx) =
self.handle_hover_events(&event, &mut invalidated_views, cx);
for (handler, view_id, position) in mouse_down_out_handlers {
event_cx.with_current_view(view_id, |event_cx| handler(position, event_cx))
@ -439,6 +387,80 @@ impl Presenter {
}
}
fn handle_hover_events<'a>(
&'a mut self,
event: &Event,
invalidated_views: &mut Vec<usize>,
cx: &'a mut MutableAppContext,
) -> (bool, EventContext<'a>) {
let mut unhovered_regions = Vec::new();
let mut hovered_regions = Vec::new();
if let Event::MouseMoved {
position,
left_mouse_down,
} = event
{
if !left_mouse_down {
let mut style_to_assign = CursorStyle::Arrow;
for region in self.cursor_regions.iter().rev() {
if region.bounds.contains_point(*position) {
style_to_assign = region.style;
break;
}
}
cx.platform().set_cursor_style(style_to_assign);
let mut hover_depth = None;
for (region, depth) in self.mouse_regions.iter().rev() {
if region.bounds.contains_point(*position)
&& hover_depth.map_or(true, |hover_depth| hover_depth == *depth)
{
hover_depth = Some(*depth);
if let Some(region_id) = region.id() {
if !self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
hovered_regions.push((region.clone(), position));
self.hovered_region_ids.insert(region_id);
}
}
} else {
if let Some(region_id) = region.id() {
if self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
unhovered_regions.push((region.clone(), position));
self.hovered_region_ids.remove(&region_id);
}
}
}
}
}
}
let mut event_cx = self.build_event_context(cx);
let mut handled = false;
for (unhovered_region, position) in unhovered_regions {
handled = true;
if let Some(hover_callback) = unhovered_region.hover {
event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
hover_callback(*position, false, event_cx);
})
}
}
for (hovered_region, position) in hovered_regions {
handled = true;
if let Some(hover_callback) = hovered_region.hover {
event_cx.with_current_view(hovered_region.view_id, |event_cx| {
hover_callback(*position, true, event_cx);
})
}
}
(handled, event_cx)
}
pub fn build_event_context<'a>(
&'a mut self,
cx: &'a mut MutableAppContext,

View File

@ -984,6 +984,7 @@ impl LspCommand for GetHover {
_: ModelHandle<Buffer>,
_: AsyncAppContext,
) -> Result<Self::Response> {
println!("Response from proto");
let range = if let (Some(start), Some(end)) = (message.start, message.end) {
language::proto::deserialize_anchor(start)
.and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end))

View File

@ -219,7 +219,7 @@ pub struct Symbol {
pub signature: [u8; 32],
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct HoverBlock {
pub text: String,
pub language: Option<String>,
@ -2926,7 +2926,6 @@ impl Project {
position: T,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Hover>>> {
// TODO: proper return type
let position = position.to_point_utf16(buffer.read(cx));
self.request_lsp(buffer.clone(), GetHover { position }, cx)
}
@ -3767,8 +3766,10 @@ impl Project {
} else if let Some(project_id) = self.remote_id() {
let rpc = self.client.clone();
let message = request.to_proto(project_id, buffer);
dbg!(&message);
return cx.spawn(|this, cx| async move {
let response = rpc.request(message).await?;
dbg!(&response);
request
.response_from_proto(response, this, buffer_handle, cx)
.await

View File

@ -629,4 +629,5 @@ pub struct HoverPopover {
pub container: ContainerStyle,
pub block_style: ContainerStyle,
pub prose: TextStyle,
pub highlight: Color,
}

View File

@ -21,6 +21,7 @@ export default function HoverPopover(theme: Theme) {
block_style: {
padding: { top: 4 },
},
prose: text(theme, "sans", "primary", { "size": "sm" })
prose: text(theme, "sans", "primary", { "size": "sm" }),
highlight: theme.editor.highlight.occurrence.value,
}
}