forked from reya/gpui-native-kit
chore: reduce component overhead (#3)
Reviewed-on: reya/gpui-native-kit#3
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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}"))
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user