chore: reduce component overhead (#3)

Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
2026-11-09 10:09:56 +00:00
parent 6815585528
commit 1c091db699
44 changed files with 1376 additions and 1241 deletions
+17 -46
View File
@@ -11,9 +11,9 @@ use gpui::{
actions, canvas, div, uniform_list,
};
use gpui_native_core::scrollbar::{Scrollbar, ScrollbarHandle, ScrollbarVisibility};
use gpui_native_theme::{ActiveTheme as _, ColorTokens, Ramps};
use gpui_native_theme::{ActiveTheme as _, ColorTokens, MONO_ADVANCE_RATIO, Ramps};
use crate::highlight::{HighlightedCode, Language, Role, highlight_with_tree};
use crate::highlight::{HighlightedCode, Language, Role, highlight};
use crate::utils::{gutter_cell, run_style};
actions!(code_view, [Copy]);
@@ -53,8 +53,6 @@ pub struct CodeViewState {
source: SharedString,
language: Language,
code: HighlightedCode,
/// The parse tree of `source`, so updates reparse incrementally instead of from scratch.
tree: Option<tree_sitter::Tree>,
handle: UniformListScrollHandle,
/// The hovered line, driving the active-line wash and number emphasis.
active_line: Option<usize>,
@@ -74,14 +72,13 @@ impl CodeViewState {
S: Into<SharedString>,
{
let source = source.into();
let (code, tree) = highlight_with_tree(&source, language, None);
let code = highlight(&source, language);
Self {
id: id.into(),
source,
language,
code,
tree,
handle: UniformListScrollHandle::new(),
active_line: None,
selection: None,
@@ -98,18 +95,9 @@ impl CodeViewState {
{
let source = source.into();
// The old tree is only reusable when the grammar stays the same.
let old_tree = if language == self.language {
self.tree.as_ref()
} else {
None
};
let (code, tree) = highlight_with_tree(&source, language, old_tree);
self.code = highlight(&source, language);
self.source = source;
self.language = language;
self.code = code;
self.tree = tree;
self.selection = None;
self.handle.scroll_to_item(0, ScrollStrategy::Top);
@@ -134,9 +122,7 @@ impl CodeViewState {
lines.splice(line_ix..line_ix + 1, replacement);
self.source = (lines.join("\n") + if trailing_newline { "\n" } else { "" }).into();
let (code, tree) = highlight_with_tree(&self.source, self.language, self.tree.as_ref());
self.code = code;
self.tree = tree;
self.code = highlight(&self.source, self.language);
self.selection = None;
cx.notify();
}
@@ -146,11 +132,6 @@ impl CodeViewState {
&self.source
}
/// The language the source highlights as.
pub fn language(&self) -> Language {
self.language
}
/// The highlighted lines.
pub fn code(&self) -> &HighlightedCode {
&self.code
@@ -223,13 +204,7 @@ impl CodeViewState {
(!selected.is_empty()).then(|| selected.join("\n"))
}
/// Drops the selection.
pub fn clear_selection(&mut self) {
self.selection = None;
self.selecting = false;
}
/// Overrides the accessibility label of the view.
// Overrides the accessibility label of the view.
pub fn accessibility_label<L>(mut self, label: L) -> Self
where
L: Into<SharedString>,
@@ -249,8 +224,7 @@ impl CodeViewState {
text_x: Pixels,
) -> SelectionPoint {
let row = (((position.y - bounds.origin.y - scroll_y) / line_height).floor() as isize)
.clamp(0, self.code.lines.len() as isize - 1)
.max(0) as usize;
.clamp(0, self.code.lines.len() as isize - 1) as usize;
let chars = self.code.lines[row]
.spans
@@ -258,9 +232,8 @@ impl CodeViewState {
.map(|span| span.text.chars().count())
.sum::<usize>();
let col = (((position.x - text_x) / char_width).round() as isize)
.clamp(0, chars as isize)
.max(0) as usize;
let col = (((position.x - text_x) / char_width).round() as isize).clamp(0, chars as isize)
as usize;
SelectionPoint { row, col }
}
@@ -279,7 +252,7 @@ impl Render for CodeViewState {
let mono_font = theme.mono_font;
let text_size = theme.sizes.small.text_size;
let line_height = text_size * 1.5;
let char_width = text_size * 0.62;
let char_width = text_size * MONO_ADVANCE_RATIO;
let root_font_size = theme.root_font_size;
let id = self.id.clone();
@@ -348,7 +321,7 @@ impl Render for CodeViewState {
code.position_at(
*bounds,
event.position,
Pixels::ZERO,
ScrollbarHandle::offset(&code.handle).y,
line_height,
char_width,
text_x,
@@ -453,7 +426,8 @@ impl Render for CodeViewState {
)
.child(rows)
.child(
Scrollbar::vertical(self.handle.clone()).visibility(ScrollbarVisibility::Platform),
Scrollbar::vertical((self.id.clone(), "v-scrollbar"), self.handle.clone())
.visibility(ScrollbarVisibility::Platform),
)
.when_some(self.a11y_label.clone(), |view, label| {
view.aria_label(label)
@@ -475,10 +449,10 @@ fn render_row(
line_height: Pixels,
cx: &App,
) -> gpui::AnyElement {
let line = code.code().lines.get(line_ix).cloned().unwrap_or_default();
let line = code.code().lines.get(line_ix);
let mut text = String::new();
for span in &line.spans {
for span in line.map_or(&[][..], |line| line.spans.as_slice()) {
text.push_str(&span.text);
}
@@ -486,7 +460,7 @@ fn render_row(
let mut roles = vec![Role::Text; char_count];
let mut offset = 0usize;
for span in &line.spans {
for span in line.map_or(&[][..], |line| line.spans.as_slice()) {
let chars = span.text.chars().count();
for slot in &mut roles[offset..offset + chars] {
*slot = span.role;
@@ -556,10 +530,7 @@ fn render_row(
};
div()
.debug_selector({
let name = format!("{id}-row-{line_ix}");
move || name
})
.debug_selector(move || format!("{id}-row-{line_ix}"))
.id(("code-row", line_ix))
.w_full()
.h(line_height)
+30 -18
View File
@@ -13,7 +13,7 @@ use gpui::{
point, uniform_list,
};
use gpui_native_core::scrollbar::{Scrollbar, ScrollbarHandle, ScrollbarVisibility};
use gpui_native_theme::ActiveTheme as _;
use gpui_native_theme::{ActiveTheme as _, MONO_ADVANCE_RATIO};
use similar::{ChangeTag, TextDiff};
use std::ops::Range;
@@ -237,6 +237,8 @@ pub fn diff_rows(old: &str, new: &str, language: Language) -> Vec<DiffRow> {
pub struct DiffViewState {
id: ElementId,
rows: Vec<DiffRow>,
/// The row whose width drives horizontal scroll extent, `rows`-derived.
widest_row: Option<usize>,
options: DiffViewOptions,
/// Drives both scroll axes of the virtualized row list.
handle: UniformListScrollHandle,
@@ -251,13 +253,32 @@ impl DiffViewState {
O: AsRef<str>,
N: AsRef<str>,
{
Self {
let mut state = Self {
id: id.into(),
rows: diff_rows(old.as_ref(), new.as_ref(), language),
rows: Vec::new(),
widest_row: None,
options: DiffViewOptions::default(),
handle: UniformListScrollHandle::new(),
a11y_label: None,
}
};
state.set_rows(old, new, language);
state
}
/// Replaces the rows, recomputing `widest_row` with them.
fn set_rows<O, N>(&mut self, old: O, new: N, language: Language)
where
O: AsRef<str>,
N: AsRef<str>,
{
self.rows = diff_rows(old.as_ref(), new.as_ref(), language);
self.widest_row = self
.rows
.iter()
.enumerate()
.filter(|(_, row)| row.kind != DiffRowKind::Hunk)
.max_by_key(|(_, row)| row.char_count())
.map(|(ix, _)| ix);
}
/// Sets how changed rows are marked.
@@ -284,7 +305,7 @@ impl DiffViewState {
O: AsRef<str>,
N: AsRef<str>,
{
self.rows = diff_rows(old.as_ref(), new.as_ref(), language);
self.set_rows(old, new, language);
self.handle.scroll_to_item(0, ScrollStrategy::Top);
// The replaced versions rarely share the old width; start at the left.
let offset = ScrollbarHandle::offset(&self.handle);
@@ -328,14 +349,7 @@ impl Render for DiffViewState {
let state = cx.entity();
let row_count = self.rows.len();
let options = self.options;
let widest_row = self
.rows
.iter()
.enumerate()
.filter(|(_, row)| row.kind != DiffRowKind::Hunk)
.max_by_key(|(_, row)| row.char_count())
.map(|(ix, _)| ix);
let widest_row = self.widest_row;
let mut rows = uniform_list((id.clone(), "rows"), row_count, {
move |range, _window, cx| {
@@ -363,13 +377,11 @@ impl Render for DiffViewState {
})
.child(rows)
.child(
Scrollbar::vertical(self.handle.clone())
.key("v-scrollbar")
Scrollbar::vertical((self.id.clone(), "v-scrollbar"), self.handle.clone())
.visibility(ScrollbarVisibility::Platform),
)
.child(
Scrollbar::horizontal(self.handle.clone())
.key("h-scrollbar")
Scrollbar::horizontal((self.id.clone(), "h-scrollbar"), self.handle.clone())
.visibility(ScrollbarVisibility::Platform),
)
}
@@ -387,7 +399,7 @@ fn render_row(
let ramps = theme.ramps;
let text_size = theme.sizes.small.text_size;
let line_height = text_size * 1.5;
let char_width = text_size * 0.62;
let char_width = text_size * MONO_ADVANCE_RATIO;
let frame = div()
.debug_selector(move || format!("diff-row-{row_ix}"))
+5 -14
View File
@@ -67,17 +67,8 @@ pub struct HighlightedCode {
/// Highlights `source` with `language`'s tree-sitter grammar.
pub fn highlight(source: &str, language: Language) -> HighlightedCode {
highlight_with_tree(source, language, None).0
}
/// Highlights `source`, reusing `old_tree` so only the changed region is re-lexed.
pub fn highlight_with_tree(
source: &str,
language: Language,
old_tree: Option<&tree_sitter::Tree>,
) -> (HighlightedCode, Option<tree_sitter::Tree>) {
let Some((grammar, query_source)) = grammar_for(language) else {
return (plain(source), None);
return plain(source);
};
HIGHLIGHTERS.with(|cache| {
@@ -104,11 +95,11 @@ pub fn highlight_with_tree(
.as_mut();
let Some(highlighter) = highlighter else {
return (plain(source), None);
return plain(source);
};
let Some(tree) = highlighter.parser.parse(source, old_tree) else {
return (plain(source), None);
let Some(tree) = highlighter.parser.parse(source, None) else {
return plain(source);
};
// One role per byte; matches arrive in query order
@@ -134,7 +125,7 @@ pub fn highlight_with_tree(
}
}
(build_lines(source, &role_at), Some(tree))
build_lines(source, &role_at)
})
}
+21 -44
View File
@@ -18,7 +18,7 @@ use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag, T
use std::ops::Range;
use crate::highlight::{HighlightedCode, highlight_with_tree, plain};
use crate::highlight::{HighlightedCode, highlight, plain};
use crate::utils::{code_line, grammar_language};
/// Heading scale steps relative to the root font size, H1 first.
@@ -101,8 +101,6 @@ pub enum InlineStyle {
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MarkdownTableCell {
pub spans: Vec<InlineSpan>,
/// 1-based column position, so renderers can align to the table's columns.
pub column: usize,
}
/// A table row; the header row precedes the body rows.
@@ -277,7 +275,8 @@ impl Render for MarkdownViewState {
.size_full()
.child(blocks)
.child(
Scrollbar::vertical(self.handle.clone()).visibility(ScrollbarVisibility::Platform),
Scrollbar::vertical((self.id.clone(), "v-scrollbar"), self.handle.clone())
.visibility(ScrollbarVisibility::Platform),
)
.when_some(self.a11y_label.clone(), |view, label| {
view.aria_label(label)
@@ -292,24 +291,6 @@ struct ListFrame {
task: Option<bool>,
}
/// The table being assembled, when the parser is inside one.
enum TableBuilder {
None,
Body { rows: Vec<MarkdownTableRow> },
}
/// One open code block, accumulating its raw text.
#[derive(Default)]
struct CodeBuilder {
text: String,
}
impl CodeBuilder {
fn push(&mut self, text: &str) {
self.text.push_str(text);
}
}
/// Accumulates inline spans across inline events.
#[derive(Default)]
struct InlineBuilder {
@@ -402,8 +383,8 @@ fn parse(source: &str, highlight_code: bool) -> Vec<MarkdownBlock> {
// Whether each open list item already flushed content rows.
let mut items: Vec<bool> = Vec::new();
let mut quote_depth = 0usize;
let mut table = TableBuilder::None;
let mut code: Option<CodeBuilder> = None;
let mut table: Option<Vec<MarkdownTableRow>> = None;
let mut code: Option<String> = None;
// The fence info string arrives on Start, its text follows.
let mut code_language: Option<SharedString> = None;
@@ -468,7 +449,7 @@ fn parse(source: &str, highlight_code: bool) -> Vec<MarkdownBlock> {
&mut blocks,
quote_depth,
);
code = Some(CodeBuilder::default());
code = Some(String::new());
if let CodeBlockKind::Fenced(info) = kind {
let info = info.trim();
code_language =
@@ -483,10 +464,10 @@ fn parse(source: &str, highlight_code: bool) -> Vec<MarkdownBlock> {
&mut blocks,
quote_depth,
);
table = TableBuilder::Body { rows: Vec::new() }
table = Some(Vec::new())
}
Tag::TableHead => {
if let TableBuilder::Body { rows } = &mut table {
if let Some(rows) = &mut table {
rows.push(MarkdownTableRow {
cells: Vec::new(),
header: true,
@@ -494,7 +475,7 @@ fn parse(source: &str, highlight_code: bool) -> Vec<MarkdownBlock> {
}
}
Tag::TableRow => {
if let TableBuilder::Body { rows } = &mut table {
if let Some(rows) = &mut table {
rows.push(MarkdownTableRow {
cells: Vec::new(),
header: false,
@@ -582,13 +563,13 @@ fn parse(source: &str, highlight_code: bool) -> Vec<MarkdownBlock> {
}
TagEnd::CodeBlock => {
let built = code.take().unwrap_or_default();
let text = built.text.trim_end_matches('\n');
let text = built.trim_end_matches('\n');
let language = code_language.take();
let lines = match highlight_code
.then(|| language.as_deref().and_then(grammar_language))
.flatten()
{
Some(grammar) => highlight_with_tree(text, grammar, None).0,
Some(grammar) => highlight(text, grammar),
None => plain(text),
};
blocks.push(MarkdownBlock::Code {
@@ -599,8 +580,7 @@ fn parse(source: &str, highlight_code: bool) -> Vec<MarkdownBlock> {
});
}
TagEnd::Table => {
let built = std::mem::replace(&mut table, TableBuilder::None);
if let TableBuilder::Body { rows } = built {
if let Some(rows) = table.take() {
blocks.push(MarkdownBlock::Table {
table: MarkdownTable { rows },
indent: lists.len(),
@@ -610,16 +590,13 @@ fn parse(source: &str, highlight_code: bool) -> Vec<MarkdownBlock> {
}
TagEnd::TableCell => {
let spans = inline.finish();
let TableBuilder::Body { rows } = &mut table else {
let Some(rows) = &mut table else {
continue;
};
let Some(row) = rows.last_mut() else {
continue;
};
row.cells.push(MarkdownTableCell {
spans,
column: row.cells.len() + 1,
});
row.cells.push(MarkdownTableCell { spans });
}
TagEnd::Emphasis
| TagEnd::Strong
@@ -629,8 +606,8 @@ fn parse(source: &str, highlight_code: bool) -> Vec<MarkdownBlock> {
_ => {}
},
Event::Text(text) => {
if let Some(built) = code.as_mut() {
built.push(&text);
if let Some(buffer) = code.as_mut() {
buffer.push_str(&text);
} else {
inline.push_text(&text);
}
@@ -659,9 +636,9 @@ fn parse(source: &str, highlight_code: bool) -> Vec<MarkdownBlock> {
frame.task = Some(checked);
}
}
Event::FootnoteReference(label) => inline.push_text(&label),
// HTML and math render nothing in this view.
Event::InlineHtml(_)
// Footnotes are disabled in the parser options.
Event::FootnoteReference(_)
| Event::InlineHtml(_)
| Event::Html(_)
| Event::InlineMath(_)
| Event::DisplayMath(_) => {}
@@ -783,8 +760,8 @@ fn block_frame(
let theme = cx.theme();
let content = div()
.debug_selector({
let name = SharedString::from(format!("{id}"));
move || name.to_string()
let id = id.clone();
move || id.to_string()
})
.w_full()
.flex()
-8
View File
@@ -305,14 +305,6 @@ fn tables_parse_header_first_with_column_positions() {
.map(|cell| spans_text(&cell.spans))
.collect();
assert_eq!(header, vec!["a", "b"]);
assert_eq!(
table.rows[0]
.cells
.iter()
.map(|cell| cell.column)
.collect::<Vec<_>>(),
vec![1, 2]
);
assert_eq!(spans_text(&table.rows[2].cells[1].spans), "4");
}