///|
pub fn[Msg] rich_text_editor(
  value~ : @core.Binding[String],
  placeholder~ : String,
  size~ : @core.Size,
  format~ : RichTextFormatter,
  font~ : @core.FontSpec,
  foreground~ : @core.Color,
  placeholder_color~ : @core.Color,
  background~ : @core.Brush,
  border? : @core.BorderStyle? = None,
  focused_border? : @core.BorderStyle? = None,
  cursor_color~ : @core.Color,
  corner_radius? : Double = 0.0,
  on_input? : ((String) -> Msg)? = None,
  on_input_edit? : ((String, Int, @core.TextRange?) -> Msg)? = None,
  on_selection_change? : ((Int, @core.TextRange?) -> Msg)? = None,
  on_prefix_click? : ((Int) -> Msg)? = None,
  on_command_click? : ((Int) -> Msg)? = None,
  input_transform? : ((String, Int, @core.TextRange?, String) -> RichTextInputTransform?)? = None,
  accepts_tab_input? : Bool = false,
  visible_scroll_y? : Double = 0.0,
  visible_viewport_height? : Double = 0.0,
  visible_overscan? : Double = 320.0,
  selection_caret? : Int = -1,
  selection_range? : @core.TextRange? = None,
  kind? : String = "RichTextEditor",
  key? : String? = None,
) -> @core.View[Msg] {
  @core.View::node(
    identity=() => @core.ViewIdentity::new(kind~, key~),
    revision=() => {
      "rich-text-editor:\{value.get()}:\{placeholder}:\{view_size_revision(size)}:\{view_font_revision(font)}:\{view_color_revision(foreground)}:\{view_color_revision(placeholder_color)}:\{view_brush_revision(background)}:\{view_border_option_revision(border)}:\{view_border_option_revision(focused_border)}:\{view_color_revision(cursor_color)}:\{corner_radius}:\{accepts_tab_input}:\{selection_caret}:\{view_text_range_option_revision(selection_range)}"
    },
    layout=ctx => {
      @core.ViewLayoutResult::new(size=ctx.constraints.constrain(size))
    },
    paint=ctx => {
      rich_text_editor_paint(
        ctx,
        value~,
        placeholder~,
        format~,
        font~,
        foreground~,
        placeholder_color~,
        background~,
        border~,
        focused_border~,
        cursor_color~,
        corner_radius~,
        visible_scroll_y~,
        visible_viewport_height~,
        visible_overscan~,
      )
    },
    event=ctx => {
      rich_text_editor_event(
        ctx,
        value~,
        format~,
        font~,
        on_input~,
        on_input_edit~,
        on_selection_change~,
        on_prefix_click~,
        on_command_click~,
        input_transform~,
        accepts_tab_input~,
      )
    },
    text_input_state=ctx => {
      Some(
        rich_text_input_state(
          text=value.get(),
          frame=ctx.frame,
          format~,
          font~,
          control=ctx.text_control,
          selection_caret~,
          selection_range~,
          text_system=ctx.text_system,
        ),
      )
    },
    text_command=(ctx, intent, paste_text) => {
      rich_text_editor_command(
        ctx,
        value~,
        format~,
        on_input~,
        on_input_edit~,
        on_selection_change~,
        input_transform~,
        intent,
        paste_text~,
      )
    },
    semantics=() => {
      @core.ViewSemanticsInfo::new(
        role=@core.SemanticsRole::TextField,
        label=placeholder,
        value=value.get(),
        actions=[
          @core.SemanticsAction::Focus,
          @core.SemanticsAction::SetText,
          @core.SemanticsAction::Submit,
        ],
      )
    },
    focusable=() => true,
  )
}

///|
pub fn[Msg] controlled_rich_text_editor(
  value~ : String,
  on_input~ : (String) -> Msg,
  placeholder~ : String,
  size~ : @core.Size,
  format~ : RichTextFormatter,
  font~ : @core.FontSpec,
  foreground~ : @core.Color,
  placeholder_color~ : @core.Color,
  background~ : @core.Brush,
  border? : @core.BorderStyle? = None,
  focused_border? : @core.BorderStyle? = None,
  cursor_color~ : @core.Color,
  corner_radius? : Double = 0.0,
  on_selection_change? : ((Int, @core.TextRange?) -> Msg)? = None,
  on_prefix_click? : ((Int) -> Msg)? = None,
  on_command_click? : ((Int) -> Msg)? = None,
  input_transform? : ((String, Int, @core.TextRange?, String) -> RichTextInputTransform?)? = None,
  accepts_tab_input? : Bool = false,
  visible_scroll_y? : Double = 0.0,
  visible_viewport_height? : Double = 0.0,
  visible_overscan? : Double = 320.0,
  selection_caret? : Int = -1,
  selection_range? : @core.TextRange? = None,
  kind? : String = "RichTextEditor",
  key? : String? = None,
) -> @core.View[Msg] {
  rich_text_editor(
    value=@core.Binding::new(get=() => value, set=_next => ()),
    placeholder~,
    size~,
    format~,
    font~,
    foreground~,
    placeholder_color~,
    background~,
    border~,
    focused_border~,
    cursor_color~,
    corner_radius~,
    on_input=Some(on_input),
    on_input_edit=None,
    on_selection_change~,
    on_prefix_click~,
    on_command_click~,
    input_transform~,
    accepts_tab_input~,
    visible_scroll_y~,
    visible_viewport_height~,
    visible_overscan~,
    selection_caret~,
    selection_range~,
    kind~,
    key~,
  )
}

///|
pub fn[Msg] controlled_rich_text_document_editor(
  value~ : String,
  document~ : RichTextDocument,
  on_input~ : (String) -> Msg,
  placeholder~ : String,
  size~ : @core.Size,
  font~ : @core.FontSpec,
  foreground~ : @core.Color,
  placeholder_color~ : @core.Color,
  background~ : @core.Brush,
  border? : @core.BorderStyle? = None,
  focused_border? : @core.BorderStyle? = None,
  cursor_color~ : @core.Color,
  corner_radius? : Double = 0.0,
  on_selection_change? : ((Int, @core.TextRange?) -> Msg)? = None,
  on_prefix_click? : ((Int) -> Msg)? = None,
  on_command_click? : ((Int) -> Msg)? = None,
  input_transform? : ((String, Int, @core.TextRange?, String) -> RichTextInputTransform?)? = None,
  accepts_tab_input? : Bool = false,
  visible_scroll_y? : Double = 0.0,
  visible_viewport_height? : Double = 0.0,
  visible_overscan? : Double = 320.0,
  selection_caret? : Int = -1,
  selection_range? : @core.TextRange? = None,
  kind? : String = "RichTextDocumentEditor",
  key? : String? = None,
) -> @core.View[Msg] {
  controlled_rich_text_editor(
    value~,
    on_input~,
    placeholder~,
    size~,
    format=_source => document,
    font~,
    foreground~,
    placeholder_color~,
    background~,
    border~,
    focused_border~,
    cursor_color~,
    corner_radius~,
    on_selection_change~,
    on_prefix_click~,
    on_command_click~,
    input_transform~,
    accepts_tab_input~,
    visible_scroll_y~,
    visible_viewport_height~,
    visible_overscan~,
    selection_caret~,
    selection_range~,
    kind~,
    key~,
  )
}

///|
fn markdown_session_current_scroll_y(
  visible_scroll_y : Double,
  visible_scroll_state : @core.ScrollState?,
) -> Double {
  match visible_scroll_state {
    Some(state) => state.offset().y
    None => visible_scroll_y
  }
}

///|
fn markdown_session_clamped_scroll_y(
  session : MarkdownDocumentSession,
  font : @core.FontSpec,
  source_mode~ : Bool,
  scroll_y : Double,
  viewport_height : Double,
) -> Double {
  if viewport_height <= 0.0 {
    return max_double(0.0, scroll_y)
  }
  let content_height = session.estimated_content_height(base=font, source_mode~) +
    96.0
  let max_scroll = max_double(0.0, content_height - viewport_height)
  clamp_double(scroll_y, 0.0, max_scroll)
}

///|
fn markdown_session_display_session(
  session : MarkdownDocumentSession,
  display_text : String,
) -> MarkdownDocumentSession {
  if display_text == session.source {
    session
  } else {
    MarkdownDocumentSession::new(display_text)
  }
}

///|
fn markdown_session_visible_window(
  session : MarkdownDocumentSession,
  selection : MarkdownEditorSelection,
  font : @core.FontSpec,
  source_mode~ : Bool,
  focus_mode~ : Bool,
  base_dir~ : String?,
  visible_scroll_y~ : Double,
  visible_viewport_height~ : Double,
  visible_overscan~ : Double,
  visible_scroll_state~ : @core.ScrollState?,
) -> MarkdownDocumentRichTextWindow {
  let scroll_y = markdown_session_clamped_scroll_y(
    session,
    font,
    source_mode~,
    markdown_session_current_scroll_y(visible_scroll_y, visible_scroll_state),
    visible_viewport_height,
  )
  session.rich_text_window(
    selection,
    base=font,
    source_mode~,
    focus_mode~,
    base_dir~,
    scroll_y~,
    viewport_height=visible_viewport_height,
    overscan=visible_overscan,
  )
}

///|
fn markdown_session_input_transform(
  source : String,
  caret : Int,
  range : @core.TextRange?,
  inserted : String,
) -> RichTextInputTransform? {
  markdown_editor_transform_input(source, caret, range, inserted)
}

///|
fn[Msg] markdown_session_on_input_edit(
  session : MarkdownDocumentSession,
  on_transaction : (MarkdownEditTransaction) -> Msg,
) -> (String, Int, @core.TextRange?) -> Msg {
  (next_source, caret, next_selection) => {
    on_transaction(
      MarkdownEditTransaction::from_sources(
        session.source,
        next_source,
        caret~,
        selection=next_selection,
        reason=TextInput,
      ),
    )
  }
}

///|
fn markdown_session_editor_paint(
  ctx : @core.ViewPaintContext,
  session~ : MarkdownDocumentSession,
  selection~ : MarkdownEditorSelection,
  placeholder~ : String,
  font~ : @core.FontSpec,
  foreground~ : @core.Color,
  placeholder_color~ : @core.Color,
  background~ : @core.Brush,
  border~ : @core.BorderStyle?,
  focused_border~ : @core.BorderStyle?,
  cursor_color~ : @core.Color,
  corner_radius~ : Double,
  source_mode~ : Bool,
  focus_mode~ : Bool,
  base_dir~ : String?,
  visible_scroll_y~ : Double,
  visible_viewport_height~ : Double,
  visible_overscan~ : Double,
  visible_scroll_state~ : @core.ScrollState?,
) -> @core.ViewPaintPlan {
  let commands : Array[@core.DrawCommand] = []
  let overlay_commands : Array[@core.DrawCommand] = []
  let rounded = @core.RoundedRect::new(rect=ctx.frame, radius=corner_radius)
  commands.push(@core.DrawCommand::FillRoundedRectBrush(rounded, background))
  append_rich_text_border(
    commands,
    rounded,
    if ctx.focused {
      focused_border
    } else {
      border
    },
  )
  let text = session.source
  let display_text = rich_text_with_composition(
    text,
    ctx.text_control,
    focused=ctx.focused,
  )
  let display_session = markdown_session_display_session(session, display_text)
  let content = rich_text_editor_content_rect(ctx.frame)
  commands.push(@core.DrawCommand::PushClip(content))
  let child_layers : Array[@core.ViewPaintLayer] = []
  if display_text == "" {
    commands.push(
      @core.DrawCommand::DrawText(
        @core.TextRun::new(
          text=placeholder,
          frame=content,
          font~,
          color=placeholder_color,
          align=@core.TextAlign::TextStart,
        ),
      ),
    )
  } else {
    let window = markdown_session_visible_window(
      display_session,
      selection,
      font,
      source_mode~,
      focus_mode~,
      base_dir~,
      visible_scroll_y~,
      visible_viewport_height~,
      visible_overscan~,
      visible_scroll_state~,
    )
    let window_content = window.content_rect(content)
    // Position blocks inside the offset `window_content`, but cull against
    // the original `content` viewport. Without this, the non-windowed cull
    // uses `window_content.origin.y + height` which is shifted up by
    // `top_padding - scroll_y`, cutting off the bottom of the viewport.
    append_rich_text_document_layers(
      child_layers,
      window.document,
      window_content,
      font,
      foreground,
      text_system=ctx.text_system,
      clip_rect=Some(content),
    )
    if ctx.focused {
      append_rich_text_editor_overlay(
        overlay_commands,
        text~,
        display_text~,
        document=window.document,
        content=window_content,
        font~,
        cursor_color~,
        control=ctx.text_control,
        text_system=ctx.text_system,
        clip_rect=Some(content),
      )
    }
  }
  // PopClip must go in post_commands (not commands) so that child_layers —
  // which are rendered between commands and post_commands — stay inside the
  // content clip. Without this, overscan blocks bleed past the editor frame
  // and overlap the chrome/status bar during scrolling.
  {
    commands,
    child_layers,
    post_commands: [@core.DrawCommand::PopClip],
    overlay_commands,
    platform_views: [],
    repaint_boundary: false,
    animating: false,
  }
}

///|
fn[Msg] markdown_session_editor_event(
  ctx : @core.ViewEventContext,
  session~ : MarkdownDocumentSession,
  selection~ : MarkdownEditorSelection,
  on_transaction~ : (MarkdownEditTransaction) -> Msg,
  font~ : @core.FontSpec,
  source_mode~ : Bool,
  focus_mode~ : Bool,
  base_dir~ : String?,
  on_selection_change~ : ((Int, @core.TextRange?) -> Msg)?,
  on_prefix_click~ : ((Int) -> Msg)?,
  on_command_click~ : ((Int) -> Msg)?,
  accepts_tab_input~ : Bool,
  visible_scroll_y~ : Double,
  visible_viewport_height~ : Double,
  visible_overscan~ : Double,
  visible_scroll_state~ : @core.ScrollState?,
) -> @core.ViewEventResult[Msg] {
  let value = @core.Binding::new(get=() => session.source, set=_next => ())
  let on_input_edit = markdown_session_on_input_edit(session, on_transaction)
  match ctx.event {
    @core.AppEvent::Pointer(event) =>
      markdown_session_pointer_event(
        ctx,
        session~,
        selection~,
        font~,
        source_mode~,
        focus_mode~,
        base_dir~,
        on_selection_change~,
        on_prefix_click~,
        on_command_click~,
        event~,
        visible_scroll_y~,
        visible_viewport_height~,
        visible_overscan~,
        visible_scroll_state~,
      )
    @core.AppEvent::Keyboard(event) => {
      // ArrowUp/ArrowDown 需要用 visible_window 的 document 来正确计算
      // 视觉位置(考虑 scroll offset),所以在这里预处理,而不是走
      // rich_text_keyboard_event 的通用 format(text) 路径。
      if event.pressed && (event.key == "ArrowUp" || event.key == "ArrowDown") {
        let text = session.source
        let window = markdown_session_visible_window(
          session,
          selection,
          font,
          source_mode~,
          focus_mode~,
          base_dir~,
          visible_scroll_y~,
          visible_viewport_height~,
          visible_overscan~,
          visible_scroll_state~,
        )
        let content = window.content_rect(
          rich_text_editor_content_rect(ctx.frame),
        )
        return rich_text_vertical_caret_move_result(
          ctx,
          text,
          window.document,
          content,
          font,
          up=event.key == "ArrowUp",
          extend=event.modifiers.shift,
          on_selection_change,
        )
      }
      rich_text_keyboard_event(
        ctx,
        value~,
        format=_source => {
          session.rich_text(
            selection,
            base=font,
            source_mode~,
            focus_mode~,
            base_dir~,
          )
        },
        font~,
        on_input=None,
        on_input_edit=Some(on_input_edit),
        on_selection_change~,
        input_transform=Some(markdown_session_input_transform),
        accepts_tab_input~,
        event~,
      )
    }
    @core.AppEvent::TextInput(event) =>
      rich_text_input_event(
        ctx,
        value~,
        on_input=None,
        on_input_edit=Some(on_input_edit),
        on_selection_change~,
        input_transform=Some(markdown_session_input_transform),
        event~,
        record_history=true,
      )
    _ => @core.ViewEventResult::ignored()
  }
}

