diff --git a/viewport/src/editor.rs b/viewport/src/editor.rs index a61cd54..5454c6e 100644 --- a/viewport/src/editor.rs +++ b/viewport/src/editor.rs @@ -334,6 +334,12 @@ impl EditorState { } 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() { self.blocks = crate::blocks::parse_blocks(text); } else { @@ -1001,74 +1007,160 @@ impl EditorState { fn view_blocks(&self) -> Element<'_, Message, Theme, iced_wgpu::Renderer> { 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> = 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() { match block.kind { BlockKind::Text => { - let top_pad = if bi == 0 { 38.0_f32 } else { 4.0 }; let block_idx = bi; + let line_h = self.font_size * 1.3; - 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::Fill) - .padding(Padding { top: top_pad, 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::( - 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 = 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, - 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)) + if single_text_block { + 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::Fill) + .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::( + settings, + |highlight, _theme| Format { + color: Some(syntax::highlight_color(highlight.kind)), + font: syntax::highlight_font(highlight.kind), + }, + ) .into(); - block_elements.push( - iced_widget::row![gutter_canvas, editor_el] - .height(Length::Fill) + let text = block.content.text(); + let result_mask: Vec = 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) + .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::( + 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 = 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, + 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::Fixed(editor_h)) + .into(); + + block_elements.push( + iced_widget::container( + iced_widget::row![gutter_canvas, editor_el] + ) + .width(Length::Fill) + .height(Length::Fixed(editor_h)) .into() - ); + ); + } } BlockKind::Heading => { let level = match block.heading_level { @@ -1093,7 +1185,18 @@ impl EditorState { ); } } - _ => {} + BlockKind::EvalResult => { + block_elements.push( + crate::eval_block::view::(&block.eval_text) + ); + } + BlockKind::Tree => { + if let Some(ref data) = block.tree_data { + block_elements.push( + crate::tree_block::view::(data) + ); + } + } } } @@ -1102,10 +1205,11 @@ impl EditorState { .width(Length::Fill) .height(Length::Fill) .into() - } else if block_elements.len() == 1 { + } else if single_text_block { block_elements.remove(0) } else { iced_widget::column(block_elements) + .width(Length::Fill) .height(Length::Fill) .into() }