Repository: helix-editor/helix
File: helix-term/src/ui/statusline.rs (Rust)

Code before:
}

fn render_mode<'a, F>(context: &mut RenderContext<'a>, write: F)
where
    F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy,
{
    let visible = context.focused;
    let config = context.editor.config();
    let modenames = &config.statusline.mode;
    let content = if visible {
        Cow::Owned(format!(
            " {} ",
            match context.editor.mode() {
                Mode::Insert => &modenames.insert,
                Mode::Select => &modenames.select,
                Mode::Normal => &modenames.normal,
            }
        ))
    } else {
        // If not focused, explicitly leave an empty space instead of returning None.
        let mode_str = match context.editor.mode() {
            Mode::Insert => &modenames.insert,
            Mode::Select => &modenames.select,
            Mode::Normal => &modenames.normal,
        };
        let width = mode_str.chars().count() + 2;
        Cow::Owned(" ".repeat(width))
    };
    let style = if visible && config.color_modes {
        match context.editor.mode() {
            Mode::Insert => context.editor.theme.get("ui.statusline.insert"),
            Mode::Select => context.editor.theme.get("ui.statusline.select"),
            Mode::Normal => context.editor.theme.get("ui.statusline.normal"),
        }
    } else {
        Style::default()
    };
    write(context, Span::styled(content, style));
}

// TODO think about handling multiple language servers
fn render_lsp_spinner<'a, F>(context: &mut RenderContext<'a>, write: F)
where
    F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy,
{
    let language_server = context.doc.language_servers().next();
    write(
        context,
        language_server
            .and_then(|srv| {
                context

Review comment (suggestion):
Here we should use `width` (by importing `helix_core::unicode::width::UnicodeWidthStr`) rather than counting chars. Counting chars doesn't work properly if these strings are configured to be grapheme clusters like emoji

Code after:
fn render_mode<'a, F>(context: &mut RenderContext<'a>, write: F)
where
    F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy,
{
    let visible = context.focused;
    let config = context.editor.config();
    let modenames = &config.statusline.mode;
    let mode_str = match context.editor.mode() {
        Mode::Insert => &modenames.insert,
        Mode::Select => &modenames.select,
        Mode::Normal => &modenames.normal,
    };
    let content = if visible {
        format!(" {mode_str} ")
    } else {
        // If not focused, explicitly leave an empty space instead of returning None.
        " ".repeat(mode_str.width() + 2)
    };
    let style = if visible && config.color_modes {
        match context.editor.mode() {
            Mode::Insert => context.editor.theme.get("ui.statusline.insert"),
            Mode::Select => context.editor.theme.get("ui.statusline.select"),
            Mode::Normal => context.editor.theme.get("ui.statusline.normal"),
        }
    } else {
        Style::default()
    };
    write(context, Span::styled(content, style));
}

// TODO think about handling multiple language servers
fn render_lsp_spinner<'a, F>(context: &mut RenderContext<'a>, write: F)
where
    F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy,
{
    let language_server = context.doc.language_servers().next();
    write(
        context,
        language_server
            .and_then(|srv| {
                context
                    .spinners
                    .get(srv.id())
                    .and_then(|spinner| spinner.frame())
            })
            // Even if there's no spinner; reserve its space to avoid elements frequently shifting.
            .unwrap_or(" ")
            .into(),
    );
}