///|
fn[Msg] markdown_session_pointer_event(
  ctx : @core.ViewEventContext,
  session~ : MarkdownDocumentSession,
  selection~ : MarkdownEditorSelection,
  font~ : @core.FontSpec,
  source_mode~ : Bool,
  focus_mode~ : Bool,
  base_dir~ : String?,
  on_selection_change~ : ((Int, @core.TextRange?) -> Msg)?,
  on_prefix_click~ : ((Int) -> Msg)?,
  on_command_click~ : ((Int) -> Msg)?,
  event~ : @core.PointerEvent,
  visible_scroll_y~ : Double,
  visible_viewport_height~ : Double,
  visible_overscan~ : Double,
  visible_scroll_state~ : @core.ScrollState?,
) -> @core.ViewEventResult[Msg] {
  let inside = ctx.frame.contains(event.position)
  if inside && (event.wheel_delta.x != 0.0 || event.wheel_delta.y != 0.0) {
    return markdown_session_scroll_event(
      session,
      font,
      source_mode~,
      event~,
      visible_viewport_height~,
      visible_scroll_state~,
    )
  }
  match event.phase {
    @core.PointerPhase::Down =>
      if inside {
        let text = session.source
        let window = markdown_session_visible_window(
          session,
          selection,
          font,
          source_mode~,
          focus_mode~,
          base_dir~,
          visible_scroll_y~,
          visible_viewport_height~,
          visible_overscan~,
          visible_scroll_state~,
        )
        let content = window.content_rect(
          rich_text_editor_content_rect(ctx.frame),
        )
        let command_modifiers = event.modifiers.meta || event.modifiers.control
        match (command_modifiers, on_command_click) {
          (true, Some(handler)) => {
            let offset = rich_text_document_source_offset_at_point(
              window.document,
              event.position,
              content,
              font,
              ctx.text_system,
            ).unwrap_or(text_length(text))
            view_event_result(
              changed=true,
              focused=true,
              captured=true,
              text_control=Some(ctx.text_control),
              dirty=@core.ViewDirtyHint::ViewPaintDirty,
              messages=[handler(offset)],
            )
          }
          _ => {
            let prefix_messages = match on_prefix_click {
              Some(handler) =>
                match
                  rich_text_document_prefix_source_offset_at_point(
                    window.document,
                    event.position,
                    content,
                    font,
                    ctx.text_system,
                  ) {
                  Some(offset) => [handler(offset)]
                  None => []
                }
              None => []
            }
            let caret = rich_text_document_source_offset_at_point(
              window.document,
              event.position,
              content,
              font,
              ctx.text_system,
            ).unwrap_or(text_length(text))
            let next_control = {
              ..ctx.text_control,
              caret,
              selection: None,
              text_selection_anchor: Some(caret),
              composition: None,
              composition_cursor: None,
            }
            view_event_result(
              changed=true,
              focused=true,
              captured=true,
              text_control=Some(next_control),
              dirty=@core.ViewDirtyHint::ViewPaintDirty,
              messages=prefix_messages +
                selection_messages(on_selection_change, caret, None),
            )
          }
        }
      } else {
        @core.ViewEventResult::ignored()
      }
    @core.PointerPhase::Move =>
      match ctx.text_control.text_selection_anchor {
        Some(anchor) => {
          let text = session.source
          let window = markdown_session_visible_window(
            session,
            selection,
            font,
            source_mode~,
            focus_mode~,
            base_dir~,
            visible_scroll_y~,
            visible_viewport_height~,
            visible_overscan~,
            visible_scroll_state~,
          )
          let content = window.content_rect(
            rich_text_editor_content_rect(ctx.frame),
          )
          let caret = rich_text_document_source_offset_at_point(
            window.document,
            event.position,
            content,
            font,
            ctx.text_system,
          ).unwrap_or(text_length(text))
          let next_selection = if caret == anchor {
            None
          } else {
            Some(@core.TextRange::new(start=anchor, end=caret))
          }
          let changed = ctx.text_control.caret != caret ||
            ctx.text_control.selection != next_selection
          if changed {
            view_event_result(
              changed=true,
              focused=true,
              captured=true,
              text_control=Some({
                ..ctx.text_control,
                caret,
                selection: next_selection,
                composition: None,
                composition_cursor: None,
              }),
              dirty=@core.ViewDirtyHint::ViewPaintDirty,
              messages=selection_messages(
                on_selection_change, caret, next_selection,
              ),
            )
          } else {
            view_event_result(
              focused=true,
              captured=true,
              text_control=Some(ctx.text_control),
            )
          }
        }
        None => @core.ViewEventResult::ignored()
      }
    @core.PointerPhase::Up =>
      if ctx.text_control.text_selection_anchor is Some(_) {
        view_event_result(
          changed=true,
          focused=true,
          captured=true,
          text_control=Some({ ..ctx.text_control, text_selection_anchor: None }),
          dirty=@core.ViewDirtyHint::ViewPaintDirty,
        )
      } else {
        @core.ViewEventResult::ignored()
      }
    _ => @core.ViewEventResult::ignored()
  }
}

///|
fn[Msg] markdown_session_scroll_event(
  session : MarkdownDocumentSession,
  font : @core.FontSpec,
  source_mode~ : Bool,
  event~ : @core.PointerEvent,
  visible_viewport_height~ : Double,
  visible_scroll_state~ : @core.ScrollState?,
) -> @core.ViewEventResult[Msg] {
  match visible_scroll_state {
    Some(state) => {
      let viewport_height = if visible_viewport_height > 0.0 {
        visible_viewport_height
      } else {
        0.0
      }
      let content_height = session.estimated_content_height(
          base=font,
          source_mode~,
        ) +
        96.0
      let max_scroll = max_double(0.0, content_height - viewport_height)
      let current = state.offset()
      let next_y = min_double(
        max_double(0.0, current.y - event.wheel_delta.y),
        max_scroll,
      )
      state.scroll_to(x=0.0, y=next_y)
      view_event_result(
        changed=next_y != current.y,
        captured=true,
        dirty=@core.ViewDirtyHint::ViewPaintDirty,
      )
    }
    None => @core.ViewEventResult::ignored()
  }
}

///|
fn markdown_session_input_state(
  text~ : String,
  frame~ : @core.Rect,
  session~ : MarkdownDocumentSession,
  selection~ : MarkdownEditorSelection,
  font~ : @core.FontSpec,
  control~ : @core.TextControlStateContext,
  source_mode~ : Bool,
  focus_mode~ : Bool,
  base_dir~ : String?,
  visible_scroll_y~ : Double,
  visible_viewport_height~ : Double,
  visible_overscan~ : Double,
  visible_scroll_state~ : @core.ScrollState?,
  text_system~ : @core.TextSystem,
) -> @core.TextInputState {
  let control = {
    ..control,
    caret: selection.caret,
    selection: selection.selection,
  }
  let caret = @core.clamp_int(control.caret, 0, session.source_length)
  if frame.size.width <= 0.0 || frame.size.height <= 0.0 {
    return {
      focused: true,
      element_id: None,
      text,
      caret,
      selection: control.selection,
      composition: control.composition,
      composition_cursor: control.composition_cursor,
      frame,
      caret_rect: @core.Rect::new(
        x=frame.origin.x,
        y=frame.origin.y,
        width=1.0,
        height=font.size,
      ),
    }
  }
  let display_text = rich_text_with_composition(text, control, focused=true)
  let display_session = markdown_session_display_session(session, display_text)
  let visual_caret = match control.composition {
    Some(_) =>
      @core.clamp_int(
        rich_text_visual_caret(text, control, focused=true),
        0,
        text_length(display_text),
      )
    None => caret
  }
  let window = markdown_session_visible_window(
    display_session,
    { caret: visual_caret, selection: control.selection },
    font,
    source_mode~,
    focus_mode~,
    base_dir~,
    visible_scroll_y~,
    visible_viewport_height~,
    visible_overscan~,
    visible_scroll_state~,
  )
  let base_content = rich_text_editor_content_rect(frame)
  let content = window.content_rect(base_content)
  let caret_rect = match
    rich_text_document_caret_rect_at_source(
      window.document,
      visual_caret,
      content,
      font,
      text_system~,
    ) {
    Some(rect) => rect
    None =>
      markdown_session_estimated_caret_rect(
        display_session,
        visual_caret,
        base_content,
        font,
        source_mode~,
        scroll_y=window.scroll_y,
      )
  }
  {
    focused: true,
    element_id: None,
    text,
    caret,
    selection: control.selection,
    composition: control.composition,
    composition_cursor: control.composition_cursor,
    frame,
    caret_rect,
  }
}

///|
fn markdown_session_estimated_caret_rect(
  session : MarkdownDocumentSession,
  source_caret : Int,
  content : @core.Rect,
  font : @core.FontSpec,
  source_mode~ : Bool,
  scroll_y~ : Double,
) -> @core.Rect {
  let source_y = session.source_offset_y(source_caret, base=font, source_mode~)
  @core.Rect::new(
    x=content.origin.x,
    y=content.origin.y + source_y - scroll_y + 4.0,
    width=1.5,
    height=max_double(10.0, font.size * 1.55 - 8.0),
  )
}

///|
fn[Msg] markdown_session_editor_command(
  ctx : @core.ViewEventContext,
  session~ : MarkdownDocumentSession,
  selection~ : MarkdownEditorSelection,
  on_transaction~ : (MarkdownEditTransaction) -> Msg,
  on_selection_change~ : ((Int, @core.TextRange?) -> Msg)?,
  font~ : @core.FontSpec,
  source_mode~ : Bool,
  focus_mode~ : Bool,
  base_dir~ : String?,
  intent : @core.CommandIntent,
  paste_text~ : String?,
) -> @core.ViewTextCommandResult[Msg] {
  let value = @core.Binding::new(get=() => session.source, set=_next => ())
  let on_input_edit = markdown_session_on_input_edit(session, on_transaction)
  let format = display_source => {
    if display_source == session.source {
      session.rich_text(
        selection,
        base=font,
        source_mode~,
        focus_mode~,
        base_dir~,
      )
    } else if source_mode {
      markdown_editor_format_source_mode(display_source, font)
    } else {
      markdown_editor_format_for_writing_mode(
        display_source,
        selection,
        base=font,
        focus_mode~,
        base_dir~,
      )
    }
  }
  rich_text_editor_command(
    ctx,
    value~,
    format~,
    on_input=None,
    on_input_edit=Some(on_input_edit),
    on_selection_change~,
    input_transform=Some(markdown_session_input_transform),
    intent,
    paste_text~,
  )
}

///|
fn rich_text_editor_paint(
  ctx : @core.ViewPaintContext,
  value~ : @core.Binding[String],
  placeholder~ : String,
  format~ : RichTextFormatter,
  font~ : @core.FontSpec,
  foreground~ : @core.Color,
  placeholder_color~ : @core.Color,
  background~ : @core.Brush,
  border~ : @core.BorderStyle?,
  focused_border~ : @core.BorderStyle?,
  cursor_color~ : @core.Color,
  corner_radius~ : Double,
  visible_scroll_y~ : Double,
  visible_viewport_height~ : Double,
  visible_overscan~ : Double,
) -> @core.ViewPaintPlan {
  let commands : Array[@core.DrawCommand] = []
  let overlay_commands : Array[@core.DrawCommand] = []
  let rounded = @core.RoundedRect::new(rect=ctx.frame, radius=corner_radius)
  commands.push(@core.DrawCommand::FillRoundedRectBrush(rounded, background))
  append_rich_text_border(
    commands,
    rounded,
    if ctx.focused {
      focused_border
    } else {
      border
    },
  )
  let text = value.get()
  let display_text = rich_text_with_composition(
    text,
    ctx.text_control,
    focused=ctx.focused,
  )
  let content = rich_text_editor_content_rect(ctx.frame)
  commands.push(@core.DrawCommand::PushClip(content))
  let child_layers : Array[@core.ViewPaintLayer] = []
  if display_text == "" {
    commands.push(
      @core.DrawCommand::DrawText(
        @core.TextRun::new(
          text=placeholder,
          frame=content,
          font~,
          color=placeholder_color,
          align=@core.TextAlign::TextStart,
        ),
      ),
    )
  } else {
    let document = format(display_text)
    append_rich_text_document_layers(
      child_layers,
      document,
      content,
      font,
      foreground,
      text_system=ctx.text_system,
      visible_scroll_y~,
      visible_viewport_height~,
      visible_overscan~,
    )
    if ctx.focused {
      append_rich_text_editor_overlay(
        overlay_commands,
        text~,
        display_text~,
        document~,
        content~,
        font~,
        cursor_color~,
        control=ctx.text_control,
        text_system=ctx.text_system,
      )
    }
  }
  // PopClip must go in post_commands so child_layers stay inside the clip.
  {
    commands,
    child_layers,
    post_commands: [@core.DrawCommand::PopClip],
    overlay_commands,
    platform_views: [],
    repaint_boundary: false,
    animating: false,
  }
}

///|
fn append_rich_text_border(
  commands : Array[@core.DrawCommand],
  rounded : @core.RoundedRect,
  border : @core.BorderStyle?,
) -> Unit {
  match border {
    Some(border) =>
      if border.width > 0.0 {
        commands.push(
          @core.DrawCommand::StrokeRoundedRectBrush(
            rounded,
            border.brush,
            border.width,
          ),
        )
      }
    None => ()
  }
}

