From 702b1d0a0fc483935882cbd451a6898fa8f895eb Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 14 May 2025 18:27:46 -0400 Subject: [PATCH] statusline: Avoid unnecessary allocations for `&'static str` spans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the statusline `write` function only accepted a string and optional Style, so all rendering functions converted text to strings. Some elements write spans with `&'static str`s, however, making this unnecessary since `Span<'a>` is a wrapper around `Cow<'a, str>` and style, and a `Span<'static>` would outlive all required lifetimes. Moreover many elements could produce `Span<'a>` according to the lifetime in `RenderContext` in the future, potentially re-borrowing from the Editor borrow, so this change could save allocations for many file-type elements (with more future changes). This is not explored in this patch since the statusline functions currently add bespoke padding per-element, but with a future refactor to make spacing consistent this could be possible. This change refactors the write function to accept a `Span<'a>` and rewrites some related code to fit the codebase better (preferring `for` to iterator's `for_each` for example). The new code is more complicated lifetime-wise but avoids allocations in these cases: * spacer for mode name when a pane is not focused * LSP spinner frames * '●' (workspace) diagnostic indicators * " W " workspace diagnostic prefix * file modification indicators * read-only indicators * spacer element ... and opens the door to avoid allocation for file name elements in the future. --- helix-term/src/ui/statusline.rs | 291 ++++++++++++++------------------ 1 file changed, 126 insertions(+), 165 deletions(-) diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 117483e26..4d4b9f2fe 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use helix_core::{coords_at_pos, encoding, Position}; use helix_lsp::lsp::DiagnosticSeverity; use helix_view::document::DEFAULT_LANGUAGE_NAME; @@ -58,25 +60,16 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface surface.set_style(viewport.with_height(1), base_style); - let write_left = |context: &mut RenderContext, text, style| { - append(&mut context.parts.left, text, &base_style, style) - }; - let write_center = |context: &mut RenderContext, text, style| { - append(&mut context.parts.center, text, &base_style, style) - }; - let write_right = |context: &mut RenderContext, text, style| { - append(&mut context.parts.right, text, &base_style, style) - }; - // Left side of the status line. let config = context.editor.config(); - let element_ids = &config.statusline.left; - element_ids - .iter() - .map(|element_id| get_render_function(*element_id)) - .for_each(|render| render(context, write_left)); + for element_id in &config.statusline.left { + let render = get_render_function(*element_id); + (render)(context, |context, span| { + append(&mut context.parts.left, span, base_style) + }); + } surface.set_spans( viewport.x, @@ -87,11 +80,12 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface // Right side of the status line. - let element_ids = &config.statusline.right; - element_ids - .iter() - .map(|element_id| get_render_function(*element_id)) - .for_each(|render| render(context, write_right)); + for element_id in &config.statusline.right { + let render = get_render_function(*element_id); + (render)(context, |context, span| { + append(&mut context.parts.right, span, base_style) + }) + } surface.set_spans( viewport.x @@ -105,11 +99,12 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface // Center of the status line. - let element_ids = &config.statusline.center; - element_ids - .iter() - .map(|element_id| get_render_function(*element_id)) - .for_each(|render| render(context, write_center)); + for element_id in &config.statusline.center { + let render = get_render_function(*element_id); + (render)(context, |context, span| { + append(&mut context.parts.center, span, base_style) + }) + } // Width of the empty space between the left and center area and between the center and right area. let spacing = 1u16; @@ -126,16 +121,14 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface ); } -fn append(buffer: &mut Spans, text: String, base_style: &Style, style: Option