Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Caret Shape

Group: Content

Code

pub struct CaretShapeDemo {
    /// Last shape actually applied via `set_caret_shape`, so repeat
    /// renders of the same variant don't re-issue the (comparatively
    /// heavy) terminal write.
    applied: Cell<Option<EditorCursorShape>>,
    calls: Cell<u32>,
}

impl CaretShapeDemo {
    pub fn new() -> Self {
        Self {
            applied: Cell::new(None),
            calls: Cell::new(0),
        }
    }

    fn shape_for_variant(variant: usize) -> EditorCursorShape {
        match variant {
            1 => EditorCursorShape::Bar,
            2 => EditorCursorShape::Underline,
            _ => EditorCursorShape::Block,
        }
    }

    fn mode_name(shape: EditorCursorShape) -> &'static str {
        match shape {
            EditorCursorShape::Block => "Normal",
            EditorCursorShape::Bar => "Insert",
            EditorCursorShape::Underline => "Replace-pending",
        }
    }

    fn build_editor(shape: EditorCursorShape, area: Rect) -> Editor {
        let text = "the quick brown fox".to_string();
        let text_len = text.len();
        let fg = Color::rgb(220, 220, 220);
        let line = EditorLine {
            raw_text: text,
            gutter_text: "   1".into(),
            spans: vec![EditorStyledSpan {
                start_byte: 0,
                end_byte: text_len,
                style: EditorStyle {
                    fg,
                    bg: None,
                    bold: false,
                    italic: false,
                    font_scale: 1.0,
                },
            }],
            line_idx: 0,
            is_current_line: true,
            is_fold_header: false,
            folded_line_count: 0,
            git_diff: None,
            diff_status: None,
            diagnostics: vec![],
            spell_errors: vec![],
            is_breakpoint: false,
            is_conditional_bp: false,
            is_dap_current: false,
            is_wrap_continuation: false,
            segment_col_offset: 0,
            annotation: None,
            ghost_suffix: None,
            is_ghost_continuation: false,
            indent_guides: vec![],
            colorcolumns: vec![],
        };
        Editor::new(WidgetId::new("gallery:caret-shape:editor"), area)
            .with_lines(vec![line])
            .with_cursor(EditorCursor {
                pos: EditorCursorPos {
                    view_line: 0,
                    col: 0,
                },
                shape,
            })
            .with_total_lines(1)
            .with_max_col(20)
            .with_gutter_char_width(4)
            .with_is_active(true)
            .with_cursorline(true)
            // `'\0'` means "no lightbulb" — this demo has nothing to
            // offer a code action for, unlike the primitive's own
            // default of `'!'`.
            .with_lightbulb_glyph('\0')
    }

    fn status_bar(&self, shape: EditorCursorShape) -> StatusBar {
        StatusBar {
            id: WidgetId::new("gallery:caret-shape:status"),
            left_segments: vec![StatusBarSegment {
                text: " Backend::set_caret_shape ".into(),
                fg: Color::rgb(255, 255, 255),
                bg: Color::rgb(40, 80, 120),
                bold: true,
                action_id: None,
            }],
            right_segments: vec![StatusBarSegment {
                text: format!(
                    " mode: {}  calls: {} ",
                    Self::mode_name(shape),
                    self.calls.get()
                ),
                fg: Color::rgb(220, 220, 220),
                bg: Color::rgb(40, 80, 120),
                bold: false,
                action_id: None,
            }],
        }
    }
}

impl Default for CaretShapeDemo {
    fn default() -> Self {
        Self::new()
    }
}

impl Demo for CaretShapeDemo {
    fn name(&self) -> &'static str {
        "Caret Shape"
    }

    fn group(&self) -> &'static str {
        "Content"
    }

    fn variants(&self) -> &'static [&'static str] {
        &["Normal (Block)", "Insert (Bar)", "Replace (Underline)"]
    }

    fn render(&self, variant: usize, backend: &mut dyn Backend, area: Rect) {
        let shape = Self::shape_for_variant(variant);
        if self.applied.get() != Some(shape) {
            backend.set_caret_shape(shape);
            self.applied.set(Some(shape));
            self.calls.set(self.calls.get() + 1);
        }

        let lh = backend.line_height();
        let bar_h = if lh > 1.5 { lh * 1.5 } else { lh };
        let editor_area = Rect::new(area.x, area.y, area.width, (area.height - bar_h).max(0.0));
        let editor = Self::build_editor(shape, editor_area);
        backend.draw_editor(editor.rect, &editor);

        let bar_rect = Rect::new(area.x, area.y + editor_area.height, area.width, bar_h);
        let _ = backend.draw_status_bar_interactive(
            bar_rect,
            &self.status_bar(shape),
            &InteractionState::new(),
        );
    }

    fn handle(
        &mut self,
        _variant: usize,
        _event: &UiEvent,
        _backend: &mut dyn Backend,
        _area: Rect,
    ) -> Reaction {
        Reaction::Continue
    }

    fn source(&self) -> &'static str {
        extract_region(SOURCE)
    }

    fn data(&self, variant: usize) -> serde_json::Value {
        let shape = Self::shape_for_variant(variant);
        serde_json::json!({
            "mode": Self::mode_name(shape),
            "set_caret_shape_calls": self.calls.get(),
        })
    }

    fn caps_note(&self, _variant: usize, _caps: &BackendCaps) -> Option<String> {
        // `Backend::set_caret_shape` is a genuine, permanent no-op on
        // every GUI backend by design (they paint their own caret and
        // have no separate hardware cursor to steer) — not a capability
        // gap `BackendCaps` tracks, so there is nothing to report here.
        None
    }
}

Backends

VariantTUIGTKmacOSWindows
Normal (Block)
Insert (Bar)
Replace (Underline)