///|
fn append_rich_text_document_layers(
  layers : Array[@core.ViewPaintLayer],
  document : RichTextDocument,
  content : @core.Rect,
  base_font : @core.FontSpec,
  foreground : @core.Color,
  text_system~ : @core.TextSystem,
  visible_scroll_y? : Double = 0.0,
  visible_viewport_height? : Double = 0.0,
  visible_overscan? : Double = 320.0,
  clip_rect? : @core.Rect? = None,
) -> Unit {
  // `content` sets the block positioning origin; `clip` sets the culling
  // bounds. They are the same for the regular (non-session) rich text editor
  // path, but the Markdown session path positions blocks inside an offset
  // `window_content` rect while culling must respect the original viewport.
  let clip = match clip_rect {
    Some(r) => r
    None => content
  }
  let mut y = content.origin.y
  let windowed = visible_viewport_height > 0.0
  let visible_top = clip.origin.y + visible_scroll_y - 12.0 - visible_overscan
  let visible_bottom = visible_top +
    visible_viewport_height +
    visible_overscan * 2.0
  for index, block in document.blocks {
    let line_height = rich_text_block_line_height(block, base_font, text_system)
    let block_height = line_height + block.inset.top + block.inset.bottom
    let block_bottom = y + block_height
    if windowed && block_bottom < visible_top {
      y = block_bottom
      continue
    }
    if windowed && y > visible_bottom {
      return
    }
    if !windowed && y > clip.origin.y + clip.size.height {
      return
    }
    let frame = @core.Rect::new(
      x=content.origin.x,
      y~,
      width=content.size.width,
      height=block_height,
    )
    let commands : Array[@core.DrawCommand] = []
    append_rich_text_block_commands(
      commands,
      block,
      frame,
      line_height,
      base_font,
      foreground,
      text_system~,
    )
    layers.push(
      @core.ViewPaintLayer::new(
        frame~,
        cache_key=rich_text_block_cache_key(index, block, frame),
        content_revision=rich_text_block_content_revision(
          block, index, frame, base_font, foreground,
        ),
        commands~,
      ),
    )
    y = block_bottom
  }
}

///|
fn append_rich_text_block_commands(
  commands : Array[@core.DrawCommand],
  block : RichTextBlock,
  block_frame : @core.Rect,
  line_height : Double,
  base_font : @core.FontSpec,
  foreground : @core.Color,
  text_system~ : @core.TextSystem,
) -> Unit {
  match block.background {
    Some(background) =>
      commands.push(
        @core.DrawCommand::FillRoundedRectBrush(
          @core.RoundedRect::new(rect=block_frame, radius=block.corner_radius),
          background,
        ),
      )
    None => ()
  }
  match block.border_left {
    Some(border) =>
      if border.width > 0.0 {
        commands.push(
          @core.DrawCommand::FillRoundedRectBrush(
            @core.RoundedRect::new(
              rect=@core.Rect::new(
                x=block_frame.origin.x,
                y=block_frame.origin.y + block.inset.top,
                width=border.width,
                height=line_height,
              ),
              radius=border.width / 2.0,
            ),
            border.brush,
          ),
        )
      }
    None => ()
  }
  if block.rule {
    commands.push(
      @core.DrawCommand::FillRect(
        @core.Rect::new(
          x=block_frame.origin.x + block.inset.left,
          y=block_frame.origin.y + block_frame.size.height / 2.0,
          width=max_double(
            0.0,
            block_frame.size.width - block.inset.left - block.inset.right,
          ),
          height=1.5,
        ),
        rich_text_block_color(block, foreground).multiply_alpha(0.25),
      ),
    )
  } else if block.table is Some(_) {
    append_rich_text_block_table(
      commands,
      block,
      @core.Rect::new(
        x=block_frame.origin.x + block.inset.left,
        y=block_frame.origin.y + block.inset.top,
        width=max_double(
          0.0,
          block_frame.size.width - block.inset.left - block.inset.right,
        ),
        height=line_height,
      ),
      base_font,
      foreground,
    )
  } else {
    append_rich_text_block_runs(
      commands,
      block,
      @core.Rect::new(
        x=block_frame.origin.x + block.inset.left,
        y=block_frame.origin.y + block.inset.top,
        width=max_double(
          0.0,
          block_frame.size.width - block.inset.left - block.inset.right,
        ),
        height=line_height,
      ),
      base_font,
      foreground,
      text_system~,
    )
  }
}

///|
fn append_rich_text_block_runs(
  commands : Array[@core.DrawCommand],
  block : RichTextBlock,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  foreground : @core.Color,
  text_system~ : @core.TextSystem,
) -> Unit {
  let block_font = rich_text_block_font(block, base_font)
  let block_color = rich_text_block_color(block, foreground)
  let line_step = rich_text_block_line_step(block, base_font, text_system)
  let mut x = frame.origin.x
  let mut line = 0
  if block.prefix != "" {
    let width = rich_text_width(block.prefix, block_font, text_system)
    commands.push(
      @core.DrawCommand::DrawText(
        @core.TextRun::new(
          text=block.prefix,
          frame=@core.Rect::new(
            x~,
            y=frame.origin.y,
            width~,
            height=frame.size.height,
          ),
          font=block_font,
          color=block_color,
          align=@core.TextAlign::TextStart,
        ),
      ),
    )
    x = x + width
  }
  for run in block.runs {
    let font = run.font.unwrap_or(block_font)
    let color = run.color.unwrap_or(block_color)
    let segments = rich_text_split_lines(rich_text_run_visual_text(run))
    for index, segment in segments {
      let width = rich_text_run_segment_width(run, segment, font, text_system)
      let line_y = frame.origin.y + line.to_double() * line_step
      let run_frame = @core.Rect::new(
        x~,
        y=line_y,
        width=max_double(
          0.0,
          min_double(width, frame.origin.x + frame.size.width - x),
        ),
        height=rich_text_run_segment_height(run, line_step),
      )
      if segment != "" || run.image_source is Some(_) {
        append_rich_text_run_segment(
          commands, run, segment, run_frame, width, font, color,
        )
      }
      if index + 1 < segments.length() {
        line = line + 1
        x = frame.origin.x
      } else {
        x = x + width
      }
    }
  }
}

///|
fn append_rich_text_run_segment(
  commands : Array[@core.DrawCommand],
  run : RichTextRun,
  text : String,
  run_frame : @core.Rect,
  width : Double,
  font : @core.FontSpec,
  color : @core.Color,
) -> Unit {
  match run.background {
    Some(background) =>
      commands.push(
        @core.DrawCommand::FillRoundedRectBrush(
          @core.RoundedRect::new(
            rect=@core.Rect::new(
              x=run_frame.origin.x - 3.0,
              y=run_frame.origin.y + 4.0,
              width=width + 6.0,
              height=max_double(12.0, run_frame.size.height - 8.0),
            ),
            radius=run.background_radius,
          ),
          background,
        ),
      )
    None => ()
  }
  match run.image_source {
    Some(source) =>
      commands.push(
        @core.DrawCommand::DrawImage({
          source,
          frame: run_frame,
          opacity: 1.0,
          fit: run.image_fit,
        }),
      )
    None =>
      commands.push(
        @core.DrawCommand::DrawText(
          @core.TextRun::new(
            text~,
            frame=run_frame,
            font~,
            color~,
            align=@core.TextAlign::TextStart,
          ),
        ),
      )
  }
  append_rich_text_run_decoration(commands, run, run_frame, width, font, color)
}

///|
fn append_rich_text_run_decoration(
  commands : Array[@core.DrawCommand],
  run : RichTextRun,
  frame : @core.Rect,
  width : Double,
  font : @core.FontSpec,
  color : @core.Color,
) -> Unit {
  match run.decoration {
    Some(RichTextDecoration::Strikethrough) => {
      let thickness = max_double(1.0, font.size / 14.0)
      commands.push(
        @core.DrawCommand::FillRect(
          @core.Rect::new(
            x=frame.origin.x,
            y=frame.origin.y + frame.size.height * 0.52,
            width~,
            height=thickness,
          ),
          color.multiply_alpha(0.72),
        ),
      )
    }
    None => ()
  }
}

///|
fn append_rich_text_block_table(
  commands : Array[@core.DrawCommand],
  block : RichTextBlock,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  foreground : @core.Color,
) -> Unit {
  match block.table {
    Some(table) => {
      let row_count = table.rows.length()
      let column_count = rich_text_table_column_count(table)
      if row_count == 0 || column_count == 0 {
        return
      }
      let column_width = frame.size.width / column_count.to_double()
      for row_index in 0..
              commands.push(
                @core.DrawCommand::FillRoundedRectBrush(
                  @core.RoundedRect::new(
                    rect=@core.Rect::new(
                      x=frame.origin.x,
                      y=row_y,
                      width=frame.size.width,
                      height=table.row_height,
                    ),
                    radius=block.corner_radius,
                  ),
                  background,
                ),
              )
            None => ()
          }
        }
        let row = table.rows[row_index]
        for column_index in 0.. ()
  }
}

///|
fn append_rich_text_editor_overlay(
  commands : Array[@core.DrawCommand],
  text~ : String,
  display_text~ : String,
  document~ : RichTextDocument,
  content~ : @core.Rect,
  font~ : @core.FontSpec,
  cursor_color~ : @core.Color,
  control~ : @core.TextControlStateContext,
  text_system~ : @core.TextSystem,
  clip_rect? : @core.Rect? = None,
) -> Unit {
  let clip = match clip_rect {
    Some(r) => r
    None => content
  }
  commands.push(@core.DrawCommand::PushClip(clip))
  if control.composition is None {
    append_rich_text_selection_commands(
      commands,
      document,
      content,
      font,
      text_system,
      control.selection,
    )
  } else {
    append_rich_text_composition_underline(
      commands,
      document,
      text~,
      display_text~,
      content~,
      font~,
      text_system~,
      cursor_color~,
      control~,
    )
  }
  let caret = @core.clamp_int(
    rich_text_visual_caret(text, control, focused=true),
    0,
    text_length(display_text),
  )
  let caret_rect = rich_text_document_caret_rect_or_plain(
    document, caret, content, font, text_system, display_text,
  )
  commands.push(@core.DrawCommand::FillRect(caret_rect, cursor_color))
  commands.push(@core.DrawCommand::PopClip)
}

///|
fn append_rich_text_composition_underline(
  commands : Array[@core.DrawCommand],
  document : RichTextDocument,
  text~ : String,
  display_text~ : String,
  content~ : @core.Rect,
  font~ : @core.FontSpec,
  text_system~ : @core.TextSystem,
  cursor_color~ : @core.Color,
  control~ : @core.TextControlStateContext,
) -> Unit {
  match control.composition {
    Some(composition) =>
      if composition != "" {
        let start = @core.composition_start_index(
          text,
          control.caret,
          control.selection,
        )
        let end = start + text_length(composition)
        let start_rect = rich_text_document_caret_rect_or_plain(
          document, start, content, font, text_system, display_text,
        )
        let end_rect = rich_text_document_caret_rect_or_plain(
          document, end, content, font, text_system, display_text,
        )
        if end_rect.origin.x > start_rect.origin.x {
          commands.push(
            @core.DrawCommand::FillRect(
              @core.Rect::new(
                x=start_rect.origin.x,
                y=start_rect.origin.y + start_rect.size.height - 5.0,
                width=end_rect.origin.x - start_rect.origin.x,
                height=1.5,
              ),
              cursor_color.with_alpha(0.72),
            ),
          )
        }
      }
    None => ()
  }
}

///|
fn[Msg] rich_text_editor_event(
  ctx : @core.ViewEventContext,
  value~ : @core.Binding[String],
  format~ : RichTextFormatter,
  font~ : @core.FontSpec,
  on_input~ : ((String) -> Msg)?,
  on_input_edit~ : ((String, Int, @core.TextRange?) -> Msg)?,
  on_selection_change~ : ((Int, @core.TextRange?) -> Msg)?,
  on_prefix_click~ : ((Int) -> Msg)?,
  on_command_click~ : ((Int) -> Msg)?,
  input_transform~ : ((String, Int, @core.TextRange?, String) -> RichTextInputTransform?)?,
  accepts_tab_input~ : Bool,
) -> @core.ViewEventResult[Msg] {
  match ctx.event {
    @core.AppEvent::Pointer(event) =>
      rich_text_pointer_event(
        ctx,
        value~,
        format~,
        font~,
        on_selection_change~,
        on_prefix_click~,
        on_command_click~,
        event~,
      )
    @core.AppEvent::Keyboard(event) =>
      rich_text_keyboard_event(
        ctx,
        value~,
        format~,
        font~,
        on_input~,
        on_input_edit~,
        on_selection_change~,
        input_transform~,
        accepts_tab_input~,
        event~,
      )
    @core.AppEvent::TextInput(event) =>
      rich_text_input_event(
        ctx,
        value~,
        on_input~,
        on_input_edit~,
        on_selection_change~,
        input_transform~,
        event~,
        record_history=true,
      )
    _ => @core.ViewEventResult::ignored()
  }
}

///|
fn[Msg] rich_text_pointer_event(
  ctx : @core.ViewEventContext,
  value~ : @core.Binding[String],
  format~ : RichTextFormatter,
  font~ : @core.FontSpec,
  on_selection_change~ : ((Int, @core.TextRange?) -> Msg)?,
  on_prefix_click~ : ((Int) -> Msg)?,
  on_command_click~ : ((Int) -> Msg)?,
  event~ : @core.PointerEvent,
) -> @core.ViewEventResult[Msg] {
  let inside = ctx.frame.contains(event.position)
  match event.phase {
    @core.PointerPhase::Down =>
      if inside {
        let text = value.get()
        let document = format(text)
        let content = rich_text_editor_content_rect(ctx.frame)
        // A Cmd/Ctrl-held click opens links/images instead of moving the caret:
        // dispatch the command-click hook with the source offset and swallow the
        // caret placement so the selection is not disturbed.
        let command_modifiers = event.modifiers.meta || event.modifiers.control
        match (command_modifiers, on_command_click) {
          (true, Some(handler)) => {
            let offset = rich_text_document_source_offset_at_point(
              document,
              event.position,
              content,
              font,
              ctx.text_system,
            ).unwrap_or(text_length(text))
            view_event_result(
              changed=true,
              focused=true,
              captured=true,
              text_control=Some(ctx.text_control),
              dirty=@core.ViewDirtyHint::ViewPaintDirty,
              messages=[handler(offset)],
            )
          }
          _ => {
            let prefix_messages = match on_prefix_click {
              Some(handler) =>
                match
                  rich_text_document_prefix_source_offset_at_point(
                    document,
                    event.position,
                    content,
                    font,
                    ctx.text_system,
                  ) {
                  Some(offset) => [handler(offset)]
                  None => []
                }
              None => []
            }
            let caret = rich_text_document_source_offset_at_point(
              document,
              event.position,
              content,
              font,
              ctx.text_system,
            ).unwrap_or(text_length(text))
            let next_control = {
              ..ctx.text_control,
              caret,
              selection: None,
              text_selection_anchor: Some(caret),
              composition: None,
              composition_cursor: None,
            }
            view_event_result(
              changed=true,
              focused=true,
              captured=true,
              text_control=Some(next_control),
              dirty=@core.ViewDirtyHint::ViewPaintDirty,
              messages=prefix_messages +
                selection_messages(on_selection_change, caret, None),
            )
          }
        }
      } else {
        @core.ViewEventResult::ignored()
      }
    @core.PointerPhase::Move =>
      match ctx.text_control.text_selection_anchor {
        Some(anchor) => {
          let text = value.get()
          let document = format(text)
          let content = rich_text_editor_content_rect(ctx.frame)
          let caret = rich_text_document_source_offset_at_point(
            document,
            event.position,
            content,
            font,
            ctx.text_system,
          ).unwrap_or(text_length(text))
          let next_selection = if caret == anchor {
            None
          } else {
            Some(@core.TextRange::new(start=anchor, end=caret))
          }
          let changed = ctx.text_control.caret != caret ||
            ctx.text_control.selection != next_selection
          let next_control = {
            ..ctx.text_control,
            caret,
            selection: next_selection,
            composition: None,
            composition_cursor: None,
          }
          view_event_result(
            changed~,
            captured=true,
            text_control=Some(next_control),
            dirty=@core.ViewDirtyHint::ViewPaintDirty,
            messages=if changed {
              selection_messages(on_selection_change, caret, next_selection)
            } else {
              []
            },
          )
        }
        None => @core.ViewEventResult::ignored()
      }
    @core.PointerPhase::Up
    | @core.PointerPhase::Cancel
    | @core.PointerPhase::Exit => {
      let changed = ctx.text_control.text_selection_anchor is Some(_)
      view_event_result(
        changed~,
        captured=changed,
        text_control=Some({ ..ctx.text_control, text_selection_anchor: None }),
        dirty=@core.ViewDirtyHint::ViewPaintDirty,
      )
    }
  }
}

