2 fix block rendering: computed heights, scrollable column, set_text guard

This commit is contained in:
jess 2026-04-08 05:36:34 -07:00
parent 80fd148280
commit a390a2cc4a
1 changed files with 163 additions and 59 deletions

View File

@ -334,6 +334,12 @@ impl EditorState {
} }
pub fn set_text(&mut self, text: &str) { pub fn set_text(&mut self, text: &str) {
if text.is_empty() && !self.blocks.is_empty() {
// Swift sends empty text for untitled docs — keep initial content if present
if self.blocks.len() > 1 || !self.blocks[0].content.text().is_empty() {
return;
}
}
if self.blocks.is_empty() { if self.blocks.is_empty() {
self.blocks = crate::blocks::parse_blocks(text); self.blocks = crate::blocks::parse_blocks(text);
} else { } else {
@ -1001,20 +1007,102 @@ impl EditorState {
fn view_blocks(&self) -> Element<'_, Message, Theme, iced_wgpu::Renderer> { fn view_blocks(&self) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
use crate::blocks::BlockKind; use crate::blocks::BlockKind;
let single_text_block = self.blocks.len() == 1
&& self.blocks[0].kind == BlockKind::Text;
let title_bar_h = 38.0_f32;
let mut block_elements: Vec<Element<'_, Message, Theme, iced_wgpu::Renderer>> = Vec::new(); let mut block_elements: Vec<Element<'_, Message, Theme, iced_wgpu::Renderer>> = Vec::new();
if !single_text_block && !self.blocks.is_empty() {
if self.blocks[0].kind != BlockKind::Text {
block_elements.push(
iced_widget::container(iced_widget::text(""))
.height(Length::Fixed(title_bar_h))
.width(Length::Fill)
.into()
);
}
}
for (bi, block) in self.blocks.iter().enumerate() { for (bi, block) in self.blocks.iter().enumerate() {
match block.kind { match block.kind {
BlockKind::Text => { BlockKind::Text => {
let top_pad = if bi == 0 { 38.0_f32 } else { 4.0 };
let block_idx = bi; let block_idx = bi;
let line_h = self.font_size * 1.3;
if single_text_block {
let editor = iced_widget::text_editor(&block.content) let editor = iced_widget::text_editor(&block.content)
.on_action(move |action| Message::BlockAction(block_idx, action)) .on_action(move |action| Message::BlockAction(block_idx, action))
.font(Font::MONOSPACE) .font(Font::MONOSPACE)
.size(self.font_size) .size(self.font_size)
.height(Length::Fill) .height(Length::Fill)
.padding(Padding { top: top_pad, right: 8.0, bottom: 8.0, left: 8.0 }) .padding(Padding { top: title_bar_h, right: 8.0, bottom: 8.0, left: 8.0 })
.wrapping(Wrapping::Word)
.key_binding(macos_key_binding)
.style(|_theme, _status| {
let p = palette::current();
Style {
background: Background::Color(p.base),
border: Border::default(),
placeholder: p.overlay0,
value: p.text,
selection: Color { a: 0.4, ..p.blue },
}
});
let settings = SyntaxSettings {
lang: self.lang.clone().unwrap_or_default(),
source: block.content.text(),
};
let editor_el: Element<'_, Message, Theme, iced_wgpu::Renderer> = editor
.highlight_with::<SyntaxHighlighter>(
settings,
|highlight, _theme| Format {
color: Some(syntax::highlight_color(highlight.kind)),
font: syntax::highlight_font(highlight.kind),
},
)
.into();
let text = block.content.text();
let result_mask: Vec<bool> = text.lines().map(|l| is_result_line(l)).collect();
let source_line_count = result_mask.iter().filter(|r| !**r).count();
let decors = compute_line_decors(&text);
let gutter = Gutter {
line_count: block.content.line_count(),
source_line_count,
font_size: self.font_size,
scroll_offset: self.scroll_offset,
cursor_line: block.content.cursor().position.line,
top_pad: title_bar_h,
result_mask,
line_decors: decors,
};
let gw = gutter.gutter_width();
let gutter_canvas: Element<'_, Message, Theme, iced_wgpu::Renderer> =
canvas::Canvas::new(gutter)
.width(Length::Fixed(gw))
.height(Length::Fill)
.into();
block_elements.push(
iced_widget::row![gutter_canvas, editor_el]
.height(Length::Fill)
.into()
);
} else {
let top_pad = if bi == 0 { title_bar_h } else { 0.0 };
let actual_lines = block.content.line_count().max(1);
let editor_h = (actual_lines as f32) * line_h + top_pad + 8.0;
let editor = iced_widget::text_editor(&block.content)
.on_action(move |action| Message::BlockAction(block_idx, action))
.font(Font::MONOSPACE)
.size(self.font_size)
.height(Length::Fixed(editor_h))
.padding(Padding { top: top_pad, right: 8.0, bottom: 4.0, left: 8.0 })
.wrapping(Wrapping::Word) .wrapping(Wrapping::Word)
.key_binding(macos_key_binding) .key_binding(macos_key_binding)
.style(|_theme, _status| { .style(|_theme, _status| {
@ -1061,15 +1149,19 @@ impl EditorState {
let gutter_canvas: Element<'_, Message, Theme, iced_wgpu::Renderer> = let gutter_canvas: Element<'_, Message, Theme, iced_wgpu::Renderer> =
canvas::Canvas::new(gutter) canvas::Canvas::new(gutter)
.width(Length::Fixed(gw)) .width(Length::Fixed(gw))
.height(Length::Fill) .height(Length::Fixed(editor_h))
.into(); .into();
block_elements.push( block_elements.push(
iced_widget::container(
iced_widget::row![gutter_canvas, editor_el] iced_widget::row![gutter_canvas, editor_el]
.height(Length::Fill) )
.width(Length::Fill)
.height(Length::Fixed(editor_h))
.into() .into()
); );
} }
}
BlockKind::Heading => { BlockKind::Heading => {
let level = match block.heading_level { let level = match block.heading_level {
1 => crate::heading_block::HeadingLevel::H1, 1 => crate::heading_block::HeadingLevel::H1,
@ -1093,7 +1185,18 @@ impl EditorState {
); );
} }
} }
_ => {} BlockKind::EvalResult => {
block_elements.push(
crate::eval_block::view::<Message>(&block.eval_text)
);
}
BlockKind::Tree => {
if let Some(ref data) = block.tree_data {
block_elements.push(
crate::tree_block::view::<Message>(data)
);
}
}
} }
} }
@ -1102,10 +1205,11 @@ impl EditorState {
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.into() .into()
} else if block_elements.len() == 1 { } else if single_text_block {
block_elements.remove(0) block_elements.remove(0)
} else { } else {
iced_widget::column(block_elements) iced_widget::column(block_elements)
.width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.into() .into()
} }