///|
fn[Msg] rich_text_keyboard_event(
  ctx : @core.ViewEventContext,
  value~ : @core.Binding[String],
  format~ : RichTextFormatter,
  font~ : @core.FontSpec,
  on_input~ : ((String) -> Msg)?,
  on_input_edit~ : ((String, Int, @core.TextRange?) -> Msg)?,
  on_selection_change~ : ((Int, @core.TextRange?) -> Msg)?,
  input_transform~ : ((String, Int, @core.TextRange?, String) -> RichTextInputTransform?)?,
  accepts_tab_input~ : Bool,
  event~ : @core.KeyboardEvent,
) -> @core.ViewEventResult[Msg] {
  if !event.pressed {
    return @core.ViewEventResult::ignored()
  }
  if event.key == "Tab" && accepts_tab_input {
    let text = if event.modifiers.shift { "\u{000b}" } else { "\t" }
    return rich_text_input_event(
      ctx,
      value~,
      on_input~,
      on_input_edit~,
      on_selection_change~,
      input_transform~,
      event=@core.TextInputEvent::InsertText(text),
      record_history=true,
    )
  }
  if event.key == "Enter" {
    return rich_text_input_event(
      ctx,
      value~,
      on_input~,
      on_input_edit~,
      on_selection_change~,
      input_transform~,
      event=@core.TextInputEvent::InsertText("\n"),
      record_history=true,
    )
  }
  if @core.is_select_all_shortcut(event) {
    let next_control = select_rich_text_control(ctx.text_control, value.get())
    return view_event_result(
      changed=true,
      captured=true,
      text_control=Some(next_control),
      dirty=@core.ViewDirtyHint::ViewPaintDirty,
      messages=selection_messages(
        on_selection_change,
        next_control.caret,
        next_control.selection,
      ),
    )
  }
  if @core.is_undo_shortcut(event) {
    let result = restore_text_history(ctx.text_control, value.get(), undo=true)
    value.set(result.text)
    return view_event_result(
      changed=result.changed,
      captured=true,
      text_control=Some(result.control),
      dirty=@core.ViewDirtyHint::ViewPaintDirty,
      messages=text_input_messages(on_input, result.text) +
        text_input_edit_messages(on_input_edit, result.text, result.control) +
        selection_messages(
          on_selection_change,
          result.control.caret,
          result.control.selection,
        ),
    )
  }
  if @core.is_redo_shortcut(event) {
    let result = restore_text_history(ctx.text_control, value.get(), undo=false)
    value.set(result.text)
    return view_event_result(
      changed=result.changed,
      captured=true,
      text_control=Some(result.control),
      dirty=@core.ViewDirtyHint::ViewPaintDirty,
      messages=text_input_messages(on_input, result.text) +
        text_input_edit_messages(on_input_edit, result.text, result.control) +
        selection_messages(
          on_selection_change,
          result.control.caret,
          result.control.selection,
        ),
    )
  }
  if event.key == "Backspace" {
    match @core.active_text_selection(ctx.text_control.selection) {
      Some(selection) =>
        return rich_text_input_event(
          ctx,
          value~,
          on_input~,
          on_input_edit~,
          on_selection_change~,
          input_transform~,
          event=@core.TextInputEvent::DeleteRange(selection),
          record_history=true,
        )
      None =>
        return rich_text_input_event(
          ctx,
          value~,
          on_input~,
          on_input_edit~,
          on_selection_change~,
          input_transform~,
          event=@core.TextInputEvent::DeleteSurrounding(1, 0),
          record_history=true,
        )
    }
  }
  if event.key == "Delete" {
    match @core.active_text_selection(ctx.text_control.selection) {
      Some(selection) =>
        return rich_text_input_event(
          ctx,
          value~,
          on_input~,
          on_input_edit~,
          on_selection_change~,
          input_transform~,
          event=@core.TextInputEvent::DeleteRange(selection),
          record_history=true,
        )
      None =>
        return rich_text_input_event(
          ctx,
          value~,
          on_input~,
          on_input_edit~,
          on_selection_change~,
          input_transform~,
          event=@core.TextInputEvent::DeleteSurrounding(0, 1),
          record_history=true,
        )
    }
  }
  if event.key == "ArrowUp" {
    let text = value.get()
    let document = format(text)
    let content = rich_text_editor_content_rect(ctx.frame)
    return rich_text_vertical_caret_move_result(
      ctx,
      text,
      document,
      content,
      font,
      up=true,
      extend=event.modifiers.shift,
      on_selection_change,
    )
  }
  if event.key == "ArrowDown" {
    let text = value.get()
    let document = format(text)
    let content = rich_text_editor_content_rect(ctx.frame)
    return rich_text_vertical_caret_move_result(
      ctx,
      text,
      document,
      content,
      font,
      up=false,
      extend=event.modifiers.shift,
      on_selection_change,
    )
  }
  if event.key == "ArrowLeft" {
    return rich_text_caret_move_result(
      ctx,
      value.get(),
      @core.TextCaretDirection::MoveLeft,
      event.modifiers.shift,
      on_selection_change,
    )
  }
  if event.key == "ArrowRight" {
    return rich_text_caret_move_result(
      ctx,
      value.get(),
      @core.TextCaretDirection::MoveRight,
      event.modifiers.shift,
      on_selection_change,
    )
  }
  if event.key == "Home" {
    return rich_text_line_edge_move_result(
      ctx,
      value.get(),
      home=true,
      on_selection_change,
    )
  }
  if event.key == "End" {
    return rich_text_line_edge_move_result(
      ctx,
      value.get(),
      home=false,
      on_selection_change,
    )
  }
  match event.text {
    Some(text) =>
      if text != "" {
        rich_text_input_event(
          ctx,
          value~,
          on_input~,
          on_input_edit~,
          on_selection_change~,
          input_transform~,
          event=@core.TextInputEvent::InsertText(text),
          record_history=true,
        )
      } else {
        @core.ViewEventResult::ignored()
      }
    None => @core.ViewEventResult::ignored()
  }
}

///|
fn[Msg] rich_text_input_event(
  ctx : @core.ViewEventContext,
  value~ : @core.Binding[String],
  on_input~ : ((String) -> Msg)?,
  on_input_edit~ : ((String, Int, @core.TextRange?) -> Msg)?,
  on_selection_change~ : ((Int, @core.TextRange?) -> Msg)?,
  input_transform~ : ((String, Int, @core.TextRange?, String) -> RichTextInputTransform?)?,
  event~ : @core.TextInputEvent,
  record_history~ : Bool,
) -> @core.ViewEventResult[Msg] {
  let text = value.get()
  let result = match event {
    @core.TextInputEvent::InsertText(inserted) =>
      if inserted == "" {
        (
          { text, control: ctx.text_control, changed: false } :
          TextInputApplyResult)
      } else {
        match
          rich_text_transform_insert(
            input_transform,
            text,
            ctx.text_control,
            inserted,
          ) {
          Some(transformed) =>
            apply_rich_text_input_transform(
              ctx.text_control,
              text,
              transformed,
              record_history~,
            )
          None =>
            apply_text_input(ctx.text_control, text, event, record_history~)
        }
      }
    @core.TextInputEvent::DeleteRange(_) =>
      match
        rich_text_transform_insert(
          input_transform,
          text,
          ctx.text_control,
          "\u{000e}",
        ) {
        Some(transformed) =>
          apply_rich_text_input_transform(
            ctx.text_control,
            text,
            transformed,
            record_history~,
          )
        None => apply_text_input(ctx.text_control, text, event, record_history~)
      }
    @core.TextInputEvent::DeleteSurrounding(before, after) => {
      let signal = if before > 0 && after == 0 {
        "\u{0008}"
      } else if before == 0 && after > 0 {
        "\u{007f}"
      } else {
        ""
      }
      if signal != "" {
        match
          rich_text_transform_insert(
            input_transform,
            text,
            ctx.text_control,
            signal,
          ) {
          Some(transformed) =>
            apply_rich_text_input_transform(
              ctx.text_control,
              text,
              transformed,
              record_history~,
            )
          None =>
            apply_text_input(ctx.text_control, text, event, record_history~)
        }
      } else {
        apply_text_input(ctx.text_control, text, event, record_history~)
      }
    }
    _ => apply_text_input(ctx.text_control, text, event, record_history~)
  }
  if result.changed {
    value.set(result.text)
  }
  view_event_result(
    changed=result.changed,
    captured=result.changed,
    text_control=Some(result.control),
    dirty=@core.ViewDirtyHint::ViewPaintDirty,
    messages=if result.changed {
      text_input_messages(on_input, result.text) +
      text_input_edit_messages(on_input_edit, result.text, result.control) +
      selection_messages(
        on_selection_change,
        result.control.caret,
        result.control.selection,
      )
    } else {
      []
    },
  )
}

///|
fn[Msg] rich_text_editor_command(
  ctx : @core.ViewEventContext,
  value~ : @core.Binding[String],
  format~ : RichTextFormatter,
  on_input~ : ((String) -> Msg)?,
  on_input_edit~ : ((String, Int, @core.TextRange?) -> Msg)?,
  on_selection_change~ : ((Int, @core.TextRange?) -> Msg)?,
  input_transform~ : ((String, Int, @core.TextRange?, String) -> RichTextInputTransform?)?,
  intent : @core.CommandIntent,
  paste_text~ : String?,
) -> @core.ViewTextCommandResult[Msg] {
  let text = value.get()
  let document = format(text)
  match intent {
    @core.CommandIntent::Copy =>
      @core.ViewTextCommandResult::new(
        result=@core.TextCommandResult::handled(
          clipboard_text=Some(
            selected_rich_text(text, document, ctx.text_control.selection),
          ),
        ),
      )
    @core.CommandIntent::Cut => {
      let copied = selected_rich_text(
        text,
        document,
        ctx.text_control.selection,
      )
      match @core.active_text_selection(ctx.text_control.selection) {
        Some(selection) => {
          let event_result = rich_text_input_event(
            ctx,
            value~,
            on_input~,
            on_input_edit~,
            on_selection_change~,
            input_transform~,
            event=@core.TextInputEvent::DeleteRange(selection),
            record_history=true,
          )
          @core.ViewTextCommandResult::new(
            result=@core.TextCommandResult::handled(
              changed=event_result.changed,
              clipboard_text=Some(copied),
            ),
            text_control=event_result.text_control,
            messages=event_result.messages,
          )
        }
        None =>
          @core.ViewTextCommandResult::new(
            result=@core.TextCommandResult::handled(clipboard_text=Some("")),
          )
      }
    }
    @core.CommandIntent::Paste =>
      match paste_text {
        Some(value_to_paste) => {
          let event_result = rich_text_input_event(
            ctx,
            value~,
            on_input~,
            on_input_edit~,
            on_selection_change~,
            input_transform~,
            event=@core.TextInputEvent::InsertText(value_to_paste),
            record_history=true,
          )
          @core.ViewTextCommandResult::new(
            result=@core.TextCommandResult::handled(
              changed=event_result.changed,
            ),
            text_control=event_result.text_control,
            messages=event_result.messages,
          )
        }
        None =>
          @core.ViewTextCommandResult::new(
            result=@core.TextCommandResult::handled(),
          )
      }
    @core.CommandIntent::Undo => {
      let result = restore_text_history(ctx.text_control, text, undo=true)
      if result.changed {
        value.set(result.text)
      }
      @core.ViewTextCommandResult::new(
        result=@core.TextCommandResult::handled(changed=result.changed),
        text_control=Some(result.control),
        messages=if result.changed {
          text_input_messages(on_input, result.text) +
          text_input_edit_messages(on_input_edit, result.text, result.control) +
          selection_messages(
            on_selection_change,
            result.control.caret,
            result.control.selection,
          )
        } else {
          []
        },
      )
    }
    @core.CommandIntent::Redo => {
      let result = restore_text_history(ctx.text_control, text, undo=false)
      if result.changed {
        value.set(result.text)
      }
      @core.ViewTextCommandResult::new(
        result=@core.TextCommandResult::handled(changed=result.changed),
        text_control=Some(result.control),
        messages=if result.changed {
          text_input_messages(on_input, result.text) +
          text_input_edit_messages(on_input_edit, result.text, result.control) +
          selection_messages(
            on_selection_change,
            result.control.caret,
            result.control.selection,
          )
        } else {
          []
        },
      )
    }
    @core.CommandIntent::SelectAll => {
      let next_control = select_rich_text_control(ctx.text_control, text)
      @core.ViewTextCommandResult::new(
        result=@core.TextCommandResult::handled(changed=true),
        text_control=Some(next_control),
        messages=selection_messages(
          on_selection_change,
          next_control.caret,
          next_control.selection,
        ),
      )
    }
    _ => @core.ViewTextCommandResult::ignored()
  }
}

///|
fn rich_text_input_state(
  text~ : String,
  frame~ : @core.Rect,
  format~ : RichTextFormatter,
  font~ : @core.FontSpec,
  control~ : @core.TextControlStateContext,
  selection_caret~ : Int,
  selection_range~ : @core.TextRange?,
  text_system~ : @core.TextSystem,
) -> @core.TextInputState {
  let boundaries = @core.TextGraphemeBoundaries::new(text~)
  let control = if selection_caret >= 0 {
    { ..control, caret: selection_caret, selection: selection_range }
  } else {
    control
  }
  let display_text = rich_text_with_composition(text, control, focused=true)
  let content = rich_text_editor_content_rect(frame)
  let caret = boundaries.nearest_boundary(control.caret)
  let visual_caret = @core.clamp_int(
    rich_text_visual_caret(text, control, focused=true),
    0,
    text_length(display_text),
  )
  let document = format(display_text)
  let caret_rect = rich_text_document_caret_rect_or_plain(
    document, visual_caret, content, font, text_system, display_text,
  )
  {
    focused: true,
    element_id: None,
    text,
    caret,
    selection: rich_text_input_grapheme_selection(boundaries, control.selection),
    composition: control.composition,
    composition_cursor: control.composition_cursor,
    frame,
    caret_rect,
  }
}

///|
fn[Msg] rich_text_caret_move_result(
  ctx : @core.ViewEventContext,
  text : String,
  direction : @core.TextCaretDirection,
  extend : Bool,
  on_selection_change : ((Int, @core.TextRange?) -> Msg)?,
) -> @core.ViewEventResult[Msg] {
  let caret_move = @core.move_text_caret(
    text,
    ctx.text_control.caret,
    ctx.text_control.selection,
    direction,
    extend~,
  )
  let next_control = {
    ..ctx.text_control,
    caret: caret_move.caret,
    selection: caret_move.selection,
    composition: None,
    composition_cursor: None,
  }
  let changed = next_control.caret != ctx.text_control.caret ||
    next_control.selection != ctx.text_control.selection ||
    ctx.text_control.composition is Some(_) ||
    ctx.text_control.composition_cursor is Some(_)
  view_event_result(
    changed~,
    captured=true,
    text_control=Some(next_control),
    dirty=@core.ViewDirtyHint::ViewPaintDirty,
    messages=if changed {
      selection_messages(
        on_selection_change,
        next_control.caret,
        next_control.selection,
      )
    } else {
      []
    },
  )
}

///|
fn rich_text_with_composition(
  text : String,
  control : @core.TextControlStateContext,
  focused~ : Bool,
) -> String {
  if !focused {
    text
  } else {
    match control.composition {
      Some(composition) =>
        replace_current_selection(
          text,
          control.caret,
          control.selection,
          composition,
        ).text
      None => text
    }
  }
}

///|
fn rich_text_visual_caret(
  text : String,
  control : @core.TextControlStateContext,
  focused~ : Bool,
) -> Int {
  let caret = @core.clamp_int(control.caret, 0, text_length(text))
  if !focused {
    caret
  } else {
    match control.composition {
      Some(composition) =>
        @core.composition_start_index(text, control.caret, control.selection) +
        @core.composition_cursor_offset(control, composition)
      None => caret
    }
  }
}

///|
fn rich_text_document_caret_rect_or_plain(
  document : RichTextDocument,
  source_caret : Int,
  content : @core.Rect,
  font : @core.FontSpec,
  text_system : @core.TextSystem,
  display_text : String,
) -> @core.Rect {
  match
    rich_text_document_caret_rect_at_source(
      document,
      source_caret,
      content,
      font,
      text_system~,
    ) {
    Some(rect) => rect
    None =>
      plain_rich_text_caret_rect(
        display_text, source_caret, content, font, text_system,
      )
  }
}

///|
fn plain_rich_text_caret_rect(
  text : String,
  caret : Int,
  content : @core.Rect,
  font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> @core.Rect {
  let prefix = string_prefix(text, caret)
  let width = text_system.measure_text(
      @core.TextLayoutInput::new(
        text=prefix,
        font~,
        max_width=content.size.width,
      ),
    ).size.width
  @core.Rect::new(
    x=content.origin.x + width,
    y=content.origin.y + 4.0,
    width=1.5,
    height=max_double(10.0, font.size * 1.55 - 8.0),
  )
}

///|
pub fn rich_text_editor_content_rect(frame : @core.Rect) -> @core.Rect {
  frame.inset(@core.Insets::symmetric(horizontal=14.0, vertical=12.0))
}

///|
fn string_prefix(text : String, end : Int) -> String {
  let chars = text.to_array()
  String::from_array(chars[:@core.clamp_int(end, 0, chars.length())])
}

///|
pub fn rich_text_document_height(
  document : RichTextDocument,
  base_font : @core.FontSpec,
  text_system? : @core.TextSystem = @core.TextSystem::fallback(),
) -> Double {
  let mut height = 0.0
  for block in document.blocks {
    height = height +
      rich_text_block_line_height(block, base_font, text_system) +
      block.inset.top +
      block.inset.bottom
  }
  height
}

///|
fn rich_text_block_line_height(
  block : RichTextBlock,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Double {
  let text_height = rich_text_block_line_step(block, base_font, text_system)
  let mut height = text_height *
    rich_text_block_visual_line_count(block).to_double()
  match block.table {
    Some(table) =>
      height = max_double(
        height,
        table.row_height * table.rows.length().to_double(),
      )
    None => ()
  }
  for run in block.runs {
    match run.image_size {
      Some(size) => height = max_double(height, size.height)
      None => ()
    }
  }
  height
}

///|
fn rich_text_block_line_step(
  block : RichTextBlock,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Double {
  block.line_height.unwrap_or(
    text_system.measure_text(
      @core.TextLayoutInput::new(
        text="Mg",
        font=rich_text_block_font(block, base_font),
      ),
    ).size.height,
  )
}

///|
fn rich_text_block_visual_line_count(block : RichTextBlock) -> Int {
  let mut line_count = 1
  line_count = line_count + rich_text_text_newline_count(block.prefix)
  for run in block.runs {
    line_count = line_count +
      rich_text_text_newline_count(rich_text_run_visual_text(run))
  }
  line_count
}

///|
fn rich_text_text_newline_count(text : String) -> Int {
  let mut count = 0
  for ch in text {
    if ch == '\n' {
      count = count + 1
    }
  }
  count
}

///|
fn rich_text_run_visual_text(run : RichTextRun) -> String {
  run.visual_text.unwrap_or(run.text)
}

///|
fn rich_text_block_font(
  block : RichTextBlock,
  base_font : @core.FontSpec,
) -> @core.FontSpec {
  block.font.unwrap_or(base_font)
}

///|
fn rich_text_block_color(
  block : RichTextBlock,
  foreground : @core.Color,
) -> @core.Color {
  block.color.unwrap_or(foreground)
}

///|
fn rich_text_width(
  text : String,
  font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Double {
  text_system.measure_text(@core.TextLayoutInput::new(text~, font~)).size.width
}

///|
fn rich_text_run_segment_width(
  run : RichTextRun,
  text : String,
  font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Double {
  match run.image_size {
    Some(size) => size.width
    None => rich_text_width(text, font, text_system)
  }
}

///|
fn rich_text_run_segment_height(
  run : RichTextRun,
  line_height : Double,
) -> Double {
  match run.image_size {
    Some(size) => max_double(line_height, size.height)
    None => line_height
  }
}

///|
fn rich_text_run_hit_width(run : RichTextRun, width : Double) -> Double? {
  match run.image_source {
    Some(_) => Some(width)
    _ => None
  }
}

///|
fn rich_text_split_lines(text : String) -> Array[String] {
  let lines : Array[String] = []
  let current : Array[Char] = []
  for ch in text {
    if ch == '\n' {
      lines.push(String::from_array(current))
      current.clear()
    } else {
      current.push(ch)
    }
  }
  lines.push(String::from_array(current))
  lines
}

///|
fn rich_text_input_grapheme_selection(
  boundaries : @core.TextGraphemeBoundaries,
  selection : @core.TextRange?,
) -> @core.TextRange? {
  match selection {
    Some(selection) => {
      let start = boundaries.nearest_boundary(selection.start)
      let end = boundaries.nearest_boundary(selection.end)
      if start == end {
        None
      } else {
        Some(@core.TextRange::new(start~, end~))
      }
    }
    _ => None
  }
}

///|
fn select_rich_text_control(
  control : @core.TextControlStateContext,
  text : String,
) -> @core.TextControlStateContext {
  let length = text_length(text)
  {
    ..control,
    caret: length,
    selection: Some(@core.TextRange::new(start=0, end=length)),
    text_selection_anchor: None,
    composition: None,
    composition_cursor: None,
  }
}

///|
fn[Msg] rich_text_line_edge_move_result(
  ctx : @core.ViewEventContext,
  text : String,
  home~ : Bool,
  on_selection_change : ((Int, @core.TextRange?) -> Msg)?,
) -> @core.ViewEventResult[Msg] {
  let caret = if home {
    multiline_line_start(text, ctx.text_control.caret)
  } else {
    multiline_line_end(text, ctx.text_control.caret)
  }
  let next_control = {
    ..ctx.text_control,
    caret,
    selection: None,
    composition: None,
    composition_cursor: None,
  }
  let changed = next_control.caret != ctx.text_control.caret ||
    ctx.text_control.selection is Some(_) ||
    ctx.text_control.composition is Some(_) ||
    ctx.text_control.composition_cursor is Some(_)
  view_event_result(
    changed~,
    captured=true,
    text_control=Some(next_control),
    dirty=@core.ViewDirtyHint::ViewPaintDirty,
    messages=if changed {
      selection_messages(
        on_selection_change,
        next_control.caret,
        next_control.selection,
      )
    } else {
      []
    },
  )
}

///|
fn rich_text_vertical_target_offset(
  document : RichTextDocument,
  source_caret : Int,
  caret_rect : @core.Rect,
  content : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
  up~ : Bool,
) -> Int {
  if document.blocks.is_empty() {
    let line_height = if caret_rect.size.height > 0.0 {
      caret_rect.size.height
    } else {
      max_double(10.0, base_font.size * 1.55 - 8.0)
    }
    let target_y = if up {
      caret_rect.origin.y - line_height * 0.5
    } else {
      caret_rect.origin.y + caret_rect.size.height + line_height * 0.5
    }
    let target_point = @core.Point::new(x=caret_rect.origin.x, y=target_y)
    return rich_text_document_source_offset_at_point(
      document, target_point, content, base_font, text_system,
    ).unwrap_or(source_caret)
  }
  let mut block_index = -1
  let mut visual_line = 0
  let mut y = content.origin.y
  for index, block in document.blocks {
    let line_height = rich_text_block_line_height(block, base_font, text_system)
    let block_height = line_height + block.inset.top + block.inset.bottom
    if rich_text_caret_before_block(source_caret, block) {
      block_index = index
      visual_line = 0
      break
    }
    match block.table {
      Some(_) => {
        block_index = index
        visual_line = 0
        break
      }
      None => ()
    }
    match rich_text_block_visual_offset(block, source_caret) {
      Some(vo) => {
        block_index = index
        visual_line = rich_text_visual_offset_to_line(block, vo)
        break
      }
      None => ()
    }
    y = y + block_height
  }
  if block_index < 0 {
    block_index = document.blocks.length() - 1
    let last_block = document.blocks[block_index]
    visual_line = rich_text_block_visual_line_count(last_block) - 1
    let last_line_height = rich_text_block_line_height(
      last_block, base_font, text_system,
    )
    let last_block_height = last_line_height +
      last_block.inset.top +
      last_block.inset.bottom
    y = y - last_block_height
  }
  let block = document.blocks[block_index]
  let step = rich_text_block_line_step(block, base_font, text_system)
  let line_height = rich_text_block_line_height(block, base_font, text_system)
  let block_top = y
  let frame_top = y + block.inset.top
  let line_center_y = if visual_line >= 0 &&
    visual_line < rich_text_block_visual_line_count(block) {
    frame_top + (visual_line.to_double() + 0.5) * step
  } else {
    caret_rect.origin.y + caret_rect.size.height * 0.5
  }
  let target_y = if up {
    if visual_line == 0 {
      block_top - 1.0
    } else {
      line_center_y - step
    }
  } else {
    let line_count = rich_text_block_visual_line_count(block)
    if visual_line >= line_count - 1 {
      block_top + line_height + block.inset.top + block.inset.bottom + 1.0
    } else {
      line_center_y + step
    }
  }
  let target_point = @core.Point::new(
    x=caret_rect.origin.x + caret_rect.size.width * 0.5,
    y=target_y,
  )
  match
    rich_text_document_source_offset_at_point(
      document, target_point, content, base_font, text_system,
    ) {
    Some(offset) =>
      if offset != source_caret {
        offset
      } else {
        rich_text_vertical_block_boundary_offset(
          document,
          block_index,
          source_caret,
          up~,
        )
      }
    None =>
      rich_text_vertical_block_boundary_offset(
        document,
        block_index,
        source_caret,
        up~,
      )
  }
}

///|
fn rich_text_vertical_block_boundary_offset(
  document : RichTextDocument,
  block_index : Int,
  source_caret : Int,
  up~ : Bool,
) -> Int {
  if up {
    if block_index <= 0 {
      return 0
    }
    let prev_block = document.blocks[block_index - 1]
    match rich_text_block_end_source_offset(prev_block) {
      Some(end) => end
      None => source_caret
    }
  } else {
    if block_index >= document.blocks.length() - 1 {
      return source_caret
    }
    let next_block = document.blocks[block_index + 1]
    match next_block.content_range {
      Some(range) => range.start
      None =>
        match next_block.source_range {
          Some(range) => range.start
          None => source_caret
        }
    }
  }
}

///|
fn rich_text_visual_offset_to_line(
  block : RichTextBlock,
  visual_offset : Int,
) -> Int {
  let prefix_len = text_length(block.prefix)
  let mut consumed = prefix_len
  let mut line = 0
  if visual_offset <= prefix_len {
    return 0
  }
  for run in block.runs {
    let segments = rich_text_split_lines(rich_text_run_visual_text(run))
    for index, segment in segments {
      let seg_len = text_length(segment)
      if consumed + seg_len >= visual_offset {
        return line
      }
      consumed = consumed + seg_len
      if index + 1 < segments.length() {
        if consumed == visual_offset {
          return line
        }
        consumed = consumed + 1
        line = line + 1
      }
    }
  }
  line
}

///|
fn[Msg] rich_text_vertical_caret_move_result(
  ctx : @core.ViewEventContext,
  text : String,
  document : RichTextDocument,
  content : @core.Rect,
  font : @core.FontSpec,
  up~ : Bool,
  extend~ : Bool,
  on_selection_change : ((Int, @core.TextRange?) -> Msg)?,
) -> @core.ViewEventResult[Msg] {
  let display_text = rich_text_with_composition(
    text,
    ctx.text_control,
    focused=true,
  )
  let visual_caret = @core.clamp_int(
    rich_text_visual_caret(text, ctx.text_control, focused=true),
    0,
    text_length(display_text),
  )
  let caret_rect = rich_text_document_caret_rect_or_plain(
    document,
    visual_caret,
    content,
    font,
    ctx.text_system,
    display_text,
  )
  let target_offset = rich_text_vertical_target_offset(
    document,
    visual_caret,
    caret_rect,
    content,
    font,
    ctx.text_system,
    up~,
  )
  if target_offset == ctx.text_control.caret && !extend {
    return @core.ViewEventResult::ignored()
  }
  let next_control = if extend {
    let anchor = match ctx.text_control.text_selection_anchor {
      Some(a) => a
      None => ctx.text_control.caret
    }
    let start = @core.min_int(anchor, target_offset)
    let end = @core.max_int(anchor, target_offset)
    {
      ..ctx.text_control,
      caret: target_offset,
      selection: Some(@core.TextRange::new(start~, end~)),
      text_selection_anchor: Some(anchor),
      composition: None,
      composition_cursor: None,
    }
  } else {
    {
      ..ctx.text_control,
      caret: target_offset,
      selection: None,
      text_selection_anchor: None,
      composition: None,
      composition_cursor: None,
    }
  }
  let changed = next_control.caret != ctx.text_control.caret ||
    next_control.selection != ctx.text_control.selection ||
    ctx.text_control.composition is Some(_) ||
    ctx.text_control.composition_cursor is Some(_)
  view_event_result(
    changed~,
    captured=true,
    text_control=Some(next_control),
    dirty=@core.ViewDirtyHint::ViewPaintDirty,
    messages=if changed {
      selection_messages(
        on_selection_change,
        next_control.caret,
        next_control.selection,
      )
    } else {
      []
    },
  )
}

///|
fn multiline_line_start(text : String, caret : Int) -> Int {
  let chars = text.to_array()
  let mut index = @core.clamp_int(caret, 0, chars.length())
  while index > 0 && chars[index - 1] != '\n' {
    index = index - 1
  }
  index
}

///|
fn multiline_line_end(text : String, caret : Int) -> Int {
  let chars = text.to_array()
  let mut index = @core.clamp_int(caret, 0, chars.length())
  while index < chars.length() && chars[index] != '\n' {
    index = index + 1
  }
  index
}

///|
fn rich_text_transform_insert(
  input_transform : ((String, Int, @core.TextRange?, String) -> RichTextInputTransform?)?,
  text : String,
  control : @core.TextControlStateContext,
  inserted : String,
) -> RichTextInputTransform? {
  match input_transform {
    Some(transform) =>
      transform(text, control.caret, control.selection, inserted)
    None => None
  }
}

///|
fn apply_rich_text_input_transform(
  control : @core.TextControlStateContext,
  previous : String,
  edit : RichTextInputTransform,
  record_history~ : Bool,
) -> TextInputApplyResult {
  let changed = edit.text != previous ||
    edit.caret != control.caret ||
    edit.selection != control.selection ||
    control.composition is Some(_) ||
    control.composition_cursor is Some(_)
  if changed {
    let undo_stack : Array[@core.TextHistoryEntryContext] = []
    undo_stack.append(control.undo_stack)
    if record_history && previous != edit.text {
      undo_stack.push({
        text: previous,
        caret: control.caret,
        selection: control.selection,
      })
    }
    {
      text: edit.text,
      control: {
        ..control,
        caret: edit.caret,
        selection: edit.selection,
        composition: None,
        composition_cursor: None,
        undo_stack,
        redo_stack: [],
      },
      changed: true,
    }
  } else {
    { text: previous, control, changed: false }
  }
}

///|
fn selected_rich_text(
  text : String,
  document : RichTextDocument,
  selection : @core.TextRange?,
) -> String {
  match @core.active_text_selection(selection) {
    Some(selection) =>
      if rich_text_document_has_source_ranges(document) {
        selected_rich_text_document_text(document, selection)
      } else {
        @core.selected_text(text, Some(selection))
      }
    None => ""
  }
}

///|
fn selected_rich_text_document_text(
  document : RichTextDocument,
  selection : @core.TextRange,
) -> String {
  let lines : Array[String] = []
  for block in document.blocks {
    match selected_rich_text_block_text(block, selection) {
      Some(text) => lines.push(text)
      None => ()
    }
  }
  join_text_lines(lines)
}

///|
fn selected_rich_text_block_text(
  block : RichTextBlock,
  selection : @core.TextRange,
) -> String? {
  match (block.source_range, block.content_range) {
    (Some(source), Some(content)) =>
      if selection.end <= source.start || selection.start >= source.end {
        None
      } else {
        let start = @core.clamp_int(selection.start, content.start, content.end)
        let end = @core.clamp_int(selection.end, content.start, content.end)
        let text = selected_rich_text_runs_text(block, start, end)
        if text == "" && start != end {
          None
        } else {
          Some(text)
        }
      }
    (_, Some(content)) =>
      if selection.end <= content.start || selection.start >= content.end {
        None
      } else {
        let start = @core.clamp_int(selection.start, content.start, content.end)
        let end = @core.clamp_int(selection.end, content.start, content.end)
        let text = selected_rich_text_runs_text(block, start, end)
        if text == "" && start != end {
          None
        } else {
          Some(text)
        }
      }
    _ => None
  }
}

///|
fn selected_rich_text_runs_text(
  block : RichTextBlock,
  start : Int,
  end : Int,
) -> String {
  let parts : Array[String] = []
  for run in block.runs {
    match run.source_range {
      Some(range) =>
        if end > range.start && start < range.end {
          let copy_text = rich_text_run_copy_text(run)
          parts.push(
            rich_text_run_slice(
              copy_text,
              rich_text_run_copy_offset(
                start - range.start,
                source_text=run.text,
                copy_text~,
              ),
              rich_text_run_copy_offset(
                end - range.start,
                source_text=run.text,
                copy_text~,
              ),
            ),
          )
        }
      None => ()
    }
  }
  join_text_lines(parts)
}

///|
fn rich_text_run_copy_text(run : RichTextRun) -> String {
  match run.visual_text {
    Some(text) => text
    None =>
      match run.image_source {
        Some(_) => rich_text_image_alt_text(run.text).unwrap_or(run.text)
        None => run.text
      }
  }
}

///|
fn rich_text_run_copy_offset(
  source_offset : Int,
  source_text~ : String,
  copy_text~ : String,
) -> Int {
  let source_length = text_length(source_text)
  let copy_length = text_length(copy_text)
  if source_length <= 0 || copy_length <= 0 {
    0
  } else {
    @core.clamp_int(
      (source_offset.to_double() *
      copy_length.to_double() /
      source_length.to_double())
      .round()
      .to_int(),
      0,
      copy_length,
    )
  }
}

///|
fn rich_text_image_alt_text(text : String) -> String? {
  let chars = text.to_array()
  if chars.length() < 5 || chars[0] != '!' || chars[1] != '[' {
    return None
  }
  let mut index = 2
  while index < chars.length() {
    if chars[index] == ']' &&
      index + 1 < chars.length() &&
      chars[index + 1] == '(' {
      return Some(String::from_array(chars[2:index]))
    }
    index = index + 1
  }
  None
}

///|
fn rich_text_run_slice(text : String, start : Int, end : Int) -> String {
  let chars = text.to_array()
  let range = @core.normalize_grapheme_range(
    @core.TextGraphemeBoundaries::new(text~),
    @core.TextRange::new(start~, end~),
  )
  let start = @core.clamp_int(range.start, 0, chars.length())
  let end = @core.clamp_int(range.end, start, chars.length())
  String::from_array(chars[start:end])
}

///|
fn join_text_lines(lines : Array[String]) -> String {
  let chars : Array[Char] = []
  for index in 0.. 0 {
      chars.push('\n')
    }
    chars.append(lines[index].to_array())
  }
  String::from_array(chars)
}

///|
fn append_rich_text_selection_commands(
  commands : Array[@core.DrawCommand],
  document : RichTextDocument,
  content : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
  selection : @core.TextRange?,
) -> Unit {
  match @core.active_text_selection(selection) {
    Some(selection) => {
      let mut y = content.origin.y
      for block in document.blocks {
        let line_height = rich_text_block_line_height(
          block, base_font, text_system,
        )
        let block_height = line_height + block.inset.top + block.inset.bottom
        let frame = @core.Rect::new(
          x=content.origin.x + block.inset.left,
          y=y + block.inset.top,
          width=max_double(
            0.0,
            content.size.width - block.inset.left - block.inset.right,
          ),
          height=line_height,
        )
        match rich_text_block_selection_visual_range(block, selection) {
          Some(range) =>
            append_rich_text_block_selection_rect(
              commands, block, range, frame, base_font, text_system,
            )
          None => ()
        }
        y = y + block_height
      }
    }
    None => ()
  }
}

///|
fn rich_text_block_selection_visual_range(
  block : RichTextBlock,
  selection : @core.TextRange,
) -> @core.TextRange? {
  match (block.source_range, block.content_range) {
    (Some(source), Some(content)) =>
      if selection.end <= source.start || selection.start >= source.end {
        None
      } else {
        let start = rich_text_block_source_offset_to_visual(
          block,
          @core.clamp_int(selection.start, source.start, source.end),
          content,
        )
        let end = rich_text_block_source_offset_to_visual(
          block,
          @core.clamp_int(selection.end, source.start, source.end),
          content,
        )
        if end > start {
          Some(@core.TextRange::new(start~, end~))
        } else {
          None
        }
      }
    (_, Some(content)) =>
      if selection.end <= content.start || selection.start >= content.end {
        None
      } else {
        let start = rich_text_block_source_offset_to_visual(
          block,
          @core.clamp_int(selection.start, content.start, content.end),
          content,
        )
        let end = rich_text_block_source_offset_to_visual(
          block,
          @core.clamp_int(selection.end, content.start, content.end),
          content,
        )
        if end > start {
          Some(@core.TextRange::new(start~, end~))
        } else {
          None
        }
      }
    _ => None
  }
}

///|
fn append_rich_text_block_selection_rect(
  commands : Array[@core.DrawCommand],
  block : RichTextBlock,
  range : @core.TextRange,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Unit {
  let start_rect = rich_text_block_caret_rect(
    block,
    range.start,
    frame,
    base_font,
    text_system,
  )
  let end_rect = rich_text_block_caret_rect(
    block,
    range.end,
    frame,
    base_font,
    text_system,
  )
  if end_rect.origin.x > start_rect.origin.x {
    commands.push(
      @core.DrawCommand::FillRect(
        @core.Rect::new(
          x=start_rect.origin.x,
          y=frame.origin.y + 4.0,
          width=end_rect.origin.x - start_rect.origin.x,
          height=max_double(10.0, frame.size.height - 8.0),
        ),
        @core.Color::blue().with_alpha(0.18),
      ),
    )
  }
}

///|
pub fn rich_text_document_caret_rect_at_source(
  document : RichTextDocument,
  source_caret : Int,
  content : @core.Rect,
  base_font : @core.FontSpec,
  text_system? : @core.TextSystem = @core.TextSystem::fallback(),
) -> @core.Rect? {
  rich_text_document_caret_rect(
    document, source_caret, content, base_font, text_system,
  )
}

///|
fn rich_text_document_caret_rect(
  document : RichTextDocument,
  source_caret : Int,
  content : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> @core.Rect? {
  if !rich_text_document_has_source_ranges(document) {
    return None
  }
  let mut y = content.origin.y
  let mut last_rect = @core.Rect::new(
    x=content.origin.x,
    y=content.origin.y + 4.0,
    width=1.5,
    height=max_double(10.0, base_font.size * 1.55 - 8.0),
  )
  for block in document.blocks {
    let line_height = rich_text_block_line_height(block, base_font, text_system)
    let block_height = line_height + block.inset.top + block.inset.bottom
    let frame = @core.Rect::new(
      x=content.origin.x + block.inset.left,
      y=y + block.inset.top,
      width=max_double(
        0.0,
        content.size.width - block.inset.left - block.inset.right,
      ),
      height=line_height,
    )
    if rich_text_caret_before_block(source_caret, block) {
      match block.table {
        Some(table) =>
          return Some(
            rich_text_block_table_caret_rect(
              block, table, source_caret, frame, base_font, text_system,
            ),
          )
        None => ()
      }
      return Some(
        rich_text_block_caret_rect(block, 0, frame, base_font, text_system),
      )
    }
    match block.table {
      Some(table) =>
        return Some(
          rich_text_block_table_caret_rect(
            block, table, source_caret, frame, base_font, text_system,
          ),
        )
      None => ()
    }
    match rich_text_block_visual_offset(block, source_caret) {
      Some(visual_offset) =>
        return Some(
          rich_text_block_caret_rect(
            block, visual_offset, frame, base_font, text_system,
          ),
        )
      None => ()
    }
    last_rect = rich_text_block_caret_rect(
      block,
      rich_text_block_visual_length(block),
      frame,
      base_font,
      text_system,
    )
    y = y + block_height
  }
  Some(last_rect)
}

///|
fn rich_text_document_source_offset_at_point(
  document : RichTextDocument,
  point : @core.Point,
  content : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Int? {
  if !rich_text_document_has_source_ranges(document) {
    return None
  }
  let mut y = content.origin.y
  let mut last_offset : Int? = None
  let mut last_block : RichTextBlock? = None
  let mut last_frame = @core.Rect::new(x=0.0, y=0.0, width=0.0, height=0.0)
  for block in document.blocks {
    let line_height = rich_text_block_line_height(block, base_font, text_system)
    let block_height = line_height + block.inset.top + block.inset.bottom
    let frame = @core.Rect::new(
      x=content.origin.x + block.inset.left,
      y=y + block.inset.top,
      width=max_double(
        0.0,
        content.size.width - block.inset.left - block.inset.right,
      ),
      height=line_height,
    )
    if point.y < y + block_height {
      match
        rich_text_block_table_source_offset_at_point(
          block, point, frame, base_font, text_system,
        ) {
        Some(offset) => return Some(offset)
        None => ()
      }
      let offset = rich_text_block_source_offset_at_point(
        block, point, frame, base_font, text_system,
      )
      return Some(offset)
    }
    last_block = Some(block)
    last_frame = frame
    last_offset = rich_text_block_end_source_offset(block)
    y = y + block_height
  }
  match last_block {
    Some(block) =>
      Some(
        rich_text_block_source_offset_at_point(
          block, point, last_frame, base_font, text_system,
        ),
      )
    None => last_offset
  }
}

///|
fn rich_text_block_end_source_offset(block : RichTextBlock) -> Int? {
  match block.source_range {
    Some(range) => Some(range.end)
    None =>
      Some(
        rich_text_block_source_offset_from_visual(
          block,
          rich_text_block_visual_length(block),
        ),
      )
  }
}

///|
fn rich_text_caret_before_block(
  source_caret : Int,
  block : RichTextBlock,
) -> Bool {
  match block.source_range {
    Some(range) => source_caret < range.start
    None => false
  }
}

///|
fn rich_text_block_visual_offset(
  block : RichTextBlock,
  source_caret : Int,
) -> Int? {
  match (block.source_range, block.content_range) {
    (Some(source), Some(content)) =>
      if source_caret < source.start || source_caret > source.end {
        None
      } else if source_caret < content.start {
        Some(0)
      } else if source_caret > content.end {
        Some(rich_text_block_visual_length(block))
      } else {
        Some(
          rich_text_block_source_offset_to_visual(block, source_caret, content),
        )
      }
    (_, Some(content)) =>
      if source_caret < content.start || source_caret > content.end {
        None
      } else {
        Some(
          rich_text_block_source_offset_to_visual(block, source_caret, content),
        )
      }
    _ => None
  }
}

///|
fn rich_text_document_prefix_source_offset_at_point(
  document : RichTextDocument,
  point : @core.Point,
  content : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Int? {
  if !rich_text_document_has_source_ranges(document) {
    return None
  }
  let mut y = content.origin.y
  let mut fallback : Int? = None
  for block in document.blocks {
    let line_height = rich_text_block_line_height(block, base_font, text_system)
    let block_height = line_height + block.inset.top + block.inset.bottom
    let frame = @core.Rect::new(
      x=content.origin.x + block.inset.left,
      y=y + block.inset.top,
      width=max_double(
        0.0,
        content.size.width - block.inset.left - block.inset.right,
      ),
      height=line_height,
    )
    if fallback is None &&
      rich_text_point_in_prefix_lane(
        block, point, frame, base_font, text_system,
      ) {
      fallback = match block.content_range {
        Some(range) => Some(range.start)
        None => None
      }
    }
    if point.y < y + block_height {
      return rich_text_block_prefix_source_offset_at_point(
        block, point, frame, base_font, text_system,
      )
    }
    y = y + block_height
  }
  fallback
}

///|
fn rich_text_block_prefix_source_offset_at_point(
  block : RichTextBlock,
  point : @core.Point,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Int? {
  if block.prefix == "" {
    return None
  }
  let font = rich_text_block_font(block, base_font)
  let width = max_double(
    rich_text_width(block.prefix, font, text_system),
    font.size * 1.8,
  )
  if point.x >= frame.origin.x && point.x <= frame.origin.x + width {
    match block.content_range {
      Some(content) => Some(content.start)
      None => None
    }
  } else {
    None
  }
}

///|
fn rich_text_point_in_prefix_lane(
  block : RichTextBlock,
  point : @core.Point,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Bool {
  if block.prefix == "" {
    return false
  }
  let font = rich_text_block_font(block, base_font)
  let width = max_double(
    rich_text_width(block.prefix, font, text_system),
    font.size * 1.8,
  )
  point.x >= frame.origin.x && point.x <= frame.origin.x + width
}

///|
fn rich_text_block_source_offset_at_point(
  block : RichTextBlock,
  point : @core.Point,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Int {
  let visual = rich_text_block_visual_offset_at_point(
    block, point, frame, base_font, text_system,
  )
  rich_text_block_source_offset_from_visual(block, visual)
}

///|
fn rich_text_block_visual_offset_at_point(
  block : RichTextBlock,
  point : @core.Point,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Int {
  let font = rich_text_block_font(block, base_font)
  let line_step = rich_text_block_line_step(block, base_font, text_system)
  let target_line = @core.clamp_int(
    ((point.y - frame.origin.y) / line_step).floor().to_int(),
    0,
    rich_text_block_visual_line_count(block) - 1,
  )
  let mut origin = frame.origin.x
  let mut visual_offset = 0
  let mut line = 0
  let mut line_end = 0
  if block.prefix != "" {
    let prefix_length = text_length(block.prefix)
    let hit = rich_text_run_visual_offset_at_x(
      block.prefix,
      font,
      point.x,
      origin,
      text_system,
    )
    let width = rich_text_width(block.prefix, font, text_system)
    if target_line == 0 && (hit < prefix_length || point.x <= origin + width) {
      return hit
    }
    visual_offset = visual_offset + prefix_length
    line_end = visual_offset
    origin = origin + width
  }
  for run in block.runs {
    let run_font = run.font.unwrap_or(font)
    let segments = rich_text_split_lines(rich_text_run_visual_text(run))
    for index, segment in segments {
      let segment_length = text_length(segment)
      let segment_width = rich_text_run_segment_width(
        run, segment, run_font, text_system,
      )
      if line == target_line {
        line_end = visual_offset + segment_length
        if point.x <= origin + segment_width || segment_length == 0 {
          return visual_offset +
            rich_text_run_visual_offset_at_x(
              segment,
              run_font,
              point.x,
              origin,
              text_system,
              width=rich_text_run_hit_width(run, segment_width),
            )
        }
      }
      visual_offset = visual_offset + segment_length
      origin = origin + segment_width
      if index + 1 < segments.length() {
        if line == target_line {
          return line_end
        }
        visual_offset = visual_offset + 1
        line = line + 1
        origin = frame.origin.x
      }
    }
  }
  line_end
}

///|
fn rich_text_run_visual_offset_at_x(
  text : String,
  font : @core.FontSpec,
  x : Double,
  origin_x : Double,
  text_system : @core.TextSystem,
  width? : Double? = None,
) -> Int {
  let chars = text.to_array()
  let boundaries = @core.TextGraphemeBoundaries::new(text~)
  if x <= origin_x {
    return 0
  }
  match width {
    Some(width) =>
      if chars.length() <= 1 {
        if x < origin_x + width / 2.0 {
          return 0
        } else {
          return chars.length()
        }
      } else {
        let relative = clamp_double((x - origin_x) / width, 0.0, 1.0)
        return boundaries.nearest_boundary(
          (relative * chars.length().to_double()).round().to_int(),
        )
      }
    None => ()
  }
  for index in 0.. Int {
  match block.content_range {
    Some(content) => {
      let prefix_length = text_length(block.prefix)
      if visual_offset <= prefix_length {
        content.start
      } else {
        match
          rich_text_block_run_source_offset_from_visual(
            block,
            visual_offset - prefix_length,
          ) {
          Some(offset) => offset
          None =>
            @core.clamp_int(
              content.start + visual_offset - prefix_length,
              content.start,
              content.end,
            )
        }
      }
    }
    None => visual_offset
  }
}

///|
fn rich_text_block_run_source_offset_from_visual(
  block : RichTextBlock,
  visual_offset : Int,
) -> Int? {
  let mut remaining = visual_offset
  let mut fallback = match block.content_range {
    Some(content) => Some(content.start)
    None => None
  }
  for run in block.runs {
    let visual_text = rich_text_run_visual_text(run)
    let visual_length = text_length(visual_text)
    if remaining <= visual_length {
      match run.source_range {
        Some(range) =>
          return Some(
            @core.clamp_int(
              range.start +
              rich_text_run_source_offset_from_visual_offset(
                remaining,
                visual_text,
                run.text,
              ),
              range.start,
              range.end,
            ),
          )
        None => return fallback
      }
    }
    remaining = remaining - visual_length
    match run.source_range {
      Some(range) => fallback = Some(range.end)
      None => ()
    }
  }
  match block.runs.last() {
    Some(run) =>
      match run.source_range {
        Some(range) => Some(range.end)
        None => fallback
      }
    None => None
  }
}

///|
fn rich_text_block_source_offset_to_visual(
  block : RichTextBlock,
  source_offset : Int,
  content : RichTextSourceRange,
) -> Int {
  if source_offset < content.start {
    0
  } else if source_offset > content.end {
    rich_text_block_visual_length(block)
  } else {
    let prefix_length = text_length(block.prefix)
    match rich_text_block_run_visual_offset_from_source(block, source_offset) {
      Some(offset) => prefix_length + offset
      None =>
        @core.clamp_int(
          prefix_length + source_offset - content.start,
          0,
          rich_text_block_visual_length(block),
        )
    }
  }
}

///|
fn rich_text_block_run_visual_offset_from_source(
  block : RichTextBlock,
  source_offset : Int,
) -> Int? {
  let mut visual = 0
  for run in block.runs {
    let visual_text = rich_text_run_visual_text(run)
    let visual_length = text_length(visual_text)
    match run.source_range {
      Some(range) =>
        if source_offset <= range.end {
          return Some(
            visual +
            rich_text_run_visual_offset_from_source_offset(
              source_offset - range.start,
              run.text,
              visual_text,
            ),
          )
        }
      None => ()
    }
    visual = visual + visual_length
  }
  Some(visual)
}

///|
fn rich_text_block_caret_rect(
  block : RichTextBlock,
  visual_offset : Int,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> @core.Rect {
  let font = rich_text_block_font(block, base_font)
  let line_step = rich_text_block_line_step(block, base_font, text_system)
  let mut remaining = @core.clamp_int(
    visual_offset,
    0,
    rich_text_block_visual_length(block),
  )
  let mut x = frame.origin.x
  let mut y = frame.origin.y
  if block.prefix != "" {
    let prefix_length = text_length(block.prefix)
    if remaining <= prefix_length {
      let prefix = rich_text_prefix(block.prefix, remaining)
      x = x + rich_text_width(prefix, font, text_system)
      return rich_text_cursor_rect_at(
        x,
        @core.Rect::new(
          x=frame.origin.x,
          y~,
          width=frame.size.width,
          height=frame.size.height,
        ),
        font,
      )
    }
    remaining = remaining - prefix_length
    x = x + rich_text_width(block.prefix, font, text_system)
  }
  for run in block.runs {
    let run_font = run.font.unwrap_or(font)
    let segments = rich_text_split_lines(rich_text_run_visual_text(run))
    for index, segment in segments {
      let segment_length = text_length(segment)
      if remaining <= segment_length {
        x = x +
          rich_text_run_segment_prefix_width(
            run, segment, remaining, run_font, text_system,
          )
        return rich_text_cursor_rect_at(
          x,
          @core.Rect::new(
            x=frame.origin.x,
            y~,
            width=frame.size.width,
            height=line_step,
          ),
          run_font,
        )
      }
      remaining = remaining - segment_length
      x = x + rich_text_run_segment_width(run, segment, run_font, text_system)
      if index + 1 < segments.length() {
        if remaining == 0 {
          return rich_text_cursor_rect_at(
            x,
            @core.Rect::new(
              x=frame.origin.x,
              y~,
              width=frame.size.width,
              height=line_step,
            ),
            run_font,
          )
        }
        remaining = remaining - 1
        y = y + line_step
        x = frame.origin.x
      }
    }
  }
  rich_text_cursor_rect_at(
    x,
    @core.Rect::new(
      x=frame.origin.x,
      y~,
      width=frame.size.width,
      height=line_step,
    ),
    font,
  )
}

///|
fn rich_text_run_segment_prefix_width(
  run : RichTextRun,
  text : String,
  visual_offset : Int,
  font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Double {
  let length = text_length(text)
  if length <= 0 || visual_offset <= 0 {
    0.0
  } else if visual_offset >= length {
    rich_text_run_segment_width(run, text, font, text_system)
  } else {
    match run.image_source {
      Some(_) =>
        rich_text_run_segment_width(run, text, font, text_system) *
        visual_offset.to_double() /
        length.to_double()
      None =>
        rich_text_width(
          rich_text_prefix(text, visual_offset),
          font,
          text_system,
        )
    }
  }
}

///|
fn rich_text_prefix(text : String, length : Int) -> String {
  let chars = text.to_array()
  let end = @core.TextGraphemeBoundaries::new(text~).nearest_boundary(length)
  String::from_array(chars[0:end])
}

///|
fn rich_text_cursor_rect_at(
  x : Double,
  frame : @core.Rect,
  font : @core.FontSpec,
) -> @core.Rect {
  @core.Rect::new(
    x~,
    y=frame.origin.y + 4.0,
    width=1.5,
    height=max_double(10.0, font.size * 1.55 - 8.0),
  )
}

///|
fn rich_text_block_visual_length(block : RichTextBlock) -> Int {
  let mut length = text_length(block.prefix)
  for run in block.runs {
    length = length + text_length(rich_text_run_visual_text(run))
  }
  length
}

///|
fn rich_text_document_has_source_ranges(document : RichTextDocument) -> Bool {
  document.blocks.any(block => {
    block.source_range is Some(_) ||
    block.content_range is Some(_) ||
    block.runs.any(run => run.source_range is Some(_)) ||
    rich_text_block_table_has_source_ranges(block)
  })
}

///|
fn rich_text_block_table_has_source_ranges(block : RichTextBlock) -> Bool {
  match block.table {
    Some(table) =>
      table.rows.any(row => row.any(cell => cell.source_range is Some(_)))
    None => false
  }
}

///|
fn rich_text_table_column_count(table : RichTextTable) -> Int {
  let mut count = 0
  for row in table.rows {
    count = @core.max_int(count, row.length())
  }
  count
}

///|
fn rich_text_block_cache_key(
  index : Int,
  block : RichTextBlock,
  frame : @core.Rect,
) -> String {
  // 注意:不要把 frame.origin 纳入 cache_key。
  // 渲染器在栅格化 cached layer 时会做 translate(-origin.x, -origin.y) 抵消,
  // pixmap 内容只依赖 block 自身 + frame.size,与 origin 无关。
  // origin 仅在 draw_cached_layer_pixmap 贴图阶段用于定位。
  // 滚动时让节点重建拿到新 origin 是通过 facade.mbt revision 加入 scroll_y 实现,
  // 那条路径不依赖 layer cache 失效,layer 仍可命中并以新 frame 贴图。
  // 把 origin 编码进 cache_key 反而会导致滚动时所有 block 缓存全 miss,触发
  // 重栅格化与缓存驱逐抖动,造成滚动后明显卡顿。
  "rich-text-block;range=\{rich_text_source_range_key(block.source_range)};index=\{index};w=\{frame.size.width};h=\{frame.size.height}"
}

///|
fn rich_text_block_content_revision(
  block : RichTextBlock,
  index : Int,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  foreground : @core.Color,
) -> Int {
  let mut hash = rich_text_hash_mix_int(5381, index)
  hash = rich_text_hash_mix_string(
    hash,
    rich_text_source_range_key(block.source_range),
  )
  hash = rich_text_hash_mix_string(
    hash,
    rich_text_source_range_key(block.content_range),
  )
  hash = rich_text_hash_mix_string(hash, block.prefix)
  hash = rich_text_hash_mix_string(
    hash,
    rich_text_font_key(block.font.unwrap_or(base_font)),
  )
  hash = rich_text_hash_mix_string(
    hash,
    rich_text_color_key(block.color.unwrap_or(foreground)),
  )
  hash = rich_text_hash_mix_string(
    hash,
    rich_text_brush_optional_key(block.background),
  )
  hash = rich_text_hash_mix_string(
    hash,
    rich_text_border_optional_key(block.border_left),
  )
  hash = rich_text_hash_mix_string(hash, rich_text_insets_key(block.inset))
  hash = rich_text_hash_mix_string(
    hash,
    rich_text_double_optional_key(block.line_height),
  )
  hash = rich_text_hash_mix_string(hash, "\{block.corner_radius}")
  hash = rich_text_hash_mix_int(hash, if block.rule { 1 } else { 0 })
  hash = rich_text_hash_mix_string(
    hash,
    "\{frame.size.width}x\{frame.size.height}",
  )
  for run in block.runs {
    hash = rich_text_hash_mix_string(hash, run.text)
    hash = rich_text_hash_mix_string(
      hash,
      rich_text_optional_string_key(run.visual_text),
    )
    hash = rich_text_hash_mix_string(
      hash,
      rich_text_optional_string_key(run.image_source),
    )
    hash = rich_text_hash_mix_string(
      hash,
      rich_text_size_optional_key(run.image_size),
    )
    hash = rich_text_hash_mix_string(
      hash,
      rich_text_image_fit_key(run.image_fit),
    )
    hash = rich_text_hash_mix_string(
      hash,
      rich_text_font_optional_key(run.font),
    )
    hash = rich_text_hash_mix_string(
      hash,
      rich_text_color_optional_key(run.color),
    )
    hash = rich_text_hash_mix_string(
      hash,
      rich_text_brush_optional_key(run.background),
    )
    hash = rich_text_hash_mix_string(hash, "\{run.background_radius}")
    hash = rich_text_hash_mix_string(
      hash,
      rich_text_decoration_key(run.decoration),
    )
    hash = rich_text_hash_mix_string(
      hash,
      rich_text_source_range_key(run.source_range),
    )
  }
  match block.table {
    Some(table) => {
      hash = rich_text_hash_mix_string(hash, "\{table.row_height}")
      hash = rich_text_hash_mix_string(
        hash,
        rich_text_insets_key(table.cell_padding),
      )
      hash = rich_text_hash_mix_string(hash, rich_text_color_key(table.border))
      hash = rich_text_hash_mix_string(
        hash,
        rich_text_brush_optional_key(table.header_background),
      )
      for row in table.rows {
        for cell in row {
          hash = rich_text_hash_mix_string(hash, cell.text)
          hash = rich_text_hash_mix_int(hash, if cell.header { 1 } else { 0 })
          hash = rich_text_hash_mix_string(
            hash,
            rich_text_align_optional_key(cell.align),
          )
          hash = rich_text_hash_mix_string(
            hash,
            rich_text_source_range_key(cell.source_range),
          )
        }
      }
    }
    None => ()
  }
  if hash < 0 {
    -hash
  } else {
    hash
  }
}

///|
fn rich_text_source_range_key(range : RichTextSourceRange?) -> String {
  match range {
    Some(range) => "len=\{@core.max_int(0, range.end - range.start)}"
    None => "none"
  }
}

///|
fn rich_text_optional_string_key(value : String?) -> String {
  value.unwrap_or("none")
}

///|
fn rich_text_size_optional_key(value : @core.Size?) -> String {
  match value {
    Some(size) => "\{size.width}x\{size.height}"
    None => "none"
  }
}

///|
fn rich_text_double_optional_key(value : Double?) -> String {
  match value {
    Some(value) => "\{value}"
    None => "none"
  }
}

///|
fn rich_text_font_optional_key(value : @core.FontSpec?) -> String {
  match value {
    Some(font) => rich_text_font_key(font)
    None => "none"
  }
}

///|
fn rich_text_font_key(font : @core.FontSpec) -> String {
  "\{font.css_family()}|\{font.size}|\{font.weight}|\{font.css_style()}"
}

///|
fn rich_text_color_optional_key(value : @core.Color?) -> String {
  match value {
    Some(color) => rich_text_color_key(color)
    None => "none"
  }
}

///|
fn rich_text_color_key(color : @core.Color) -> String {
  "\{color.r},\{color.g},\{color.b},\{color.a}"
}

///|
fn rich_text_brush_optional_key(value : @core.Brush?) -> String {
  match value {
    Some(brush) => rich_text_brush_key(brush)
    None => "none"
  }
}

///|
fn rich_text_brush_key(brush : @core.Brush) -> String {
  match brush {
    @core.Brush::Solid(color) => "solid:\{rich_text_color_key(color)}"
    @core.Brush::LinearGradient(spec) =>
      "linear:\{spec.start.x},\{spec.start.y},\{spec.end.x},\{spec.end.y},\{rich_text_color_key(spec.start_color)},\{rich_text_color_key(spec.end_color)}"
    @core.Brush::RadialGradient(spec) =>
      "radial:\{spec.center.x},\{spec.center.y},\{spec.radius},\{rich_text_color_key(spec.center_color)},\{rich_text_color_key(spec.edge_color)}"
  }
}

///|
fn rich_text_border_optional_key(value : @core.BorderStyle?) -> String {
  match value {
    Some(border) => "\{rich_text_brush_key(border.brush)}|\{border.width}"
    None => "none"
  }
}

///|
fn rich_text_insets_key(insets : @core.Insets) -> String {
  "\{insets.top},\{insets.right},\{insets.bottom},\{insets.left}"
}

///|
fn rich_text_image_fit_key(fit : @core.ImageFit) -> String {
  match fit {
    @core.ImageFit::Contain => "contain"
    @core.ImageFit::Cover => "cover"
    @core.ImageFit::Stretch => "stretch"
    @core.ImageFit::ScaleDown => "scale-down"
    @core.ImageFit::FitWidth => "fit-width"
    @core.ImageFit::FitHeight => "fit-height"
  }
}

///|
fn rich_text_decoration_key(value : RichTextDecoration?) -> String {
  match value {
    Some(RichTextDecoration::Strikethrough) => "strikethrough"
    None => "none"
  }
}

///|
fn rich_text_align_optional_key(value : @core.TextAlign?) -> String {
  match value {
    Some(@core.TextAlign::TextStart) => "start"
    Some(@core.TextAlign::TextCenter) => "center"
    Some(@core.TextAlign::TextEnd) => "end"
    None => "none"
  }
}

///|
fn rich_text_string_hash(value : String) -> Int {
  let mut hash = 5381
  for ch in value.to_array() {
    hash = hash * 33 + ch.to_int()
  }
  hash
}

///|
fn rich_text_hash_mix_int(hash : Int, value : Int) -> Int {
  hash * 33 + value
}

///|
fn rich_text_hash_mix_string(hash : Int, value : String) -> Int {
  hash * 33 + rich_text_string_hash(value)
}

///|
fn rich_text_block_table_source_offset_at_point(
  block : RichTextBlock,
  point : @core.Point,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> Int? {
  match block.table {
    Some(table) => {
      let row_count = table.rows.length()
      let column_count = rich_text_table_column_count(table)
      if row_count == 0 || column_count == 0 || table.row_height <= 0.0 {
        return None
      }
      let row_index = @core.clamp_int(
        ((point.y - frame.origin.y) / table.row_height).floor().to_int(),
        0,
        row_count - 1,
      )
      let column_width = frame.size.width / column_count.to_double()
      let column_index = @core.clamp_int(
        ((point.x - frame.origin.x) / column_width).floor().to_int(),
        0,
        column_count - 1,
      )
      let row = table.rows[row_index]
      if column_index >= row.length() {
        return None
      }
      let cell = row[column_index]
      match cell.source_range {
        Some(range) => {
          // 与渲染端 (append_rich_text_block_table) 保持一致的几何:
          // cell 文本 frame = cell_frame.inset(cell_padding),按 cell.align 决定 x 起点。
          let cell_x = frame.origin.x + column_width * column_index.to_double()
          let inner_x = cell_x + table.cell_padding.left
          let inner_width = max_double(
            0.0,
            column_width - table.cell_padding.left - table.cell_padding.right,
          )
          // 空 cell 直接钳到 range.start,避免后续除零/无效测量。
          if range.end <= range.start || cell.text == "" {
            return Some(range.start)
          }
          let base_block_font = rich_text_block_font(block, base_font)
          // 渲染端对 header cell 加重 weight=700,body 用 400。
          let cell_font = if cell.header {
            { ..base_block_font, weight: 700 }
          } else {
            { ..base_block_font, weight: 400 }
          }
          let text_width = rich_text_width(cell.text, cell_font, text_system)
          if text_width <= 0.0 {
            return Some(range.start)
          }
          let text_x = match cell.align.unwrap_or(@core.TextAlign::TextStart) {
            TextStart => inner_x
            TextCenter => inner_x + (inner_width - text_width) / 2.0
            TextEnd => inner_x + inner_width - text_width
          }
          let text_x_end = text_x + text_width
          // 点击落在文本左右两侧空白时,clamp 到 range 边界。
          if point.x < text_x {
            return Some(range.start)
          }
          if point.x > text_x_end {
            return Some(range.end)
          }
          // 点击落在文本渲染范围内:按逐字符中点命中,再钳到图形素边界。
          // 参考 rich_text_run_visual_offset_at_x 的 for index in 0.. None
      }
    }
    None => None
  }
}

///|
fn rich_text_block_table_caret_rect(
  block : RichTextBlock,
  table : RichTextTable,
  source_caret : Int,
  frame : @core.Rect,
  base_font : @core.FontSpec,
  text_system : @core.TextSystem,
) -> @core.Rect {
  let column_count = rich_text_table_column_count(table)
  let row_count = table.rows.length()
  let column_width = if column_count > 0 {
    frame.size.width / column_count.to_double()
  } else {
    0.0
  }
  // 默认 caret 矩形:表格起点。
  let base_font_resolved = rich_text_block_font(block, base_font)
  let line_step = rich_text_block_line_step(block, base_font, text_system)
  let caret_rect = rich_text_cursor_rect_at(
    frame.origin.x,
    @core.Rect::new(
      x=frame.origin.x,
      y=frame.origin.y,
      width=frame.size.width,
      height=line_step,
    ),
    base_font_resolved,
  )
  if column_count == 0 || row_count == 0 {
    return caret_rect
  }
  // 查找 source_caret 所在的 cell。
  let mut found_row = -1
  let mut found_col = -1
  let mut found_cell : RichTextTableCell? = None
  for row_index in 0..= row.length() {
        continue
      }
      let cell = row[col_index]
      match cell.source_range {
        Some(range) =>
          if source_caret >= range.start && source_caret <= range.end {
            found_row = row_index
            found_col = col_index
            found_cell = Some(cell)
            break
          }
        None => ()
      }
    }
    if found_row >= 0 {
      break
    }
  }
  // 若 source_caret 超出所有 cell 范围:当 source_caret 在第一个 cell 之前
  // (例如 position 0 位于表格 block 起点、但首个 cell 的 source_range 从
  // "| " 之后才开始),定位到首个 cell;否则定位到末尾 cell。
  if found_row < 0 {
    let first_row = table.rows[0]
    let first_col_index = @core.clamp_int(
      first_row.length() - 1,
      0,
      column_count - 1,
    )
    let caret_before_first_cell = match
      first_row[first_col_index].source_range {
      Some(range) => source_caret < range.start
      None => false
    }
    if caret_before_first_cell {
      found_row = 0
      found_col = first_col_index
      found_cell = Some(first_row[first_col_index])
    } else {
      let last_row_index = row_count - 1
      let last_row = table.rows[last_row_index]
      let last_col_index = @core.clamp_int(
        last_row.length() - 1,
        0,
        column_count - 1,
      )
      if last_col_index >= 0 && last_col_index < last_row.length() {
        found_row = last_row_index
        found_col = last_col_index
        found_cell = Some(last_row[last_col_index])
      }
    }
  }
  if found_row < 0 {
    return caret_rect
  }
  let cell = match found_cell {
    Some(c) => c
    None => return caret_rect
  }
  // 与渲染端一致的 cell 几何。
  let cell_x = frame.origin.x + column_width * found_col.to_double()
  let row_y = frame.origin.y + table.row_height * found_row.to_double()
  let text_frame = @core.Rect::new(
    x=cell_x + table.cell_padding.left,
    y=row_y + table.cell_padding.top,
    width=max_double(
      0.0,
      column_width - table.cell_padding.left - table.cell_padding.right,
    ),
    height=table.row_height - table.cell_padding.top - table.cell_padding.bottom,
  )
  // cell 字体(header 加重,与渲染端一致)。
  let cell_font = if cell.header {
    { ..base_font_resolved, weight: 700 }
  } else {
    { ..base_font_resolved, weight: 400 }
  }
  // 空 cell 直接返回 cell 文本起点。
  if cell.text == "" {
    return rich_text_cursor_rect_at(text_frame.origin.x, text_frame, cell_font)
  }
  match cell.source_range {
    Some(range) => {
      // local_offset = source_caret 相对 cell.source_range.start 的偏移,钳到 [0, text.length]。
      let local_offset = @core.clamp_int(
        source_caret - range.start,
        0,
        cell.text.to_array().length(),
      )
      // prefix 宽度。
      let chars = cell.text.to_array()
      let prefix = String::from_array(
        chars[0:@core.clamp_int(local_offset, 0, chars.length())],
      )
      let prefix_width = rich_text_width(prefix, cell_font, text_system)
      // 按 align 计算 text_x(与点击映射对称)。
      let text_width = rich_text_width(cell.text, cell_font, text_system)
      let inner_width = text_frame.size.width
      let text_x = match cell.align.unwrap_or(@core.TextAlign::TextStart) {
        TextStart => text_frame.origin.x
        TextCenter => text_frame.origin.x + (inner_width - text_width) / 2.0
        TextEnd => text_frame.origin.x + inner_width - text_width
      }
      rich_text_cursor_rect_at(text_x + prefix_width, text_frame, cell_font)
    }
    None => rich_text_cursor_rect_at(text_frame.origin.x, text_frame, cell_font)
  }
}

///|
fn rich_text_run_visual_offset_from_source_offset(
  source_offset : Int,
  source_text : String,
  visual_text : String,
) -> Int {
  let source_boundaries = @core.TextGraphemeBoundaries::new(text=source_text)
  let visual_boundaries = @core.TextGraphemeBoundaries::new(text=visual_text)
  let source_length = source_boundaries.length()
  let visual_length = visual_boundaries.length()
  let source_offset = source_boundaries.nearest_boundary(source_offset)
  if source_length <= 0 || visual_length <= 0 {
    0
  } else if source_offset <= 0 {
    0
  } else if source_offset >= source_length {
    visual_length
  } else {
    visual_boundaries.nearest_boundary(
      (source_offset.to_double() *
      visual_length.to_double() /
      source_length.to_double())
      .round()
      .to_int(),
    )
  }
}

///|
fn rich_text_run_source_offset_from_visual_offset(
  visual_offset : Int,
  visual_text : String,
  source_text : String,
) -> Int {
  let visual_boundaries = @core.TextGraphemeBoundaries::new(text=visual_text)
  let source_boundaries = @core.TextGraphemeBoundaries::new(text=source_text)
  let visual_length = visual_boundaries.length()
  let source_length = source_boundaries.length()
  let visual_offset = visual_boundaries.nearest_boundary(visual_offset)
  if visual_length <= 0 || source_length <= 0 {
    0
  } else if visual_offset <= 0 {
    0
  } else if visual_offset >= visual_length {
    source_length
  } else {
    source_boundaries.nearest_boundary(
      (visual_offset.to_double() *
      source_length.to_double() /
      visual_length.to_double())
      .round()
      .to_int(),
    )
  }
}

///|
fn[Msg] selection_messages(
  on_selection_change : ((Int, @core.TextRange?) -> Msg)?,
  caret : Int,
  selection : @core.TextRange?,
) -> Array[Msg] {
  match on_selection_change {
    Some(handler) => [handler(caret, selection)]
    None => []
  }
}